diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a487e427..44d8caba6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +* Add `ActiveRecord::Base.ransack!` which raises error if passed unknown condition + + *Aaron Lipman* + ## 2.4.0 - 2020-11-27 * diff --git a/README.md b/README.md index ec4c1a992..af3234f39 100644 --- a/README.md +++ b/README.md @@ -41,27 +41,14 @@ gem 'ransack', github: 'activerecord-hackery/ransack' ## Usage -Ransack can be used in one of two modes, simple or advanced. +Ransack can be used in one of two modes, simple or advanced. For +searching/filtering not requiring complex boolean logic, Ransack's simple +mode should meet your needs. -### Simple Mode - -This mode works much like MetaSearch, for those of you who are familiar with -it, and requires very little setup effort. - -If you're coming from MetaSearch, things to note: +If you're coming from MetaSearch (Ransack's predecessor), refer to the +[Updating From MetaSearch](#updating-from-metasearch) section - 1. The default param key for search params is now `:q`, instead of `:search`. - This is primarily to shorten query strings, though advanced queries (below) - will still run afoul of URL length limits in most browsers and require a - switch to HTTP POST requests. This key is [configurable](https://github.com/activerecord-hackery/ransack/wiki/Configuration). - - 2. `form_for` is now `search_form_for`, and validates that a Ransack::Search - object is passed to it. - - 3. Common ActiveRecord::Relation methods are no longer delegated by the - search object. Instead, you will get your search results (an - ActiveRecord::Relation in the case of the ActiveRecord adapter) via a call to - `Ransack#result`. +### Simple Mode #### In your controller @@ -84,6 +71,20 @@ def index end ``` +##### Default search parameter + +Ransack uses a default `:q` param key for search params. This may be changed by +setting the `search_key` option in a Ransack initializer file (typically +`config/initializers/ransack.rb`): + +``` +Ransack.configure do |c| + # Change default search parameter key name. + # Default key name is :q + c.search_key = :query +end +``` + #### In your view The two primary Ransack view helpers are `search_form_for` and `sort_link`, @@ -670,6 +671,43 @@ Trying it out in `rails console`: That's it! Now you know how to whitelist/blacklist various elements in Ransack. +### Handling unknown predicates or attributes + +By default, Ransack will ignore any unknown predicates or attributes: + +```ruby +Article.ransack(unknown_attr_eq: 'Ernie').result.to_sql +=> SELECT "articles".* FROM "articles" +``` + +Ransack may be configured to raise an error if passed an unknown predicate or +attributes, by setting the `ignore_unknown_conditions` option to `false` in your +Ransack initializer file at `config/initializers/ransack.rb`: + +```ruby +Ransack.configure do |c| + # Raise errors if a query contains an unknown predicate or attribute. + # Default is true (do not raise error on unknown conditions). + c.ignore_unknown_conditions = false +end +``` + +```ruby +Article.ransack(unknown_attr_eq: 'Ernie') +# ArgumentError (Invalid search term unknown_attr_eq) +``` + +As an alternative to setting a global configuration option, the `.ransack!` +class method also raises an error if passed an unknown condition: + +```ruby +Article.ransack!(unknown_attr_eq: 'Ernie') +# ArgumentError: Invalid search term unknown_attr_eq +``` + +This is equivilent to the `ignore_unknown_conditions` configuration option, +except it may be applied on a case-by-case basis. + ### Using Scopes/Class Methods Continuing on from the preceding section, searching by scopes requires defining @@ -866,6 +904,28 @@ en: title: Old Ransack Namespaced Title ``` +### Updating From MetaSearch + +Ransack works much like MetaSearch, for those of you who are familiar with +it, and requires very little setup effort. + +If you're coming from MetaSearch, things to note: + + 1. The default param key for search params is now `:q`, instead of `:search`. + This is primarily to shorten query strings, though advanced queries (below) + will still run afoul of URL length limits in most browsers and require a + switch to HTTP POST requests. This key is + [configurable](default-search-parameter) via setting the `search_key` option + in your Ransack intitializer file. + + 2. `form_for` is now `search_form_for`, and validates that a Ransack::Search + object is passed to it. + + 3. Common ActiveRecord::Relation methods are no longer delegated by the + search object. Instead, you will get your search results (an + ActiveRecord::Relation in the case of the ActiveRecord adapter) via a call to + `Ransack#result`. + ## Mongoid Mongoid support has been moved to its own gem at [ransack-mongoid](https://github.com/activerecord-hackery/ransack-mongoid). diff --git a/lib/ransack/adapters/active_record/base.rb b/lib/ransack/adapters/active_record/base.rb index fa8ed32ee..9ad348a75 100644 --- a/lib/ransack/adapters/active_record/base.rb +++ b/lib/ransack/adapters/active_record/base.rb @@ -18,6 +18,10 @@ def ransack(params = {}, options = {}) Search.new(self, params, options) end + def ransack!(params = {}, options = {}) + ransack(params, options.merge(ignore_unknown_conditions: false)) + end + def ransacker(name, opts = {}, &block) self._ransackers = _ransackers.merge name.to_s => Ransacker .new(self, name, opts, &block) diff --git a/lib/ransack/search.rb b/lib/ransack/search.rb index 9b4384443..35ca06539 100644 --- a/lib/ransack/search.rb +++ b/lib/ransack/search.rb @@ -30,6 +30,7 @@ def initialize(object, params = {}, options = {}) ) @scope_args = {} @sorts ||= [] + @ignore_unknown_conditions = options[:ignore_unknown_conditions] == false ? false : true build(params.with_indifferent_access) end @@ -45,7 +46,7 @@ def build(params) base.send("#{key}=", value) elsif @context.ransackable_scope?(key, @context.object) add_scope(key, value) - elsif !Ransack.options[:ignore_unknown_conditions] + elsif !Ransack.options[:ignore_unknown_conditions] || !@ignore_unknown_conditions raise ArgumentError, "Invalid search term #{key}" end end diff --git a/spec/ransack/adapters/active_record/base_spec.rb b/spec/ransack/adapters/active_record/base_spec.rb index 180d5e6a1..3ede567d1 100644 --- a/spec/ransack/adapters/active_record/base_spec.rb +++ b/spec/ransack/adapters/active_record/base_spec.rb @@ -122,6 +122,10 @@ module ActiveRecord expect { Person.ransack('') }.to_not raise_error end + it 'raises exception if ransack! called with unknown condition' do + expect { Person.ransack!(unknown_attr_eq: 'Ernie') }.to raise_error + end + it 'does not modify the parameters' do params = { name_eq: '' } expect { Person.ransack(params) }.not_to change { params } diff --git a/spec/ransack/search_spec.rb b/spec/ransack/search_spec.rb index a824f1373..0b134b2c5 100644 --- a/spec/ransack/search_spec.rb +++ b/spec/ransack/search_spec.rb @@ -232,7 +232,7 @@ module Ransack context 'with an invalid condition' do subject { Search.new(Person, unknown_attr_eq: 'Ernie') } - context 'when ignore_unknown_conditions is false' do + context 'when ignore_unknown_conditions configuration option is false' do before do Ransack.configure { |c| c.ignore_unknown_conditions = false } end @@ -240,13 +240,39 @@ module Ransack specify { expect { subject }.to raise_error ArgumentError } end - context 'when ignore_unknown_conditions is true' do + context 'when ignore_unknown_conditions configuration option is true' do before do Ransack.configure { |c| c.ignore_unknown_conditions = true } end specify { expect { subject }.not_to raise_error } end + + subject(:with_ignore_unknown_conditions_false) { + Search.new(Person, + { unknown_attr_eq: 'Ernie' }, + { ignore_unknown_conditions: false } + ) + } + + subject(:with_ignore_unknown_conditions_true) { + Search.new(Person, + { unknown_attr_eq: 'Ernie' }, + { ignore_unknown_conditions: true } + ) + } + + context 'when ignore_unknown_conditions search parameter is absent' do + specify { expect { subject }.not_to raise_error } + end + + context 'when ignore_unknown_conditions search parameter is false' do + specify { expect { with_ignore_unknown_conditions_false }.to raise_error ArgumentError } + end + + context 'when ignore_unknown_conditions search parameter is true' do + specify { expect { with_ignore_unknown_conditions_true }.not_to raise_error } + end end it 'does not modify the parameters' do