ransack 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- params = params.presence || {}
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
- def ransortable_attributes(auth_object = nil)
30
- # Here so users can overwrite the attributes
31
- # that show up in the sort_select
32
- ransackable_attributes(auth_object)
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
- # For overriding with a whitelist of symbols
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
@@ -11,4 +11,4 @@ module Arel
11
11
  end
12
12
 
13
13
  end
14
- end
14
+ end
@@ -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| TRUE_VALUES.include?(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| TRUE_VALUES.include?(v) },
52
- :formatter => proc { |v| !v }
78
+ :validator => proc { |v| BOOLEAN_VALUES.include?(v) },
79
+ :formatter => proc { |v| false }
53
80
  }
54
81
  ],
55
82
  ['present', {
@@ -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] || Arel::OuterJoin
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"
@@ -14,9 +14,12 @@ module Ransack
14
14
  :translate, :to => :base
15
15
 
16
16
  def initialize(object, params = {}, options = {})
17
- params = {} unless params.is_a?(Hash)
18
- (params ||= {})
19
- .delete_if { |k, v| [*v].all? { |i| i.blank? && i != false } }
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')
@@ -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
- :"ransack.attributes.#{i18n_key(klass)}.#{original_name}"
30
+ "ransack.attributes.#{i18n_key(klass)}.#{original_name}".to_sym
31
31
  end
32
32
 
33
- translated_names = attribute_names.map do |attr|
34
- attribute_name(context, attr, options[:include_associations])
33
+ translated_names = attribute_names.map do |name|
34
+ attribute_name(context, name, options[:include_associations])
35
35
  end
36
36
 
37
- interpolations = {}
38
- interpolations[:attributes] = translated_names.join(
39
- " #{Translate.word(combinator)} "
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 = key.blank? ?
60
- [:"#{context.klass.i18n_scope}.models.#{i18n_key(context.klass)}"] :
61
- [:"ransack.associations.#{i18n_key(context.klass)}.#{key}"]
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
- private
69
+ private
68
70
 
69
71
  def self.attribute_name(context, name, include_associations = nil)
70
- assoc_path = context.association_path(name)
71
- associated_class = context.traverse(assoc_path) if assoc_path.present?
72
- attr_name = name.sub(/^#{assoc_path}_/, '')
73
- interpolations = {}
74
- interpolations[:attr_fallback_name] = I18n.translate(
75
- :"ransack.attributes.#{
76
- i18n_key(associated_class || context.klass)
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('.', '/')
@@ -1,3 +1,3 @@
1
1
  module Ransack
2
- VERSION = "1.3.0"
2
+ VERSION = "1.4.0"
3
3
  end
@@ -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