ransack 3.2.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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/codeql.yml +72 -0
  3. data/.github/workflows/test.yml +22 -24
  4. data/.rubocop.yml +3 -0
  5. data/CHANGELOG.md +72 -1
  6. data/CONTRIBUTING.md +37 -15
  7. data/Gemfile +9 -9
  8. data/README.md +8 -13
  9. data/bug_report_templates/test-ransacker-arel-present-predicate.rb +4 -0
  10. data/docs/docs/getting-started/advanced-mode.md +1 -1
  11. data/docs/docs/getting-started/search-matches.md +1 -1
  12. data/docs/docs/getting-started/simple-mode.md +30 -26
  13. data/docs/docs/getting-started/sorting.md +1 -1
  14. data/docs/docs/going-further/acts-as-taggable-on.md +10 -10
  15. data/docs/docs/going-further/exporting-to-csv.md +2 -2
  16. data/docs/docs/going-further/form-customisation.md +1 -1
  17. data/docs/docs/going-further/i18n.md +3 -3
  18. data/docs/docs/going-further/other-notes.md +1 -1
  19. data/docs/docs/going-further/saving-queries.md +1 -1
  20. data/docs/docs/going-further/searching-postgres.md +1 -1
  21. data/docs/docs/intro.md +2 -2
  22. data/docs/docusaurus.config.js +14 -1
  23. data/docs/package.json +7 -2
  24. data/docs/yarn.lock +3036 -1917
  25. data/lib/ransack/{adapters/active_record.rb → active_record.rb} +0 -0
  26. data/lib/ransack/adapters/active_record/base.rb +78 -7
  27. data/lib/ransack/configuration.rb +25 -12
  28. data/lib/ransack/constants.rb +125 -0
  29. data/lib/ransack/context.rb +34 -5
  30. data/lib/ransack/helpers/form_builder.rb +3 -3
  31. data/lib/ransack/helpers/form_helper.rb +3 -2
  32. data/lib/ransack/nodes/attribute.rb +2 -2
  33. data/lib/ransack/nodes/condition.rb +80 -7
  34. data/lib/ransack/nodes/grouping.rb +3 -3
  35. data/lib/ransack/nodes/node.rb +1 -1
  36. data/lib/ransack/nodes/value.rb +2 -2
  37. data/lib/ransack/predicate.rb +1 -1
  38. data/lib/ransack/ransacker.rb +1 -1
  39. data/lib/ransack/search.rb +9 -4
  40. data/lib/ransack/translate.rb +2 -2
  41. data/lib/ransack/version.rb +1 -1
  42. data/lib/ransack/visitor.rb +38 -2
  43. data/lib/ransack.rb +3 -6
  44. data/spec/ransack/adapters/active_record/base_spec.rb +73 -0
  45. data/spec/ransack/configuration_spec.rb +9 -9
  46. data/spec/ransack/helpers/form_builder_spec.rb +8 -8
  47. data/spec/ransack/helpers/form_helper_spec.rb +36 -2
  48. data/spec/ransack/nodes/condition_spec.rb +24 -0
  49. data/spec/ransack/nodes/value_spec.rb +115 -0
  50. data/spec/ransack/predicate_spec.rb +36 -1
  51. data/spec/ransack/translate_spec.rb +1 -1
  52. data/spec/support/schema.rb +27 -10
  53. metadata +7 -12
  54. data/lib/polyamorous.rb +0 -1
  55. data/lib/ransack/adapters/active_record/ransack/constants.rb +0 -128
  56. data/lib/ransack/adapters/active_record/ransack/context.rb +0 -56
  57. data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +0 -61
  58. data/lib/ransack/adapters/active_record/ransack/translate.rb +0 -8
  59. data/lib/ransack/adapters/active_record/ransack/visitor.rb +0 -47
  60. data/lib/ransack/adapters.rb +0 -64
  61. data/lib/ransack/nodes.rb +0 -8
@@ -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,22 +217,22 @@ 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
@@ -308,7 +325,7 @@ module Schema
308
325
  end
309
326
 
310
327
  module SubDB
311
- class Base < ActiveRecord::Base
328
+ class Base < ApplicationRecord
312
329
  self.abstract_class = true
313
330
  establish_connection(
314
331
  adapter: 'sqlite3',
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ransack
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.0
4
+ version: 4.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ernie Miller
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2022-05-09 00:00:00.000000000 Z
15
+ date: 2023-02-09 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: activerecord
@@ -69,6 +69,7 @@ extra_rdoc_files: []
69
69
  files:
70
70
  - ".github/FUNDING.yml"
71
71
  - ".github/SECURITY.md"
72
+ - ".github/workflows/codeql.yml"
72
73
  - ".github/workflows/cronjob.yml"
73
74
  - ".github/workflows/deploy.yml"
74
75
  - ".github/workflows/rubocop.yml"
@@ -139,7 +140,6 @@ files:
139
140
  - docs/static/logo/ransack.png
140
141
  - docs/static/logo/ransack.svg
141
142
  - docs/yarn.lock
142
- - lib/polyamorous.rb
143
143
  - lib/polyamorous/activerecord_6.1_ruby_2/join_association.rb
144
144
  - lib/polyamorous/activerecord_6.1_ruby_2/join_dependency.rb
145
145
  - lib/polyamorous/activerecord_6.1_ruby_2/reflection.rb
@@ -154,15 +154,9 @@ files:
154
154
  - lib/polyamorous/swapping_reflection_class.rb
155
155
  - lib/polyamorous/tree_node.rb
156
156
  - lib/ransack.rb
157
- - lib/ransack/adapters.rb
158
- - lib/ransack/adapters/active_record.rb
157
+ - lib/ransack/active_record.rb
159
158
  - lib/ransack/adapters/active_record/base.rb
160
159
  - lib/ransack/adapters/active_record/context.rb
161
- - lib/ransack/adapters/active_record/ransack/constants.rb
162
- - lib/ransack/adapters/active_record/ransack/context.rb
163
- - lib/ransack/adapters/active_record/ransack/nodes/condition.rb
164
- - lib/ransack/adapters/active_record/ransack/translate.rb
165
- - lib/ransack/adapters/active_record/ransack/visitor.rb
166
160
  - lib/ransack/configuration.rb
167
161
  - lib/ransack/constants.rb
168
162
  - lib/ransack/context.rb
@@ -196,7 +190,6 @@ files:
196
190
  - lib/ransack/locale/zh-CN.yml
197
191
  - lib/ransack/locale/zh-TW.yml
198
192
  - lib/ransack/naming.rb
199
- - lib/ransack/nodes.rb
200
193
  - lib/ransack/nodes/attribute.rb
201
194
  - lib/ransack/nodes/bindable.rb
202
195
  - lib/ransack/nodes/condition.rb
@@ -230,6 +223,7 @@ files:
230
223
  - spec/ransack/helpers/form_helper_spec.rb
231
224
  - spec/ransack/nodes/condition_spec.rb
232
225
  - spec/ransack/nodes/grouping_spec.rb
226
+ - spec/ransack/nodes/value_spec.rb
233
227
  - spec/ransack/predicate_spec.rb
234
228
  - spec/ransack/search_spec.rb
235
229
  - spec/ransack/translate_spec.rb
@@ -255,7 +249,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
255
249
  - !ruby/object:Gem::Version
256
250
  version: '0'
257
251
  requirements: []
258
- rubygems_version: 3.4.0.dev
252
+ rubygems_version: 3.4.6
259
253
  signing_key:
260
254
  specification_version: 4
261
255
  summary: Object-based searching for Active Record.
@@ -279,6 +273,7 @@ test_files:
279
273
  - spec/ransack/helpers/form_helper_spec.rb
280
274
  - spec/ransack/nodes/condition_spec.rb
281
275
  - spec/ransack/nodes/grouping_spec.rb
276
+ - spec/ransack/nodes/value_spec.rb
282
277
  - spec/ransack/predicate_spec.rb
283
278
  - spec/ransack/search_spec.rb
284
279
  - spec/ransack/translate_spec.rb
data/lib/polyamorous.rb DELETED
@@ -1 +0,0 @@
1
- require 'polyamorous/polyamorous'
@@ -1,128 +0,0 @@
1
- module Ransack
2
- module Constants
3
- DISTINCT = 'DISTINCT '.freeze
4
-
5
- DERIVED_PREDICATES = [
6
- [CONT, {
7
- arel_predicate: 'matches'.freeze,
8
- formatter: proc { |v| "%#{escape_wildcards(v)}%" }
9
- }
10
- ],
11
- ['not_cont'.freeze, {
12
- arel_predicate: 'does_not_match'.freeze,
13
- formatter: proc { |v| "%#{escape_wildcards(v)}%" }
14
- }
15
- ],
16
- ['i_cont'.freeze, {
17
- arel_predicate: 'matches'.freeze,
18
- formatter: proc { |v| "%#{escape_wildcards(v.downcase)}%" },
19
- case_insensitive: true
20
- }
21
- ],
22
- ['not_i_cont'.freeze, {
23
- arel_predicate: 'does_not_match'.freeze,
24
- formatter: proc { |v| "%#{escape_wildcards(v.downcase)}%" },
25
- case_insensitive: true
26
- }
27
- ],
28
- ['start'.freeze, {
29
- arel_predicate: 'matches'.freeze,
30
- formatter: proc { |v| "#{escape_wildcards(v)}%" }
31
- }
32
- ],
33
- ['not_start'.freeze, {
34
- arel_predicate: 'does_not_match'.freeze,
35
- formatter: proc { |v| "#{escape_wildcards(v)}%" }
36
- }
37
- ],
38
- ['end'.freeze, {
39
- arel_predicate: 'matches'.freeze,
40
- formatter: proc { |v| "%#{escape_wildcards(v)}" }
41
- }
42
- ],
43
- ['not_end'.freeze, {
44
- arel_predicate: 'does_not_match'.freeze,
45
- formatter: proc { |v| "%#{escape_wildcards(v)}" }
46
- }
47
- ],
48
- ['true'.freeze, {
49
- arel_predicate: proc { |v| v ? EQ : NOT_EQ },
50
- compounds: false,
51
- type: :boolean,
52
- validator: proc { |v| BOOLEAN_VALUES.include?(v) },
53
- formatter: proc { |v| true }
54
- }
55
- ],
56
- ['not_true'.freeze, {
57
- arel_predicate: proc { |v| v ? NOT_EQ : EQ },
58
- compounds: false,
59
- type: :boolean,
60
- validator: proc { |v| BOOLEAN_VALUES.include?(v) },
61
- formatter: proc { |v| true }
62
- }
63
- ],
64
- ['false'.freeze, {
65
- arel_predicate: proc { |v| v ? EQ : NOT_EQ },
66
- compounds: false,
67
- type: :boolean,
68
- validator: proc { |v| BOOLEAN_VALUES.include?(v) },
69
- formatter: proc { |v| false }
70
- }
71
- ],
72
- ['not_false'.freeze, {
73
- arel_predicate: proc { |v| v ? NOT_EQ : EQ },
74
- compounds: false,
75
- type: :boolean,
76
- validator: proc { |v| BOOLEAN_VALUES.include?(v) },
77
- formatter: proc { |v| false }
78
- }
79
- ],
80
- ['present'.freeze, {
81
- arel_predicate: proc { |v| v ? NOT_EQ_ALL : EQ_ANY },
82
- compounds: false,
83
- type: :boolean,
84
- validator: proc { |v| BOOLEAN_VALUES.include?(v) },
85
- formatter: proc { |v| [nil, ''.freeze].freeze }
86
- }
87
- ],
88
- ['blank'.freeze, {
89
- arel_predicate: proc { |v| v ? EQ_ANY : NOT_EQ_ALL },
90
- compounds: false,
91
- type: :boolean,
92
- validator: proc { |v| BOOLEAN_VALUES.include?(v) },
93
- formatter: proc { |v| [nil, ''.freeze].freeze }
94
- }
95
- ],
96
- ['null'.freeze, {
97
- arel_predicate: proc { |v| v ? EQ : NOT_EQ },
98
- compounds: false,
99
- type: :boolean,
100
- validator: proc { |v| BOOLEAN_VALUES.include?(v) },
101
- formatter: proc { |v| nil }
102
- }
103
- ],
104
- ['not_null'.freeze, {
105
- arel_predicate: proc { |v| v ? NOT_EQ : EQ },
106
- compounds: false,
107
- type: :boolean,
108
- validator: proc { |v| BOOLEAN_VALUES.include?(v) },
109
- formatter: proc { |v| nil } }
110
- ]
111
- ].freeze
112
-
113
- module_function
114
- # replace % \ to \% \\
115
- def escape_wildcards(unescaped)
116
- case ActiveRecord::Base.connection.adapter_name
117
- when "Mysql2".freeze
118
- # Necessary for MySQL
119
- unescaped.to_s.gsub(/([\\%_])/, '\\\\\\1')
120
- when "PostgreSQL".freeze
121
- # Necessary for PostgreSQL
122
- unescaped.to_s.gsub(/([\\%_.])/, '\\\\\\1')
123
- else
124
- unescaped
125
- end
126
- end
127
- end
128
- end
@@ -1,56 +0,0 @@
1
- require 'ransack/visitor'
2
-
3
- module Ransack
4
- class Context
5
- attr_reader :arel_visitor
6
-
7
- class << self
8
-
9
- def for_class(klass, options = {})
10
- if klass < ActiveRecord::Base
11
- Adapters::ActiveRecord::Context.new(klass, options)
12
- end
13
- end
14
-
15
- def for_object(object, options = {})
16
- case object
17
- when ActiveRecord::Relation
18
- Adapters::ActiveRecord::Context.new(object.klass, options)
19
- end
20
- end
21
-
22
- end # << self
23
-
24
- def initialize(object, options = {})
25
- @object = relation_for(object)
26
- @klass = @object.klass
27
- @join_dependency = join_dependency(@object)
28
- @join_type = options[:join_type] || Polyamorous::OuterJoin
29
- @search_key = options[:search_key] || Ransack.options[:search_key]
30
- @associations_pot = {}
31
- @tables_pot = {}
32
- @lock_associations = []
33
-
34
- @base = @join_dependency.instance_variable_get(:@join_root)
35
- end
36
-
37
- def bind_pair_for(key)
38
- @bind_pairs ||= {}
39
-
40
- @bind_pairs[key] ||= begin
41
- parent, attr_name = get_parent_and_attribute_name(key.to_s)
42
- [parent, attr_name] if parent && attr_name
43
- end
44
- end
45
-
46
- def klassify(obj)
47
- if Class === obj && ::ActiveRecord::Base > obj
48
- obj
49
- elsif obj.respond_to? :klass
50
- obj.klass
51
- else
52
- raise ArgumentError, "Don't know how to klassify #{obj.inspect}"
53
- end
54
- end
55
- end
56
- end
@@ -1,61 +0,0 @@
1
- module Ransack
2
- module Nodes
3
- class Condition
4
-
5
- def arel_predicate
6
- attributes.map { |attribute|
7
- association = attribute.parent
8
- if negative? && attribute.associated_collection?
9
- query = context.build_correlated_subquery(association)
10
- context.remove_association(association)
11
- if self.predicate_name == 'not_null' && self.value
12
- query.where(format_predicate(attribute))
13
- Arel::Nodes::In.new(context.primary_key, Arel.sql(query.to_sql))
14
- else
15
- query.where(format_predicate(attribute).not)
16
- Arel::Nodes::NotIn.new(context.primary_key, Arel.sql(query.to_sql))
17
- end
18
- else
19
- format_predicate(attribute)
20
- end
21
- }.reduce(combinator_method)
22
- end
23
-
24
- private
25
-
26
- def combinator_method
27
- combinator === Constants::OR ? :or : :and
28
- end
29
-
30
- def format_predicate(attribute)
31
- arel_pred = arel_predicate_for_attribute(attribute)
32
- arel_values = formatted_values_for_attribute(attribute)
33
- predicate = attr_value_for_attribute(attribute).public_send(arel_pred, arel_values)
34
-
35
- if in_predicate?(predicate)
36
- predicate.right = predicate.right.map do |pr|
37
- casted_array?(pr) ? format_values_for(pr) : pr
38
- end
39
- end
40
-
41
- predicate
42
- end
43
-
44
- def in_predicate?(predicate)
45
- return unless defined?(Arel::Nodes::Casted)
46
- predicate.class == Arel::Nodes::In || predicate.class == Arel::Nodes::NotIn
47
- end
48
-
49
- def casted_array?(predicate)
50
- predicate.value.is_a?(Array) && predicate.is_a?(Arel::Nodes::Casted)
51
- end
52
-
53
- def format_values_for(predicate)
54
- predicate.value.map do |val|
55
- val.is_a?(String) ? Arel::Nodes.build_quoted(val) : val
56
- end
57
- end
58
-
59
- end
60
- end
61
- end
@@ -1,8 +0,0 @@
1
- module Ransack
2
- module Translate
3
-
4
- def self.i18n_key(klass)
5
- klass.model_name.i18n_key
6
- end
7
- end
8
- end
@@ -1,47 +0,0 @@
1
- module Ransack
2
- class Visitor
3
- def visit_and(object)
4
- nodes = object.values.map { |o| accept(o) }.compact
5
- return nil unless nodes.size > 0
6
-
7
- if nodes.size > 1
8
- Arel::Nodes::Grouping.new(Arel::Nodes::And.new(nodes))
9
- else
10
- nodes.first
11
- end
12
- end
13
-
14
- def quoted?(object)
15
- case object
16
- when Arel::Nodes::SqlLiteral, Bignum, Fixnum
17
- false
18
- else
19
- true
20
- end
21
- end
22
-
23
- def visit_Ransack_Nodes_Sort(object)
24
- if object.valid?
25
- if object.attr.is_a?(Arel::Attributes::Attribute)
26
- object.attr.send(object.dir)
27
- else
28
- ordered(object)
29
- end
30
- else
31
- scope_name = :"sort_by_#{object.name}_#{object.dir}"
32
- scope_name if object.context.object.respond_to?(scope_name)
33
- end
34
- end
35
-
36
- private
37
-
38
- def ordered(object)
39
- case object.dir
40
- when 'asc'.freeze
41
- Arel::Nodes::Ascending.new(object.attr)
42
- when 'desc'.freeze
43
- Arel::Nodes::Descending.new(object.attr)
44
- end
45
- end
46
- end
47
- end
@@ -1,64 +0,0 @@
1
- module Ransack
2
- module Adapters
3
-
4
- def self.object_mapper
5
- @object_mapper ||= instantiate_object_mapper
6
- end
7
-
8
- def self.instantiate_object_mapper
9
- if defined?(::ActiveRecord::Base)
10
- ActiveRecordAdapter.new
11
- elsif defined?(::Mongoid)
12
- MongoidAdapter.new
13
- else
14
- raise "Unsupported adapter"
15
- end
16
- end
17
-
18
- class ActiveRecordAdapter
19
- def require_constants
20
- require 'ransack/adapters/active_record/ransack/constants'
21
- end
22
-
23
- def require_adapter
24
- require 'ransack/adapters/active_record/ransack/translate'
25
- require 'ransack/adapters/active_record'
26
- end
27
-
28
- def require_context
29
- require 'ransack/adapters/active_record/ransack/visitor'
30
- end
31
-
32
- def require_nodes
33
- require 'ransack/adapters/active_record/ransack/nodes/condition'
34
- end
35
-
36
- def require_search
37
- require 'ransack/adapters/active_record/ransack/context'
38
- end
39
- end
40
-
41
- class MongoidAdapter
42
- def require_constants
43
- require 'ransack/adapters/mongoid/ransack/constants'
44
- end
45
-
46
- def require_adapter
47
- require 'ransack/adapters/mongoid/ransack/translate'
48
- require 'ransack/adapters/mongoid'
49
- end
50
-
51
- def require_context
52
- require 'ransack/adapters/mongoid/ransack/visitor'
53
- end
54
-
55
- def require_nodes
56
- require 'ransack/adapters/mongoid/ransack/nodes/condition'
57
- end
58
-
59
- def require_search
60
- require 'ransack/adapters/mongoid/ransack/context'
61
- end
62
- end
63
- end
64
- end
data/lib/ransack/nodes.rb DELETED
@@ -1,8 +0,0 @@
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
- Ransack::Adapters.object_mapper.require_nodes
7
- require 'ransack/nodes/sort'
8
- require 'ransack/nodes/grouping'