ransack 3.0.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/codeql.yml +72 -0
  3. data/.github/workflows/deploy.yml +35 -0
  4. data/.github/workflows/test-deploy.yml +29 -0
  5. data/.github/workflows/test.yml +22 -39
  6. data/.rubocop.yml +3 -0
  7. data/CHANGELOG.md +118 -0
  8. data/CONTRIBUTING.md +38 -16
  9. data/Gemfile +10 -10
  10. data/README.md +9 -14
  11. data/bug_report_templates/test-ransacker-arel-present-predicate.rb +4 -0
  12. data/docs/.gitignore +0 -1
  13. data/docs/docs/getting-started/advanced-mode.md +1 -1
  14. data/docs/docs/getting-started/search-matches.md +1 -1
  15. data/docs/docs/getting-started/simple-mode.md +30 -26
  16. data/docs/docs/getting-started/sorting.md +1 -1
  17. data/docs/docs/getting-started/using-predicates.md +1 -1
  18. data/docs/docs/going-further/acts-as-taggable-on.md +114 -0
  19. data/docs/docs/going-further/documentation.md +14 -2
  20. data/docs/docs/going-further/exporting-to-csv.md +2 -2
  21. data/docs/docs/going-further/form-customisation.md +1 -1
  22. data/docs/docs/going-further/i18n.md +3 -3
  23. data/docs/docs/going-further/merging-searches.md +1 -1
  24. data/docs/docs/going-further/other-notes.md +1 -1
  25. data/docs/docs/going-further/polymorphic-search.md +40 -0
  26. data/docs/docs/going-further/saving-queries.md +1 -1
  27. data/docs/docs/going-further/searching-postgres.md +1 -1
  28. data/docs/docs/going-further/wiki-contributors.md +82 -0
  29. data/docs/docs/intro.md +2 -2
  30. data/docs/docusaurus.config.js +16 -4
  31. data/docs/package.json +7 -2
  32. data/docs/yarn.lock +3036 -1917
  33. data/lib/polyamorous/activerecord_6.1_ruby_2/reflection.rb +11 -1
  34. data/lib/polyamorous/activerecord_7.1_ruby_2/join_association.rb +1 -0
  35. data/lib/polyamorous/activerecord_7.1_ruby_2/join_dependency.rb +1 -0
  36. data/lib/polyamorous/activerecord_7.1_ruby_2/reflection.rb +1 -0
  37. data/lib/ransack/adapters/active_record/base.rb +78 -7
  38. data/lib/ransack/adapters/active_record/context.rb +17 -49
  39. data/lib/ransack/configuration.rb +25 -12
  40. data/lib/ransack/constants.rb +125 -3
  41. data/lib/ransack/context.rb +34 -5
  42. data/lib/ransack/helpers/form_builder.rb +3 -3
  43. data/lib/ransack/helpers/form_helper.rb +4 -3
  44. data/lib/ransack/nodes/attribute.rb +2 -2
  45. data/lib/ransack/nodes/condition.rb +80 -7
  46. data/lib/ransack/nodes/grouping.rb +3 -3
  47. data/lib/ransack/nodes/node.rb +1 -1
  48. data/lib/ransack/nodes/value.rb +2 -2
  49. data/lib/ransack/predicate.rb +1 -1
  50. data/lib/ransack/ransacker.rb +1 -1
  51. data/lib/ransack/search.rb +9 -4
  52. data/lib/ransack/translate.rb +2 -2
  53. data/lib/ransack/version.rb +1 -1
  54. data/lib/ransack/visitor.rb +38 -2
  55. data/lib/ransack.rb +3 -6
  56. data/ransack.gemspec +3 -3
  57. data/spec/helpers/polyamorous_helper.rb +2 -8
  58. data/spec/ransack/adapters/active_record/base_spec.rb +73 -0
  59. data/spec/ransack/configuration_spec.rb +9 -9
  60. data/spec/ransack/helpers/form_builder_spec.rb +8 -8
  61. data/spec/ransack/helpers/form_helper_spec.rb +60 -2
  62. data/spec/ransack/nodes/condition_spec.rb +24 -0
  63. data/spec/ransack/nodes/value_spec.rb +115 -0
  64. data/spec/ransack/predicate_spec.rb +36 -1
  65. data/spec/ransack/translate_spec.rb +1 -1
  66. data/spec/support/schema.rb +27 -10
  67. metadata +20 -21
  68. data/docs/package-lock.json +0 -9207
  69. data/lib/polyamorous/activerecord_6.0_ruby_2/join_association.rb +0 -20
  70. data/lib/polyamorous/activerecord_6.0_ruby_2/join_dependency.rb +0 -79
  71. data/lib/polyamorous/activerecord_6.0_ruby_2/reflection.rb +0 -11
  72. data/lib/polyamorous.rb +0 -1
  73. data/lib/ransack/adapters/active_record/ransack/constants.rb +0 -128
  74. data/lib/ransack/adapters/active_record/ransack/context.rb +0 -56
  75. data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +0 -69
  76. data/lib/ransack/adapters/active_record/ransack/translate.rb +0 -8
  77. data/lib/ransack/adapters/active_record/ransack/visitor.rb +0 -47
  78. data/lib/ransack/adapters.rb +0 -64
  79. data/lib/ransack/nodes.rb +0 -8
  80. /data/lib/ransack/{adapters/active_record.rb → active_record.rb} +0 -0
@@ -7,9 +7,9 @@ module Ransack
7
7
  alias :m= :combinator=
8
8
 
9
9
  i18n_word :condition, :and, :or
10
- i18n_alias :c => :condition, :n => :and, :o => :or
10
+ i18n_alias c: :condition, n: :and, o: :or
11
11
 
12
- delegate :each, :to => :values
12
+ delegate :each, to: :values
13
13
 
14
14
  def initialize(context, combinator = nil)
15
15
  super(context)
@@ -22,7 +22,7 @@ module Ransack
22
22
 
23
23
  def translate(key, options = {})
24
24
  super or Translate.attribute(
25
- key.to_s, options.merge(:context => context)
25
+ key.to_s, options.merge(context: context)
26
26
  )
27
27
  end
28
28
 
@@ -2,7 +2,7 @@ module Ransack
2
2
  module Nodes
3
3
  class Node
4
4
  attr_reader :context
5
- delegate :contextualize, :to => :context
5
+ delegate :contextualize, to: :context
6
6
  class_attribute :i18n_words
7
7
  class_attribute :i18n_aliases
8
8
  self.i18n_words = []
@@ -2,7 +2,7 @@ module Ransack
2
2
  module Nodes
3
3
  class Value < Node
4
4
  attr_accessor :value
5
- delegate :present?, :blank?, :to => :value
5
+ delegate :present?, :blank?, to: :value
6
6
 
7
7
  def initialize(context, value = nil)
8
8
  super(context)
@@ -26,7 +26,7 @@ module Ransack
26
26
  case type
27
27
  when :date
28
28
  cast_to_date(value)
29
- when :datetime, :timestamp, :time
29
+ when :datetime, :timestamp, :time, :timestamptz
30
30
  cast_to_time(value)
31
31
  when :boolean
32
32
  cast_to_boolean(value)
@@ -10,7 +10,7 @@ module Ransack
10
10
  end
11
11
 
12
12
  def named(name)
13
- Ransack.predicates[name.to_s]
13
+ Ransack.predicates[(name || Ransack.options[:default_predicate]).to_s]
14
14
  end
15
15
 
16
16
  def detect_and_strip_from_string!(str)
@@ -3,7 +3,7 @@ module Ransack
3
3
 
4
4
  attr_reader :name, :type, :formatter, :args
5
5
 
6
- delegate :call, :to => :@callable
6
+ delegate :call, to: :@callable
7
7
 
8
8
  def initialize(klass, name, opts = {}, &block)
9
9
  @klass, @name = klass, name
@@ -1,6 +1,11 @@
1
- require 'ransack/nodes'
1
+ require 'ransack/nodes/bindable'
2
+ require 'ransack/nodes/node'
3
+ require 'ransack/nodes/attribute'
4
+ require 'ransack/nodes/value'
5
+ require 'ransack/nodes/condition'
6
+ require 'ransack/nodes/sort'
7
+ require 'ransack/nodes/grouping'
2
8
  require 'ransack/context'
3
- Ransack::Adapters.object_mapper.require_search
4
9
  require 'ransack/naming'
5
10
 
6
11
  module Ransack
@@ -9,10 +14,10 @@ module Ransack
9
14
 
10
15
  attr_reader :base, :context
11
16
 
12
- delegate :object, :klass, :to => :context
17
+ delegate :object, :klass, to: :context
13
18
  delegate :new_grouping, :new_condition,
14
19
  :build_grouping, :build_condition,
15
- :translate, :to => :base
20
+ :translate, to: :base
16
21
 
17
22
  def initialize(object, params = {}, options = {})
18
23
  strip_whitespace = options.fetch(:strip_whitespace, Ransack.options[:strip_whitespace])
@@ -66,7 +66,7 @@ module Ransack
66
66
  [:"ransack.associations.#{i18n_key(context.klass)}.#{key}"]
67
67
  end
68
68
  defaults << context.traverse(key).model_name.human
69
- options = { :count => 1, :default => defaults }
69
+ options = { count: 1, default: defaults }
70
70
  I18n.translate(defaults.shift, **options)
71
71
  end
72
72
 
@@ -149,7 +149,7 @@ module Ransack
149
149
  end
150
150
 
151
151
  def i18n_key(klass)
152
- raise "not implemented"
152
+ klass.model_name.i18n_key
153
153
  end
154
154
  end
155
155
  end
@@ -1,3 +1,3 @@
1
1
  module Ransack
2
- VERSION = '3.0.0'
2
+ VERSION = '4.0.0'
3
3
  end
@@ -26,7 +26,14 @@ module Ransack
26
26
  end
27
27
 
28
28
  def visit_and(object)
29
- raise "not implemented"
29
+ nodes = object.values.map { |o| accept(o) }.compact
30
+ return nil unless nodes.size > 0
31
+
32
+ if nodes.size > 1
33
+ Arel::Nodes::Grouping.new(Arel::Nodes::And.new(nodes))
34
+ else
35
+ nodes.first
36
+ end
30
37
  end
31
38
 
32
39
  def visit_or(object)
@@ -35,17 +42,46 @@ module Ransack
35
42
  end
36
43
 
37
44
  def quoted?(object)
38
- raise "not implemented"
45
+ case object
46
+ when Arel::Nodes::SqlLiteral, Bignum, Fixnum
47
+ false
48
+ else
49
+ true
50
+ end
39
51
  end
40
52
 
41
53
  def visit(object)
42
54
  send(DISPATCH[object.class], object)
43
55
  end
44
56
 
57
+ def visit_Ransack_Nodes_Sort(object)
58
+ if object.valid?
59
+ if object.attr.is_a?(Arel::Attributes::Attribute)
60
+ object.attr.send(object.dir)
61
+ else
62
+ ordered(object)
63
+ end
64
+ else
65
+ scope_name = :"sort_by_#{object.name}_#{object.dir}"
66
+ scope_name if object.context.object.respond_to?(scope_name)
67
+ end
68
+ end
69
+
45
70
  DISPATCH = Hash.new do |hash, klass|
46
71
  hash[klass] = "visit_#{
47
72
  klass.name.gsub(Constants::TWO_COLONS, Constants::UNDERSCORE)
48
73
  }"
49
74
  end
75
+
76
+ private
77
+
78
+ def ordered(object)
79
+ case object.dir
80
+ when 'asc'.freeze
81
+ Arel::Nodes::Ascending.new(object.attr)
82
+ when 'desc'.freeze
83
+ Arel::Nodes::Descending.new(object.attr)
84
+ end
85
+ end
50
86
  end
51
87
  end
data/lib/ransack.rb CHANGED
@@ -1,10 +1,7 @@
1
1
  require 'active_support/core_ext'
2
2
  require 'ransack/configuration'
3
- require 'ransack/adapters'
4
3
  require 'polyamorous/polyamorous'
5
4
 
6
- Ransack::Adapters.object_mapper.require_constants
7
-
8
5
  module Ransack
9
6
  extend Configuration
10
7
  class UntraversableAssociationError < StandardError; end
@@ -12,7 +9,7 @@ end
12
9
 
13
10
  Ransack.configure do |config|
14
11
  Ransack::Constants::AREL_PREDICATES.each do |name|
15
- config.add_predicate name, :arel_predicate => name
12
+ config.add_predicate name, arel_predicate: name
16
13
  end
17
14
  Ransack::Constants::DERIVED_PREDICATES.each do |args|
18
15
  config.add_predicate(*args)
@@ -22,8 +19,8 @@ end
22
19
  require 'ransack/search'
23
20
  require 'ransack/ransacker'
24
21
  require 'ransack/translate'
25
-
26
- Ransack::Adapters.object_mapper.require_adapter
22
+ require 'ransack/active_record'
23
+ require 'ransack/context'
27
24
 
28
25
  ActiveSupport.on_load(:action_controller) do
29
26
  require 'ransack/helpers'
data/ransack.gemspec CHANGED
@@ -12,11 +12,11 @@ Gem::Specification.new do |s|
12
12
  s.homepage = "https://github.com/activerecord-hackery/ransack"
13
13
  s.summary = %q{Object-based searching for Active Record.}
14
14
  s.description = %q{Ransack is the successor to the MetaSearch gem. It improves and expands upon MetaSearch's functionality, but does not have a 100%-compatible API.}
15
- s.required_ruby_version = '>= 2.6'
15
+ s.required_ruby_version = '>= 2.7'
16
16
  s.license = 'MIT'
17
17
 
18
- s.add_dependency 'activerecord', '>= 6.0.4'
19
- s.add_dependency 'activesupport', '>= 6.0.4'
18
+ s.add_dependency 'activerecord', '>= 6.1.5'
19
+ s.add_dependency 'activesupport', '>= 6.1.5'
20
20
  s.add_dependency 'i18n'
21
21
 
22
22
  s.files = `git ls-files`.split("\n")
@@ -3,14 +3,8 @@ module PolyamorousHelper
3
3
  Polyamorous::JoinAssociation.new reflection, children, klass
4
4
  end
5
5
 
6
- if ActiveRecord.version >= ::Gem::Version.new("6.0.0.rc1")
7
- def new_join_dependency(klass, associations = {})
8
- Polyamorous::JoinDependency.new klass, klass.arel_table, associations, Polyamorous::InnerJoin
9
- end
10
- else
11
- def new_join_dependency(klass, associations = {})
12
- Polyamorous::JoinDependency.new klass, klass.arel_table, associations
13
- end
6
+ def new_join_dependency(klass, associations = {})
7
+ Polyamorous::JoinDependency.new klass, klass.arel_table, associations, Polyamorous::InnerJoin
14
8
  end
15
9
 
16
10
  def new_join(name, type = Polyamorous::InnerJoin, klass = nil)
@@ -657,6 +657,37 @@ module Ransack
657
657
  it { should_not include 'only_sort' }
658
658
  it { should include 'only_admin' }
659
659
  end
660
+
661
+ context 'when not defined in model, nor in ApplicationRecord' do
662
+ subject { Article.ransackable_attributes }
663
+
664
+ it "raises a helpful error" do
665
+ without_application_record_method(:ransackable_attributes) do
666
+ expect { subject }.to raise_error(RuntimeError, /Ransack needs Article attributes explicitly allowlisted/)
667
+ end
668
+ end
669
+ end
670
+
671
+ context 'when defined only in model by delegating to super' do
672
+ subject { Article.ransackable_attributes }
673
+
674
+ around do |example|
675
+ Article.singleton_class.define_method(:ransackable_attributes) do
676
+ super(nil) - super(nil)
677
+ end
678
+
679
+ example.run
680
+ ensure
681
+ Article.singleton_class.remove_method(:ransackable_attributes)
682
+ end
683
+
684
+ it "returns the allowlist in the model, and warns" do
685
+ without_application_record_method(:ransackable_attributes) do
686
+ expect { subject }.to output(/Ransack's builtin `ransackable_attributes` method is deprecated/).to_stderr
687
+ expect(subject).to be_empty
688
+ end
689
+ end
690
+ end
660
691
  end
661
692
 
662
693
  describe '#ransortable_attributes' do
@@ -689,6 +720,37 @@ module Ransack
689
720
  it { should include 'parent' }
690
721
  it { should include 'children' }
691
722
  it { should include 'articles' }
723
+
724
+ context 'when not defined in model, nor in ApplicationRecord' do
725
+ subject { Article.ransackable_associations }
726
+
727
+ it "raises a helpful error" do
728
+ without_application_record_method(:ransackable_associations) do
729
+ expect { subject }.to raise_error(RuntimeError, /Ransack needs Article associations explicitly allowlisted/)
730
+ end
731
+ end
732
+ end
733
+
734
+ context 'when defined only in model by delegating to super' do
735
+ subject { Article.ransackable_associations }
736
+
737
+ around do |example|
738
+ Article.singleton_class.define_method(:ransackable_associations) do
739
+ super(nil) - super(nil)
740
+ end
741
+
742
+ example.run
743
+ ensure
744
+ Article.singleton_class.remove_method(:ransackable_associations)
745
+ end
746
+
747
+ it "returns the allowlist in the model, and warns" do
748
+ without_application_record_method(:ransackable_associations) do
749
+ expect { subject }.to output(/Ransack's builtin `ransackable_associations` method is deprecated/).to_stderr
750
+ expect(subject).to be_empty
751
+ end
752
+ end
753
+ end
692
754
  end
693
755
 
694
756
  describe '#ransackable_scopes' do
@@ -704,6 +766,17 @@ module Ransack
704
766
  end
705
767
 
706
768
  private
769
+
770
+ def without_application_record_method(method)
771
+ ApplicationRecord.singleton_class.alias_method :"original_#{method}", :"#{method}"
772
+ ApplicationRecord.singleton_class.remove_method :"#{method}"
773
+
774
+ yield
775
+ ensure
776
+ ApplicationRecord.singleton_class.alias_method :"#{method}", :"original_#{method}"
777
+ ApplicationRecord.singleton_class.remove_method :"original_#{method}"
778
+ end
779
+
707
780
  def rails7_and_mysql
708
781
  ::ActiveRecord::VERSION::MAJOR >= 7 && ENV['DB'] == 'mysql'
709
782
  end
@@ -20,7 +20,7 @@ module Ransack
20
20
  Ransack.configure do |config|
21
21
  config.add_predicate(
22
22
  :test_predicate_without_compound,
23
- :compounds => false
23
+ compounds: false
24
24
  )
25
25
  end
26
26
  expect(Ransack.predicates)
@@ -138,8 +138,8 @@ module Ransack
138
138
  Ransack.configure do |config|
139
139
  config.add_predicate(
140
140
  :test_array_predicate,
141
- :wants_array => true,
142
- :compounds => true
141
+ wants_array: true,
142
+ compounds: true
143
143
  )
144
144
  end
145
145
 
@@ -153,11 +153,11 @@ module Ransack
153
153
  Ransack.configure do |config|
154
154
  config.add_predicate(
155
155
  :test_in_predicate,
156
- :arel_predicate => 'in'
156
+ arel_predicate: 'in'
157
157
  )
158
158
  config.add_predicate(
159
159
  :test_not_in_predicate,
160
- :arel_predicate => 'not_in'
160
+ arel_predicate: 'not_in'
161
161
  )
162
162
  end
163
163
 
@@ -171,13 +171,13 @@ module Ransack
171
171
  Ransack.configure do |config|
172
172
  config.add_predicate(
173
173
  :test_in_predicate_no_array,
174
- :arel_predicate => 'in',
175
- :wants_array => false
174
+ arel_predicate: 'in',
175
+ wants_array: false
176
176
  )
177
177
  config.add_predicate(
178
178
  :test_not_in_predicate_no_array,
179
- :arel_predicate => 'not_in',
180
- :wants_array => false
179
+ arel_predicate: 'not_in',
180
+ wants_array: false
181
181
  )
182
182
  end
183
183
 
@@ -26,7 +26,7 @@ module Ransack
26
26
  # @s.created_at_eq = date_values # This works in Rails 4.x but not 3.x
27
27
  @s.created_at_eq = [2011, 1, 2, 3, 4, 5] # so we have to do this
28
28
  html = @f.datetime_select(
29
- :created_at_eq, :use_month_numbers => true, :include_seconds => true
29
+ :created_at_eq, use_month_numbers: true, include_seconds: true
30
30
  )
31
31
  date_values.each { |val| expect(html).to include date_select_html(val) }
32
32
  end
@@ -70,13 +70,13 @@ module Ransack
70
70
 
71
71
  describe '#sort_link' do
72
72
  it 'sort_link for ransack attribute' do
73
- sort_link = @f.sort_link :name, :controller => 'people'
73
+ sort_link = @f.sort_link :name, controller: 'people'
74
74
  expect(sort_link).to match /people\?q(%5B|\[)s(%5D|\])=name\+asc/
75
75
  expect(sort_link).to match /sort_link/
76
76
  expect(sort_link).to match /Full Name<\/a>/
77
77
  end
78
78
  it 'sort_link for common attribute' do
79
- sort_link = @f.sort_link :id, :controller => 'people'
79
+ sort_link = @f.sort_link :id, controller: 'people'
80
80
  expect(sort_link).to match /id<\/a>/
81
81
  end
82
82
  end
@@ -99,14 +99,14 @@ module Ransack
99
99
  it 'returns ransackable attributes for associations with :associations' do
100
100
  attributes = Person.ransackable_attributes +
101
101
  Article.ransackable_attributes.map { |a| "articles_#{a}" }
102
- html = @f.attribute_select(:associations => ['articles'])
102
+ html = @f.attribute_select(associations: ['articles'])
103
103
  expect(html.split(/\n/).size).to eq(attributes.size)
104
104
  attributes.each do |attribute|
105
105
  expect(html).to match /<option value="#{attribute}">/
106
106
  end
107
107
  end
108
108
  it 'returns option groups for base and associations with :associations' do
109
- html = @f.attribute_select(:associations => ['articles'])
109
+ html = @f.attribute_select(associations: ['articles'])
110
110
  [Person, Article].each do |model|
111
111
  expect(html).to match /<optgroup label="#{model}">/
112
112
  end
@@ -121,19 +121,19 @@ module Ransack
121
121
  end
122
122
  end
123
123
  it 'filters predicates with single-value :only' do
124
- html = @f.predicate_select :only => 'eq'
124
+ html = @f.predicate_select only: 'eq'
125
125
  Predicate.names.reject { |k| k =~ /^eq/ }.each do |key|
126
126
  expect(html).not_to match /<option value="#{key}">/
127
127
  end
128
128
  end
129
129
  it 'filters predicates with multi-value :only' do
130
- html = @f.predicate_select :only => [:eq, :lt]
130
+ html = @f.predicate_select only: [:eq, :lt]
131
131
  Predicate.names.reject { |k| k =~ /^(eq|lt)/ }.each do |key|
132
132
  expect(html).not_to match /<option value="#{key}">/
133
133
  end
134
134
  end
135
135
  it 'excludes compounds when compounds: false' do
136
- html = @f.predicate_select :compounds => false
136
+ html = @f.predicate_select compounds: false
137
137
  Predicate.names.select { |k| k =~ /_(any|all)$/ }.each do |key|
138
138
  expect(html).not_to match /<option value="#{key}">/
139
139
  end
@@ -140,6 +140,32 @@ module Ransack
140
140
  }
141
141
  end
142
142
 
143
+ describe '#sort_link works even if search params are a string' do
144
+ before { @controller.view_context.params[:q] = 'input error' }
145
+ specify {
146
+ expect { @controller.view_context
147
+ .sort_link(
148
+ Person.ransack({}),
149
+ :name,
150
+ controller: 'people'
151
+ )
152
+ }.not_to raise_error
153
+ }
154
+ end
155
+
156
+ describe '#sort_url works even if search params are a string' do
157
+ before { @controller.view_context.params[:q] = 'input error' }
158
+ specify {
159
+ expect { @controller.view_context
160
+ .sort_url(
161
+ Person.ransack({}),
162
+ :name,
163
+ controller: 'people'
164
+ )
165
+ }.not_to raise_error
166
+ }
167
+ end
168
+
143
169
  describe '#sort_link with search_key defined as a string' do
144
170
  subject { @controller.view_context
145
171
  .sort_link(
@@ -474,7 +500,7 @@ module Ransack
474
500
  describe 'with symbol q:, #sort_link should include search params' do
475
501
  subject { @controller.view_context.sort_link(Person.ransack, :name) }
476
502
  let(:params) { ActionController::Parameters.new(
477
- { :q => { name_eq: 'TEST' }, controller: 'people' }
503
+ { q: { name_eq: 'TEST' }, controller: 'people' }
478
504
  ) }
479
505
  before { @controller.instance_variable_set(:@params, params) }
480
506
 
@@ -489,7 +515,7 @@ module Ransack
489
515
  describe 'with symbol q:, #sort_url should include search params' do
490
516
  subject { @controller.view_context.sort_url(Person.ransack, :name) }
491
517
  let(:params) { ActionController::Parameters.new(
492
- { :q => { name_eq: 'TEST' }, controller: 'people' }
518
+ { q: { name_eq: 'TEST' }, controller: 'people' }
493
519
  ) }
494
520
  before { @controller.instance_variable_set(:@params, params) }
495
521
 
@@ -758,6 +784,38 @@ module Ransack
758
784
  end
759
785
  end
760
786
 
787
+ describe '#sort_link with data option' do
788
+ subject { @controller.view_context
789
+ .sort_link(
790
+ [:main_app, Person.ransack(sorts: ['name desc'])],
791
+ :name,
792
+ data: { turbo_action: :advance }, controller: 'people'
793
+ )
794
+ }
795
+ it { should match /data-turbo-action="advance"/ }
796
+ it { should_not match /people\?data%5Bturbo_action%5D=advance/ }
797
+ end
798
+
799
+ describe "#sort_link with host option" do
800
+ subject { @controller.view_context
801
+ .sort_link(
802
+ [:main_app, Person.ransack(sorts: ['name desc'])],
803
+ :name,
804
+ host: 'foo', controller: 'people'
805
+ )
806
+ }
807
+ it { should match /href="\/people\?q/ }
808
+ it { should_not match /href=".*foo/ }
809
+ end
810
+
811
+ describe "#sort_link ignores host in params" do
812
+ before { @controller.view_context.params[:host] = 'other_domain' }
813
+ subject { @controller.view_context.sort_link(Person.ransack, :name, controller: 'people') }
814
+
815
+ it { should match /href="\/people\?q/ }
816
+ it { should_not match /href=".*other_domain/ }
817
+ end
818
+
761
819
  describe '#search_form_for with default format' do
762
820
  subject { @controller.view_context
763
821
  .search_form_for(Person.ransack) {} }
@@ -74,6 +74,30 @@ module Ransack
74
74
  specify { expect(subject).to be_nil }
75
75
  end
76
76
  end
77
+
78
+ context 'with an empty predicate' do
79
+ subject {
80
+ Condition.extract(
81
+ Context.for(Person), 'full_name', Person.first.name
82
+ )
83
+ }
84
+
85
+ context "when default_predicate = nil" do
86
+ before do
87
+ Ransack.configure { |c| c.default_predicate = nil }
88
+ end
89
+
90
+ specify { expect(subject).to be_nil }
91
+ end
92
+
93
+ context "when default_predicate = 'eq'" do
94
+ before do
95
+ Ransack.configure { |c| c.default_predicate = 'eq' }
96
+ end
97
+
98
+ specify { expect(subject).to eq Condition.extract(Context.for(Person), 'full_name_eq', Person.first.name) }
99
+ end
100
+ end
77
101
  end
78
102
  end
79
103
  end