ransack 1.3.0 → 1.4.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/.travis.yml +4 -1
- data/CHANGELOG.md +93 -0
- data/Gemfile +1 -1
- data/README.md +237 -71
- data/lib/ransack/adapters/active_record/3.0/context.rb +10 -0
- data/lib/ransack/adapters/active_record/3.1/context.rb +10 -0
- data/lib/ransack/adapters/active_record/base.rb +21 -10
- data/lib/ransack/adapters/active_record/compat.rb +1 -1
- data/lib/ransack/constants.rb +32 -5
- data/lib/ransack/context.rb +1 -1
- data/lib/ransack/locale/ro.yml +70 -0
- data/lib/ransack/search.rb +6 -3
- data/lib/ransack/translate.rb +88 -44
- data/lib/ransack/version.rb +1 -1
- data/spec/ransack/adapters/active_record/base_spec.rb +8 -0
- data/spec/ransack/adapters/active_record/context_spec.rb +27 -12
- data/spec/ransack/nodes/condition_spec.rb +10 -2
- data/spec/ransack/predicate_spec.rb +64 -0
- data/spec/ransack/search_spec.rb +23 -4
- data/spec/support/schema.rb +7 -1
- metadata +4 -2
@@ -174,6 +174,7 @@ module Ransack
|
|
174
174
|
:build, Polyamorous::Join.new(name, @join_type, klass), parent
|
175
175
|
)
|
176
176
|
found_association = @join_dependency.join_associations.last
|
177
|
+
apply_default_conditions(found_association)
|
177
178
|
# Leverage the stashed association functionality in AR
|
178
179
|
@object = @object.joins(found_association)
|
179
180
|
end
|
@@ -181,6 +182,15 @@ module Ransack
|
|
181
182
|
found_association
|
182
183
|
end
|
183
184
|
|
185
|
+
def apply_default_conditions(join_association)
|
186
|
+
reflection = join_association.reflection
|
187
|
+
default_scope = join_association.active_record.scoped
|
188
|
+
default_conditions = default_scope.arel.where_clauses
|
189
|
+
if default_conditions.any?
|
190
|
+
reflection.options[:conditions] = default_conditions
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
184
194
|
end
|
185
195
|
end
|
186
196
|
end
|
@@ -189,6 +189,7 @@ module Ransack
|
|
189
189
|
:build, Polyamorous::Join.new(name, @join_type, klass), parent
|
190
190
|
)
|
191
191
|
found_association = @join_dependency.join_associations.last
|
192
|
+
apply_default_conditions(found_association)
|
192
193
|
# Leverage the stashed association functionality in AR
|
193
194
|
@object = @object.joins(found_association)
|
194
195
|
end
|
@@ -196,6 +197,15 @@ module Ransack
|
|
196
197
|
found_association
|
197
198
|
end
|
198
199
|
|
200
|
+
def apply_default_conditions(join_association)
|
201
|
+
reflection = join_association.reflection
|
202
|
+
default_scope = join_association.active_record.scoped
|
203
|
+
default_conditions = default_scope.arel.where_clauses
|
204
|
+
if default_conditions.any?
|
205
|
+
reflection.options[:conditions] = default_conditions
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
199
209
|
end
|
200
210
|
end
|
201
211
|
end
|
@@ -12,9 +12,7 @@ module Ransack
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def ransack(params = {}, options = {})
|
15
|
-
|
16
|
-
Search.new(self, params ? params.delete_if {
|
17
|
-
|k, v| v.blank? && v != false } : params, options)
|
15
|
+
Search.new(self, params, options)
|
18
16
|
end
|
19
17
|
|
20
18
|
def ransacker(name, opts = {}, &block)
|
@@ -22,21 +20,34 @@ module Ransack
|
|
22
20
|
.new(self, name, opts, &block)
|
23
21
|
end
|
24
22
|
|
23
|
+
# Ransackable_attributes, by default, returns all column names
|
24
|
+
# and any defined ransackers as an array of strings.
|
25
|
+
# For overriding with a whitelist array of strings.
|
26
|
+
#
|
25
27
|
def ransackable_attributes(auth_object = nil)
|
26
28
|
column_names + _ransackers.keys
|
27
29
|
end
|
28
30
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
end
|
34
|
-
|
31
|
+
# Ransackable_associations, by default, returns the names
|
32
|
+
# of all associations as an array of strings.
|
33
|
+
# For overriding with a whitelist array of strings.
|
34
|
+
#
|
35
35
|
def ransackable_associations(auth_object = nil)
|
36
36
|
reflect_on_all_associations.map { |a| a.name.to_s }
|
37
37
|
end
|
38
38
|
|
39
|
-
#
|
39
|
+
# Ransortable_attributes, by default, returns the names
|
40
|
+
# of all attributes available for sorting as an array of strings.
|
41
|
+
# For overriding with a whitelist array of strings.
|
42
|
+
#
|
43
|
+
def ransortable_attributes(auth_object = nil)
|
44
|
+
ransackable_attributes(auth_object)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Ransackable_scopes, by default, returns an empty array
|
48
|
+
# i.e. no class methods/scopes are authorized.
|
49
|
+
# For overriding with a whitelist array of *symbols*.
|
50
|
+
#
|
40
51
|
def ransackable_scopes(auth_object = nil)
|
41
52
|
[]
|
42
53
|
end
|
data/lib/ransack/constants.rb
CHANGED
@@ -12,11 +12,21 @@ module Ransack
|
|
12
12
|
:formatter => proc { |v| "%#{escape_wildcards(v)}%" }
|
13
13
|
}
|
14
14
|
],
|
15
|
+
['i_cont', {
|
16
|
+
:arel_predicate => 'i_matches',
|
17
|
+
:formatter => proc { |v| "%#{escape_wildcards(v)}%" }
|
18
|
+
}
|
19
|
+
],
|
15
20
|
['not_cont', {
|
16
21
|
:arel_predicate => 'does_not_match',
|
17
22
|
:formatter => proc { |v| "%#{escape_wildcards(v)}%" }
|
18
23
|
}
|
19
24
|
],
|
25
|
+
['i_not_cont', {
|
26
|
+
:arel_predicate => 'i_does_not_match',
|
27
|
+
:formatter => proc { |v| "%#{escape_wildcards(v)}%" }
|
28
|
+
}
|
29
|
+
],
|
20
30
|
['start', {
|
21
31
|
:arel_predicate => 'matches',
|
22
32
|
:formatter => proc { |v| "#{escape_wildcards(v)}%" }
|
@@ -38,18 +48,35 @@ module Ransack
|
|
38
48
|
}
|
39
49
|
],
|
40
50
|
['true', {
|
41
|
-
:arel_predicate => 'eq',
|
51
|
+
:arel_predicate => proc { |v| v ? 'eq' : 'not_eq' },
|
52
|
+
:compounds => false,
|
53
|
+
:type => :boolean,
|
54
|
+
:validator => proc { |v| BOOLEAN_VALUES.include?(v) },
|
55
|
+
:formatter => proc { |v| true }
|
56
|
+
}
|
57
|
+
],
|
58
|
+
['not_true', {
|
59
|
+
:arel_predicate => proc { |v| v ? 'not_eq' : 'eq' },
|
42
60
|
:compounds => false,
|
43
61
|
:type => :boolean,
|
44
|
-
:validator => proc { |v|
|
62
|
+
:validator => proc { |v| BOOLEAN_VALUES.include?(v) },
|
63
|
+
:formatter => proc { |v| true }
|
45
64
|
}
|
46
65
|
],
|
47
66
|
['false', {
|
48
|
-
:arel_predicate => 'eq',
|
67
|
+
:arel_predicate => proc { |v| v ? 'eq' : 'not_eq' },
|
68
|
+
:compounds => false,
|
69
|
+
:type => :boolean,
|
70
|
+
:validator => proc { |v| BOOLEAN_VALUES.include?(v) },
|
71
|
+
:formatter => proc { |v| false }
|
72
|
+
}
|
73
|
+
],
|
74
|
+
['not_false', {
|
75
|
+
:arel_predicate => proc { |v| v ? 'not_eq' : 'eq' },
|
49
76
|
:compounds => false,
|
50
77
|
:type => :boolean,
|
51
|
-
:validator => proc { |v|
|
52
|
-
:formatter => proc { |v|
|
78
|
+
:validator => proc { |v| BOOLEAN_VALUES.include?(v) },
|
79
|
+
:formatter => proc { |v| false }
|
53
80
|
}
|
54
81
|
],
|
55
82
|
['present', {
|
data/lib/ransack/context.rb
CHANGED
@@ -34,7 +34,7 @@ module Ransack
|
|
34
34
|
@object = relation_for(object)
|
35
35
|
@klass = @object.klass
|
36
36
|
@join_dependency = join_dependency(@object)
|
37
|
-
@join_type = options[:join_type] ||
|
37
|
+
@join_type = options[:join_type] || Polyamorous::OuterJoin
|
38
38
|
@search_key = options[:search_key] || Ransack.options[:search_key]
|
39
39
|
|
40
40
|
if ::ActiveRecord::VERSION::STRING >= "4.1"
|
@@ -0,0 +1,70 @@
|
|
1
|
+
ro:
|
2
|
+
ransack:
|
3
|
+
search: "caută"
|
4
|
+
predicate: "predicat"
|
5
|
+
and: "și"
|
6
|
+
or: "sau"
|
7
|
+
any: "oricare"
|
8
|
+
all: "toate"
|
9
|
+
combinator: "combinator"
|
10
|
+
attribute: "atribut"
|
11
|
+
value: "valoare"
|
12
|
+
condition: "condiție"
|
13
|
+
sort: "sortează"
|
14
|
+
asc: "crescător"
|
15
|
+
desc: "descrescător"
|
16
|
+
predicates:
|
17
|
+
eq: "egal cu"
|
18
|
+
eq_any: "egal cu unul din"
|
19
|
+
eq_all: "egal cu toate"
|
20
|
+
not_eq: "diferit de"
|
21
|
+
not_eq_any: "diferit de toate"
|
22
|
+
not_eq_all: "nu este egal cu toate"
|
23
|
+
matches: "corespunde"
|
24
|
+
matches_any: "corespunde cu unul din"
|
25
|
+
matches_all: "corespunde cu toate"
|
26
|
+
does_not_match: "nu corespunde"
|
27
|
+
does_not_match_any: "nu corespunde cu nici un"
|
28
|
+
does_not_match_all: "nu corespunde cu toate"
|
29
|
+
lt: "mai mic de"
|
30
|
+
lt_any: "mai mic decât cel puțin unul din"
|
31
|
+
lt_all: "mai mic decât toate"
|
32
|
+
lteq: "mai mic sau egal decât"
|
33
|
+
lteq_any: "mai mic sau egal decât cel puțin unul din"
|
34
|
+
lteq_all: "mai mic sau egal decât toate"
|
35
|
+
gt: "mai mare de"
|
36
|
+
gt_any: "mai mare decât cel puțin unul din"
|
37
|
+
gt_all: "mai mare decât toate"
|
38
|
+
gteq: "mai mare sau egal decât"
|
39
|
+
gteq_any: "mai mare sau egal decât cel puțin unul din"
|
40
|
+
gteq_all: "mai mare sau egal decât toate"
|
41
|
+
in: "inclus în"
|
42
|
+
in_any: "inclus într-unul din"
|
43
|
+
in_all: "inclus în toate"
|
44
|
+
not_in: "nu este inclus în"
|
45
|
+
not_in_any: "nu este inclus într-unul din"
|
46
|
+
not_in_all: "nu este inclus în toate"
|
47
|
+
cont: "conține"
|
48
|
+
cont_any: "conține unul din"
|
49
|
+
cont_all: "conține toate"
|
50
|
+
not_cont: "nu conține"
|
51
|
+
not_cont_any: "nu conține unul din"
|
52
|
+
not_cont_all: "nu conține toate"
|
53
|
+
start: "începe cu"
|
54
|
+
start_any: "începe cu unul din"
|
55
|
+
start_all: "începe cu toate"
|
56
|
+
not_start: "nu începe"
|
57
|
+
not_start_any: "nu începe cu unul din"
|
58
|
+
not_start_all: "nu începe cu toate"
|
59
|
+
end: "se termină cu"
|
60
|
+
end_any: "se termină cu unul din"
|
61
|
+
end_all: "se termină cu toate"
|
62
|
+
not_end: "nu se termină cu"
|
63
|
+
not_end_any: "nu se termină cu unul din"
|
64
|
+
not_end_all: "nu se termină cu toate"
|
65
|
+
'true': "este adevărat"
|
66
|
+
'false': "este fals"
|
67
|
+
present: "este prezent"
|
68
|
+
blank: "este gol"
|
69
|
+
'null': "este nul"
|
70
|
+
not_null: "nu este nul"
|
data/lib/ransack/search.rb
CHANGED
@@ -14,9 +14,12 @@ module Ransack
|
|
14
14
|
:translate, :to => :base
|
15
15
|
|
16
16
|
def initialize(object, params = {}, options = {})
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
if params.is_a? Hash
|
18
|
+
params = params.dup
|
19
|
+
params.delete_if { |k, v| [*v].all?{ |i| i.blank? && i != false } }
|
20
|
+
else
|
21
|
+
params = {}
|
22
|
+
end
|
20
23
|
@context = options[:context] || Context.for(object, options)
|
21
24
|
@context.auth_object = options[:auth_object]
|
22
25
|
@base = Nodes::Grouping.new(@context, options[:grouping] || 'and')
|
data/lib/ransack/translate.rb
CHANGED
@@ -27,23 +27,22 @@ module Ransack
|
|
27
27
|
attribute_names = attributes_str.split(/_and_|_or_/)
|
28
28
|
combinator = attributes_str.match(/_and_/) ? :and : :or
|
29
29
|
defaults = base_ancestors.map do |klass|
|
30
|
-
|
30
|
+
"ransack.attributes.#{i18n_key(klass)}.#{original_name}".to_sym
|
31
31
|
end
|
32
32
|
|
33
|
-
translated_names = attribute_names.map do |
|
34
|
-
attribute_name(context,
|
33
|
+
translated_names = attribute_names.map do |name|
|
34
|
+
attribute_name(context, name, options[:include_associations])
|
35
35
|
end
|
36
36
|
|
37
|
-
interpolations = {
|
38
|
-
|
39
|
-
|
40
|
-
)
|
37
|
+
interpolations = {
|
38
|
+
:attributes => translated_names.join(" #{Translate.word(combinator)} ")
|
39
|
+
}
|
41
40
|
|
42
41
|
if predicate
|
43
|
-
defaults << "%{attributes} %{predicate}"
|
42
|
+
defaults << "%{attributes} %{predicate}".freeze
|
44
43
|
interpolations[:predicate] = Translate.predicate(predicate)
|
45
44
|
else
|
46
|
-
defaults << "%{attributes}"
|
45
|
+
defaults << "%{attributes}".freeze
|
47
46
|
end
|
48
47
|
|
49
48
|
defaults << options.delete(:default) if options[:default]
|
@@ -56,52 +55,97 @@ module Ransack
|
|
56
55
|
raise ArgumentError, "A context is required to translate associations"
|
57
56
|
end
|
58
57
|
|
59
|
-
defaults =
|
60
|
-
|
61
|
-
|
58
|
+
defaults =
|
59
|
+
if key.blank?
|
60
|
+
[:"#{context.klass.i18n_scope}.models.#{i18n_key(context.klass)}"]
|
61
|
+
else
|
62
|
+
[:"ransack.associations.#{i18n_key(context.klass)}.#{key}"]
|
63
|
+
end
|
62
64
|
defaults << context.traverse(key).model_name.human
|
63
65
|
options = { :count => 1, :default => defaults }
|
64
66
|
I18n.translate(defaults.shift, options)
|
65
67
|
end
|
66
68
|
|
67
|
-
|
69
|
+
private
|
68
70
|
|
69
71
|
def self.attribute_name(context, name, include_associations = nil)
|
70
|
-
|
71
|
-
|
72
|
-
attr_name = name.sub(/^#{assoc_path}_/, '')
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
}.#{attr_name}",
|
78
|
-
:default => [
|
79
|
-
(
|
80
|
-
if associated_class
|
81
|
-
:"#{associated_class.i18n_scope}.attributes.#{
|
82
|
-
i18n_key(associated_class)}.#{attr_name}"
|
83
|
-
else
|
84
|
-
:"#{context.klass.i18n_scope}.attributes.#{
|
85
|
-
i18n_key(context.klass)}.#{attr_name}"
|
86
|
-
end
|
87
|
-
),
|
88
|
-
:".attributes.#{attr_name}",
|
89
|
-
attr_name.humanize
|
90
|
-
]
|
91
|
-
)
|
92
|
-
defaults = [:"ransack.attributes.#{i18n_key(context.klass)}.#{name}"]
|
93
|
-
if include_associations && associated_class
|
94
|
-
defaults << '%{association_name} %{attr_fallback_name}'
|
95
|
-
interpolations[:association_name] = association(
|
96
|
-
assoc_path, :context => context
|
97
|
-
)
|
98
|
-
else
|
99
|
-
defaults << '%{attr_fallback_name}'
|
100
|
-
end
|
72
|
+
@context, @name = context, name
|
73
|
+
@assoc_path = context.association_path(name)
|
74
|
+
@attr_name = @name.sub(/^#{@assoc_path}_/, '')
|
75
|
+
associated_class = @context.traverse(@assoc_path) if @assoc_path.present?
|
76
|
+
@include_associated = include_associations && associated_class
|
77
|
+
|
78
|
+
defaults = default_attribute_name << fallback_args
|
101
79
|
options = { :count => 1, :default => defaults }
|
80
|
+
interpolations = build_interpolations(associated_class)
|
81
|
+
|
102
82
|
I18n.translate(defaults.shift, options.merge(interpolations))
|
103
83
|
end
|
104
84
|
|
85
|
+
def self.default_attribute_name
|
86
|
+
["ransack.attributes.#{i18n_key(@context.klass)}.#{@name}".to_sym]
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.fallback_args
|
90
|
+
if @include_associated
|
91
|
+
'%{association_name} %{attr_fallback_name}'.freeze
|
92
|
+
else
|
93
|
+
'%{attr_fallback_name}'.freeze
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.build_interpolations(associated_class)
|
98
|
+
{
|
99
|
+
:attr_fallback_name => attr_fallback_name(associated_class),
|
100
|
+
:association_name => association_name
|
101
|
+
}
|
102
|
+
.reject! { |_, value| value.nil? }
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.attr_fallback_name(associated_class)
|
106
|
+
I18n.t(
|
107
|
+
:"ransack.attributes.#{fallback_class(associated_class)}.#{@attr_name}",
|
108
|
+
:default => default_interpolation(associated_class)
|
109
|
+
)
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.fallback_class(associated_class)
|
113
|
+
i18n_key(associated_class || @context.klass)
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.association_name
|
117
|
+
association(@assoc_path, :context => @context) if @include_associated
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.default_interpolation(associated_class)
|
121
|
+
[
|
122
|
+
associated_attribute(associated_class),
|
123
|
+
".attributes.#{@attr_name}".to_sym,
|
124
|
+
@attr_name.humanize
|
125
|
+
]
|
126
|
+
.flatten
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.associated_attribute(associated_class)
|
130
|
+
if associated_class
|
131
|
+
translated_attribute(associated_class)
|
132
|
+
else
|
133
|
+
translated_ancestor_attributes
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def self.translated_attribute(associated_class)
|
138
|
+
key = "#{associated_class.i18n_scope}.attributes.#{
|
139
|
+
i18n_key(associated_class)}.#{@attr_name}"
|
140
|
+
["#{key}.one".to_sym, key.to_sym]
|
141
|
+
end
|
142
|
+
|
143
|
+
def self.translated_ancestor_attributes
|
144
|
+
@context.klass.ancestors
|
145
|
+
.select { |ancestor| ancestor.respond_to?(:model_name) }
|
146
|
+
.map { |ancestor| translated_attribute(ancestor) }
|
147
|
+
end
|
148
|
+
|
105
149
|
def self.i18n_key(klass)
|
106
150
|
if ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR == 0
|
107
151
|
klass.model_name.i18n_key.to_s.tr('.', '/')
|
data/lib/ransack/version.rb
CHANGED
@@ -50,6 +50,14 @@ module Ransack
|
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
53
|
+
it 'does not raise exception for string :params argument' do
|
54
|
+
lambda { Person.search('') }.should_not raise_error
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'does not modify the parameters' do
|
58
|
+
params = { :name_eq => '' }
|
59
|
+
expect { Person.search(params) }.not_to change { params }
|
60
|
+
end
|
53
61
|
end
|
54
62
|
|
55
63
|
describe '#ransacker' do
|