ransack 1.2.3 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -4
  3. data/CONTRIBUTING.md +12 -4
  4. data/Gemfile +4 -5
  5. data/README.md +160 -55
  6. data/lib/ransack.rb +1 -1
  7. data/lib/ransack/adapters/active_record/3.0/context.rb +16 -0
  8. data/lib/ransack/adapters/active_record/3.1/context.rb +24 -0
  9. data/lib/ransack/adapters/active_record/base.rb +6 -0
  10. data/lib/ransack/adapters/active_record/context.rb +49 -1
  11. data/lib/ransack/configuration.rb +23 -6
  12. data/lib/ransack/constants.rb +46 -45
  13. data/lib/ransack/context.rb +19 -2
  14. data/lib/ransack/helpers/form_builder.rb +5 -4
  15. data/lib/ransack/helpers/form_helper.rb +34 -14
  16. data/lib/ransack/locale/hu.yml +70 -0
  17. data/lib/ransack/locale/nl.yml +70 -0
  18. data/lib/ransack/nodes/attribute.rb +2 -2
  19. data/lib/ransack/nodes/condition.rb +29 -12
  20. data/lib/ransack/nodes/grouping.rb +6 -6
  21. data/lib/ransack/nodes/node.rb +1 -1
  22. data/lib/ransack/nodes/value.rb +1 -1
  23. data/lib/ransack/predicate.rb +4 -5
  24. data/lib/ransack/ransacker.rb +1 -1
  25. data/lib/ransack/search.rb +39 -13
  26. data/lib/ransack/translate.rb +7 -8
  27. data/lib/ransack/version.rb +1 -1
  28. data/ransack.gemspec +5 -5
  29. data/spec/ransack/adapters/active_record/base_spec.rb +78 -35
  30. data/spec/ransack/adapters/active_record/context_spec.rb +58 -15
  31. data/spec/ransack/configuration_spec.rb +18 -18
  32. data/spec/ransack/dependencies_spec.rb +1 -1
  33. data/spec/ransack/helpers/form_builder_spec.rb +29 -29
  34. data/spec/ransack/helpers/form_helper_spec.rb +14 -1
  35. data/spec/ransack/nodes/condition_spec.rb +21 -2
  36. data/spec/ransack/predicate_spec.rb +49 -9
  37. data/spec/ransack/search_spec.rb +178 -143
  38. data/spec/ransack/translate_spec.rb +1 -1
  39. data/spec/spec_helper.rb +1 -0
  40. data/spec/support/schema.rb +26 -21
  41. metadata +15 -11
@@ -0,0 +1,70 @@
1
+ hu:
2
+ ransack:
3
+ search: "keresés"
4
+ predicate: "állítás"
5
+ and: "és"
6
+ or: "vagy"
7
+ any: "bármely"
8
+ all: "mindegyik"
9
+ combinator: "combinator"
10
+ attribute: "attribute"
11
+ value: "érték"
12
+ condition: "feltétel"
13
+ sort: "rendezés"
14
+ asc: "növekvő"
15
+ desc: "csökkenő"
16
+ predicates:
17
+ eq: "egyenlő"
18
+ eq_any: "bármelyikkel egyenlő"
19
+ eq_all: "minddel egyenlő"
20
+ not_eq: "nem egyenlő"
21
+ not_eq_any: "nem egyenlő bármelyikkel"
22
+ not_eq_all: "nem egyenlő egyikkel sem"
23
+ matches: "egyezik"
24
+ matches_any: "bármelyikkel egyezik"
25
+ matches_all: "minddel egyezik"
26
+ does_not_match: "nem egyezik"
27
+ does_not_match_any: "nem egyezik semelyikkel"
28
+ does_not_match_all: "nem egyezik az összessel"
29
+ lt: "kisebb, mint"
30
+ lt_any: "bármelyiknél kisebb"
31
+ lt_all: "mindegyiknél kisebb"
32
+ lteq: "kisebb vagy egyenlő, mint"
33
+ lteq_any: "bármelyiknél kisebb vagy egyenlő"
34
+ lteq_all: "mindegyiknél kisebb vagy egyenlő"
35
+ gt: "nagyobb, mint"
36
+ gt_any: "bármelyiknél nagyobb"
37
+ gt_all: "mindegyiknél nagyobb"
38
+ gteq: "nagyobb vagy egyenlő, mint"
39
+ gteq_any: "bármelyiknél nagyobb vagy egyenlő"
40
+ gteq_all: "mindegyiknél nagyobb vagy egyenlő"
41
+ in: "értéke"
42
+ in_any: "értéke bármelyik"
43
+ in_all: "értéke mindegyik"
44
+ not_in: "nem ez az értéke"
45
+ not_in_any: "értéke egyik sem"
46
+ not_in_all: "értéke nem ezek az elemek"
47
+ cont: "tartalmazza"
48
+ cont_any: "bármelyiket tartalmazza"
49
+ cont_all: "mindet tartalmazza"
50
+ not_cont: "nem tartalmazza"
51
+ not_cont_any: "egyiket sem tartalmazza"
52
+ not_cont_all: "nem tartalmazza mindet"
53
+ start: "így kezdődik"
54
+ start_any: "bármelyikkel kezdődik"
55
+ start_all: "ezekkel kezdődik"
56
+ not_start: "nem így kezdődik"
57
+ not_start_any: "nem ezek egyikével kezdődik"
58
+ not_start_all: "nem ezekkel kezdődik"
59
+ end: "így végződik"
60
+ end_any: "bármelyikkel végződik"
61
+ end_all: "ezekkel végződik"
62
+ not_end: "nem úgy végződik"
63
+ not_end_any: "nem ezek egyikével végződik"
64
+ not_end_all: "nem ezekkel végződik"
65
+ 'true': "igaz"
66
+ 'false': "hamis"
67
+ present: "létezik"
68
+ blank: "üres"
69
+ 'null': "null"
70
+ not_null: "nem null"
@@ -0,0 +1,70 @@
1
+ nl:
2
+ ransack:
3
+ search: "zoeken"
4
+ predicate: "eigenschap"
5
+ and: "en"
6
+ or: "of"
7
+ any: "enig"
8
+ all: "alle"
9
+ combinator: "combinator"
10
+ attribute: "attribuut"
11
+ value: "waarde"
12
+ condition: "conditie"
13
+ sort: "sorteren"
14
+ asc: "oplopend"
15
+ desc: "aflopend"
16
+ predicates:
17
+ eq: "gelijk"
18
+ eq_any: "gelijk enig"
19
+ eq_all: "gelijk alle"
20
+ not_eq: "niet gelijk aan"
21
+ not_eq_any: "niet gelijk aan enig"
22
+ not_eq_all: "niet gelijk aan alle"
23
+ matches: "evenaart"
24
+ matches_any: "evenaart enig"
25
+ matches_all: "evenaart alle"
26
+ does_not_match: "evenaart niet"
27
+ does_not_match_any: "evenaart niet voor enig"
28
+ does_not_match_all: "evenaart niet voor alle"
29
+ lt: "kleiner dan"
30
+ lt_any: "kleiner dan enig"
31
+ lt_all: "kleiner dan alle"
32
+ lteq: "kleiner dan of gelijk aan"
33
+ lteq_any: "kleiner dan of gelijk aan enig"
34
+ lteq_all: "kleiner dan of gelijk aan alle"
35
+ gt: "groter dan"
36
+ gt_any: "groter dan enig"
37
+ gt_all: "groter dan alle"
38
+ gteq: "groter dan or equal to"
39
+ gteq_any: "groter dan or equal to enig"
40
+ gteq_all: "groter dan or equal to alle"
41
+ in: "in"
42
+ in_any: "in enig"
43
+ in_all: "in alle"
44
+ not_in: "niet in"
45
+ not_in_any: "niet in enig"
46
+ not_in_all: "niet in alle"
47
+ cont: "bevat"
48
+ cont_any: "bevat enig"
49
+ cont_all: "bevat alle"
50
+ not_cont: "bevat niet"
51
+ not_cont_any: "bevat niet enig"
52
+ not_cont_all: "bevat niet alle"
53
+ start: "start met"
54
+ start_any: "start met enig"
55
+ start_all: "start met alle"
56
+ not_start: "start niet met"
57
+ not_start_any: "start niet met enig"
58
+ not_start_all: "start niet met alle"
59
+ end: "eindigt met"
60
+ end_any: "eindigt met enig"
61
+ end_all: "eindigt met alle"
62
+ not_end: "eindigt niet met"
63
+ not_end_any: "eindigt niet met enig"
64
+ not_end_all: "eindigt niet met alle"
65
+ 'true': "is waar"
66
+ 'false': "is niet waar"
67
+ present: "is present"
68
+ blank: "is afwezig"
69
+ 'null': "is null"
70
+ not_null: "is niet null"
@@ -5,8 +5,8 @@ module Ransack
5
5
 
6
6
  attr_reader :name
7
7
 
8
- delegate :blank?, :present?, :==, to: :name
9
- delegate :engine, to: :context
8
+ delegate :blank?, :present?, :==, :to => :name
9
+ delegate :engine, :to => :context
10
10
 
11
11
  def initialize(context, name = nil)
12
12
  super(context)
@@ -10,20 +10,22 @@ module Ransack
10
10
  class << self
11
11
  def extract(context, key, values)
12
12
  attributes, predicate = extract_attributes_and_predicate(key)
13
- if attributes.size > 0
13
+ if attributes.size > 0 && predicate
14
14
  combinator = key.match(/_(or|and)_/) ? $1 : nil
15
15
  condition = self.new(context)
16
16
  condition.build(
17
- a: attributes,
18
- p: predicate.name,
19
- m: combinator,
20
- v: predicate.wants_array ? Array(values) : [values]
17
+ :a => attributes,
18
+ :p => predicate.name,
19
+ :m => combinator,
20
+ :v => predicate.wants_array ? Array(values) : [values]
21
21
  )
22
22
  # TODO: Figure out what to do with multiple types of attributes,
23
- # if anything.
24
- # Tempted to go with "garbage in, garbage out" on this one
25
- predicate.validate(condition.values, condition.default_type) ?
26
- condition : nil
23
+ # if anything. Tempted to go with "garbage in, garbage out" here.
24
+ if predicate.validate(condition.values, condition.default_type)
25
+ condition
26
+ else
27
+ nil
28
+ end
27
29
  end
28
30
  end
29
31
 
@@ -33,7 +35,9 @@ module Ransack
33
35
  str = key.dup
34
36
  name = Predicate.detect_and_strip_from_string!(str)
35
37
  predicate = Predicate.named(name)
36
- raise ArgumentError, "No valid predicate for #{key}" unless predicate
38
+ unless predicate || Ransack.options[:ignore_unknown_conditions]
39
+ raise ArgumentError, "No valid predicate for #{key}"
40
+ end
37
41
  attributes = str.split(/_and_|_or_/)
38
42
  [attributes, predicate]
39
43
  end
@@ -169,8 +173,9 @@ module Ransack
169
173
  def arel_predicate
170
174
  predicates = attributes.map do |attr|
171
175
  attr.attr.send(
172
- predicate.arel_predicate, formatted_values_for_attribute(attr)
173
- )
176
+ arel_predicate_for_attribute(attr),
177
+ formatted_values_for_attribute(attr)
178
+ )
174
179
  end
175
180
 
176
181
  if predicates.size > 1
@@ -203,6 +208,18 @@ module Ransack
203
208
  predicate.wants_array ? formatted : formatted.first
204
209
  end
205
210
 
211
+ def arel_predicate_for_attribute(attr)
212
+ if predicate.arel_predicate === Proc
213
+ values = casted_values_for_attribute(attr)
214
+ predicate.arel_predicate.call(
215
+ predicate.wants_array ? values : values.first
216
+ )
217
+ else
218
+ predicate.arel_predicate
219
+ end
220
+ end
221
+
222
+
206
223
  def default_type
207
224
  predicate.type || (attributes.first && attributes.first.type)
208
225
  end
@@ -9,7 +9,7 @@ module Ransack
9
9
  i18n_word :condition, :and, :or
10
10
  i18n_alias :c => :condition, :n => :and, :o => :or
11
11
 
12
- delegate :each, to: :values
12
+ delegate :each, :to => :values
13
13
 
14
14
  def initialize(context, combinator = nil)
15
15
  super(context)
@@ -21,7 +21,9 @@ module Ransack
21
21
  end
22
22
 
23
23
  def translate(key, options = {})
24
- super or Translate.attribute(key.to_s, options.merge(context: context))
24
+ super or Translate.attribute(
25
+ key.to_s, options.merge(:context => context)
26
+ )
25
27
  end
26
28
 
27
29
  def conditions
@@ -159,10 +161,8 @@ module Ransack
159
161
  end
160
162
 
161
163
  def inspect
162
- data = [
163
- ['conditions', conditions], ['combinator', combinator]
164
- ].
165
- reject { |e| e[1].blank? }
164
+ data = [['conditions', conditions], ['combinator', combinator]]
165
+ .reject { |e| e[1].blank? }
166
166
  .map { |v| "#{v[0]}: #{v[1]}" }
167
167
  .join(', ')
168
168
  "Grouping <#{data}>"
@@ -2,7 +2,7 @@ module Ransack
2
2
  module Nodes
3
3
  class Node
4
4
  attr_reader :context
5
- delegate :contextualize, to: :context
5
+ delegate :contextualize, :to => :context
6
6
  class_attribute :i18n_words
7
7
  class_attribute :i18n_aliases
8
8
  self.i18n_words = []
@@ -2,7 +2,7 @@ module Ransack
2
2
  module Nodes
3
3
  class Value < Node
4
4
  attr_accessor :value
5
- delegate :present?, :blank?, to: :value
5
+ delegate :present?, :blank?, :to => :value
6
6
 
7
7
  def initialize(context, value = nil)
8
8
  super(context)
@@ -45,12 +45,11 @@ module Ransack
45
45
  @arel_predicate = opts[:arel_predicate]
46
46
  @type = opts[:type]
47
47
  @formatter = opts[:formatter]
48
- @validator = opts[:validator] || lambda {
49
- |v| v.respond_to?(:empty?) ? !v.empty? : !v.nil?
50
- }
48
+ @validator = opts[:validator] ||
49
+ lambda { |v| v.respond_to?(:empty?) ? !v.empty? : !v.nil? }
51
50
  @compound = opts[:compound]
52
- @wants_array = opts[:wants_array] == true || @compound || ['in', 'not_in'].
53
- include?(@arel_predicate)
51
+ @wants_array = opts[:wants_array] == true || @compound ||
52
+ ['in', 'not_in'].include?(@arel_predicate)
54
53
  end
55
54
 
56
55
  def eql?(other)
@@ -3,7 +3,7 @@ module Ransack
3
3
 
4
4
  attr_reader :name, :type, :formatter, :args
5
5
 
6
- delegate :call, to: :@callable
6
+ delegate :call, :to => :@callable
7
7
 
8
8
  def initialize(klass, name, opts = {}, &block)
9
9
  @klass, @name = klass, name
@@ -8,17 +8,19 @@ module Ransack
8
8
 
9
9
  attr_reader :base, :context
10
10
 
11
- delegate :object, :klass, to: :context
11
+ delegate :object, :klass, :to => :context
12
12
  delegate :new_grouping, :new_condition,
13
13
  :build_grouping, :build_condition,
14
- :translate, to: :base
14
+ :translate, :to => :base
15
15
 
16
16
  def initialize(object, params = {}, options = {})
17
17
  params = {} unless params.is_a?(Hash)
18
- (params ||= {}).delete_if { |k, v| [*v].all?{|i| i.blank? && i != false } }
19
- @context = Context.for(object, options)
18
+ (params ||= {})
19
+ .delete_if { |k, v| [*v].all? { |i| i.blank? && i != false } }
20
+ @context = options[:context] || Context.for(object, options)
20
21
  @context.auth_object = options[:auth_object]
21
- @base = Nodes::Grouping.new(@context, 'and')
22
+ @base = Nodes::Grouping.new(@context, options[:grouping] || 'and')
23
+ @scope_args = {}
22
24
  build(params.with_indifferent_access)
23
25
  end
24
26
 
@@ -28,11 +30,14 @@ module Ransack
28
30
 
29
31
  def build(params)
30
32
  collapse_multiparameter_attributes!(params).each do |key, value|
31
- case key
32
- when 's', 'sorts'
33
+ if ['s', 'sorts'].include?(key)
33
34
  send("#{key}=", value)
34
- else
35
- base.send("#{key}=", value) if base.attribute_method?(key)
35
+ elsif base.attribute_method?(key)
36
+ base.send("#{key}=", value)
37
+ elsif @context.ransackable_scope?(key, @context.object)
38
+ add_scope(key, value)
39
+ elsif !Ransack.options[:ignore_unknown_conditions]
40
+ raise ArgumentError, "Invalid search term #{key}"
36
41
  end
37
42
  end
38
43
  self
@@ -57,7 +62,8 @@ module Ransack
57
62
  when String
58
63
  self.sorts = [args]
59
64
  else
60
- raise ArgumentError, "Invalid argument (#{args.class}) supplied to sorts="
65
+ raise ArgumentError,
66
+ "Invalid argument (#{args.class}) supplied to sorts="
61
67
  end
62
68
  end
63
69
  alias :s= :sorts=
@@ -79,20 +85,40 @@ module Ransack
79
85
 
80
86
  def method_missing(method_id, *args)
81
87
  method_name = method_id.to_s
82
- writer = method_name.sub!(/\=$/, '')
83
- if base.attribute_method?(method_name)
88
+ getter_name = method_name.sub(/=$/, '')
89
+ if base.attribute_method?(getter_name)
84
90
  base.send(method_id, *args)
91
+ elsif @context.ransackable_scope?(getter_name, @context.object)
92
+ if method_name =~ /=$/
93
+ add_scope getter_name, args
94
+ else
95
+ @scope_args[method_name]
96
+ end
85
97
  else
86
98
  super
87
99
  end
88
100
  end
89
101
 
90
102
  def inspect
91
- "Ransack::Search<class: #{klass.name}, base: #{base.inspect}>"
103
+ details = [
104
+ [:class, klass.name],
105
+ ([:scope, @scope_args] if @scope_args.present?),
106
+ [:base, base.inspect]
107
+ ].compact.map { |d| d.join(': ') }.join(', ')
108
+ "Ransack::Search<#{details}>"
92
109
  end
93
110
 
94
111
  private
95
112
 
113
+ def add_scope(key, args)
114
+ if @context.scope_arity(key) == 1
115
+ @scope_args[key] = args.is_a?(Array) ? args[0] : args
116
+ else
117
+ @scope_args[key] = args
118
+ end
119
+ @context.chain_scope(key, args)
120
+ end
121
+
96
122
  def collapse_multiparameter_attributes!(attrs)
97
123
  attrs.keys.each do |k|
98
124
  if k.include?("(")
@@ -5,11 +5,11 @@ I18n.load_path += Dir[File.join(File.dirname(__FILE__), 'locale', '*.yml')]
5
5
  module Ransack
6
6
  module Translate
7
7
  def self.word(key, options = {})
8
- I18n.translate(:"ransack.#{key}", default: key.to_s)
8
+ I18n.translate(:"ransack.#{key}", :default => key.to_s)
9
9
  end
10
10
 
11
11
  def self.predicate(key, options = {})
12
- I18n.translate(:"ransack.predicates.#{key}", default: key.to_s)
12
+ I18n.translate(:"ransack.predicates.#{key}", :default => key.to_s)
13
13
  end
14
14
 
15
15
  def self.attribute(key, options = {})
@@ -47,7 +47,7 @@ module Ransack
47
47
  end
48
48
 
49
49
  defaults << options.delete(:default) if options[:default]
50
- options.reverse_merge! count: 1, default: defaults
50
+ options.reverse_merge! :count => 1, :default => defaults
51
51
  I18n.translate(defaults.shift, options.merge(interpolations))
52
52
  end
53
53
 
@@ -60,7 +60,7 @@ module Ransack
60
60
  [:"#{context.klass.i18n_scope}.models.#{i18n_key(context.klass)}"] :
61
61
  [:"ransack.associations.#{i18n_key(context.klass)}.#{key}"]
62
62
  defaults << context.traverse(key).model_name.human
63
- options = { count: 1, default: defaults }
63
+ options = { :count => 1, :default => defaults }
64
64
  I18n.translate(defaults.shift, options)
65
65
  end
66
66
 
@@ -75,7 +75,7 @@ module Ransack
75
75
  :"ransack.attributes.#{
76
76
  i18n_key(associated_class || context.klass)
77
77
  }.#{attr_name}",
78
- default: [
78
+ :default => [
79
79
  (
80
80
  if associated_class
81
81
  :"#{associated_class.i18n_scope}.attributes.#{
@@ -93,13 +93,12 @@ module Ransack
93
93
  if include_associations && associated_class
94
94
  defaults << '%{association_name} %{attr_fallback_name}'
95
95
  interpolations[:association_name] = association(
96
- assoc_path,
97
- context: context
96
+ assoc_path, :context => context
98
97
  )
99
98
  else
100
99
  defaults << '%{attr_fallback_name}'
101
100
  end
102
- options = { count: 1, default: defaults }
101
+ options = { :count => 1, :default => defaults }
103
102
  I18n.translate(defaults.shift, options.merge(interpolations))
104
103
  end
105
104