ransack 3.2.1 → 4.1.1

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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/codeql.yml +72 -0
  3. data/.github/workflows/cronjob.yml +6 -9
  4. data/.github/workflows/deploy.yml +1 -1
  5. data/.github/workflows/rubocop.yml +2 -2
  6. data/.github/workflows/test-deploy.yml +1 -1
  7. data/.github/workflows/test.yml +23 -22
  8. data/.rubocop.yml +7 -1
  9. data/CHANGELOG.md +86 -0
  10. data/CONTRIBUTING.md +40 -21
  11. data/Gemfile +10 -10
  12. data/README.md +4 -9
  13. data/bug_report_templates/test-ransack-scope-and-column-same-name.rb +1 -1
  14. data/bug_report_templates/test-ransacker-arel-present-predicate.rb +5 -1
  15. data/docs/docs/getting-started/advanced-mode.md +1 -1
  16. data/docs/docs/getting-started/search-matches.md +1 -1
  17. data/docs/docs/getting-started/simple-mode.md +6 -2
  18. data/docs/docs/getting-started/sorting.md +45 -53
  19. data/docs/docs/going-further/acts-as-taggable-on.md +4 -4
  20. data/docs/docs/going-further/form-customisation.md +1 -1
  21. data/docs/docs/going-further/i18n.md +3 -3
  22. data/docs/docs/going-further/other-notes.md +2 -2
  23. data/docs/docs/going-further/polymorphic-search.md +6 -0
  24. data/docs/docs/going-further/saving-queries.md +1 -1
  25. data/docs/docs/going-further/searching-postgres.md +1 -1
  26. data/docs/package.json +7 -3
  27. data/docs/yarn.lock +2371 -1928
  28. data/lib/polyamorous/{activerecord_6.1_ruby_2 → activerecord}/join_dependency.rb +14 -4
  29. data/lib/polyamorous/polyamorous.rb +3 -4
  30. data/lib/ransack/adapters/active_record/base.rb +78 -7
  31. data/lib/ransack/adapters/active_record/context.rb +2 -1
  32. data/lib/ransack/configuration.rb +25 -12
  33. data/lib/ransack/constants.rb +125 -0
  34. data/lib/ransack/context.rb +34 -5
  35. data/lib/ransack/helpers/form_builder.rb +3 -3
  36. data/lib/ransack/helpers/form_helper.rb +3 -2
  37. data/lib/ransack/nodes/attribute.rb +2 -2
  38. data/lib/ransack/nodes/condition.rb +80 -7
  39. data/lib/ransack/nodes/grouping.rb +3 -3
  40. data/lib/ransack/nodes/node.rb +1 -1
  41. data/lib/ransack/nodes/value.rb +1 -1
  42. data/lib/ransack/predicate.rb +1 -1
  43. data/lib/ransack/ransacker.rb +1 -1
  44. data/lib/ransack/search.rb +9 -4
  45. data/lib/ransack/translate.rb +2 -2
  46. data/lib/ransack/version.rb +1 -1
  47. data/lib/ransack/visitor.rb +38 -2
  48. data/lib/ransack.rb +3 -6
  49. data/ransack.gemspec +1 -1
  50. data/spec/ransack/adapters/active_record/base_spec.rb +89 -0
  51. data/spec/ransack/configuration_spec.rb +9 -9
  52. data/spec/ransack/helpers/form_builder_spec.rb +8 -8
  53. data/spec/ransack/helpers/form_helper_spec.rb +36 -2
  54. data/spec/ransack/nodes/condition_spec.rb +24 -0
  55. data/spec/ransack/predicate_spec.rb +36 -1
  56. data/spec/ransack/translate_spec.rb +1 -1
  57. data/spec/support/schema.rb +55 -10
  58. metadata +9 -22
  59. data/lib/polyamorous/activerecord_7.0_ruby_2/join_association.rb +0 -1
  60. data/lib/polyamorous/activerecord_7.0_ruby_2/join_dependency.rb +0 -1
  61. data/lib/polyamorous/activerecord_7.0_ruby_2/reflection.rb +0 -1
  62. data/lib/polyamorous/activerecord_7.1_ruby_2/join_association.rb +0 -1
  63. data/lib/polyamorous/activerecord_7.1_ruby_2/join_dependency.rb +0 -1
  64. data/lib/polyamorous/activerecord_7.1_ruby_2/reflection.rb +0 -1
  65. data/lib/polyamorous.rb +0 -1
  66. data/lib/ransack/adapters/active_record/ransack/constants.rb +0 -128
  67. data/lib/ransack/adapters/active_record/ransack/context.rb +0 -56
  68. data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +0 -61
  69. data/lib/ransack/adapters/active_record/ransack/translate.rb +0 -8
  70. data/lib/ransack/adapters/active_record/ransack/visitor.rb +0 -47
  71. data/lib/ransack/adapters.rb +0 -64
  72. data/lib/ransack/nodes.rb +0 -8
  73. /data/lib/polyamorous/{activerecord_6.1_ruby_2 → activerecord}/join_association.rb +0 -0
  74. /data/lib/polyamorous/{activerecord_6.1_ruby_2 → activerecord}/reflection.rb +0 -0
  75. /data/lib/ransack/{adapters/active_record.rb → active_record.rb} +0 -0
@@ -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.2.1'
2
+ VERSION = '4.1.1'
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,7 +12,7 @@ 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.7'
15
+ s.required_ruby_version = '>= 3.0'
16
16
  s.license = 'MIT'
17
17
 
18
18
  s.add_dependency 'activerecord', '>= 6.1.5'
@@ -141,6 +141,22 @@ module Ransack
141
141
  end
142
142
  end
143
143
 
144
+ context 'has_one through associations' do
145
+ let(:address) { Address.create!(city: 'Boston') }
146
+ let(:org) { Organization.create!(name: 'Testorg', address: address) }
147
+ let!(:employee) { Employee.create!(name: 'Ernie', organization: org) }
148
+
149
+ it 'works when has_one through association is first' do
150
+ s = Employee.ransack(address_city_eq: 'Boston', organization_name_eq: 'Testorg')
151
+ expect(s.result.to_a).to include(employee)
152
+ end
153
+
154
+ it 'works when has_one through association is last' do
155
+ s = Employee.ransack(organization_name_eq: 'Testorg', address_city_eq: 'Boston')
156
+ expect(s.result.to_a).to include(employee)
157
+ end
158
+ end
159
+
144
160
  context 'negative conditions on HABTM associations' do
145
161
  let(:medieval) { Tag.create!(name: 'Medieval') }
146
162
  let(:fantasy) { Tag.create!(name: 'Fantasy') }
@@ -657,6 +673,37 @@ module Ransack
657
673
  it { should_not include 'only_sort' }
658
674
  it { should include 'only_admin' }
659
675
  end
676
+
677
+ context 'when not defined in model, nor in ApplicationRecord' do
678
+ subject { Article.ransackable_attributes }
679
+
680
+ it "raises a helpful error" do
681
+ without_application_record_method(:ransackable_attributes) do
682
+ expect { subject }.to raise_error(RuntimeError, /Ransack needs Article attributes explicitly allowlisted/)
683
+ end
684
+ end
685
+ end
686
+
687
+ context 'when defined only in model by delegating to super' do
688
+ subject { Article.ransackable_attributes }
689
+
690
+ around do |example|
691
+ Article.singleton_class.define_method(:ransackable_attributes) do
692
+ super(nil) - super(nil)
693
+ end
694
+
695
+ example.run
696
+ ensure
697
+ Article.singleton_class.remove_method(:ransackable_attributes)
698
+ end
699
+
700
+ it "returns the allowlist in the model, and warns" do
701
+ without_application_record_method(:ransackable_attributes) do
702
+ expect { subject }.to output(/Ransack's builtin `ransackable_attributes` method is deprecated/).to_stderr
703
+ expect(subject).to be_empty
704
+ end
705
+ end
706
+ end
660
707
  end
661
708
 
662
709
  describe '#ransortable_attributes' do
@@ -689,6 +736,37 @@ module Ransack
689
736
  it { should include 'parent' }
690
737
  it { should include 'children' }
691
738
  it { should include 'articles' }
739
+
740
+ context 'when not defined in model, nor in ApplicationRecord' do
741
+ subject { Article.ransackable_associations }
742
+
743
+ it "raises a helpful error" do
744
+ without_application_record_method(:ransackable_associations) do
745
+ expect { subject }.to raise_error(RuntimeError, /Ransack needs Article associations explicitly allowlisted/)
746
+ end
747
+ end
748
+ end
749
+
750
+ context 'when defined only in model by delegating to super' do
751
+ subject { Article.ransackable_associations }
752
+
753
+ around do |example|
754
+ Article.singleton_class.define_method(:ransackable_associations) do
755
+ super(nil) - super(nil)
756
+ end
757
+
758
+ example.run
759
+ ensure
760
+ Article.singleton_class.remove_method(:ransackable_associations)
761
+ end
762
+
763
+ it "returns the allowlist in the model, and warns" do
764
+ without_application_record_method(:ransackable_associations) do
765
+ expect { subject }.to output(/Ransack's builtin `ransackable_associations` method is deprecated/).to_stderr
766
+ expect(subject).to be_empty
767
+ end
768
+ end
769
+ end
692
770
  end
693
771
 
694
772
  describe '#ransackable_scopes' do
@@ -704,6 +782,17 @@ module Ransack
704
782
  end
705
783
 
706
784
  private
785
+
786
+ def without_application_record_method(method)
787
+ ApplicationRecord.singleton_class.alias_method :"original_#{method}", :"#{method}"
788
+ ApplicationRecord.singleton_class.remove_method :"#{method}"
789
+
790
+ yield
791
+ ensure
792
+ ApplicationRecord.singleton_class.alias_method :"#{method}", :"original_#{method}"
793
+ ApplicationRecord.singleton_class.remove_method :"original_#{method}"
794
+ end
795
+
707
796
  def rails7_and_mysql
708
797
  ::ActiveRecord::VERSION::MAJOR >= 7 && ENV['DB'] == 'mysql'
709
798
  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
 
@@ -782,6 +808,14 @@ module Ransack
782
808
  it { should_not match /href=".*foo/ }
783
809
  end
784
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
+
785
819
  describe '#search_form_for with default format' do
786
820
  subject { @controller.view_context
787
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
@@ -35,6 +35,13 @@ module Ransack
35
35
  @s.awesome_eq = nil
36
36
  expect(@s.result.to_sql).not_to match /WHERE/
37
37
  end
38
+
39
+ it 'generates a = condition with a huge integer value' do
40
+ val = 123456789012345678901
41
+ @s.salary_eq = val
42
+ field = "#{quote_table_name("people")}.#{quote_column_name("salary")}"
43
+ expect(@s.result.to_sql).to match /#{field} = #{val}/
44
+ end
38
45
  end
39
46
 
40
47
  describe 'lteq' do
@@ -56,6 +63,13 @@ module Ransack
56
63
  @s.salary_lteq = nil
57
64
  expect(@s.result.to_sql).not_to match /WHERE/
58
65
  end
66
+
67
+ it 'generates a <= condition with a huge integer value' do
68
+ val = 123456789012345678901
69
+ @s.salary_lteq = val
70
+ field = "#{quote_table_name("people")}.#{quote_column_name("salary")}"
71
+ expect(@s.result.to_sql).to match /#{field} <= #{val}/
72
+ end
59
73
  end
60
74
 
61
75
  describe 'lt' do
@@ -77,6 +91,13 @@ module Ransack
77
91
  @s.salary_lt = nil
78
92
  expect(@s.result.to_sql).not_to match /WHERE/
79
93
  end
94
+
95
+ it 'generates a = condition with a huge integer value' do
96
+ val = 123456789012345678901
97
+ @s.salary_lt = val
98
+ field = "#{quote_table_name("people")}.#{quote_column_name("salary")}"
99
+ expect(@s.result.to_sql).to match /#{field} < #{val}/
100
+ end
80
101
  end
81
102
 
82
103
  describe 'gteq' do
@@ -98,6 +119,13 @@ module Ransack
98
119
  @s.salary_gteq = nil
99
120
  expect(@s.result.to_sql).not_to match /WHERE/
100
121
  end
122
+
123
+ it 'generates a >= condition with a huge integer value' do
124
+ val = 123456789012345678901
125
+ @s.salary_gteq = val
126
+ field = "#{quote_table_name("people")}.#{quote_column_name("salary")}"
127
+ expect(@s.result.to_sql).to match /#{field} >= #{val}/
128
+ end
101
129
  end
102
130
 
103
131
  describe 'gt' do
@@ -119,6 +147,13 @@ module Ransack
119
147
  @s.salary_gt = nil
120
148
  expect(@s.result.to_sql).not_to match /WHERE/
121
149
  end
150
+
151
+ it 'generates a > condition with a huge integer value' do
152
+ val = 123456789012345678901
153
+ @s.salary_gt = val
154
+ field = "#{quote_table_name("people")}.#{quote_column_name("salary")}"
155
+ expect(@s.result.to_sql).to match /#{field} > #{val}/
156
+ end
122
157
  end
123
158
 
124
159
  describe 'cont' do
@@ -368,7 +403,7 @@ module Ransack
368
403
  expect(@s.result.to_sql).to match /#{field} IS NULL/
369
404
  end
370
405
 
371
- describe 'with association qeury' do
406
+ describe 'with association query' do
372
407
  it 'generates a value IS NOT NULL query' do
373
408
  @s.comments_id_not_null = true
374
409
  sql = @s.result.to_sql
@@ -8,7 +8,7 @@ module Ransack
8
8
  ar_translation = ::Namespace::Article.human_attribute_name(:title)
9
9
  ransack_translation = Ransack::Translate.attribute(
10
10
  :title,
11
- :context => ::Namespace::Article.ransack.context
11
+ context: ::Namespace::Article.ransack.context
12
12
  )
13
13
  expect(ransack_translation).to eq ar_translation
14
14
  end
@@ -28,7 +28,24 @@ else
28
28
  )
29
29
  end
30
30
 
31
- class Person < ActiveRecord::Base
31
+ # This is just a test app with no sensitive data, so we explicitly allowlist all
32
+ # attributes and associations for search. In general, end users should
33
+ # explicitly authorize each model, but this shows a way to configure the
34
+ # unrestricted default behavior of versions prior to Ransack 4.
35
+ #
36
+ class ApplicationRecord < ActiveRecord::Base
37
+ self.abstract_class = true
38
+
39
+ def self.ransackable_attributes(auth_object = nil)
40
+ authorizable_ransackable_attributes
41
+ end
42
+
43
+ def self.ransackable_associations(auth_object = nil)
44
+ authorizable_ransackable_associations
45
+ end
46
+ end
47
+
48
+ class Person < ApplicationRecord
32
49
  default_scope { order(id: :desc) }
33
50
  belongs_to :parent, class_name: 'Person', foreign_key: :parent_id
34
51
  has_many :children, class_name: 'Person', foreign_key: :parent_id
@@ -111,9 +128,9 @@ class Person < ActiveRecord::Base
111
128
 
112
129
  def self.ransackable_attributes(auth_object = nil)
113
130
  if auth_object == :admin
114
- super - ['only_sort']
131
+ authorizable_ransackable_attributes - ['only_sort']
115
132
  else
116
- super - ['only_sort', 'only_admin']
133
+ authorizable_ransackable_attributes - ['only_sort', 'only_admin']
117
134
  end
118
135
  end
119
136
 
@@ -129,7 +146,7 @@ end
129
146
  class Musician < Person
130
147
  end
131
148
 
132
- class Article < ActiveRecord::Base
149
+ class Article < ApplicationRecord
133
150
  belongs_to :person
134
151
  has_many :comments
135
152
  has_and_belongs_to_many :tags
@@ -182,7 +199,7 @@ end
182
199
  class StoryArticle < Article
183
200
  end
184
201
 
185
- class Recommendation < ActiveRecord::Base
202
+ class Recommendation < ApplicationRecord
186
203
  belongs_to :person
187
204
  belongs_to :target_person, class_name: 'Person'
188
205
  belongs_to :article
@@ -200,26 +217,40 @@ module Namespace
200
217
  end
201
218
  end
202
219
 
203
- class Comment < ActiveRecord::Base
220
+ class Comment < ApplicationRecord
204
221
  belongs_to :article
205
222
  belongs_to :person
206
223
 
207
224
  default_scope { where(disabled: false) }
208
225
  end
209
226
 
210
- class Tag < ActiveRecord::Base
227
+ class Tag < ApplicationRecord
211
228
  has_and_belongs_to_many :articles
212
229
  end
213
230
 
214
- class Note < ActiveRecord::Base
231
+ class Note < ApplicationRecord
215
232
  belongs_to :notable, polymorphic: true
216
233
  end
217
234
 
218
- class Account < ActiveRecord::Base
235
+ class Account < ApplicationRecord
219
236
  belongs_to :agent_account, class_name: "Account"
220
237
  belongs_to :trade_account, class_name: "Account"
221
238
  end
222
239
 
240
+ class Address < ApplicationRecord
241
+ has_one :organization
242
+ end
243
+
244
+ class Organization < ApplicationRecord
245
+ belongs_to :address
246
+ has_many :employees
247
+ end
248
+
249
+ class Employee < ApplicationRecord
250
+ belongs_to :organization
251
+ has_one :address, through: :organization
252
+ end
253
+
223
254
  module Schema
224
255
  def self.create
225
256
  ActiveRecord::Migration.verbose = false
@@ -283,6 +314,20 @@ module Schema
283
314
  t.belongs_to :agent_account
284
315
  t.belongs_to :trade_account
285
316
  end
317
+
318
+ create_table :addresses, force: true do |t|
319
+ t.string :city
320
+ end
321
+
322
+ create_table :organizations, force: true do |t|
323
+ t.string :name
324
+ t.integer :address_id
325
+ end
326
+
327
+ create_table :employees, force: true do |t|
328
+ t.string :name
329
+ t.integer :organization_id
330
+ end
286
331
  end
287
332
 
288
333
  10.times do
@@ -308,7 +353,7 @@ module Schema
308
353
  end
309
354
 
310
355
  module SubDB
311
- class Base < ActiveRecord::Base
356
+ class Base < ApplicationRecord
312
357
  self.abstract_class = true
313
358
  establish_connection(
314
359
  adapter: 'sqlite3',