diff --git a/README.md b/README.md index af3234f39..655ff0ccf 100644 --- a/README.md +++ b/README.md @@ -251,6 +251,20 @@ the order indicator arrow by passing `hide_indicator: true` in the sort link: default_order: { last_name: 'asc', first_name: 'desc' }) %> ``` +#### PostgreSQL's sort option + +The `NULLS FIRST` and `NULLS LAST` options can be used to determine whether nulls appear before or after non-null values in the sort ordering. + +You may want to configure it like this: + +```rb +Ransack.configure do |c| + c.postgres_fields_sort_option = :nulls_first # or :nulls_last +end +``` + +See this feature: https://www.postgresql.org/docs/13/queries-order.html + ### Advanced Mode "Advanced" searches (ab)use Rails' nested attributes functionality in order to diff --git a/lib/ransack/adapters/active_record/context.rb b/lib/ransack/adapters/active_record/context.rb index 72c2b91f7..e92e82413 100644 --- a/lib/ransack/adapters/active_record/context.rb +++ b/lib/ransack/adapters/active_record/context.rb @@ -42,6 +42,13 @@ def evaluate(search, opts = {}) if scope_or_sort.is_a?(Symbol) relation = relation.send(scope_or_sort) else + case Ransack.options[:postgres_fields_sort_option] + when :nulls_first + scope_or_sort = scope_or_sort.direction == :asc ? "#{scope_or_sort.to_sql} NULLS FIRST" : "#{scope_or_sort.to_sql} NULLS LAST" + when :nulls_last + scope_or_sort = scope_or_sort.direction == :asc ? "#{scope_or_sort.to_sql} NULLS LAST" : "#{scope_or_sort.to_sql} NULLS FIRST" + end + relation = relation.order(scope_or_sort) end end diff --git a/lib/ransack/configuration.rb b/lib/ransack/configuration.rb index a2dc4d1c8..9a77b6acf 100644 --- a/lib/ransack/configuration.rb +++ b/lib/ransack/configuration.rb @@ -33,7 +33,8 @@ def []=(key, value) :up_arrow => '▼'.freeze, :down_arrow => '▲'.freeze, :default_arrow => nil, - :sanitize_scope_args => true + :sanitize_scope_args => true, + :postgres_fields_sort_option => nil } def configure @@ -141,6 +142,21 @@ def sanitize_custom_scope_booleans=(boolean) self.options[:sanitize_scope_args] = boolean end + # The `NULLS FIRST` and `NULLS LAST` options can be used to determine + # whether nulls appear before or after non-null values in the sort ordering. + # + # User may want to configure it like this: + # + # Ransack.configure do |c| + # c.postgres_fields_sort_option = :nulls_first # or :nulls_last + # end + # + # See this feature: https://www.postgresql.org/docs/13/queries-order.html + # + def postgres_fields_sort_option=(setting) + self.options[:postgres_fields_sort_option] = setting + end + # By default, Ransack displays sort order indicator arrows in sort links. # The default may be globally overridden in an initializer file like # `config/initializers/ransack.rb` as follows: diff --git a/spec/ransack/configuration_spec.rb b/spec/ransack/configuration_spec.rb index ca82f2feb..220f213b5 100644 --- a/spec/ransack/configuration_spec.rb +++ b/spec/ransack/configuration_spec.rb @@ -173,5 +173,15 @@ module Ransack .to eq false end end + + it "PG's sort option", if: ::ActiveRecord::Base.connection.adapter_name == "PostgreSQL" do + default = Ransack.options.clone + + Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_first } + + expect(Ransack.options[:postgres_fields_sort_option]).to eq :nulls_first + + Ransack.options = default + end end end diff --git a/spec/ransack/search_spec.rb b/spec/ransack/search_spec.rb index 0b134b2c5..72c54dd4f 100644 --- a/spec/ransack/search_spec.rb +++ b/spec/ransack/search_spec.rb @@ -533,6 +533,27 @@ def remove_quotes_and_backticks(str) @s.sorts = 'id asc' expect(@s.result.first.id).to eq 1 end + + it "PG's sort option", if: ::ActiveRecord::Base.connection.adapter_name == "PostgreSQL" do + default = Ransack.options.clone + + s = Search.new(Person, s: 'name asc') + expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" ASC" + + Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_first } + s = Search.new(Person, s: 'name asc') + expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" ASC NULLS FIRST" + s = Search.new(Person, s: 'name desc') + expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" DESC NULLS LAST" + + Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_last } + s = Search.new(Person, s: 'name asc') + expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" ASC NULLS LAST" + s = Search.new(Person, s: 'name desc') + expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" DESC NULLS FIRST" + + Ransack.options = default + end end describe '#method_missing' do