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

ransackable_scope with boolean value is skipped #1375

Open
stereobooster opened this issue Nov 12, 2022 · 2 comments
Open

ransackable_scope with boolean value is skipped #1375

stereobooster opened this issue Nov 12, 2022 · 2 comments

Comments

@stereobooster
Copy link
Contributor

stereobooster commented Nov 12, 2022

This issue was reported before, but I'm not sure how it is resolved #831

# test-ransack-scope-and-column-same-name.rb

# This is a stand-alone test case.

# Run it in your console with: `ruby test-ransack-scope-and-column-same-name.rb`

# If you change the gem dependencies, run it with:
# `rm gemfile* && ruby test-ransack-scope-and-column-same-name.rb`

unless File.exist?('Gemfile')
  File.write('Gemfile', <<-GEMFILE)
    source 'https://rubygems.org'

    # Rails master
    gem 'rails', github: 'rails/rails', branch: '6-1-stable'

    # Rails last release
    # gem 'rails'

    gem 'sqlite3'
    gem 'ransack', github: 'activerecord-hackery/ransack'
  GEMFILE

  system 'bundle install'
end

require 'bundler'
Bundler.setup(:default)

require 'active_record'
require 'minitest/autorun'
require 'logger'
require 'ransack'

# This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
ActiveRecord::Base.logger = Logger.new(STDOUT)

# Display versions.
message = "Running test case with Ruby #{RUBY_VERSION}, Active Record #{
  ::ActiveRecord::VERSION::STRING}, Arel #{Arel::VERSION} and #{
  ::ActiveRecord::Base.connection.adapter_name}"
line = '=' * message.length
puts line, message, line

ActiveRecord::Schema.define do
  create_table :users, force: true do |t|
    t.boolean :active, null: false, default: true
  end
end

Ransack.configure do |c|
  c.sanitize_custom_scope_booleans = false
end

class User < ActiveRecord::Base
  scope :activated, -> (boolean = true) { where(active: boolean) }

  private

  def self.ransackable_scopes(auth_object = nil)
    %i(activated)
  end
end

class BugTest < Minitest::Test
  # ok
  def test_activated_scope_equals_true
    sql = User.ransack({ activated: true }).result.to_sql
    puts sql
    assert_equal(
      "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"active\" = 1", sql
      )
  end

  # ok
  def test_activated_scope_equals_false_arr
    sql = User.ransack({ activated: [false] }).result.to_sql
    assert_equal(
      "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"active\" = 0", sql
      )
  end

  # not ok
  def test_activated_scope_equals_false
    sql = User.ransack({ activated: false }).result.to_sql
    puts sql
    assert_equal(
      "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"active\" = 0", sql
      )
  end

end

I pass hash directly to ransack I would expect that value would be passed to activated (boolean: false)

Possible solution:

scope :activated, -> { ... } scope :activated, -> (boolean)
true .activated .activated(true)
false .activated(false)

Related https://stackoverflow.com/a/34390913/1190041

@stereobooster
Copy link
Contributor Author

stereobooster commented Nov 13, 2022

Problem runs down to if we can detect arity of scope or not. This question was asked before rails/rails#28198

First problem is that all scopes are wrapped by Rails https://github.com/rails/rails/blob/8015c2c2cf5c8718449677570f372ceb01318a32/activerecord/lib/active_record/scoping/named.rb#L173-L188

          if body.respond_to?(:to_proc)
            # arity = 1
            singleton_class.define_method(name) do |*args|
              scope = all._exec_scope(*args, &body)
              scope = scope.extending(extension) if extension
              scope
            end
          else
            # arity = body.arity
            singleton_class.define_method(name) do |*args|
              scope = body.call(*args) || all
              scope = scope.extending(extension) if extension
              scope
            end
          end
          singleton_class.send(:ruby2_keywords, name)

So it means that what ever we do @klass.method(scope).arity would return -1

Side note (what to_proc means):

  • (1..3).collect(&:to_s)
  • (1..3).collect { |x| :to_s.to_proc.call(x) }
  • (1..3).collect { |x| x.to_s }

Second problem is that there are two ways to define scopes: with Proc and with Lambda. And arity behaves differently for them:

@eric-hemasystems
Copy link

For those looking for a workaround. I disabled the automatic type conversion of booleans with:

Ransack.configure do |c|
  c.sanitize_custom_scope_booleans = false
end

This means the querystring param is a string of "false" instead of false so the scope is not skipped. It does mean I need to do the type conversion myself in the scope :( but that is doable for now:

  scope :active, ->(bool=true) {
    # Maybe coming in as a string that is "true" or "false"
    bool = ActiveModel::Type::Boolean.new.cast bool

    ....
  }

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