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

Set new error to replace ArgumentError #1478

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
73 changes: 73 additions & 0 deletions docs/docs/getting-started/error_handling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
---
sidebar_position: 6
title: Error Handling
---

Ransack can be configured to raise errors with attributes, predicates or sorting misusing

## Configuring error raising

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
```

The error raised by Ransack is `Ransack::InvalidSearchError`:

```ruby
Article.ransack(unknown_attr_eq: 'Ernie')
# Ransack::InvalidSearchError (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')
# Ransack::InvalidSearchError: Invalid search term unknown_attr_eq
```

This is equivalent to the `ignore_unknown_conditions` configuration option,
except it may be applied on a case-by-case basis.

Same way, Ransack with raise an error related to invalid `sorting` argument. If an invalid
sorting value is passed and Ransack is enabled to raise errors, it will raise the same `Ransack::InvalidSearchError` error:

```ruby
Article.ransack!(content_eq: 'Ernie', sorts: 1234)
# Ransack::InvalidSearchError: Invalid sorting parameter provided
```

## Rescuing errors

If you want, it's possible to rescue `Ransack::InvalidSearchError` to handle an API response instead
of having this error massing a request. For example, if can add something like this on our `ApplicationController`:

```ruby
class ApplicationController < ActionController::Base
rescue_from Ransack::InvalidSearchError, with: :invalid_response

private

def invalid_response
render 'shared/errors'
end
end
```

Here `Ransack::InvalidSearchError` is being rescued and a view `shared/errors` is rendered. If you want, it's also possible
to use this same strategy when using API-only applications.
1 change: 1 addition & 0 deletions docs/docs/getting-started/search-matches.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
---
sidebar_position: 4
title: Search Matchers
---

Expand Down
1 change: 1 addition & 0 deletions docs/docs/getting-started/sorting.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
---
sidebar_position: 5
title: Sorting
---

Expand Down
37 changes: 0 additions & 37 deletions docs/docs/going-further/other-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,43 +243,6 @@ 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 equivalent 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
3 changes: 3 additions & 0 deletions lib/ransack/invalid_search_error.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Ransack
class InvalidSearchError < ArgumentError; end
end
8 changes: 5 additions & 3 deletions lib/ransack/search.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
require 'ransack/nodes/grouping'
require 'ransack/context'
require 'ransack/naming'
require 'ransack/invalid_search_error'

module Ransack
class Search
Expand Down Expand Up @@ -53,7 +54,8 @@ def build(params)
elsif base.attribute_method?(key)
base.send("#{key}=", value)
elsif !Ransack.options[:ignore_unknown_conditions] || !@ignore_unknown_conditions
raise ArgumentError, "Invalid search term #{key}"
puts '[DEPRECATED] ArgumentError raising will be depreated soon. Take care of replacing related rescues from ArgumentError to InvalidSearchError'
raise InvalidSearchError, "Invalid search term #{key}"
end
end
self
Expand All @@ -78,8 +80,8 @@ def sorts=(args)
when String
self.sorts = [args]
else
raise ArgumentError,
"Invalid argument (#{args.class}) supplied to sorts="
puts '[DEPRECATED] ArgumentError raising will be depreated soon. Take care of replacing related rescues from ArgumentError to InvalidSearchError'
raise InvalidSearchError, "Invalid sorting parameter provided"
end
end
alias :s= :sorts=
Expand Down
12 changes: 11 additions & 1 deletion spec/ransack/adapters/active_record/base_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,20 @@ module ActiveRecord
expect { Person.ransack('') }.to_not raise_error
end

it 'raises exception if ransack! called with unknown condition' do
it 'raises InvalidSearchError exception if ransack! called with unknown condition' do
expect { Person.ransack!(unknown_attr_eq: 'Ernie') }.to raise_error(InvalidSearchError)
end

it 'raises ArgumentError exception if ransack! called with unknown condition' do
expect { Person.ransack!(unknown_attr_eq: 'Ernie') }.to raise_error(ArgumentError)
end

it 'writes a deprecation message about ArgumentError error raising' do
expect do
Person.ransack!(unknown_attr_eq: 'Ernie') rescue ArgumentError
end.to output("[DEPRECATED] ArgumentError raising will be depreated soon. Take care of replacing related rescues from ArgumentError to InvalidSearchError\n").to_stdout
end

it 'does not modify the parameters' do
params = { name_eq: '' }
expect { Person.ransack(params) }.not_to change { params }
Expand Down
50 changes: 40 additions & 10 deletions spec/ransack/search_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,13 @@ module Ransack
end

specify { expect { subject }.to raise_error ArgumentError }
specify { expect { subject }.to raise_error InvalidSearchError }

it 'writes a deprecation message about ArgumentError error raising' do
expect do
subject rescue ArgumentError
end.to output("[DEPRECATED] ArgumentError raising will be depreated soon. Take care of replacing related rescues from ArgumentError to InvalidSearchError\n").to_stdout
end
end

context 'when ignore_unknown_conditions configuration option is true' do
Expand Down Expand Up @@ -300,6 +307,13 @@ module Ransack

context 'when ignore_unknown_conditions search parameter is false' do
specify { expect { with_ignore_unknown_conditions_false }.to raise_error ArgumentError }
specify { expect { with_ignore_unknown_conditions_false }.to raise_error InvalidSearchError }

it 'writes a deprecation message about ArgumentError error raising' do
expect do
with_ignore_unknown_conditions_false rescue ArgumentError
end.to output("[DEPRECATED] ArgumentError raising will be depreated soon. Take care of replacing related rescues from ArgumentError to InvalidSearchError\n").to_stdout
end
end

context 'when ignore_unknown_conditions search parameter is true' do
Expand Down Expand Up @@ -350,8 +364,7 @@ module Ransack
it 'evaluates conditions contextually' do
s = Search.new(Person, children_name_eq: 'Ernie')
expect(s.result).to be_an ActiveRecord::Relation
expect(s.result.to_sql).to match /#{
children_people_name_field} = 'Ernie'/
expect(s.result.to_sql).to match /#{children_people_name_field} = 'Ernie'/
end

it 'use appropriate table alias' do
Expand Down Expand Up @@ -423,10 +436,10 @@ module Ransack
expect(s).to be_an ActiveRecord::Relation
first, last = s.to_sql.split(/ AND /)
expect(first).to match /#{children_people_name_field} = 'Ernie'/
expect(last).to match /#{
people_name_field} = 'Ernie' OR #{
quote_table_name("children_people_2")}.#{
quote_column_name("name")} = 'Ernie'/
expect(last).to match(Regexp.new(
"#{people_name_field} = 'Ernie' OR " +
"#{quote_table_name("children_people_2")}.#{quote_column_name("name")} = 'Ernie'")
)
end

it 'evaluates arrays of groupings' do
Expand All @@ -438,10 +451,8 @@ module Ransack
).result
expect(s).to be_an ActiveRecord::Relation
first, last = s.to_sql.split(/ AND /)
expect(first).to match /#{people_name_field} = 'Ernie' OR #{
children_people_name_field} = 'Ernie'/
expect(last).to match /#{people_name_field} = 'Bert' OR #{
children_people_name_field} = 'Bert'/
expect(first).to match /#{people_name_field} = 'Ernie' OR #{children_people_name_field} = 'Ernie'/
expect(last).to match /#{people_name_field} = 'Bert' OR #{children_people_name_field} = 'Bert'/
end

it 'returns distinct records when passed distinct: true' do
Expand Down Expand Up @@ -614,6 +625,25 @@ def remove_quotes_and_backticks(str)
expect(@s.result.first.id).to eq 1
end

it 'raises InvalidSearchError when a invalid argument is sent' do
expect do
@s.sorts = 1234
end.to raise_error(Ransack::InvalidSearchError, "Invalid sorting parameter provided")
end

it 'raises ArgumentError when a invalid argument is sent' do
expect do
@s.sorts = 1234
end.to raise_error(ArgumentError, "Invalid sorting parameter provided")
end

it 'writes a deprecation message about ArgumentError error raising' do
expect do
@s.sorts = 1234
rescue ArgumentError
end.to output("[DEPRECATED] ArgumentError raising will be depreated soon. Take care of replacing related rescues from ArgumentError to InvalidSearchError\n").to_stdout
end

it "PG's sort option", if: ::ActiveRecord::Base.connection.adapter_name == "PostgreSQL" do
default = Ransack.options.clone

Expand Down
Loading