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.
@@ -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