ransack 3.2.1 → 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.
- checksums.yaml +4 -4
- data/.github/workflows/codeql.yml +72 -0
- data/.github/workflows/test.yml +6 -8
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +51 -0
- data/CONTRIBUTING.md +33 -11
- data/Gemfile +9 -9
- data/README.md +4 -9
- data/bug_report_templates/test-ransacker-arel-present-predicate.rb +4 -0
- data/docs/docs/getting-started/advanced-mode.md +1 -1
- data/docs/docs/getting-started/search-matches.md +1 -1
- data/docs/docs/getting-started/simple-mode.md +6 -2
- data/docs/docs/going-further/acts-as-taggable-on.md +4 -4
- data/docs/docs/going-further/form-customisation.md +1 -1
- data/docs/docs/going-further/i18n.md +3 -3
- data/docs/docs/going-further/other-notes.md +1 -1
- data/docs/docs/going-further/saving-queries.md +1 -1
- data/docs/docs/going-further/searching-postgres.md +1 -1
- data/docs/package.json +7 -3
- data/docs/yarn.lock +2255 -1901
- data/lib/ransack/{adapters/active_record.rb → active_record.rb} +0 -0
- data/lib/ransack/adapters/active_record/base.rb +78 -7
- data/lib/ransack/configuration.rb +25 -12
- data/lib/ransack/constants.rb +125 -0
- data/lib/ransack/context.rb +34 -5
- data/lib/ransack/helpers/form_builder.rb +3 -3
- data/lib/ransack/helpers/form_helper.rb +3 -2
- data/lib/ransack/nodes/attribute.rb +2 -2
- data/lib/ransack/nodes/condition.rb +80 -7
- data/lib/ransack/nodes/grouping.rb +3 -3
- data/lib/ransack/nodes/node.rb +1 -1
- data/lib/ransack/nodes/value.rb +1 -1
- data/lib/ransack/predicate.rb +1 -1
- data/lib/ransack/ransacker.rb +1 -1
- data/lib/ransack/search.rb +9 -4
- data/lib/ransack/translate.rb +2 -2
- data/lib/ransack/version.rb +1 -1
- data/lib/ransack/visitor.rb +38 -2
- data/lib/ransack.rb +3 -6
- data/spec/ransack/adapters/active_record/base_spec.rb +73 -0
- data/spec/ransack/configuration_spec.rb +9 -9
- data/spec/ransack/helpers/form_builder_spec.rb +8 -8
- data/spec/ransack/helpers/form_helper_spec.rb +36 -2
- data/spec/ransack/nodes/condition_spec.rb +24 -0
- data/spec/ransack/predicate_spec.rb +36 -1
- data/spec/ransack/translate_spec.rb +1 -1
- data/spec/support/schema.rb +27 -10
- metadata +5 -12
- data/lib/polyamorous.rb +0 -1
- data/lib/ransack/adapters/active_record/ransack/constants.rb +0 -128
- data/lib/ransack/adapters/active_record/ransack/context.rb +0 -56
- data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +0 -61
- data/lib/ransack/adapters/active_record/ransack/translate.rb +0 -8
- data/lib/ransack/adapters/active_record/ransack/visitor.rb +0 -47
- data/lib/ransack/adapters.rb +0 -64
- data/lib/ransack/nodes.rb +0 -8
File without changes
|
@@ -35,12 +35,7 @@ module Ransack
|
|
35
35
|
# For overriding with a whitelist array of strings.
|
36
36
|
#
|
37
37
|
def ransackable_attributes(auth_object = nil)
|
38
|
-
@ransackable_attributes ||=
|
39
|
-
column_names + _ransackers.keys + _ransack_aliases.keys +
|
40
|
-
attribute_aliases.keys
|
41
|
-
else
|
42
|
-
column_names + _ransackers.keys + _ransack_aliases.keys
|
43
|
-
end
|
38
|
+
@ransackable_attributes ||= deprecated_ransackable_list(:ransackable_attributes)
|
44
39
|
end
|
45
40
|
|
46
41
|
# Ransackable_associations, by default, returns the names
|
@@ -48,7 +43,7 @@ module Ransack
|
|
48
43
|
# For overriding with a whitelist array of strings.
|
49
44
|
#
|
50
45
|
def ransackable_associations(auth_object = nil)
|
51
|
-
@ransackable_associations ||=
|
46
|
+
@ransackable_associations ||= deprecated_ransackable_list(:ransackable_associations)
|
52
47
|
end
|
53
48
|
|
54
49
|
# Ransortable_attributes, by default, returns the names
|
@@ -75,6 +70,82 @@ module Ransack
|
|
75
70
|
[]
|
76
71
|
end
|
77
72
|
|
73
|
+
# Bare list of all potentially searchable attributes. Searchable attributes
|
74
|
+
# need to be explicitly allowlisted through the `ransackable_attributes`
|
75
|
+
# method in each model, but if you're allowing almost everything to be
|
76
|
+
# searched, this list can be used as a base for exclusions.
|
77
|
+
#
|
78
|
+
def authorizable_ransackable_attributes
|
79
|
+
if Ransack::SUPPORTS_ATTRIBUTE_ALIAS
|
80
|
+
column_names + _ransackers.keys + _ransack_aliases.keys +
|
81
|
+
attribute_aliases.keys
|
82
|
+
else
|
83
|
+
column_names + _ransackers.keys + _ransack_aliases.keys
|
84
|
+
end.uniq
|
85
|
+
end
|
86
|
+
|
87
|
+
# Bare list of all potentially searchable associations. Searchable
|
88
|
+
# associations need to be explicitly allowlisted through the
|
89
|
+
# `ransackable_associations` method in each model, but if you're
|
90
|
+
# allowing almost everything to be searched, this list can be used as a
|
91
|
+
# base for exclusions.
|
92
|
+
#
|
93
|
+
def authorizable_ransackable_associations
|
94
|
+
reflect_on_all_associations.map { |a| a.name.to_s }
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def deprecated_ransackable_list(method)
|
100
|
+
list_type = method.to_s.delete_prefix("ransackable_")
|
101
|
+
|
102
|
+
if explicitly_defined?(method)
|
103
|
+
warn_deprecated <<~ERROR
|
104
|
+
Ransack's builtin `#{method}` method is deprecated and will result
|
105
|
+
in an error in the future. If you want to authorize the full list
|
106
|
+
of searchable #{list_type} for this model, use
|
107
|
+
`authorizable_#{method}` instead of delegating to `super`.
|
108
|
+
ERROR
|
109
|
+
|
110
|
+
public_send("authorizable_#{method}")
|
111
|
+
else
|
112
|
+
raise <<~MESSAGE
|
113
|
+
Ransack needs #{name} #{list_type} explicitly allowlisted as
|
114
|
+
searchable. Define a `#{method}` class method in your `#{name}`
|
115
|
+
model, watching out for items you DON'T want searchable (for
|
116
|
+
example, `encrypted_password`, `password_reset_token`, `owner` or
|
117
|
+
other sensitive information). You can use the following as a base:
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
class #{name} < ApplicationRecord
|
121
|
+
|
122
|
+
# ...
|
123
|
+
|
124
|
+
def self.#{method}(auth_object = nil)
|
125
|
+
#{public_send("authorizable_#{method}").sort.inspect}
|
126
|
+
end
|
127
|
+
|
128
|
+
# ...
|
129
|
+
|
130
|
+
end
|
131
|
+
```
|
132
|
+
MESSAGE
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def explicitly_defined?(method)
|
137
|
+
definer_ancestor = singleton_class.ancestors.find do |ancestor|
|
138
|
+
ancestor.instance_methods(false).include?(method)
|
139
|
+
end
|
140
|
+
|
141
|
+
definer_ancestor != Ransack::Adapters::ActiveRecord::Base
|
142
|
+
end
|
143
|
+
|
144
|
+
def warn_deprecated(message)
|
145
|
+
caller_location = caller_locations.find { |location| !location.path.start_with?(File.expand_path("../..", __dir__)) }
|
146
|
+
|
147
|
+
warn "DEPRECATION WARNING: #{message.squish} (called at #{caller_location.path}:#{caller_location.lineno})"
|
148
|
+
end
|
78
149
|
end
|
79
150
|
end
|
80
151
|
end
|
@@ -27,15 +27,15 @@ module Ransack
|
|
27
27
|
self.predicates = PredicateCollection.new
|
28
28
|
|
29
29
|
self.options = {
|
30
|
-
:
|
31
|
-
:
|
32
|
-
:
|
33
|
-
:
|
34
|
-
:
|
35
|
-
:
|
36
|
-
:
|
37
|
-
:
|
38
|
-
:
|
30
|
+
search_key: :q,
|
31
|
+
ignore_unknown_conditions: true,
|
32
|
+
hide_sort_order_indicators: false,
|
33
|
+
up_arrow: '▼'.freeze,
|
34
|
+
down_arrow: '▲'.freeze,
|
35
|
+
default_arrow: nil,
|
36
|
+
sanitize_scope_args: true,
|
37
|
+
postgres_fields_sort_option: nil,
|
38
|
+
strip_whitespace: true
|
39
39
|
}
|
40
40
|
|
41
41
|
def configure
|
@@ -55,11 +55,11 @@ module Ransack
|
|
55
55
|
compound_name = name + suffix
|
56
56
|
self.predicates[compound_name] = Predicate.new(
|
57
57
|
opts.merge(
|
58
|
-
:
|
59
|
-
:
|
58
|
+
name: compound_name,
|
59
|
+
arel_predicate: arel_predicate_with_suffix(
|
60
60
|
opts[:arel_predicate], suffix
|
61
61
|
),
|
62
|
-
:
|
62
|
+
compound: true
|
63
63
|
)
|
64
64
|
)
|
65
65
|
end if compounds
|
@@ -101,6 +101,19 @@ module Ransack
|
|
101
101
|
self.options[:ignore_unknown_conditions] = boolean
|
102
102
|
end
|
103
103
|
|
104
|
+
# By default Ransack ignores empty predicates. Ransack can also fallback to
|
105
|
+
# a default predicate by setting it in an initializer file
|
106
|
+
# like `config/initializers/ransack.rb` as follows:
|
107
|
+
#
|
108
|
+
# Ransack.configure do |config|
|
109
|
+
# # Use the 'eq' predicate if an unknown predicate is passed
|
110
|
+
# config.default_predicate = 'eq'
|
111
|
+
# end
|
112
|
+
#
|
113
|
+
def default_predicate=(name)
|
114
|
+
self.options[:default_predicate] = name
|
115
|
+
end
|
116
|
+
|
104
117
|
# By default, Ransack displays sort order indicator arrows with HTML codes:
|
105
118
|
#
|
106
119
|
# up_arrow: '▼'
|
data/lib/ransack/constants.rb
CHANGED
@@ -47,5 +47,130 @@ module Ransack
|
|
47
47
|
|
48
48
|
RANSACK_SLASH_SEARCHES = 'ransack/searches'.freeze
|
49
49
|
RANSACK_SLASH_SEARCHES_SLASH_SEARCH = 'ransack/searches/search'.freeze
|
50
|
+
|
51
|
+
DISTINCT = 'DISTINCT '.freeze
|
52
|
+
|
53
|
+
DERIVED_PREDICATES = [
|
54
|
+
[CONT, {
|
55
|
+
arel_predicate: 'matches'.freeze,
|
56
|
+
formatter: proc { |v| "%#{escape_wildcards(v)}%" }
|
57
|
+
}
|
58
|
+
],
|
59
|
+
['not_cont'.freeze, {
|
60
|
+
arel_predicate: 'does_not_match'.freeze,
|
61
|
+
formatter: proc { |v| "%#{escape_wildcards(v)}%" }
|
62
|
+
}
|
63
|
+
],
|
64
|
+
['i_cont'.freeze, {
|
65
|
+
arel_predicate: 'matches'.freeze,
|
66
|
+
formatter: proc { |v| "%#{escape_wildcards(v.downcase)}%" },
|
67
|
+
case_insensitive: true
|
68
|
+
}
|
69
|
+
],
|
70
|
+
['not_i_cont'.freeze, {
|
71
|
+
arel_predicate: 'does_not_match'.freeze,
|
72
|
+
formatter: proc { |v| "%#{escape_wildcards(v.downcase)}%" },
|
73
|
+
case_insensitive: true
|
74
|
+
}
|
75
|
+
],
|
76
|
+
['start'.freeze, {
|
77
|
+
arel_predicate: 'matches'.freeze,
|
78
|
+
formatter: proc { |v| "#{escape_wildcards(v)}%" }
|
79
|
+
}
|
80
|
+
],
|
81
|
+
['not_start'.freeze, {
|
82
|
+
arel_predicate: 'does_not_match'.freeze,
|
83
|
+
formatter: proc { |v| "#{escape_wildcards(v)}%" }
|
84
|
+
}
|
85
|
+
],
|
86
|
+
['end'.freeze, {
|
87
|
+
arel_predicate: 'matches'.freeze,
|
88
|
+
formatter: proc { |v| "%#{escape_wildcards(v)}" }
|
89
|
+
}
|
90
|
+
],
|
91
|
+
['not_end'.freeze, {
|
92
|
+
arel_predicate: 'does_not_match'.freeze,
|
93
|
+
formatter: proc { |v| "%#{escape_wildcards(v)}" }
|
94
|
+
}
|
95
|
+
],
|
96
|
+
['true'.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| true }
|
102
|
+
}
|
103
|
+
],
|
104
|
+
['not_true'.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| true }
|
110
|
+
}
|
111
|
+
],
|
112
|
+
['false'.freeze, {
|
113
|
+
arel_predicate: proc { |v| v ? EQ : NOT_EQ },
|
114
|
+
compounds: false,
|
115
|
+
type: :boolean,
|
116
|
+
validator: proc { |v| BOOLEAN_VALUES.include?(v) },
|
117
|
+
formatter: proc { |v| false }
|
118
|
+
}
|
119
|
+
],
|
120
|
+
['not_false'.freeze, {
|
121
|
+
arel_predicate: proc { |v| v ? NOT_EQ : EQ },
|
122
|
+
compounds: false,
|
123
|
+
type: :boolean,
|
124
|
+
validator: proc { |v| BOOLEAN_VALUES.include?(v) },
|
125
|
+
formatter: proc { |v| false }
|
126
|
+
}
|
127
|
+
],
|
128
|
+
['present'.freeze, {
|
129
|
+
arel_predicate: proc { |v| v ? NOT_EQ_ALL : EQ_ANY },
|
130
|
+
compounds: false,
|
131
|
+
type: :boolean,
|
132
|
+
validator: proc { |v| BOOLEAN_VALUES.include?(v) },
|
133
|
+
formatter: proc { |v| [nil, ''.freeze].freeze }
|
134
|
+
}
|
135
|
+
],
|
136
|
+
['blank'.freeze, {
|
137
|
+
arel_predicate: proc { |v| v ? EQ_ANY : NOT_EQ_ALL },
|
138
|
+
compounds: false,
|
139
|
+
type: :boolean,
|
140
|
+
validator: proc { |v| BOOLEAN_VALUES.include?(v) },
|
141
|
+
formatter: proc { |v| [nil, ''.freeze].freeze }
|
142
|
+
}
|
143
|
+
],
|
144
|
+
['null'.freeze, {
|
145
|
+
arel_predicate: proc { |v| v ? EQ : NOT_EQ },
|
146
|
+
compounds: false,
|
147
|
+
type: :boolean,
|
148
|
+
validator: proc { |v| BOOLEAN_VALUES.include?(v) },
|
149
|
+
formatter: proc { |v| nil }
|
150
|
+
}
|
151
|
+
],
|
152
|
+
['not_null'.freeze, {
|
153
|
+
arel_predicate: proc { |v| v ? NOT_EQ : EQ },
|
154
|
+
compounds: false,
|
155
|
+
type: :boolean,
|
156
|
+
validator: proc { |v| BOOLEAN_VALUES.include?(v) },
|
157
|
+
formatter: proc { |v| nil } }
|
158
|
+
]
|
159
|
+
].freeze
|
160
|
+
|
161
|
+
module_function
|
162
|
+
# replace % \ to \% \\
|
163
|
+
def escape_wildcards(unescaped)
|
164
|
+
case ActiveRecord::Base.connection.adapter_name
|
165
|
+
when "Mysql2".freeze
|
166
|
+
# Necessary for MySQL
|
167
|
+
unescaped.to_s.gsub(/([\\%_])/, '\\\\\\1')
|
168
|
+
when "PostgreSQL".freeze
|
169
|
+
# Necessary for PostgreSQL
|
170
|
+
unescaped.to_s.gsub(/([\\%_.])/, '\\\\\\1')
|
171
|
+
else
|
172
|
+
unescaped
|
173
|
+
end
|
174
|
+
end
|
50
175
|
end
|
51
176
|
end
|
data/lib/ransack/context.rb
CHANGED
@@ -1,19 +1,24 @@
|
|
1
1
|
require 'ransack/visitor'
|
2
|
-
Ransack::Adapters.object_mapper.require_context
|
3
2
|
|
4
3
|
module Ransack
|
5
4
|
class Context
|
6
5
|
attr_reader :search, :object, :klass, :base, :engine, :arel_visitor
|
7
6
|
attr_accessor :auth_object, :search_key
|
7
|
+
attr_reader :arel_visitor
|
8
8
|
|
9
9
|
class << self
|
10
10
|
|
11
11
|
def for_class(klass, options = {})
|
12
|
-
|
12
|
+
if klass < ActiveRecord::Base
|
13
|
+
Adapters::ActiveRecord::Context.new(klass, options)
|
14
|
+
end
|
13
15
|
end
|
14
16
|
|
15
17
|
def for_object(object, options = {})
|
16
|
-
|
18
|
+
case object
|
19
|
+
when ActiveRecord::Relation
|
20
|
+
Adapters::ActiveRecord::Context.new(object.klass, options)
|
21
|
+
end
|
17
22
|
end
|
18
23
|
|
19
24
|
def for(object, options = {})
|
@@ -30,11 +35,35 @@ module Ransack
|
|
30
35
|
end # << self
|
31
36
|
|
32
37
|
def initialize(object, options = {})
|
33
|
-
|
38
|
+
@object = relation_for(object)
|
39
|
+
@klass = @object.klass
|
40
|
+
@join_dependency = join_dependency(@object)
|
41
|
+
@join_type = options[:join_type] || Polyamorous::OuterJoin
|
42
|
+
@search_key = options[:search_key] || Ransack.options[:search_key]
|
43
|
+
@associations_pot = {}
|
44
|
+
@tables_pot = {}
|
45
|
+
@lock_associations = []
|
46
|
+
|
47
|
+
@base = @join_dependency.instance_variable_get(:@join_root)
|
48
|
+
end
|
49
|
+
|
50
|
+
def bind_pair_for(key)
|
51
|
+
@bind_pairs ||= {}
|
52
|
+
|
53
|
+
@bind_pairs[key] ||= begin
|
54
|
+
parent, attr_name = get_parent_and_attribute_name(key.to_s)
|
55
|
+
[parent, attr_name] if parent && attr_name
|
56
|
+
end
|
34
57
|
end
|
35
58
|
|
36
59
|
def klassify(obj)
|
37
|
-
|
60
|
+
if Class === obj && ::ActiveRecord::Base > obj
|
61
|
+
obj
|
62
|
+
elsif obj.respond_to? :klass
|
63
|
+
obj.klass
|
64
|
+
else
|
65
|
+
raise ArgumentError, "Don't know how to klassify #{obj.inspect}"
|
66
|
+
end
|
38
67
|
end
|
39
68
|
|
40
69
|
# Convert a string representing a chain of associations and an attribute
|
@@ -33,7 +33,7 @@ module Ransack
|
|
33
33
|
text = args.first
|
34
34
|
i18n = options[:i18n] || {}
|
35
35
|
text ||= object.translate(
|
36
|
-
method, i18n.reverse_merge(:
|
36
|
+
method, i18n.reverse_merge(include_associations: true)
|
37
37
|
) if object.respond_to? :translate
|
38
38
|
super(method, text, options, &block)
|
39
39
|
end
|
@@ -240,7 +240,7 @@ module Ransack
|
|
240
240
|
def get_attribute_element(action, base)
|
241
241
|
begin
|
242
242
|
[
|
243
|
-
Translate.association(base, :
|
243
|
+
Translate.association(base, context: object.context),
|
244
244
|
collection_for_base(action, base)
|
245
245
|
]
|
246
246
|
rescue UntraversableAssociationError
|
@@ -253,7 +253,7 @@ module Ransack
|
|
253
253
|
[
|
254
254
|
attr_from_base_and_column(base, c),
|
255
255
|
Translate.attribute(
|
256
|
-
attr_from_base_and_column(base, c), :
|
256
|
+
attr_from_base_and_column(base, c), context: object.context
|
257
257
|
)
|
258
258
|
]
|
259
259
|
end
|
@@ -129,7 +129,7 @@ module Ransack
|
|
129
129
|
end
|
130
130
|
|
131
131
|
def url_options
|
132
|
-
@params.merge(
|
132
|
+
@params.except(:host).merge(
|
133
133
|
@options.except(:class, :data, :host).merge(
|
134
134
|
@search.context.search_key => search_and_sort_params))
|
135
135
|
end
|
@@ -180,7 +180,8 @@ module Ransack
|
|
180
180
|
end
|
181
181
|
|
182
182
|
def search_params
|
183
|
-
@params[@search.context.search_key]
|
183
|
+
query_params = @params[@search.context.search_key]
|
184
|
+
query_params.is_a?(Hash) ? query_params : {}
|
184
185
|
end
|
185
186
|
|
186
187
|
def sort_params
|
@@ -5,8 +5,8 @@ module Ransack
|
|
5
5
|
|
6
6
|
attr_reader :name, :ransacker_args
|
7
7
|
|
8
|
-
delegate :blank?, :present?, :
|
9
|
-
delegate :engine, :
|
8
|
+
delegate :blank?, :present?, to: :name
|
9
|
+
delegate :engine, to: :context
|
10
10
|
|
11
11
|
def initialize(context, name = nil, ransacker_args = [])
|
12
12
|
super(context)
|
@@ -2,8 +2,8 @@ module Ransack
|
|
2
2
|
module Nodes
|
3
3
|
class Condition < Node
|
4
4
|
i18n_word :attribute, :predicate, :combinator, :value
|
5
|
-
i18n_alias :
|
6
|
-
:
|
5
|
+
i18n_alias a: :attribute, p: :predicate,
|
6
|
+
m: :combinator, v: :value
|
7
7
|
|
8
8
|
attr_accessor :predicate
|
9
9
|
|
@@ -15,10 +15,10 @@ module Ransack
|
|
15
15
|
if attributes.size > 0 && predicate
|
16
16
|
condition = self.new(context)
|
17
17
|
condition.build(
|
18
|
-
:
|
19
|
-
:
|
20
|
-
:
|
21
|
-
:
|
18
|
+
a: attributes,
|
19
|
+
p: predicate.name,
|
20
|
+
m: combinator,
|
21
|
+
v: predicate.wants_array ? Array(values) : [values]
|
22
22
|
)
|
23
23
|
# TODO: Figure out what to do with multiple types of attributes,
|
24
24
|
# if anything. Tempted to go with "garbage in, garbage out" here.
|
@@ -283,12 +283,85 @@ module Ransack
|
|
283
283
|
predicate.negative?
|
284
284
|
end
|
285
285
|
|
286
|
+
def arel_predicate
|
287
|
+
predicate = attributes.map { |attribute|
|
288
|
+
association = attribute.parent
|
289
|
+
if negative? && attribute.associated_collection?
|
290
|
+
query = context.build_correlated_subquery(association)
|
291
|
+
context.remove_association(association)
|
292
|
+
if self.predicate_name == 'not_null' && self.value
|
293
|
+
query.where(format_predicate(attribute))
|
294
|
+
Arel::Nodes::In.new(context.primary_key, Arel.sql(query.to_sql))
|
295
|
+
else
|
296
|
+
query.where(format_predicate(attribute).not)
|
297
|
+
Arel::Nodes::NotIn.new(context.primary_key, Arel.sql(query.to_sql))
|
298
|
+
end
|
299
|
+
else
|
300
|
+
format_predicate(attribute)
|
301
|
+
end
|
302
|
+
}.reduce(combinator_method)
|
303
|
+
|
304
|
+
if replace_right_node?(predicate)
|
305
|
+
# Replace right node object to plain integer value in order to avoid
|
306
|
+
# ActiveModel::RangeError from Arel::Node::Casted.
|
307
|
+
# The error can be ignored here because RDBMSs accept large numbers
|
308
|
+
# in condition clauses.
|
309
|
+
plain_value = predicate.right.value
|
310
|
+
predicate.right = plain_value
|
311
|
+
end
|
312
|
+
|
313
|
+
predicate
|
314
|
+
end
|
315
|
+
|
286
316
|
private
|
287
317
|
|
318
|
+
def combinator_method
|
319
|
+
combinator === Constants::OR ? :or : :and
|
320
|
+
end
|
321
|
+
|
322
|
+
def format_predicate(attribute)
|
323
|
+
arel_pred = arel_predicate_for_attribute(attribute)
|
324
|
+
arel_values = formatted_values_for_attribute(attribute)
|
325
|
+
predicate = attr_value_for_attribute(attribute).public_send(arel_pred, arel_values)
|
326
|
+
|
327
|
+
if in_predicate?(predicate)
|
328
|
+
predicate.right = predicate.right.map do |pr|
|
329
|
+
casted_array?(pr) ? format_values_for(pr) : pr
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
predicate
|
334
|
+
end
|
335
|
+
|
336
|
+
def in_predicate?(predicate)
|
337
|
+
return unless defined?(Arel::Nodes::Casted)
|
338
|
+
predicate.class == Arel::Nodes::In || predicate.class == Arel::Nodes::NotIn
|
339
|
+
end
|
340
|
+
|
341
|
+
def casted_array?(predicate)
|
342
|
+
predicate.value.is_a?(Array) && predicate.is_a?(Arel::Nodes::Casted)
|
343
|
+
end
|
344
|
+
|
345
|
+
def format_values_for(predicate)
|
346
|
+
predicate.value.map do |val|
|
347
|
+
val.is_a?(String) ? Arel::Nodes.build_quoted(val) : val
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
def replace_right_node?(predicate)
|
352
|
+
return false unless predicate.is_a?(Arel::Nodes::Binary)
|
353
|
+
|
354
|
+
arel_node = predicate.right
|
355
|
+
return false unless arel_node.is_a?(Arel::Nodes::Casted)
|
356
|
+
|
357
|
+
relation, name = arel_node.attribute.values
|
358
|
+
attribute_type = relation.type_for_attribute(name).type
|
359
|
+
attribute_type == :integer && arel_node.value.is_a?(Integer)
|
360
|
+
end
|
361
|
+
|
288
362
|
def valid_combinator?
|
289
363
|
attributes.size < 2 || Constants::AND_OR.include?(combinator)
|
290
364
|
end
|
291
|
-
|
292
365
|
end
|
293
366
|
end
|
294
367
|
end
|
@@ -7,9 +7,9 @@ module Ransack
|
|
7
7
|
alias :m= :combinator=
|
8
8
|
|
9
9
|
i18n_word :condition, :and, :or
|
10
|
-
i18n_alias :
|
10
|
+
i18n_alias c: :condition, n: :and, o: :or
|
11
11
|
|
12
|
-
delegate :each, :
|
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(:
|
25
|
+
key.to_s, options.merge(context: context)
|
26
26
|
)
|
27
27
|
end
|
28
28
|
|
data/lib/ransack/nodes/node.rb
CHANGED
data/lib/ransack/nodes/value.rb
CHANGED
data/lib/ransack/predicate.rb
CHANGED
data/lib/ransack/ransacker.rb
CHANGED
data/lib/ransack/search.rb
CHANGED
@@ -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, :
|
17
|
+
delegate :object, :klass, to: :context
|
13
18
|
delegate :new_grouping, :new_condition,
|
14
19
|
:build_grouping, :build_condition,
|
15
|
-
:translate, :
|
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])
|
data/lib/ransack/translate.rb
CHANGED
@@ -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 = { :
|
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
|
-
|
152
|
+
klass.model_name.i18n_key
|
153
153
|
end
|
154
154
|
end
|
155
155
|
end
|
data/lib/ransack/version.rb
CHANGED