ransack 1.5.1 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +47 -3
  3. data/CHANGELOG.md +106 -18
  4. data/CONTRIBUTING.md +56 -23
  5. data/Gemfile +16 -5
  6. data/README.md +114 -38
  7. data/Rakefile +30 -2
  8. data/lib/ransack.rb +9 -0
  9. data/lib/ransack/adapters/active_record/3.0/compat.rb +11 -8
  10. data/lib/ransack/adapters/active_record/3.0/context.rb +14 -22
  11. data/lib/ransack/adapters/active_record/3.1/context.rb +14 -22
  12. data/lib/ransack/adapters/active_record/context.rb +36 -31
  13. data/lib/ransack/adapters/active_record/ransack/constants.rb +113 -0
  14. data/lib/ransack/adapters/active_record/ransack/context.rb +64 -0
  15. data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +48 -0
  16. data/lib/ransack/adapters/active_record/ransack/translate.rb +12 -0
  17. data/lib/ransack/adapters/active_record/ransack/visitor.rb +24 -0
  18. data/lib/ransack/adapters/mongoid.rb +13 -0
  19. data/lib/ransack/adapters/mongoid/3.2/.gitkeep +0 -0
  20. data/lib/ransack/adapters/mongoid/attributes/attribute.rb +37 -0
  21. data/lib/ransack/adapters/mongoid/attributes/order_predications.rb +17 -0
  22. data/lib/ransack/adapters/mongoid/attributes/predications.rb +141 -0
  23. data/lib/ransack/adapters/mongoid/base.rb +126 -0
  24. data/lib/ransack/adapters/mongoid/context.rb +208 -0
  25. data/lib/ransack/adapters/mongoid/inquiry_hash.rb +23 -0
  26. data/lib/ransack/adapters/mongoid/ransack/constants.rb +88 -0
  27. data/lib/ransack/adapters/mongoid/ransack/context.rb +60 -0
  28. data/lib/ransack/adapters/mongoid/ransack/nodes/condition.rb +27 -0
  29. data/lib/ransack/adapters/mongoid/ransack/translate.rb +13 -0
  30. data/lib/ransack/adapters/mongoid/ransack/visitor.rb +24 -0
  31. data/lib/ransack/adapters/mongoid/table.rb +35 -0
  32. data/lib/ransack/configuration.rb +22 -4
  33. data/lib/ransack/constants.rb +26 -120
  34. data/lib/ransack/context.rb +32 -60
  35. data/lib/ransack/helpers/form_builder.rb +50 -36
  36. data/lib/ransack/helpers/form_helper.rb +148 -104
  37. data/lib/ransack/naming.rb +11 -11
  38. data/lib/ransack/nodes.rb +2 -0
  39. data/lib/ransack/nodes/bindable.rb +12 -4
  40. data/lib/ransack/nodes/condition.rb +5 -22
  41. data/lib/ransack/nodes/grouping.rb +9 -10
  42. data/lib/ransack/nodes/sort.rb +3 -2
  43. data/lib/ransack/nodes/value.rb +1 -2
  44. data/lib/ransack/predicate.rb +3 -3
  45. data/lib/ransack/search.rb +46 -13
  46. data/lib/ransack/translate.rb +8 -8
  47. data/lib/ransack/version.rb +1 -1
  48. data/lib/ransack/visitor.rb +4 -16
  49. data/ransack.gemspec +1 -0
  50. data/spec/mongoid/adapters/mongoid/base_spec.rb +276 -0
  51. data/spec/mongoid/adapters/mongoid/context_spec.rb +56 -0
  52. data/spec/mongoid/configuration_spec.rb +66 -0
  53. data/spec/mongoid/dependencies_spec.rb +8 -0
  54. data/spec/mongoid/helpers/ransack_helper.rb +11 -0
  55. data/spec/mongoid/nodes/condition_spec.rb +34 -0
  56. data/spec/mongoid/nodes/grouping_spec.rb +13 -0
  57. data/spec/mongoid/predicate_spec.rb +155 -0
  58. data/spec/mongoid/search_spec.rb +446 -0
  59. data/spec/mongoid/support/mongoid.yml +6 -0
  60. data/spec/mongoid/support/schema.rb +128 -0
  61. data/spec/mongoid/translate_spec.rb +14 -0
  62. data/spec/mongoid_spec_helper.rb +59 -0
  63. data/spec/ransack/adapters/active_record/base_spec.rb +68 -35
  64. data/spec/ransack/dependencies_spec.rb +3 -1
  65. data/spec/ransack/helpers/form_builder_spec.rb +6 -6
  66. data/spec/ransack/helpers/form_helper_spec.rb +114 -47
  67. data/spec/ransack/nodes/condition_spec.rb +2 -2
  68. data/spec/ransack/search_spec.rb +2 -6
  69. data/spec/ransack/translate_spec.rb +1 -1
  70. data/spec/spec_helper.rb +2 -3
  71. data/spec/support/schema.rb +9 -0
  72. metadata +49 -4
@@ -2,21 +2,25 @@ module Ransack
2
2
  module Helpers
3
3
  module FormHelper
4
4
 
5
+ # +search_form_for+
6
+ #
7
+ # <%= search_form_for(@q) do |f| %>
8
+ #
5
9
  def search_form_for(record, options = {}, &proc)
6
- if record.is_a?(Ransack::Search)
10
+ if record.is_a? Ransack::Search
7
11
  search = record
8
12
  options[:url] ||= polymorphic_path(
9
13
  search.klass, format: options.delete(:format)
10
14
  )
11
- elsif record.is_a?(Array) &&
12
- (search = record.detect { |o| o.is_a?(Ransack::Search) })
15
+ elsif record.is_a? Array &&
16
+ (search = record.detect { |o| o.is_a? Ransack::Search })
13
17
  options[:url] ||= polymorphic_path(
14
- record.map { |o| o.is_a?(Ransack::Search) ? o.klass : o },
18
+ record.map { |o| o.is_a? Ransack::Search ? o.klass : o },
15
19
  format: options.delete(:format)
16
20
  )
17
21
  else
18
22
  raise ArgumentError,
19
- "No Ransack::Search object was provided to search_form_for!"
23
+ 'No Ransack::Search object was provided to search_form_for!'
20
24
  end
21
25
  options[:html] ||= {}
22
26
  html_options = {
@@ -28,135 +32,175 @@ module Ransack
28
32
  "#{search.klass.to_s.underscore}_search",
29
33
  :method => :get
30
34
  }
31
- options[:as] ||= Ransack::Constants::DEFAULT_SEARCH_KEY
35
+ options[:as] ||= Ransack.options[:search_key]
32
36
  options[:html].reverse_merge!(html_options)
33
37
  options[:builder] ||= FormBuilder
34
38
 
35
39
  form_for(record, options, &proc)
36
40
  end
37
41
 
38
- # sort_link @q, :name, [:name, 'kind ASC'], 'Player Name'
39
- def sort_link(search, attribute, *args)
40
- # Extract out a routing proxy for url_for scoping later
41
- if search.is_a?(Array)
42
- routing_proxy = search.shift
43
- search = search.first
42
+ # +sort_link+
43
+ #
44
+ # <%= sort_link(@q, :name, [:name, 'kind ASC'], 'Player Name') %>
45
+ #
46
+ def sort_link(search_object, attribute, *args)
47
+ search, routing_proxy = extract_search_and_routing_proxy(search_object)
48
+ unless Search === search
49
+ raise TypeError, 'First argument must be a Ransack::Search!'
44
50
  end
51
+ s = SortLink.new(search, attribute, args, params)
52
+ link_to(s.name, url(routing_proxy, s.url_options), s.html_options(args))
53
+ end
45
54
 
46
- raise TypeError, "First argument must be a Ransack::Search!" unless
47
- Search === search
48
-
49
- # This is the field that this link represents. The direction of the sort icon (up/down arrow) will
50
- # depend on the sort status of this field
51
- field_name = attribute.to_s
55
+ private
52
56
 
53
- # Determine the fields we want to sort on
54
- sort_fields = if Array === args.first
55
- args.shift
56
- else
57
- Array(field_name)
57
+ def extract_search_and_routing_proxy(search)
58
+ if search.is_a? Array
59
+ [search.second, search.first]
60
+ else
61
+ [search, nil]
62
+ end
58
63
  end
59
64
 
60
- label_text =
61
- if String === args.first
62
- args.shift.to_s
65
+ def url(routing_proxy, options_for_url)
66
+ if routing_proxy && respond_to?(routing_proxy)
67
+ send(routing_proxy).url_for(options_for_url)
63
68
  else
64
- Translate.attribute(field_name, :context => search.context)
69
+ url_for(options_for_url)
65
70
  end
71
+ end
66
72
 
67
- options = args.first.is_a?(Hash) ? args.shift.dup : {}
68
- default_order = options.delete :default_order
69
- default_order_is_a_hash = Hash === default_order
73
+ class SortLink
74
+ def initialize(search, attribute, args, params)
75
+ @search = search
76
+ @params = params
77
+ @field = attribute.to_s
78
+ sort_fields = extract_sort_fields_and_mutate_args!(args).compact
79
+ @current_dir = existing_sort_direction
80
+ @label_text = extract_label_and_mutate_args!(args)
81
+ @options = extract_options_and_mutate_args!(args)
82
+ @hide_indicator = @options.delete :hide_indicator
83
+ @default_order = @options.delete :default_order
84
+ @sort_params = build_sort(sort_fields)
85
+ @sort_params = @sort_params.first if @sort_params.size == 1
86
+ end
70
87
 
71
- # If the default order is a hash of fields, duplicate it and let us access it with strings or symbols
72
- default_order = default_order.dup.with_indifferent_access if
73
- default_order_is_a_hash
88
+ def name
89
+ [ERB::Util.h(@label_text), order_indicator]
90
+ .compact
91
+ .join(Constants::NON_BREAKING_SPACE)
92
+ .html_safe
93
+ end
74
94
 
75
- search_params = params[search.context.search_key].presence ||
76
- {}.with_indifferent_access
95
+ def url_options
96
+ @params.merge(
97
+ @options.merge(
98
+ @search.context.search_key => search_and_sort_params))
99
+ end
77
100
 
78
- # Find the current direction (if there is one) of the primary sort field
79
- if existing_sort = search.sorts.detect { |s| s.name == field_name }
80
- field_current_dir = existing_sort.dir
101
+ def html_options(args)
102
+ html_options = extract_options_and_mutate_args!(args)
103
+ html_options.merge(class:
104
+ [[Constants::SORT_LINK, @current_dir], html_options[:class]]
105
+ .compact.join(Constants::SPACE)
106
+ )
81
107
  end
82
108
 
83
- sort_params = []
109
+ private
84
110
 
85
- Array(sort_fields).each do |sort_field|
86
- attr_name, new_dir = sort_field.to_s.downcase.split(/\s+/)
87
- current_dir = nil
111
+ def extract_sort_fields_and_mutate_args!(args)
112
+ if args.first.is_a? Array
113
+ args.shift
114
+ else
115
+ [@field]
116
+ end
117
+ end
88
118
 
89
- # if the user didn't specify the sort direction, detect the previous
90
- # sort direction on this field and reverse it
91
- if Ransack::Constants::ASC_DESC.none? { |d| d == new_dir }
92
- if existing_sort = search.sorts.detect { |s| s.name == attr_name }
93
- current_dir = existing_sort.dir
119
+ def extract_label_and_mutate_args!(args)
120
+ if args.first.is_a? String
121
+ args.shift
122
+ else
123
+ Translate.attribute(@field, :context => @search.context)
94
124
  end
125
+ end
95
126
 
96
- new_dir =
97
- if current_dir
98
- if current_dir == Ransack::Constants::DESC
99
- Ransack::Constants::ASC
100
- else
101
- Ransack::Constants::DESC
102
- end
103
- elsif default_order_is_a_hash
104
- default_order[attr_name] || Ransack::Constants::ASC
105
- else
106
- default_order || Ransack::Constants::ASC
107
- end
108
- end
109
-
110
- sort_params << "#{attr_name} #{new_dir}"
111
- end
127
+ def extract_options_and_mutate_args!(args)
128
+ if args.first.is_a? Hash
129
+ args.shift.with_indifferent_access
130
+ else
131
+ {}
132
+ end
133
+ end
112
134
 
113
- # if there is only one sort parameter, remove it from the array and just
114
- # use the string as the parameter
115
- sort_params = sort_params.first if sort_params.size == 1
116
-
117
- html_options = args.first.is_a?(Hash) ? args.shift.dup : {}
118
- css = [Ransack::Constants::SORT_LINK, field_current_dir]
119
- .compact.join(Ransack::Constants::SPACE)
120
- html_options[:class] = [css, html_options[:class]]
121
- .compact.join(Ransack::Constants::SPACE)
122
-
123
- query_hash = {}
124
- query_hash[search.context.search_key] = search_params
125
- .merge(:s => sort_params)
126
- options.merge!(query_hash)
127
- options_for_url = params.merge(options)
128
-
129
- url =
130
- if routing_proxy && respond_to?(routing_proxy)
131
- send(routing_proxy).url_for(options_for_url)
132
- else
133
- url_for(options_for_url)
134
- end
135
+ def search_and_sort_params
136
+ search_params.merge(:s => @sort_params)
137
+ end
135
138
 
136
- name = link_name(label_text, field_current_dir)
139
+ def search_params
140
+ @params[@search.context.search_key].presence || {}
141
+ end
137
142
 
138
- link_to(name, url, html_options)
139
- end
143
+ def build_sort(fields)
144
+ return [] if fields.empty?
145
+ [parse_sort(fields[0])] + build_sort(fields.drop(1))
146
+ end
140
147
 
141
- private
148
+ def parse_sort(field)
149
+ attr_name, new_dir = field.to_s.split(/\s+/)
150
+ if no_sort_direction_specified?(new_dir)
151
+ new_dir = detect_previous_sort_direction_and_invert_it(attr_name)
152
+ end
153
+ "#{attr_name} #{new_dir}"
154
+ end
142
155
 
143
- def link_name(label_text, dir)
144
- [ERB::Util.h(label_text), order_indicator_for(dir)]
145
- .compact
146
- .join(Ransack::Constants::NON_BREAKING_SPACE)
147
- .html_safe
148
- end
156
+ def detect_previous_sort_direction_and_invert_it(attr_name)
157
+ sort_dir = existing_sort_direction(attr_name)
158
+ if sort_dir
159
+ direction_text(sort_dir)
160
+ else
161
+ default_sort_order(attr_name) || Constants::ASC
162
+ end
163
+ end
149
164
 
150
- def order_indicator_for(dir)
151
- if dir == Ransack::Constants::ASC
152
- Ransack::Constants::ASC_ARROW
153
- elsif dir == Ransack::Constants::DESC
154
- Ransack::Constants::DESC_ARROW
155
- else
156
- nil
157
- end
158
- end
165
+ def existing_sort_direction(attr_name = @field)
166
+ if sort = @search.sorts.detect { |s| s && s.name == attr_name }
167
+ sort.dir
168
+ end
169
+ end
170
+
171
+ def default_sort_order(attr_name)
172
+ Hash === @default_order ? @default_order[attr_name] : @default_order
173
+ end
159
174
 
175
+ def order_indicator
176
+ if @hide_indicator || no_sort_direction_specified?
177
+ nil
178
+ else
179
+ direction_arrow
180
+ end
181
+ end
182
+
183
+ def no_sort_direction_specified?(dir = @current_dir)
184
+ !Constants::ASC_DESC.include?(dir)
185
+ end
186
+
187
+ def direction_arrow
188
+ if @current_dir == Constants::DESC
189
+ Constants::DESC_ARROW
190
+ else
191
+ Constants::ASC_ARROW
192
+ end
193
+ end
194
+
195
+ def direction_text(dir)
196
+ if dir == Constants::DESC
197
+ Constants::ASC
198
+ else
199
+ Constants::DESC
200
+ end
201
+ end
202
+
203
+ end
160
204
  end
161
205
  end
162
206
  end
@@ -28,16 +28,16 @@ module Ransack
28
28
  alias_method :cache_key, :collection
29
29
 
30
30
  def initialize
31
- super("Search".freeze)
32
- @singular = "search".freeze
33
- @plural = "searches".freeze
34
- @element = "search".freeze
35
- @human = "Search".freeze
36
- @collection = "ransack/searches".freeze
37
- @partial_path = "#{@collection}/#{@element}".freeze
38
- @param_key = "q".freeze
39
- @route_key = "searches".freeze
40
- @i18n_key = :ransack
31
+ super(Constants::CAP_SEARCH)
32
+ @singular = Constants::SEARCH
33
+ @plural = Constants::SEARCHES
34
+ @element = Constants::SEARCH
35
+ @human = Constants::CAP_SEARCH
36
+ @collection = Constants::RANSACK_SLASH_SEARCHES
37
+ @partial_path = Constants::RANSACK_SLASH_SEARCHES_SLASH_SEARCH
38
+ @param_key = Constants::Q
39
+ @route_key = Constants::SEARCHES
40
+ @i18n_key = :ransack
41
41
  end
42
42
  end
43
43
 
@@ -51,4 +51,4 @@ module Ransack
51
51
  end
52
52
  end
53
53
 
54
- end
54
+ end
@@ -3,5 +3,7 @@ require 'ransack/nodes/node'
3
3
  require 'ransack/nodes/attribute'
4
4
  require 'ransack/nodes/value'
5
5
  require 'ransack/nodes/condition'
6
+ require 'ransack/adapters/active_record/ransack/nodes/condition' if defined?(::ActiveRecord::Base)
7
+ require 'ransack/adapters/mongoid/ransack/nodes/condition' if defined?(::Mongoid)
6
8
  require 'ransack/nodes/sort'
7
9
  require 'ransack/nodes/grouping'
@@ -5,9 +5,7 @@ module Ransack
5
5
  attr_accessor :parent, :attr_name
6
6
 
7
7
  def attr
8
- @attr ||= ransacker ?
9
- ransacker.attr_from(self) :
10
- context.table_for(parent)[attr_name]
8
+ @attr ||= get_arel_attribute
11
9
  end
12
10
  alias :arel_attribute :attr
13
11
 
@@ -27,6 +25,16 @@ module Ransack
27
25
  @parent = @attr_name = @attr = @klass = nil
28
26
  end
29
27
 
28
+ private
29
+
30
+ def get_arel_attribute
31
+ if ransacker
32
+ ransacker.attr_from(self)
33
+ else
34
+ context.table_for(parent)[attr_name]
35
+ end
36
+ end
37
+
30
38
  end
31
39
  end
32
- end
40
+ end
@@ -105,7 +105,7 @@ module Ransack
105
105
  end
106
106
 
107
107
  def combinator=(val)
108
- @combinator = Ransack::Constants::AND_OR.detect { |v| v == val.to_s } || nil
108
+ @combinator = Constants::AND_OR.detect { |v| v == val.to_s } || nil
109
109
  end
110
110
  alias :m= :combinator=
111
111
  alias :m :combinator
@@ -171,23 +171,7 @@ module Ransack
171
171
  alias :p :predicate_name
172
172
 
173
173
  def arel_predicate
174
- predicates = attributes.map do |attr|
175
- attr.attr.send(
176
- arel_predicate_for_attribute(attr),
177
- formatted_values_for_attribute(attr)
178
- )
179
- end
180
-
181
- if predicates.size > 1
182
- case combinator
183
- when Ransack::Constants::AND
184
- Arel::Nodes::Grouping.new(Arel::Nodes::And.new(predicates))
185
- when Ransack::Constants::OR
186
- predicates.inject(&:or)
187
- end
188
- else
189
- predicates.first
190
- end
174
+ raise "not implemented"
191
175
  end
192
176
 
193
177
  def validated_values
@@ -228,20 +212,19 @@ module Ransack
228
212
  data = [
229
213
  ['attributes'.freeze, a.try(:map, &:name)],
230
214
  ['predicate'.freeze, p],
231
- [Ransack::Constants::COMBINATOR, m],
215
+ [Constants::COMBINATOR, m],
232
216
  ['values'.freeze, v.try(:map, &:value)]
233
217
  ]
234
218
  .reject { |e| e[1].blank? }
235
219
  .map { |v| "#{v[0]}: #{v[1]}" }
236
- .join(Ransack::Constants::COMMA_SPACE)
220
+ .join(Constants::COMMA_SPACE)
237
221
  "Condition <#{data}>"
238
222
  end
239
223
 
240
224
  private
241
225
 
242
226
  def valid_combinator?
243
- attributes.size < 2 ||
244
- Ransack::Constants::AND_OR.include?(combinator)
227
+ attributes.size < 2 || Constants::AND_OR.include?(combinator)
245
228
  end
246
229
 
247
230
  end
@@ -44,7 +44,6 @@ module Ransack
44
44
  self.conditions << condition if condition.valid?
45
45
  end
46
46
  end
47
-
48
47
  self.conditions.uniq!
49
48
  end
50
49
  alias :c= :conditions=
@@ -69,7 +68,7 @@ module Ransack
69
68
  def respond_to?(method_id)
70
69
  super or begin
71
70
  method_name = method_id.to_s
72
- writer = method_name.sub!(/\=$/, Ransack::Constants::EMPTY)
71
+ writer = method_name.sub!(/\=$/, Constants::EMPTY)
73
72
  attribute_method?(method_name) ? true : false
74
73
  end
75
74
  end
@@ -115,11 +114,13 @@ module Ransack
115
114
 
116
115
  def method_missing(method_id, *args)
117
116
  method_name = method_id.to_s
118
- writer = method_name.sub!(/\=$/, Ransack::Constants::EMPTY)
117
+ writer = method_name.sub!(/\=$/, Constants::EMPTY)
119
118
  if attribute_method?(method_name)
120
- writer ?
121
- write_attribute(method_name, *args) :
119
+ if writer
120
+ write_attribute(method_name, *args)
121
+ else
122
122
  read_attribute(method_name)
123
+ end
123
124
  else
124
125
  super
125
126
  end
@@ -135,8 +136,7 @@ module Ransack
135
136
  else
136
137
  stripped_name
137
138
  .split(/_and_|_or_/)
138
- .select { |n| !@context.attribute_method?(n) }
139
- .empty?
139
+ .none? { |n| !@context.attribute_method?(n) }
140
140
  end
141
141
  end
142
142
 
@@ -165,12 +165,11 @@ module Ransack
165
165
 
166
166
  def inspect
167
167
  data = [
168
- ['conditions'.freeze, conditions],
169
- [Ransack::Constants::COMBINATOR, combinator]
168
+ ['conditions'.freeze, conditions], [Constants::COMBINATOR, combinator]
170
169
  ]
171
170
  .reject { |e| e[1].blank? }
172
171
  .map { |v| "#{v[0]}: #{v[1]}" }
173
- .join(Ransack::Constants::COMMA_SPACE)
172
+ .join(Constants::COMMA_SPACE)
174
173
  "Grouping <#{data}>"
175
174
  end
176
175