ransack 3.2.1 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) 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/join_dependency.rb +14 -4
  29. data/lib/ransack/adapters/active_record/base.rb +78 -7
  30. data/lib/ransack/adapters/active_record/context.rb +2 -1
  31. data/lib/ransack/configuration.rb +25 -12
  32. data/lib/ransack/constants.rb +125 -0
  33. data/lib/ransack/context.rb +34 -5
  34. data/lib/ransack/helpers/form_builder.rb +3 -3
  35. data/lib/ransack/helpers/form_helper.rb +3 -2
  36. data/lib/ransack/nodes/attribute.rb +2 -2
  37. data/lib/ransack/nodes/condition.rb +80 -7
  38. data/lib/ransack/nodes/grouping.rb +3 -3
  39. data/lib/ransack/nodes/node.rb +1 -1
  40. data/lib/ransack/nodes/value.rb +1 -1
  41. data/lib/ransack/predicate.rb +1 -1
  42. data/lib/ransack/ransacker.rb +1 -1
  43. data/lib/ransack/search.rb +9 -4
  44. data/lib/ransack/translate.rb +2 -2
  45. data/lib/ransack/version.rb +1 -1
  46. data/lib/ransack/visitor.rb +38 -2
  47. data/lib/ransack.rb +3 -6
  48. data/ransack.gemspec +1 -1
  49. data/spec/ransack/adapters/active_record/base_spec.rb +89 -0
  50. data/spec/ransack/configuration_spec.rb +9 -9
  51. data/spec/ransack/helpers/form_builder_spec.rb +8 -8
  52. data/spec/ransack/helpers/form_helper_spec.rb +36 -2
  53. data/spec/ransack/nodes/condition_spec.rb +24 -0
  54. data/spec/ransack/predicate_spec.rb +36 -1
  55. data/spec/ransack/translate_spec.rb +1 -1
  56. data/spec/support/schema.rb +55 -10
  57. metadata +6 -13
  58. data/lib/polyamorous.rb +0 -1
  59. data/lib/ransack/adapters/active_record/ransack/constants.rb +0 -128
  60. data/lib/ransack/adapters/active_record/ransack/context.rb +0 -56
  61. data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +0 -61
  62. data/lib/ransack/adapters/active_record/ransack/translate.rb +0 -8
  63. data/lib/ransack/adapters/active_record/ransack/visitor.rb +0 -47
  64. data/lib/ransack/adapters.rb +0 -64
  65. data/lib/ransack/nodes.rb +0 -8
  66. /data/lib/ransack/{adapters/active_record.rb → active_record.rb} +0 -0
@@ -1,3 +1,3 @@
1
1
  module Ransack
2
- VERSION = '3.2.1'
2
+ VERSION = '4.1.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,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',