diff --git a/.github/workflows/cronjob.yml b/.github/workflows/cronjob.yml index 5677add..2a7397b 100644 --- a/.github/workflows/cronjob.yml +++ b/.github/workflows/cronjob.yml @@ -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 diff --git a/.github/workflows/rubocop.yml b/.github/workflows/rubocop.yml index 420333c..32e0f8b 100644 --- a/.github/workflows/rubocop.yml +++ b/.github/workflows/rubocop.yml @@ -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 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d84d0e0..cc3809c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index d5261bd..367bf13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2c2c5f1..c71f5e7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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: diff --git a/README.md b/README.md index 6930c51..836e70a 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/lib/polyamorous/activerecord_5.2_ruby_2/join_association.rb b/lib/polyamorous/activerecord_5.2_ruby_2/join_association.rb deleted file mode 100644 index edfff82..0000000 --- a/lib/polyamorous/activerecord_5.2_ruby_2/join_association.rb +++ /dev/null @@ -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 diff --git a/lib/polyamorous/activerecord_5.2_ruby_2/join_dependency.rb b/lib/polyamorous/activerecord_5.2_ruby_2/join_dependency.rb deleted file mode 100644 index 58b9f67..0000000 --- a/lib/polyamorous/activerecord_5.2_ruby_2/join_dependency.rb +++ /dev/null @@ -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 diff --git a/lib/polyamorous/activerecord_5.2_ruby_2/reflection.rb b/lib/polyamorous/activerecord_5.2_ruby_2/reflection.rb deleted file mode 100644 index bea4b97..0000000 --- a/lib/polyamorous/activerecord_5.2_ruby_2/reflection.rb +++ /dev/null @@ -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 diff --git a/lib/polyamorous/activerecord_6.0_ruby_2/join_association.rb b/lib/polyamorous/activerecord_6.0_ruby_2/join_association.rb index 7a03623..fd836fa 100644 --- a/lib/polyamorous/activerecord_6.0_ruby_2/join_association.rb +++ b/lib/polyamorous/activerecord_6.0_ruby_2/join_association.rb @@ -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 diff --git a/lib/polyamorous/activerecord_6.0_ruby_2/join_dependency.rb b/lib/polyamorous/activerecord_6.0_ruby_2/join_dependency.rb index e2ce3e3..201cb53 100644 --- a/lib/polyamorous/activerecord_6.0_ruby_2/join_dependency.rb +++ b/lib/polyamorous/activerecord_6.0_ruby_2/join_dependency.rb @@ -1,4 +1,3 @@ -# active_record_6.0_ruby_2/join_dependency.rb module Polyamorous module JoinDependencyExtensions # Replaces ActiveRecord::Associations::JoinDependency#build diff --git a/lib/polyamorous/activerecord_6.0_ruby_2/reflection.rb b/lib/polyamorous/activerecord_6.0_ruby_2/reflection.rb index 54ff04e..bea4b97 100644 --- a/lib/polyamorous/activerecord_6.0_ruby_2/reflection.rb +++ b/lib/polyamorous/activerecord_6.0_ruby_2/reflection.rb @@ -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 diff --git a/lib/polyamorous/activerecord_6.1_ruby_2/join_association.rb b/lib/polyamorous/activerecord_6.1_ruby_2/join_association.rb index 386c25e..42eaf47 100644 --- a/lib/polyamorous/activerecord_6.1_ruby_2/join_association.rb +++ b/lib/polyamorous/activerecord_6.1_ruby_2/join_association.rb @@ -66,9 +66,5 @@ module Polyamorous joins end - - def ==(other) - base_klass == other.base_klass - end end end diff --git a/lib/polyamorous/activerecord_6.1_ruby_2/join_dependency.rb b/lib/polyamorous/activerecord_6.1_ruby_2/join_dependency.rb index 81253e8..f9cd1d8 100644 --- a/lib/polyamorous/activerecord_6.1_ruby_2/join_dependency.rb +++ b/lib/polyamorous/activerecord_6.1_ruby_2/join_dependency.rb @@ -1,4 +1,3 @@ -# active_record_6.1_ruby_2/join_dependency.rb module Polyamorous module JoinDependencyExtensions # Replaces ActiveRecord::Associations::JoinDependency#build diff --git a/lib/ransack/adapters/active_record/base.rb b/lib/ransack/adapters/active_record/base.rb index 9ad348a..a6883b9 100644 --- a/lib/ransack/adapters/active_record/base.rb +++ b/lib/ransack/adapters/active_record/base.rb @@ -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 diff --git a/lib/ransack/adapters/active_record/context.rb b/lib/ransack/adapters/active_record/context.rb index d00ca0f..3b2c55a 100644 --- a/lib/ransack/adapters/active_record/context.rb +++ b/lib/ransack/adapters/active_record/context.rb @@ -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) diff --git a/lib/ransack/adapters/active_record/ransack/nodes/condition.rb b/lib/ransack/adapters/active_record/ransack/nodes/condition.rb index f2b436e..d6e0942 100644 --- a/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +++ b/lib/ransack/adapters/active_record/ransack/nodes/condition.rb @@ -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 diff --git a/lib/ransack/configuration.rb b/lib/ransack/configuration.rb index 4347db5..1ca0434 100644 --- a/lib/ransack/configuration.rb +++ b/lib/ransack/configuration.rb @@ -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 diff --git a/lib/ransack/helpers/form_helper.rb b/lib/ransack/helpers/form_helper.rb index 03a84ca..e91cb38 100644 --- a/lib/ransack/helpers/form_helper.rb +++ b/lib/ransack/helpers/form_helper.rb @@ -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 diff --git a/lib/ransack/nodes/sort.rb b/lib/ransack/nodes/sort.rb index 5fc90b4..a7db7af 100644 --- a/lib/ransack/nodes/sort.rb +++ b/lib/ransack/nodes/sort.rb @@ -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) diff --git a/lib/ransack/search.rb b/lib/ransack/search.rb index b8c1ab6..95980c0 100644 --- a/lib/ransack/search.rb +++ b/lib/ransack/search.rb @@ -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 diff --git a/lib/ransack/version.rb b/lib/ransack/version.rb index 2666258..d0678c7 100644 --- a/lib/ransack/version.rb +++ b/lib/ransack/version.rb @@ -1,3 +1,3 @@ module Ransack - VERSION = '2.4.2' + VERSION = '2.6.0' end diff --git a/ransack.gemspec b/ransack.gemspec index 89ea950..be992ad 100644 --- a/ransack.gemspec +++ b/ransack.gemspec @@ -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") diff --git a/spec/polyamorous/activerecord_compatibility_spec.rb b/spec/polyamorous/activerecord_compatibility_spec.rb new file mode 100644 index 0000000..614c338 --- /dev/null +++ b/spec/polyamorous/activerecord_compatibility_spec.rb @@ -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 diff --git a/spec/polyamorous/join_association_spec.rb b/spec/polyamorous/join_association_spec.rb index b07db29..04be060 100644 --- a/spec/polyamorous/join_association_spec.rb +++ b/spec/polyamorous/join_association_spec.rb @@ -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| diff --git a/spec/polyamorous/join_dependency_spec.rb b/spec/polyamorous/join_dependency_spec.rb index c40e72a..4800c2b 100644 --- a/spec/polyamorous/join_dependency_spec.rb +++ b/spec/polyamorous/join_dependency_spec.rb @@ -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 diff --git a/spec/ransack/adapters/active_record/base_spec.rb b/spec/ransack/adapters/active_record/base_spec.rb index 2dc740b..44ecc27 100644 --- a/spec/ransack/adapters/active_record/base_spec.rb +++ b/spec/ransack/adapters/active_record/base_spec.rb @@ -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 diff --git a/spec/ransack/helpers/form_helper_spec.rb b/spec/ransack/helpers/form_helper_spec.rb index d8edd08..d794328 100644 --- a/spec/ransack/helpers/form_helper_spec.rb +++ b/spec/ransack/helpers/form_helper_spec.rb @@ -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 ▼/ } 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) {} } diff --git a/spec/ransack/nodes/condition_spec.rb b/spec/ransack/nodes/condition_spec.rb index 8520fd8..f4d7c51 100644 --- a/spec/ransack/nodes/condition_spec.rb +++ b/spec/ransack/nodes/condition_spec.rb @@ -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 { diff --git a/spec/ransack/search_spec.rb b/spec/ransack/search_spec.rb index e66c2e5..6426a20 100644 --- a/spec/ransack/search_spec.rb +++ b/spec/ransack/search_spec.rb @@ -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 diff --git a/spec/support/schema.rb b/spec/support/schema.rb index a97eef8..7262613 100644 --- a/spec/support/schema.rb +++ b/spec/support/schema.rb @@ -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