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 ransack! method which raises error if passed unknown condition #1132

Merged
merged 2 commits into from
Dec 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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

*
Expand Down
98 changes: 79 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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`,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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).
Expand Down
4 changes: 4 additions & 0 deletions lib/ransack/adapters/active_record/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion lib/ransack/search.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions spec/ransack/adapters/active_record/base_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
30 changes: 28 additions & 2 deletions spec/ransack/search_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -232,21 +232,47 @@ 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

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
Expand Down