ransack 1.2.3 → 1.3.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.
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