ransack 1.5.1 → 1.6.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 (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