Merge branch 'master' into allow-scopes-with-string-joins

This commit is contained in:
Sean 2022-03-28 01:53:27 +02:00 committed by GitHub
commit 1c69b9ddbf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 413 additions and 302 deletions

View File

@ -11,8 +11,8 @@ jobs:
fail-fast: false
matrix:
ruby:
- 3.0.0
- 2.7.2
- 3.0.2
- 2.7.4
env:
DB: sqlite3
RAILS: main
@ -33,8 +33,8 @@ jobs:
fail-fast: false
matrix:
ruby:
- 3.0.0
- 2.7.2
- 3.0.2
- 2.7.4
env:
DB: mysql
RAILS: main
@ -64,8 +64,8 @@ jobs:
fail-fast: false
matrix:
ruby:
- 3.0.0
- 2.7.2
- 3.0.2
- 2.7.4
env:
DB: postgres
RAILS: main

View File

@ -13,7 +13,7 @@ jobs:
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.0.0
ruby-version: 3.0.1
- name: Install gems
run: bundle install --jobs 4 --retry 3
- name: Run RuboCop

View File

@ -11,20 +11,17 @@ jobs:
fail-fast: false
matrix:
rails:
- v6.1.3
- v6.0.3
- v7.0.0
- v6.1.4
- v6.0.4
- 6-0-stable
- 5-2-stable
- v5.2.4
ruby:
- 3.0.0
- 2.7.2
- 2.6.6
- 3.0.2
- 2.7.4
- 2.6.7
exclude:
- rails: v5.2.4
ruby: 3.0.0
- rails: 5-2-stable
ruby: 3.0.0
- rails: v7.0.0
ruby: 2.6.7
env:
DB: sqlite3
RAILS: ${{ matrix.rails }}
@ -45,20 +42,17 @@ jobs:
fail-fast: false
matrix:
rails:
- v6.1.3
- v6.0.3
- v7.0.0
- v6.1.4
- v6.0.4
- 6-0-stable
- 5-2-stable
- v5.2.4
ruby:
- 3.0.0
- 2.7.2
- 2.6.6
- 3.0.2
- 2.7.4
- 2.6.7
exclude:
- rails: v5.2.4
ruby: 3.0.0
- rails: 5-2-stable
ruby: 3.0.0
- rails: v7.0.0
ruby: 2.6.7
env:
DB: mysql
RAILS: ${{ matrix.rails }}
@ -88,20 +82,17 @@ jobs:
fail-fast: false
matrix:
rails:
- v6.1.3
- v6.0.3
- v7.0.0
- v6.1.4
- v6.0.4
- 6-0-stable
- 5-2-stable
- v5.2.4
ruby:
- 3.0.0
- 2.7.2
- 2.6.6
- 3.0.2
- 2.7.4
- 2.6.7
exclude:
- rails: v5.2.4
ruby: 3.0.0
- rails: 5-2-stable
ruby: 3.0.0
- rails: v7.0.0
ruby: 2.6.7
env:
DB: postgres
RAILS: ${{ matrix.rails }}
@ -144,7 +135,7 @@ jobs:
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.0.0
ruby-version: 3.0.2
- name: Install dependencies
run: bundle install
- name: Run bug report templates

View File

@ -1,22 +1,83 @@
# Change Log
* Drop support for rubies under 2.5. PR #1189
## Unreleased
* Improve `sort_link` documentation.
* Deprecate passing two trailing hashes to `sort_link`, for example:
```ruby
sort_link(@q, :bussiness_name, "bussines_name", {}, class: "foo")
```
Pass a single hash with all options instead.
* Fix `:class` option to `sort_link` not being passed to the generated link
correctly when no additional options are passed. For example:
```ruby
sort_link(@q, :bussiness_name, class: "foo")
```
## 2.6.0 - 2022-03-08
* Fix regression when joining a table with itself.
PR [1275](https://github.com/activerecord-hackery/ransack/pull/1276)
* Drop support for ActiveRecord older than 6.0.4.
PR [1276](https://github.com/activerecord-hackery/ransack/pull/1276)
## 2.5.0 - 2021-12-26
* Document release process by @scarroll32 in https://github.com/activerecord-hackery/ransack/pull/1199, https://github.com/activerecord-hackery/ransack/pull/1200.
* Support Rails 7 by @yahonda in https://github.com/activerecord-hackery/ransack/pull/1205, https://github.com/activerecord-hackery/ransack/pull/1209, https://github.com/activerecord-hackery/ransack/pull/1234, https://github.com/activerecord-hackery/ransack/pull/1230, https://github.com/activerecord-hackery/ransack/pull/1266
* Fix for `ActiveRecord::UnknownAttributeReference` in ransack by @TechnologyHypofriend in https://github.com/activerecord-hackery/ransack/pull/1207
* Make gem compatible with old polyamorous require by @rtweeks in https://github.com/activerecord-hackery/ransack/pull/1145
* Adding swedish translations by @johanandre in https://github.com/activerecord-hackery/ransack/pull/1208
* Document how to do case insensitive searches by @scarroll32 in https://github.com/activerecord-hackery/ransack/pull/1213
* Add the ability to disable whitespace stripping for string searches by @DCrow in https://github.com/activerecord-hackery/ransack/pull/1214
* Fix `:default` option in `Translate.attribute` method by @coreyaus in https://github.com/activerecord-hackery/ransack/pull/1218
* Fix typo in README.md by @d-m-u in https://github.com/activerecord-hackery/ransack/pull/1220
* Fix another typo in README.md by @plan-do-break-fix in https://github.com/activerecord-hackery/ransack/pull/1221
* Fix several documentation typos @wonda-tea-coffee in https://github.com/activerecord-hackery/ransack/pull/1233
* Allow ransack to treat nulls as always first or last by @mollerhoj in https://github.com/activerecord-hackery/ransack/pull/1226
* Consider ransack aliases when sorting by @faragorn and @waldyr in https://github.com/activerecord-hackery/ransack/pull/1223
* Fix non-casted array predicates by @danielpclark in https://github.com/activerecord-hackery/ransack/pull/1246
* Remove Squeel references from README by @Schwad in https://github.com/activerecord-hackery/ransack/pull/1249
* Remove part of the README that might lead to incorrect results by @RadekMolenda in https://github.com/activerecord-hackery/ransack/pull/1258
* ActiveRecord 7.0 support
## 2.4.2 - 2021-01-23
* Enable RuboCop and configure GitHub Actions to run RuboCop by @yahonda in https://github.com/activerecord-hackery/ransack/pull/1185
* Add Ruby 3.0.0 support by @yahonda in https://github.com/activerecord-hackery/ransack/pull/1190
* Drop Ruby 2.5 or older versions of Ruby by @yahonda in https://github.com/activerecord-hackery/ransack/pull/1189
* Move bug report templates into ransack repository and run templates at CI by @yahonda in https://github.com/activerecord-hackery/ransack/pull/1191
* Allow Ransack to be tested with Rails main branch by @yahonda in https://github.com/activerecord-hackery/ransack/pull/1192
## 2.4.1 - 2020-12-21
* Add `ActiveRecord::Base.ransack!` which raises error if passed unknown condition
*Aaron Lipman*
* Links to Tidelift subscription by @deivid-rodriguez in https://github.com/activerecord-hackery/ransack/pull/1178
* Enable GitHub Actions by @yahonda in https://github.com/activerecord-hackery/ransack/pull/1180
* Move security contact information to SECURITY.md by @deivid-rodriguez in https://github.com/activerecord-hackery/ransack/pull/1179
* Add `ActiveRecord::Base.ransack!` which raises error if passed unknown condition by @alipman88 in https://github.com/activerecord-hackery/ransack/pull/1132
* Add ability to config PostgreSQL ORDER BY ... NULLS FIRST or NULLS LAST by @itsalongstory in https://github.com/activerecord-hackery/ransack/pull/1184
## 2.4.0 - 2020-11-27
* Support ActiveRecord 6.1.0.rc1.
PR [1172](https://github.com/activerecord-hackery/ransack/pull/1172)
* Fix for ActiveRecord 5.2.4 (note security fix in 5.2.4.2 for ActiveView's escape_javascript CVE-2020-5267 for all earlier versions)
* Drop support for ActiveRecord older than 5.2.4.
PR [1166](https://github.com/activerecord-hackery/ransack/pull/1166)
* Specify actual version of polyamorous, so we can release that separately by @gregmolnar in https://github.com/activerecord-hackery/ransack/pull/1101
* Only include necessary files in gem package by @tvdeyen in https://github.com/activerecord-hackery/ransack/pull/1104
* Test/Fix for subquery in Rails 5.2.4 by @stevenjonescgm in https://github.com/activerecord-hackery/ransack/pull/1112
* Polyamorous module by @varyonic in https://github.com/activerecord-hackery/ransack/pull/1113
* Remove duplicated rows by @sasharevzin in https://github.com/activerecord-hackery/ransack/pull/1116
* Fix Ruby 2.7 deprecation warnings by @terracatta in https://github.com/activerecord-hackery/ransack/pull/1121
* Fixes polymorphic joins. by @PhilCoggins in https://github.com/activerecord-hackery/ransack/pull/1122
* Drop support for activerecord older than 5.2.4 by @deivid-rodriguez in https://github.com/activerecord-hackery/ransack/pull/1166
* Adapt to quoting change in Rails by @deivid-rodriguez in https://github.com/activerecord-hackery/ransack/pull/1165
* Typo in docs by @brett-anderson in https://github.com/activerecord-hackery/ransack/pull/1155
* Add Rails 6.1 support by @deivid-rodriguez in https://github.com/activerecord-hackery/ransack/pull/1172
* Strip Leading & Trailing Whitespace Before Searching by @itsalongstory in https://github.com/activerecord-hackery/ransack/pull/1126
* Use unfrozen version of symbol to string by @fauno in https://github.com/activerecord-hackery/ransack/pull/1149
## 2.3.2 - 2020-01-11
@ -200,7 +261,7 @@
ignored when block parameter is specified.
PR [#818](https://github.com/activerecord-hackery/ransack/pull/818).
* No need pass some arugments to JoinAssociation#join_constraints in Rails 5.1.
* No need pass some arguments to JoinAssociation#join_constraints in Rails 5.1.
PR [#814](https://github.com/activerecord-hackery/ransack/pull/814).
Fixes [#807](https://github.com/activerecord-hackery/ransack/issues/807).
Reference [rails/rails#28267](https://github.com/rails/rails/pull/28267)

View File

@ -64,9 +64,7 @@ Here's a quick guide:
2. Create a thoughtfully-named branch for your changes (`git checkout -b my-new-feature`).
3. Install the development dependencies by running `bundle install`.
To install rails other than latest (set in Gemfile): `RAILS='5-2-stable' bundle install`
$ RAILS='5-2-stable' bundle install
To install rails other than latest (set in Gemfile): `RAILS='6-0-stable' bundle install`
4. Begin by running the tests. We only take pull requests with passing tests,
and it's great to know that you have a clean slate:

View File

@ -11,25 +11,22 @@ Ransack enables the creation of both
for your Ruby on Rails application
([demo source code here](https://github.com/activerecord-hackery/ransack_demo)).
If you're looking for something that simplifies query generation at the model
or controller layer, you're probably not looking for Ransack (or MetaSearch,
for that matter). Try [Squeel](https://github.com/activerecord-hackery/squeel)
instead.
or controller layer, you're probably not looking for Ransack.
## Getting started
Ransack is supported for Rails 6.1, 6.0, 5.2 on Ruby 2.6.6 and later.
Ransack is supported for Rails 7.0, 6.x on Ruby 2.6.6 and later.
In your Gemfile, for the last officially released gem:
To install `ransack` and add it to your Gemfile, run
```ruby
gem 'ransack'
bundle add ransack
```
If you would like to use the latest updates (recommended), use the `master`
branch:
If you would like to use the latest updates, use the `master` branch:
```ruby
gem 'ransack', github: 'activerecord-hackery/ransack'
bundle add ransack --github "activerecord-hackery/ransack"
```
## Issues tracker
@ -65,9 +62,6 @@ this example, with preloading each Person's Articles and pagination):
def index
@q = Person.ransack(params[:q])
@people = @q.result.includes(:articles).page(params[:page])
# or use `to_a.uniq` to remove duplicates (can also be done in the view):
@people = @q.result.includes(:articles).page(params[:page]).to_a.uniq
end
```
@ -152,8 +146,11 @@ The `search_form_for` answer format can be set like this:
```erb
<%= sort_link(@q, :name) %>
```
Additional options can be passed after the column attribute, like a different
column title or a default sort order:
Additional options can be passed after the column parameter, like a different
column title or a default sort order.
If the first option after the column parameter is a String, it's considered a
custom label for the link:
```erb
<%= sort_link(@q, :name, 'Last Name', default_order: :desc) %>
@ -175,7 +172,8 @@ explicitly to avoid an `uninitialized constant Model::Xxxable` error (see issue
<%= sort_link(@q, :xxxable_of_Ymodel_type_some_attribute, 'Attribute Name') %>
```
You can also sort on multiple fields by specifying an ordered array:
If the first option after the column parameter and/or the label parameter is an
Array, it will be used for sorting on multiple fields:
```erb
<%= sort_link(@q, :last_name, [:last_name, 'first_name asc'], 'Last Name') %>
@ -185,7 +183,8 @@ In the example above, clicking the link will sort by `last_name` and then
`first_name`. Specifying the sort direction on a field in the array tells
Ransack to _always_ sort that particular field in the specified direction.
Multiple `default_order` fields may also be specified with a hash:
Multiple `default_order` fields may also be specified with a trailing options
Hash:
```erb
<%= sort_link(@q, :last_name, %i(last_name first_name),
@ -214,6 +213,9 @@ and you can then sort by this virtual field:
<%= sort_link(@q, :reverse_name) %>
```
The trailing options Hash can also be used for passing additional options to the
generated link, like `class:`.
The sort link order indicator arrows may be globally customized by setting a
`custom_arrows` option in an initializer file like
`config/initializers/ransack.rb`.
@ -278,11 +280,19 @@ Ransack.configure do |c|
end
```
To treat nulls as having the lowest or highest value respectively. To force nulls to always be first or last, use
```rb
Ransack.configure do |c|
c.postgres_fields_sort_option = :nulls_always_first # or :nulls_always_last
end
```
See this feature: https://www.postgresql.org/docs/13/queries-order.html
#### Case Insensitive Sorting in PostgreSQL
In order to request PostgresSQL to do a case insensitive sort for all string columns of a model at once, Ransack can be extended by using this approach:
In order to request PostgreSQL to do a case insensitive sort for all string columns of a model at once, Ransack can be extended by using this approach:
```ruby
module RansackObject
@ -307,7 +317,7 @@ end
If this approach is taken, it is advisable to [add a functional index](https://www.postgresql.org/docs/13/citext.html).
This was originally asked in [a Ransack issue](https://github.com/activerecord-hackery/ransack/issues/1201) and a solution was found on [Stack Overflow](https://stackoverflow.com/a/34677378).
This was originally asked in [a Ransack issue](https://github.com/activerecord-hackery/ransack/issues/1201) and a solution was found on [Stack Overflow](https://stackoverflow.com/a/34677378).
### Advanced Mode
@ -349,32 +359,6 @@ construct much more complex search forms, such as the one on the
[demo app](http://ransack-demo.herokuapp.com/users/advanced_search)
(source code [here](https://github.com/activerecord-hackery/ransack_demo)).
### Ransack #search method
Ransack will try to make the class method `#search` available in your
models, but if `#search` has already been defined elsewhere, you can always use
the default `#ransack` class method. So the following are equivalent:
```ruby
Article.ransack(params[:q])
Article.search(params[:q])
```
Users have reported issues of `#search` name conflicts with other gems, so
the `#search` method alias will be deprecated in the next major version of
Ransack (2.0). It's advisable to use the default `#ransack` instead.
For now, if Ransack's `#search` method conflicts with the name of another
method named `search` in your code or another gem, you may resolve it either by
patching the `extended` class_method in `Ransack::Adapters::ActiveRecord::Base`
to remove the line `alias :search :ransack unless base.respond_to? :search`, or
by placing the following line in your Ransack initializer file at
`config/initializers/ransack.rb`:
```ruby
Ransack::Adapters::ActiveRecord::Base.class_eval('remove_method :search')
```
### Associations
You can easily use Ransack to search for objects in `has_many` and `belongs_to`
@ -467,6 +451,25 @@ query parameters in your URLs.
<% end %>
```
You can also use `ransack_alias` for sorting.
```ruby
class Post < ActiveRecord::Base
belongs_to :author
# Abbreviate :author_first_name to :author
ransack_alias :author, :author_first_name
end
```
Now, you can use `:author` instead of `:author_first_name` in a `sort_link`.
```erb
<%= sort_link(@q, :author) %>
```
Note that using `:author_first_name_or_author_last_name_cont` would produce an invalid sql query. In those cases, Ransack ignores the sorting clause.
### Search Matchers
List of all possible predicates

View File

@ -1,24 +0,0 @@
module Polyamorous
module JoinAssociationExtensions
include SwappingReflectionClass
def self.prepended(base)
base.class_eval { attr_reader :join_type }
end
def initialize(reflection, children, polymorphic_class = nil, join_type = Arel::Nodes::InnerJoin)
@join_type = join_type
if polymorphic_class && ::ActiveRecord::Base > polymorphic_class
swapping_reflection_klass(reflection, polymorphic_class) do |reflection|
super(reflection, children)
self.reflection.options[:polymorphic] = true
end
else
super(reflection, children)
end
end
def ==(other)
base_klass == other.base_klass
end
end
end

View File

@ -1,79 +0,0 @@
module Polyamorous
module JoinDependencyExtensions
# Replaces ActiveRecord::Associations::JoinDependency#build
def build(associations, base_klass)
associations.map do |name, right|
if name.is_a? Join
reflection = find_reflection base_klass, name.name
reflection.check_validity!
reflection.check_eager_loadable!
klass = if reflection.polymorphic?
name.klass || base_klass
else
reflection.klass
end
JoinAssociation.new(reflection, build(right, klass), name.klass, name.type)
else
reflection = find_reflection base_klass, name
reflection.check_validity!
reflection.check_eager_loadable!
if reflection.polymorphic?
raise ActiveRecord::EagerLoadPolymorphicError.new(reflection)
end
JoinAssociation.new(reflection, build(right, reflection.klass))
end
end
end
def join_constraints(joins_to_add, join_type, alias_tracker)
@alias_tracker = alias_tracker
construct_tables!(join_root)
joins = make_join_constraints(join_root, join_type)
joins.concat joins_to_add.flat_map { |oj|
construct_tables!(oj.join_root)
if join_root.match?(oj.join_root) && join_root.table.name == oj.join_root.table.name
walk join_root, oj.join_root
else
make_join_constraints(oj.join_root, join_type)
end
}
end
private
def make_constraints(parent, child, join_type = Arel::Nodes::OuterJoin)
foreign_table = parent.table
foreign_klass = parent.base_klass
join_type = child.join_type || join_type if join_type == Arel::Nodes::InnerJoin
joins = child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker)
joins.concat child.children.flat_map { |c| make_constraints(child, c, join_type) }
end
module ClassMethods
# Prepended before ActiveRecord::Associations::JoinDependency#walk_tree
#
def walk_tree(associations, hash)
case associations
when TreeNode
associations.add_to_tree(hash)
when Hash
associations.each do |k, v|
cache =
if TreeNode === k
k.add_to_tree(hash)
else
hash[k] ||= {}
end
walk_tree(v, cache)
end
else
super(associations, hash)
end
end
end
end
end

View File

@ -1,11 +0,0 @@
module Polyamorous
module ReflectionExtensions
def join_scope(table, foreign_table, foreign_klass)
if respond_to?(:polymorphic?) && polymorphic?
super.where!(foreign_table[foreign_type].eq(klass.name))
else
super
end
end
end
end

View File

@ -1 +1,20 @@
require 'polyamorous/activerecord_5.2_ruby_2/join_association'
module Polyamorous
module JoinAssociationExtensions
include SwappingReflectionClass
def self.prepended(base)
base.class_eval { attr_reader :join_type }
end
def initialize(reflection, children, polymorphic_class = nil, join_type = Arel::Nodes::InnerJoin)
@join_type = join_type
if polymorphic_class && ::ActiveRecord::Base > polymorphic_class
swapping_reflection_klass(reflection, polymorphic_class) do |reflection|
super(reflection, children)
self.reflection.options[:polymorphic] = true
end
else
super(reflection, children)
end
end
end
end

View File

@ -1,4 +1,3 @@
# active_record_6.0_ruby_2/join_dependency.rb
module Polyamorous
module JoinDependencyExtensions
# Replaces ActiveRecord::Associations::JoinDependency#build

View File

@ -1 +1,11 @@
require 'polyamorous/activerecord_5.2_ruby_2/reflection'
module Polyamorous
module ReflectionExtensions
def join_scope(table, foreign_table, foreign_klass)
if respond_to?(:polymorphic?) && polymorphic?
super.where!(foreign_table[foreign_type].eq(klass.name))
else
super
end
end
end
end

View File

@ -66,9 +66,5 @@ module Polyamorous
joins
end
def ==(other)
base_klass == other.base_klass
end
end
end

View File

@ -1,4 +1,3 @@
# active_record_6.1_ruby_2/join_dependency.rb
module Polyamorous
module JoinDependencyExtensions
# Replaces ActiveRecord::Associations::JoinDependency#build

View File

@ -4,7 +4,6 @@ module Ransack
module Base
def self.extended(base)
alias :search :ransack unless base.respond_to? :search
base.class_eval do
class_attribute :_ransackers
class_attribute :_ransack_aliases
@ -14,7 +13,6 @@ module Ransack
end
def ransack(params = {}, options = {})
ActiveSupport::Deprecation.warn("#search is deprecated and will be removed in 2.3, please use #ransack instead") if __callee__ == :search
Search.new(self, params, options)
end
@ -70,7 +68,7 @@ module Ransack
end
# ransack_scope_skip_sanitize_args, by default, returns an empty array.
# i.e. use the sanitize_scope_args setting to determin if args should be converted.
# i.e. use the sanitize_scope_args setting to determine if args should be converted.
# For overriding with a list of scopes which should be passed the args as-is.
#
def ransackable_scopes_skip_sanitize_args

View File

@ -47,6 +47,10 @@ module Ransack
scope_or_sort = scope_or_sort.direction == :asc ? Arel.sql("#{scope_or_sort.to_sql} NULLS FIRST") : Arel.sql("#{scope_or_sort.to_sql} NULLS LAST")
when :nulls_last
scope_or_sort = scope_or_sort.direction == :asc ? Arel.sql("#{scope_or_sort.to_sql} NULLS LAST") : Arel.sql("#{scope_or_sort.to_sql} NULLS FIRST")
when :nulls_always_first
scope_or_sort = Arel.sql("#{scope_or_sort.to_sql} NULLS FIRST")
when :nulls_always_last
scope_or_sort = Arel.sql("#{scope_or_sort.to_sql} NULLS LAST")
end
relation = relation.order(scope_or_sort)

View File

@ -47,18 +47,19 @@ module Ransack
end
def casted_array?(predicate)
(predicate.respond_to?(:value) && predicate.value.is_a?(Array)) || # Rails 6.1
(predicate.respond_to?(:val) && predicate.val.is_a?(Array)) # Rails 5.2, 6.0
value_from(predicate).is_a?(Array) && predicate.is_a?(Arel::Nodes::Casted)
end
def value_from(predicate)
if predicate.respond_to?(:value)
predicate.value # Rails 6.1
elsif predicate.respond_to?(:val)
predicate.val # Rails 6.0
end
end
def format_values_for(predicate)
value = if predicate.respond_to?(:value)
predicate.value # Rails 6.1
else
predicate.val # Rails 5.2, 6.0
end
value.map do |val|
value_from(predicate).map do |val|
val.is_a?(String) ? Arel::Nodes.build_quoted(val) : val
end
end

View File

@ -149,7 +149,7 @@ module Ransack
# User may want to configure it like this:
#
# Ransack.configure do |c|
# c.postgres_fields_sort_option = :nulls_first # or :nulls_last
# c.postgres_fields_sort_option = :nulls_first # or e.g. :nulls_always_last
# end
#
# See this feature: https://www.postgresql.org/docs/13/queries-order.html

View File

@ -130,12 +130,20 @@ module Ransack
def url_options
@params.merge(
@options.merge(
@options.except(:class).merge(
@search.context.search_key => search_and_sort_params))
end
def html_options(args)
html_options = extract_options_and_mutate_args!(args)
if args.empty?
html_options = @options
else
deprecation_message = "Passing two trailing hashes to `sort_link` is deprecated, merge the trailing hashes into a single one."
caller_location = caller_locations(2, 2).first
warn "#{deprecation_message} (called at #{caller_location.path}:#{caller_location.lineno})"
html_options = extract_options_and_mutate_args!(args)
end
html_options.merge(
class: [['sort_link'.freeze, @current_dir], html_options[:class]]
.compact.join(' '.freeze)
@ -145,7 +153,7 @@ module Ransack
private
def parameters_hash(params)
if ::ActiveRecord::VERSION::MAJOR >= 5 && params.respond_to?(:to_unsafe_h)
if params.respond_to?(:to_unsafe_h)
params.to_unsafe_h
else
params

View File

@ -31,8 +31,8 @@ module Ransack
end
def name=(name)
@name = name
context.bind(self, name)
@name = context.ransackable_alias(name) || name
context.bind(self, @name)
end
def dir=(dir)

View File

@ -43,10 +43,10 @@ module Ransack
collapse_multiparameter_attributes!(params).each do |key, value|
if ['s'.freeze, 'sorts'.freeze].freeze.include?(key)
send("#{key}=", value)
elsif base.attribute_method?(key)
base.send("#{key}=", value)
elsif @context.ransackable_scope?(key, @context.object)
add_scope(key, value)
elsif base.attribute_method?(key)
base.send("#{key}=", value)
elsif !Ransack.options[:ignore_unknown_conditions] || !@ignore_unknown_conditions
raise ArgumentError, "Invalid search term #{key}"
end

View File

@ -1,3 +1,3 @@
module Ransack
VERSION = '2.4.2'
VERSION = '2.6.0'
end

View File

@ -15,8 +15,8 @@ Gem::Specification.new do |s|
s.required_ruby_version = '>= 2.6'
s.license = 'MIT'
s.add_dependency 'activerecord', '>= 5.2.4'
s.add_dependency 'activesupport', '>= 5.2.4'
s.add_dependency 'activerecord', '>= 6.0.4'
s.add_dependency 'activesupport', '>= 6.0.4'
s.add_dependency 'i18n'
s.files = `git ls-files`.split("\n")

View File

@ -0,0 +1,15 @@
require 'spec_helper'
module Polyamorous
describe "ActiveRecord Compatibility" do
it 'works with self joins and includes' do
trade_account = Account.create!
Account.create!(trade_account: trade_account)
accounts = Account.joins(:trade_account).includes(:trade_account, :agent_account)
account = accounts.first
expect(account.agent_account).to be_nil
end
end
end

View File

@ -12,12 +12,7 @@ module Polyamorous
subject { new_join_association(reflection, parent.children, Person) }
it 'respects polymorphism on equality test' do
expect(subject).to eq new_join_association(reflection, parent.children, Person)
expect(subject).not_to eq new_join_association(reflection, parent.children, Article)
end
it 'leaves the orginal reflection intact for thread safety' do
it 'leaves the original reflection intact for thread safety' do
reflection.instance_variable_set(:@klass, Article)
join_association
.swapping_reflection_klass(reflection, Person) do |new_reflection|

View File

@ -77,21 +77,5 @@ module Polyamorous
specify { expect(subject.send(:join_root).drop(1)[1].table_name)
.to eq 'comments' }
end
context '#left_outer_join in Rails 5 overrides join type specified',
if: ActiveRecord::VERSION::MAJOR >= 5 && ActiveRecord::VERSION::MAJOR < 6 && ActiveRecord::VERSION::MINOR < 2 do
let(:join_type_class) do
new_join_dependency(
Person,
new_join(:articles)
).join_constraints(
[],
Arel::Nodes::OuterJoin
).first.joins.map(&:class)
end
specify { expect(join_type_class).to eq [Arel::Nodes::OuterJoin] }
end
end
end

View File

@ -8,7 +8,6 @@ module Ransack
subject { ::ActiveRecord::Base }
it { should respond_to :ransack }
it { should respond_to :search }
describe '#search' do
subject { Person.ransack }
@ -44,12 +43,12 @@ module Ransack
it 'applies stringy boolean scopes with true value in an array' do
s = Person.ransack('of_age' => ['true'])
expect(s.result.to_sql).to (include 'age >= 18')
expect(s.result.to_sql).to (include rails7_and_mysql ? %q{(age >= '18')} : 'age >= 18')
end
it 'applies stringy boolean scopes with false value in an array' do
s = Person.ransack('of_age' => ['false'])
expect(s.result.to_sql).to (include 'age < 18')
expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age < '18'} : 'age < 18')
end
it 'ignores unlisted scopes' do
@ -69,12 +68,12 @@ module Ransack
it 'passes values to scopes' do
s = Person.ransack('over_age' => 18)
expect(s.result.to_sql).to (include 'age > 18')
expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '18'} : 'age > 18')
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 rails7_and_mysql ? %q{age > '18'} : 'age > 18')
expect(s.result.to_sql).to (include 'active = 1')
end
@ -99,12 +98,12 @@ module Ransack
it 'passes true values to scopes' do
s = Person.ransack('over_age' => 1)
expect(s.result.to_sql).to (include 'age > 1')
expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '1'} : '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')
expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '0'} : 'age > 0')
end
end
@ -117,12 +116,12 @@ module Ransack
it 'passes true values to scopes' do
s = Person.ransack('over_age' => 1)
expect(s.result.to_sql).to (include 'age > 1')
expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '1'} : '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')
expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '0'} : 'age > 0')
end
end
@ -324,7 +323,11 @@ module Ransack
end
it 'should function correctly with a multi-parameter attribute' do
::ActiveRecord::Base.default_timezone = :utc
if ::ActiveRecord::VERSION::MAJOR >= 7
::ActiveRecord.default_timezone = :utc
else
::ActiveRecord::Base.default_timezone = :utc
end
Time.zone = 'UTC'
date = Date.current
@ -700,6 +703,10 @@ module Ransack
it { should eq [] }
end
private
def rails7_and_mysql
::ActiveRecord::VERSION::MAJOR >= 7 && ENV['DB'] == 'mysql'
end
end
end
end

View File

@ -469,8 +469,7 @@ module Ransack
it { should match /exist\=existing/ }
end
context 'using a real ActionController::Parameter object',
if: ::ActiveRecord::VERSION::MAJOR > 3 do
context 'using a real ActionController::Parameter object' do
describe 'with symbol q:, #sort_link should include search params' do
subject { @controller.view_context.sort_link(Person.ransack, :name) }
@ -727,6 +726,38 @@ module Ransack
it { should match /Block label&nbsp;&#9660;/ }
end
describe '#sort_link with class option' do
subject { @controller.view_context
.sort_link(
[:main_app, Person.ransack(sorts: ['name desc'])],
:name,
class: 'people', controller: 'people'
)
}
it { should match /class="sort_link desc people"/ }
it { should_not match /people\?class=people/ }
end
describe '#sort_link with class option workaround' do
it "generates a correct link and prints a deprecation" do
expect do
link = @controller.view_context
.sort_link(
[:main_app, Person.ransack(sorts: ['name desc'])],
:name,
'name',
{ controller: 'people' },
class: 'people'
)
expect(link).to match(/class="sort_link desc people"/)
expect(link).not_to match(/people\?class=people/)
end.to output(
/Passing two trailing hashes to `sort_link` is deprecated, merge the trailing hashes into a single one\. \(called at #{Regexp.escape(__FILE__)}:/
).to_stderr
end
end
describe '#search_form_for with default format' do
subject { @controller.view_context
.search_form_for(Person.ransack) {} }

View File

@ -3,6 +3,19 @@ require 'spec_helper'
module Ransack
module Nodes
describe Condition do
context 'bug report #1245' do
it 'preserves tuple behavior' do
ransack_hash = {
m: 'and',
g: [
{ title_type_in: ['["title 1", ""]'] }
]
}
sql = Article.ransack(ransack_hash).result.to_sql
expect(sql).to include("IN (('title 1', ''))")
end
end
context 'with an alias' do
subject {

View File

@ -312,6 +312,29 @@ module Ransack
expect { Search.new(Person, params) }.not_to change { params }
end
context "ransackable_scope" do
around(:each) do |example|
Person.define_singleton_method(:name_eq) do |name|
self.where(name: name)
end
begin
example.run
ensure
Person.singleton_class.undef_method :name_eq
end
end
it "is prioritized over base predicates" do
allow(Person).to receive(:ransackable_scopes)
.and_return(Person.ransackable_scopes + [:name_eq])
s = Search.new(Person, name_eq: "Johny")
expect(s.instance_variable_get(:@scope_args)["name_eq"]).to eq("Johny")
expect(s.base[:name_eq]).to be_nil
end
end
end
describe '#result' do
@ -332,8 +355,6 @@ module Ransack
end
it 'use appropriate table alias' do
skip "Rails 6 regressed here, but it's fixed in 6-0-stable since https://github.com/rails/rails/commit/f9ba52477ca288e7effa5f6794ae3df3f4e982bc" if ENV["RAILS"] == "v6.0.3"
s = Search.new(Person, {
name_eq: "person_name_query",
articles_title_eq: "person_article_title_query",
@ -483,82 +504,109 @@ module Ransack
expect(sort.dir).to eq 'asc'
end
it 'creates sorts based on multiple attributes/directions in array format' do
@s.sorts = ['id desc', { name: 'name', dir: 'asc' }]
it 'creates sorts based on a single alias/direction' do
@s.sorts = 'daddy desc'
expect(@s.sorts.size).to eq(1)
sort = @s.sorts.first
expect(sort).to be_a Nodes::Sort
expect(sort.name).to eq 'parent_name'
expect(sort.dir).to eq 'desc'
end
it 'creates sorts based on a single alias and uppercase direction' do
@s.sorts = 'daddy DESC'
expect(@s.sorts.size).to eq(1)
sort = @s.sorts.first
expect(sort).to be_a Nodes::Sort
expect(sort.name).to eq 'parent_name'
expect(sort.dir).to eq 'desc'
end
it 'creates sorts based on a single alias and without direction' do
@s.sorts = 'daddy'
expect(@s.sorts.size).to eq(1)
sort = @s.sorts.first
expect(sort).to be_a Nodes::Sort
expect(sort.name).to eq 'parent_name'
expect(sort.dir).to eq 'asc'
end
it 'creates sorts based on attributes, alias and directions in array format' do
@s.sorts = ['id desc', { name: 'daddy', dir: 'asc' }]
expect(@s.sorts.size).to eq(2)
sort1, sort2 = @s.sorts
expect(sort1).to be_a Nodes::Sort
expect(sort1.name).to eq 'id'
expect(sort1.dir).to eq 'desc'
expect(sort2).to be_a Nodes::Sort
expect(sort2.name).to eq 'name'
expect(sort2.name).to eq 'parent_name'
expect(sort2.dir).to eq 'asc'
end
it 'creates sorts based on multiple attributes and uppercase directions in array format' do
@s.sorts = ['id DESC', { name: 'name', dir: 'ASC' }]
it 'creates sorts based on attributes, alias and uppercase directions in array format' do
@s.sorts = ['id DESC', { name: 'daddy', dir: 'ASC' }]
expect(@s.sorts.size).to eq(2)
sort1, sort2 = @s.sorts
expect(sort1).to be_a Nodes::Sort
expect(sort1.name).to eq 'id'
expect(sort1.dir).to eq 'desc'
expect(sort2).to be_a Nodes::Sort
expect(sort2.name).to eq 'name'
expect(sort2.name).to eq 'parent_name'
expect(sort2.dir).to eq 'asc'
end
it 'creates sorts based on multiple attributes and different directions
it 'creates sorts based on attributes, alias and different directions
in array format' do
@s.sorts = ['id DESC', { name: 'name', dir: nil }]
@s.sorts = ['id DESC', { name: 'daddy', dir: nil }]
expect(@s.sorts.size).to eq(2)
sort1, sort2 = @s.sorts
expect(sort1).to be_a Nodes::Sort
expect(sort1.name).to eq 'id'
expect(sort1.dir).to eq 'desc'
expect(sort2).to be_a Nodes::Sort
expect(sort2.name).to eq 'name'
expect(sort2.name).to eq 'parent_name'
expect(sort2.dir).to eq 'asc'
end
it 'creates sorts based on multiple attributes/directions in hash format' do
it 'creates sorts based on attributes, alias and directions in hash format' do
@s.sorts = {
'0' => { name: 'id', dir: 'desc' },
'1' => { name: 'name', dir: 'asc' }
'1' => { name: 'daddy', dir: 'asc' }
}
expect(@s.sorts.size).to eq(2)
expect(@s.sorts).to be_all { |s| Nodes::Sort === s }
id_sort = @s.sorts.detect { |s| s.name == 'id' }
name_sort = @s.sorts.detect { |s| s.name == 'name' }
daddy_sort = @s.sorts.detect { |s| s.name == 'parent_name' }
expect(id_sort.dir).to eq 'desc'
expect(name_sort.dir).to eq 'asc'
expect(daddy_sort.dir).to eq 'asc'
end
it 'creates sorts based on multiple attributes and uppercase directions
it 'creates sorts based on attributes, alias and uppercase directions
in hash format' do
@s.sorts = {
'0' => { name: 'id', dir: 'DESC' },
'1' => { name: 'name', dir: 'ASC' }
'1' => { name: 'daddy', dir: 'ASC' }
}
expect(@s.sorts.size).to eq(2)
expect(@s.sorts).to be_all { |s| Nodes::Sort === s }
id_sort = @s.sorts.detect { |s| s.name == 'id' }
name_sort = @s.sorts.detect { |s| s.name == 'name' }
daddy_sort = @s.sorts.detect { |s| s.name == 'parent_name' }
expect(id_sort.dir).to eq 'desc'
expect(name_sort.dir).to eq 'asc'
expect(daddy_sort.dir).to eq 'asc'
end
it 'creates sorts based on multiple attributes and different directions
it 'creates sorts based on attributes, alias and different directions
in hash format' do
@s.sorts = {
'0' => { name: 'id', dir: 'DESC' },
'1' => { name: 'name', dir: nil }
'1' => { name: 'daddy', dir: nil }
}
expect(@s.sorts.size).to eq(2)
expect(@s.sorts).to be_all { |s| Nodes::Sort === s }
id_sort = @s.sorts.detect { |s| s.name == 'id' }
name_sort = @s.sorts.detect { |s| s.name == 'name' }
daddy_sort = @s.sorts.detect { |s| s.name == 'parent_name' }
expect(id_sort.dir).to eq 'desc'
expect(name_sort.dir).to eq 'asc'
expect(daddy_sort.dir).to eq 'asc'
end
it 'overrides existing sort' do
@ -605,6 +653,18 @@ module Ransack
s = Search.new(Person, s: 'doubled_name desc')
expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" DESC NULLS FIRST"
Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_always_first }
s = Search.new(Person, s: 'doubled_name asc')
expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" ASC NULLS FIRST"
s = Search.new(Person, s: 'doubled_name desc')
expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" DESC NULLS FIRST"
Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_always_last }
s = Search.new(Person, s: 'doubled_name asc')
expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" ASC NULLS LAST"
s = Search.new(Person, s: 'doubled_name desc')
expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" DESC NULLS LAST"
Ransack.options = default
end
end

View File

@ -154,6 +154,29 @@ class Article < ActiveRecord::Base
joins(join).where("latest_comment.body ILIKE ?", "%#{msg}%")
}
ransacker :title_type, formatter: lambda { |tuples|
title, type = JSON.parse(tuples)
Arel::Nodes::Grouping.new(
[
Arel::Nodes.build_quoted(title),
Arel::Nodes.build_quoted(type)
]
)
} do |_parent|
articles = Article.arel_table
Arel::Nodes::Grouping.new(
%i[title type].map do |field|
Arel::Nodes::NamedFunction.new(
'COALESCE',
[
Arel::Nodes::NamedFunction.new('TRIM', [articles[field]]),
Arel::Nodes.build_quoted('')
]
)
end
)
end
end
class StoryArticle < Article
@ -192,6 +215,11 @@ class Note < ActiveRecord::Base
belongs_to :notable, polymorphic: true
end
class Account < ActiveRecord::Base
belongs_to :agent_account, class_name: "Account"
belongs_to :trade_account, class_name: "Account"
end
module Schema
def self.create
ActiveRecord::Migration.verbose = false
@ -250,6 +278,11 @@ module Schema
t.integer :target_person_id
t.integer :article_id
end
create_table :accounts, force: true do |t|
t.belongs_to :agent_account
t.belongs_to :trade_account
end
end
10.times do