Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add own SQL strategy #844

Open
23tux opened this issue Apr 17, 2024 · 1 comment
Open

Add own SQL strategy #844

23tux opened this issue Apr 17, 2024 · 1 comment

Comments

@23tux
Copy link

23tux commented Apr 17, 2024

Steps to reproduce

I want to implement my own SQL strategy to fight some performance issues regarding subqueries and distinct keywords in the left_join strategy. My approach is, to skip the distinct as along as the all joins are belongs_to or has_one. This way, the query will run in 1/30 of the time on my system.

However, adding a custom strategy requires some monkey patching, which is quiet ugly:

# config/initializers/cancancan.rb
CanCan.module_eval do
  class << self
    alias _valid_accessible_by_strategies valid_accessible_by_strategies
    def valid_accessible_by_strategies
      _valid_accessible_by_strategies + [CanCanLeftJoinOptimizedWithFallback::STRATEGY_NAME]
    end
  end
end

Rails.configuration.to_prepare { CanCanLeftJoinOptimizedWithFallback.install }
# app/lib/can_can_left_join_optimized_with_fallback.rb
class CanCanLeftJoinOptimizedWithFallback < CanCan::ModelAdapters::Strategies::Base
  STRATEGY_NAME = name.delete_prefix("CanCan").underscore.to_sym
  class << self
    def install
      const = STRATEGY_NAME.to_s.camelize.to_sym
      if CanCan::ModelAdapters::Strategies.const_defined?(const)
        CanCan::ModelAdapters::Strategies.send(:remove_const, const)
      end
      CanCan::ModelAdapters::Strategies.const_set(const, self)
      CanCan.accessible_by_strategy = STRATEGY_NAME
    end
  end

  def execute!
    # ...
  end
end

Expected behavior

It would be nice to add a custom strategy like this:

Rails.configuration.to_prepare do
  CanCan.accessible_by_strategy = CanCanLeftJoinOptimizedWithFallback
end

The setter could recognize if the argument is a class or a symbol and add it to the allowed strategies. It would also have to be compatible with Zeitwerks, so when the to_prepare hook from Rails is triggered during development, the constant is removed and added again like I do in my .install method.

I'll be happy to try for a PR, but I would need some guidance on how this could be implemented.

Actual behavior

> CanCan.accessible_by_strategy = CanCanLeftJoinOptimizedWithFallback
ArgumentError: accessible_by_strategy must be one of left_join, joined_alias_exists_subquery, joined_alias_each_rule_as_exists_subquery, subquery

System configuration

Rails version:
7.0.8.1

Ruby version:
3.2.1

CanCanCan version
3.5.0

@coorasse
Copy link
Member

I am very happy to accept a PR for this feature 👍
I don't have much guidance to give, but I am happy to review the PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants