diff --git a/CHANGELOG.md b/CHANGELOG.md index 434b387f0..334eb60c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,11 @@ *Garett Arrowood* +* Add ability to turn off sanitization of custom scope arguments. + PR [#742](https://github.com/activerecord-hackery/ransack/pull/742). + + *Garett Arrowood* + ### Fixed * Use class attributes properly so that inheritance is respected. diff --git a/README.md b/README.md index 7aa51c370..bc2013b28 100644 --- a/README.md +++ b/README.md @@ -661,7 +661,21 @@ Employee.ransack({ salary_gt: 100_000 }, { auth_object: current_user }) In Rails 3 and 4, if the `true` value is being passed via url params or some other mechanism that will convert it to a string, the true value may not be passed to the ransackable scope unless you wrap it in an array -(i.e. `activated: ['true']`). This is currently resolved in Rails 5 :smiley: +(i.e. `activated: ['true']`). Ransack will take care of changing 'true' into a +boolean. This is currently resolved in Rails 5 :smiley: + +However, perhaps you have `user_id: [1]` and you do not want Ransack to convert +1 into a boolean. (Values sanitized to booleans can be found in the +[constants.rb](https://github.com/activerecord-hackery/ransack/blob/master/lib/ransack/constants.rb#L28)). +To turn this off, and handle type conversions yourself, set +`sanitize_custom_scope_booleans` to false in an initializer file like +config/initializers/ransack.rb: + +```ruby +Ransack.configure do |c| + c.sanitize_custom_scope_booleans = false +end +``` Scopes are a recent addition to Ransack and currently have a few caveats: First, a scope involving child associations needs to be defined in the parent diff --git a/lib/ransack/configuration.rb b/lib/ransack/configuration.rb index a1c21133a..d5064be41 100644 --- a/lib/ransack/configuration.rb +++ b/lib/ransack/configuration.rb @@ -11,7 +11,8 @@ module Configuration :ignore_unknown_conditions => true, :hide_sort_order_indicators => false, :up_arrow => '▼'.freeze, - :down_arrow => '▲'.freeze + :down_arrow => '▲'.freeze, + :sanitize_scope_args => true } def configure @@ -98,6 +99,22 @@ def custom_arrows=(opts = {}) self.options[:down_arrow] = opts[:down_arrow].freeze if opts[:down_arrow] end + # Ransack sanitizes many values in your custom scopes into booleans. + # [1, '1', 't', 'T', 'true', 'TRUE'] all evaluate to true. + # [0, '0', 'f', 'F', 'false', 'FALSE'] all evaluate to false. + # + # This default may be globally overridden in an initializer file like + # `config/initializers/ransack.rb` as follows: + # + # Ransack.configure do |config| + # # Accept my custom scope values as what they are. + # config.sanitize_custom_scope_booleans = false + # end + # + def sanitize_custom_scope_booleans=(boolean) + self.options[:sanitize_scope_args] = boolean + end + # By default, Ransack displays sort order indicator arrows in sort links. # The default may be globally overridden in an initializer file like # `config/initializers/ransack.rb` as follows: diff --git a/lib/ransack/search.rb b/lib/ransack/search.rb index e73cdebcf..419687ca4 100644 --- a/lib/ransack/search.rb +++ b/lib/ransack/search.rb @@ -123,12 +123,18 @@ def inspect private def add_scope(key, args) + sanitized_args = if Ransack.options[:sanitize_scope_args] + sanitized_scope_args(args) + else + args + end + if @context.scope_arity(key) == 1 @scope_args[key] = args.is_a?(Array) ? args[0] : args else - @scope_args[key] = args.is_a?(Array) ? sanitized_scope_args(args) : args + @scope_args[key] = args.is_a?(Array) ? sanitized_args : args end - @context.chain_scope(key, sanitized_scope_args(args)) + @context.chain_scope(key, sanitized_args) end def sanitized_scope_args(args) diff --git a/spec/ransack/adapters/active_record/base_spec.rb b/spec/ransack/adapters/active_record/base_spec.rb index dc6b72ea1..31ca60415 100644 --- a/spec/ransack/adapters/active_record/base_spec.rb +++ b/spec/ransack/adapters/active_record/base_spec.rb @@ -65,25 +65,31 @@ module ActiveRecord expect(s.result.to_sql).to (include 'age > 18') end - # TODO: Implement a way to pass true/false values like 0 or 1 to - # scopes (e.g. with `in` / `not_in` predicates), without Ransack - # converting them to true/false boolean values instead. - - # it 'passes true values to scopes', focus: true do - # s = Person.ransack('over_age' => 1) - # expect(s.result.to_sql).to (include 'age > 1') - # end - - # it 'passes false values to scopes', focus: true do - # s = Person.ransack('over_age' => 0) - # expect(s.result.to_sql).to (include 'age > 0') - # end - it 'chains scopes' do s = Person.ransack('over_age' => 18, 'active' => true) expect(s.result.to_sql).to (include 'age > 18') expect(s.result.to_sql).to (include 'active = 1') end + + context "with sanitize_custom_scope_booleans set to false" do + before(:all) do + Ransack.configure { |c| c.sanitize_custom_scope_booleans = false } + end + + after(:all) do + Ransack.configure { |c| c.sanitize_custom_scope_booleans = true } + end + + it 'passes true values to scopes' do + s = Person.ransack('over_age' => 1) + expect(s.result.to_sql).to (include 'age > 1') + end + + it 'passes false values to scopes' do + s = Person.ransack('over_age' => 0) + expect(s.result.to_sql).to (include 'age > 0') + end + end end it 'does not raise exception for string :params argument' do