searchlogic 1.5.4 → 1.5.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,268 @@
1
+ module Searchlogic
2
+ module Conditions
3
+ # = Magic Methods
4
+ #
5
+ # Handles all method magic, creating methods on the fly, etc. This is needed for modifiers.
6
+ module MagicMethods
7
+ def self.included(klass)
8
+ klass.metaclass.class_eval do
9
+ include ClassMethods
10
+ attr_accessor :added_column_equals_conditions, :added_associations
11
+ end
12
+
13
+ klass.class_eval do
14
+ include InstanceMethods
15
+ alias_method_chain :initialize, :magic_methods
16
+ alias_method_chain :method_missing, :magic_methods
17
+ end
18
+ end
19
+
20
+ module ClassMethods # :nodoc:
21
+ def column_details
22
+ return @column_details if @column_details
23
+
24
+ @column_details = []
25
+
26
+ klass.columns.each do |column|
27
+ column_detail = {:column => column}
28
+ column_detail[:aliases] = case column.type
29
+ when :datetime, :time, :timestamp
30
+ [column.name.gsub(/_at$/, "")]
31
+ when :date
32
+ [column.name.gsub(/_at$/, "")]
33
+ else
34
+ []
35
+ end
36
+
37
+ @column_details << column_detail
38
+ end
39
+
40
+ @column_details
41
+ end
42
+ end
43
+
44
+ module InstanceMethods # :nodoc:
45
+ def initialize_with_magic_methods(*args)
46
+ add_associations!
47
+ add_column_equals_conditions!
48
+ initialize_without_magic_methods(*args)
49
+ end
50
+
51
+ private
52
+ def add_associations!
53
+ return true if self.class.added_associations
54
+
55
+ klass.reflect_on_all_associations.each do |association|
56
+ self.class.class_eval <<-"end_eval", __FILE__, __LINE__
57
+ def #{association.name}
58
+ return @#{association.name} unless @#{association.name}.nil?
59
+ @#{association.name} = Searchlogic::Conditions::Base.create_virtual_class(#{association.class_name}).new
60
+ @#{association.name}.object_name = :#{association.name}
61
+ @#{association.name}.protect = protect
62
+ objects << @#{association.name}
63
+ @#{association.name}
64
+ end
65
+
66
+ def #{association.name}=(conditions)
67
+ @conditions = nil
68
+ #{association.name}.conditions = conditions
69
+ end
70
+
71
+ def reset_#{association.name}!
72
+ objects.delete(#{association.name})
73
+ @#{association.name} = nil
74
+ end
75
+ end_eval
76
+ end
77
+
78
+ self.class.added_associations = true
79
+ end
80
+
81
+ def add_column_equals_conditions!
82
+ return true if self.class.added_column_equals_conditions
83
+ klass.column_names.each { |name| setup_condition(name) }
84
+ self.class.added_column_equals_conditions = true
85
+ end
86
+
87
+ def sanitize_method_name(name)
88
+ name.gsub("=", "").gsub(/^(and|or)_/, "")
89
+ end
90
+
91
+ def extract_column_and_condition_from_method_name(name)
92
+ name_parts = sanitize_method_name(name).split("_")
93
+
94
+ condition_parts = []
95
+ column = nil
96
+ while column.nil? && name_parts.size > 0
97
+ possible_column_name = name_parts.join("_")
98
+
99
+ self.class.column_details.each do |column_detail|
100
+ if column_detail[:column].name == possible_column_name || column_detail[:aliases].include?(possible_column_name)
101
+ column = column_detail
102
+ break
103
+ end
104
+ end
105
+
106
+ condition_parts << name_parts.pop if !column
107
+ end
108
+
109
+ return if column.nil?
110
+
111
+ condition_name = condition_parts.reverse.join("_")
112
+ condition = nil
113
+
114
+ # Find the real condition
115
+ self.class.conditions.each do |condition_klass|
116
+ if condition_klass.condition_names_for_column.include?(condition_name)
117
+ condition = condition_klass
118
+ break
119
+ end
120
+ end
121
+
122
+ [column, condition]
123
+ end
124
+
125
+ def breakdown_method_name(name)
126
+ column_detail, condition_klass = extract_column_and_condition_from_method_name(name)
127
+ if !column_detail.nil? && !condition_klass.nil?
128
+ # There were no modifiers
129
+ return [[], column_detail, condition_klass]
130
+ else
131
+ # There might be modifiers
132
+ name_parts = name.split("_of_")
133
+ column_detail, condition_klass = extract_column_and_condition_from_method_name(name_parts.pop)
134
+ if !column_detail.nil? && !condition_klass.nil?
135
+ # There were modifiers, lets get their real names
136
+ modifier_klasses = []
137
+ name_parts.each do |modifier_name|
138
+ size_before = modifier_klasses.size
139
+ self.class.modifiers.each do |modifier_klass|
140
+ if modifier_klass.modifier_names.include?(modifier_name)
141
+ modifier_klasses << modifier_klass
142
+ break
143
+ end
144
+ end
145
+ return if modifier_klasses.size == size_before # there was an invalid modifer, return nil for everything and let it act as a nomethoderror
146
+ end
147
+
148
+ return [modifier_klasses, column_detail, condition_klass]
149
+ end
150
+ end
151
+
152
+ nil
153
+ end
154
+
155
+ def build_method_name(modifier_klasses, column_name, condition_name)
156
+ modifier_name_parts = []
157
+ modifier_klasses.each { |modifier_klass| modifier_name_parts << modifier_klass.modifier_names.first }
158
+ method_name_parts = []
159
+ method_name_parts << modifier_name_parts.join("_of_") + "_of" unless modifier_name_parts.blank?
160
+ method_name_parts << column_name
161
+ method_name_parts << condition_name unless condition_name.blank?
162
+ method_name_parts.join("_").underscore
163
+ end
164
+
165
+ def method_missing_with_magic_methods(name, *args, &block)
166
+ if setup_condition(name)
167
+ send(name, *args, &block)
168
+ else
169
+ method_missing_without_magic_methods(name, *args, &block)
170
+ end
171
+ end
172
+
173
+ def setup_condition(name)
174
+ modifier_klasses, column_detail, condition_klass = breakdown_method_name(name.to_s)
175
+ if !column_detail.nil? && !condition_klass.nil?
176
+ method_name = build_method_name(modifier_klasses, column_detail[:column].name, condition_klass.condition_names_for_column.first)
177
+
178
+ if !added_condition?(method_name)
179
+ column_type = column_sql = nil
180
+ if !modifier_klasses.blank?
181
+ # Find the column type
182
+ column_type = modifier_klasses.first.return_type
183
+
184
+ # Build the column sql
185
+ column_sql = "{table}.{column}"
186
+ modifier_klasses.each do |modifier_klass|
187
+ next unless klass.connection.respond_to?(modifier_klass.adapter_method_name)
188
+ column_sql = klass.connection.send(modifier_klass.adapter_method_name, column_sql)
189
+ end
190
+ end
191
+
192
+ add_condition!(condition_klass, method_name, :column => column_detail[:column], :column_type => column_type, :column_sql_format => column_sql)
193
+
194
+ ([column_detail[:column].name] + column_detail[:aliases]).each do |column_name|
195
+ condition_klass.condition_names_for_column.each do |condition_name|
196
+ alias_method_name = build_method_name(modifier_klasses, column_name, condition_name)
197
+ add_condition_alias!(alias_method_name, method_name) unless added_condition?(alias_method_name)
198
+ end
199
+ end
200
+ end
201
+
202
+ alias_method_name = sanitize_method_name(name.to_s)
203
+ add_condition_alias!(alias_method_name, method_name) unless added_condition?(alias_method_name)
204
+
205
+ return true
206
+ end
207
+
208
+ false
209
+ end
210
+
211
+ def add_condition!(condition, name, options = {})
212
+ options[:column] = options[:column].name
213
+
214
+ self.class.class_eval <<-"end_eval", __FILE__, __LINE__
215
+ def #{name}_object
216
+ return @#{name} unless @#{name}.nil?
217
+ @#{name} = #{condition.name}.new(klass, #{options.inspect})
218
+ @#{name}.object_name = :#{name}
219
+ objects << @#{name}
220
+ @#{name}
221
+ end
222
+
223
+ def #{name}
224
+ #{name}_object.value
225
+ end
226
+
227
+ def #{name}=(value)
228
+ @conditions = nil
229
+ #{name}_object.value = value
230
+ reset_#{name}! if #{name}_object.value_is_meaningless?
231
+ value
232
+ end
233
+
234
+ def and_#{name}=(value)
235
+ #{name}_object.any = false
236
+ self.#{name} = value
237
+ end
238
+
239
+ def or_#{name}=(value)
240
+ #{name}_object.any = true
241
+ self.#{name} = value
242
+ end
243
+
244
+ def reset_#{name}!
245
+ objects.delete(#{name}_object)
246
+ @#{name} = nil
247
+ end
248
+ end_eval
249
+ end
250
+
251
+ def added_condition?(name)
252
+ respond_to?("#{name}_object")
253
+ end
254
+
255
+ def add_condition_alias!(alias_name, name)
256
+ self.class.class_eval do
257
+ alias_method "#{alias_name}_object", "#{name}_object"
258
+ alias_method alias_name, name
259
+ alias_method "#{alias_name}=", "#{name}="
260
+ alias_method "and_#{alias_name}=", "and_#{name}="
261
+ alias_method "or_#{alias_name}=", "or_#{name}="
262
+ alias_method "reset_#{alias_name}!", "reset_#{name}!"
263
+ end
264
+ end
265
+ end
266
+ end
267
+ end
268
+ end
@@ -23,7 +23,7 @@ module Searchlogic
23
23
  end
24
24
 
25
25
  def protect=(value)
26
- associations.each { |name, obj| obj.protect = value }
26
+ association_objects.each { |obj| obj.protect = value }
27
27
  @protect = value
28
28
  end
29
29
 
@@ -31,18 +31,20 @@ module Searchlogic
31
31
  # The class name for used in the order_as_link helper
32
32
  #
33
33
  # * <tt>Default:</tt> "order_as"
34
- # * <tt>Accepts:</tt> String
34
+ # * <tt>Accepts:</tt> String, set to nil to disable
35
35
  def order_as_link_class_name
36
- @order_as_link_class_name ||= "order_as"
36
+ return @order_as_link_class_name if defined?(@order_as_link_class_name)
37
+ @order_as_link_class_name = "order_as"
37
38
  end
38
39
  attr_writer :order_as_link_class_name
39
40
 
40
41
  # The class name for used in the order_as_select helper
41
42
  #
42
43
  # * <tt>Default:</tt> "order_as"
43
- # * <tt>Accepts:</tt> String
44
+ # * <tt>Accepts:</tt> String, set to nil to disable
44
45
  def order_as_select_class_name
45
- @order_as_select_class_name ||= "order_as"
46
+ return @order_as_select_class_name if defined?(@order_as_select_class_name)
47
+ @order_as_select_class_name = "order_as"
46
48
  end
47
49
  attr_writer :order_as_select_class_name
48
50
 
@@ -63,9 +65,10 @@ module Searchlogic
63
65
  # The class name for used in the order_by_link helper
64
66
  #
65
67
  # * <tt>Default:</tt> "order_by"
66
- # * <tt>Accepts:</tt> String
68
+ # * <tt>Accepts:</tt> String, set to nil to disable
67
69
  def order_by_link_class_name
68
- @order_by_link_class_name ||= "order_by"
70
+ return @order_by_link_class_name if defined?(@order_by_link_class_name)
71
+ @order_by_link_class_name = "order_by"
69
72
  end
70
73
  attr_writer :order_by_link_class_name
71
74
 
@@ -80,9 +83,10 @@ module Searchlogic
80
83
  # * <tt>Default:</tt> "# The class name for used in the page_link helper
81
84
  #
82
85
  # * <tt>Default:</tt> "page"
83
- # * <tt>Accepts:</tt> String
86
+ # * <tt>Accepts:</tt> String, set to nil to disable
84
87
  def order_by_links_ordering_by_class_name
85
- @order_by_links_ordering_by_class_name ||= "ordering_by"
88
+ return @order_by_links_ordering_by_class_name if defined?(@order_by_links_ordering_by_class_name)
89
+ @order_by_links_ordering_by_class_name = "ordering_by"
86
90
  end
87
91
  attr_writer :order_by_links_ordering_by_class_name
88
92
 
@@ -95,12 +99,27 @@ module Searchlogic
95
99
  end
96
100
  attr_writer :order_by_select_class_name
97
101
 
102
+ # Makes page_links look just like the output of will_paginate.
103
+ #
104
+ # * <tt>Default:</tt> false
105
+ # * <tt>Accepts:</tt> Boolean
106
+ def page_links_act_like_will_paginate
107
+ @page_links_act_like_will_paginate ||= false
108
+ end
109
+ attr_writer :page_links_act_like_will_paginate
110
+
111
+ # Convenience methods for determining if page_links_act_like_will_paginate is set to true
112
+ def page_links_act_like_will_paginate?
113
+ page_links_act_like_will_paginate == true
114
+ end
115
+
98
116
  # The class name for used in the page_link helper
99
117
  #
100
118
  # * <tt>Default:</tt> "page"
101
- # * <tt>Accepts:</tt> String
119
+ # * <tt>Accepts:</tt> String, set to nil to disable
102
120
  def page_link_class_name
103
- @page_link_class_name ||= "page"
121
+ return @page_link_class_name if defined?(@page_link_class_name)
122
+ @page_link_class_name = "page"
104
123
  end
105
124
  attr_writer :page_link_class_name
106
125
 
@@ -115,7 +134,8 @@ module Searchlogic
115
134
  # * <tt>Default:</tt> "current_page"
116
135
  # * <tt>Accepts:</tt> String, set to nil to disable
117
136
  def page_links_current_page_class_name
118
- @page_links_current_page_class_name ||= "current_page"
137
+ return @page_links_current_page_class_name if defined?(@page_links_current_page_class_name)
138
+ @page_links_current_page_class_name = page_links_act_like_will_paginate? ? "current" : "current_page"
119
139
  end
120
140
  attr_writer :page_links_current_page_class_name
121
141
 
@@ -124,10 +144,31 @@ module Searchlogic
124
144
  # * <tt>Default:</tt> "disabled_page"
125
145
  # * <tt>Accepts:</tt> String, set to nil to disable
126
146
  def page_links_disabled_class_name
127
- @page_links_disabled_class_name ||= "disabled_page"
147
+ return @page_links_disabled_class_name if defined?(@page_links_disabled_class_name)
148
+ @page_links_disabled_class_name = page_links_act_like_will_paginate? ? "disabled" : "disabled_page"
128
149
  end
129
150
  attr_writer :page_links_disabled_class_name
130
151
 
152
+ # Wraps page links in a div
153
+ #
154
+ # * <tt>Default:</tt> false
155
+ # * <tt>Accepts:</tt> Boolean
156
+ def page_links_div_wrapper
157
+ return @page_links_div_wrapper if defined?(@page_links_div_wrapper)
158
+ @page_links_div_wrapper = page_links_act_like_will_paginate?
159
+ end
160
+ attr_writer :page_links_div_wrapper
161
+
162
+ # If page_links_div_wrapper is true you can specify a class name here.
163
+ #
164
+ # * <tt>Default:</tt> "pagination"
165
+ # * <tt>Accepts:</tt> String, set to nil to disable
166
+ def page_links_div_wrapper_class_name
167
+ return @page_links_div_wrapper_class_name if defined?(@page_links_div_wrapper_class_name)
168
+ @page_links_div_wrapper_class_name = "pagination"
169
+ end
170
+ attr_writer :page_links_div_wrapper_class_name
171
+
131
172
  # The default for the :first option for the page_links helper.
132
173
  #
133
174
  # * <tt>Default:</tt> nil
@@ -149,9 +190,10 @@ module Searchlogic
149
190
  # The class for the first page link
150
191
  #
151
192
  # * <tt>Default:</tt> "first_page"
152
- # * <tt>Accepts:</tt> String, nil to disable
193
+ # * <tt>Accepts:</tt> String, set to nil to disable
153
194
  def page_links_first_page_class_name
154
- @page_links_first_page_class_name ||= "first_page"
195
+ return @page_links_first_page_class_name if defined?(@page_links_first_page_class_name)
196
+ @page_links_first_page_class_name = "first_page"
155
197
  end
156
198
  attr_writer :page_links_first_page_class_name
157
199
 
@@ -167,9 +209,10 @@ module Searchlogic
167
209
  # The class for the last page link
168
210
  #
169
211
  # * <tt>Default:</tt> "last_page"
170
- # * <tt>Accepts:</tt> String, nil to disable
212
+ # * <tt>Accepts:</tt> String, set to nil to disable
171
213
  def page_links_last_page_class_name
172
- @page_links_last_page_class_name ||= "last_page"
214
+ return @page_links_last_page_class_name if defined?(@page_links_last_page_class_name)
215
+ @page_links_last_page_class_name = "last_page"
173
216
  end
174
217
  attr_writer :page_links_last_page_class_name
175
218
 
@@ -185,9 +228,10 @@ module Searchlogic
185
228
  # The class for the next page link
186
229
  #
187
230
  # * <tt>Default:</tt> "next_page"
188
- # * <tt>Accepts:</tt> String, nil to disable
231
+ # * <tt>Accepts:</tt> String, set to nil to disable
189
232
  def page_links_next_page_class_name
190
- @page_links_next_page_class_name ||= "next_page"
233
+ return @page_links_next_page_class_name if defined?(@page_links_next_page_class_name)
234
+ @page_links_next_page_class_name = "next_page"
191
235
  end
192
236
  attr_writer :page_links_next_page_class_name
193
237
 
@@ -203,9 +247,10 @@ module Searchlogic
203
247
  # The class for the previous page link
204
248
  #
205
249
  # * <tt>Default:</tt> "prev_page"
206
- # * <tt>Accepts:</tt> String, nil to disable
250
+ # * <tt>Accepts:</tt> String, set to nil to disable
207
251
  def page_links_prev_page_class_name
208
- @page_links_prev_page_class_name ||= "prev_page"
252
+ return @page_links_prev_page_class_name if defined?(@page_links_prev_page_class_name)
253
+ @page_links_prev_page_class_name = "prev_page"
209
254
  end
210
255
  attr_writer :page_links_prev_page_class_name
211
256
 
@@ -221,18 +266,20 @@ module Searchlogic
221
266
  # The class name for used in the page_seect helper
222
267
  #
223
268
  # * <tt>Default:</tt> "page"
224
- # * <tt>Accepts:</tt> String
269
+ # * <tt>Accepts:</tt> String, set to nil to disable
225
270
  def page_select_class_name
226
- @page_select_class_name ||= "page"
271
+ return @page_select_class_name if defined?(@page_select_class_name)
272
+ @page_select_class_name = "page"
227
273
  end
228
274
  attr_writer :page_select_class_name
229
275
 
230
276
  # The class name for used in the per_page_link helper
231
277
  #
232
278
  # * <tt>Default:</tt> "per_page"
233
- # * <tt>Accepts:</tt> String
279
+ # * <tt>Accepts:</tt> String, set to nil to disable
234
280
  def per_page_link_class_name
235
- @per_page_link_class_name ||= "per_page"
281
+ return @per_page_link_class_name if defined?(@per_page_link_class_name)
282
+ @per_page_link_class_name = "per_page"
236
283
  end
237
284
  attr_writer :per_page_link_class_name
238
285
 
@@ -251,9 +298,10 @@ module Searchlogic
251
298
  # The class name for used in the per_page_select helper
252
299
  #
253
300
  # * <tt>Default:</tt> "per_page"
254
- # * <tt>Accepts:</tt> String
301
+ # * <tt>Accepts:</tt> String, set to nil to disable
255
302
  def per_page_select_class_name
256
- @per_page_select_class_name ||= "per_page"
303
+ return @per_page_select_class_name if defined?(@per_page_select_class_name)
304
+ @per_page_select_class_name = "per_page"
257
305
  end
258
306
  attr_writer :per_page_select_class_name
259
307
 
@@ -269,9 +317,10 @@ module Searchlogic
269
317
  # The class name for used in the priority_order_by_link helper
270
318
  #
271
319
  # * <tt>Default:</tt> "priority_order_by"
272
- # * <tt>Accepts:</tt> String
320
+ # * <tt>Accepts:</tt> String, set to nil to disable
273
321
  def priority_order_by_link_class_name
274
- @priority_order_by_link_class_name ||= "priority_order_by"
322
+ return @priority_order_by_link_class_name if defined?(@priority_order_by_link_class_name)
323
+ @priority_order_by_link_class_name = "priority_order_by"
275
324
  end
276
325
  attr_writer :priority_order_by_link_class_name
277
326
 
@@ -159,6 +159,7 @@ module Searchlogic
159
159
 
160
160
  html += span_or_page_link(:next, options.deep_dup, options[:current_page] == options[:last_page]) if options[:next]
161
161
  html += span_or_page_link(:last, options.deep_dup, options[:current_page] == options[:last_page]) if options[:last]
162
+ html = content_tag(:div, html, :class => Config.helpers.page_links_div_wrapper_class_name) if Config.helpers.page_links_div_wrapper
162
163
  html
163
164
  end
164
165
 
@@ -70,7 +70,7 @@ module Searchlogic
70
70
  when String, Symbol
71
71
  args.unshift(search_object).unshift(first)
72
72
  else
73
- name = search_object.is_a?(Conditions::Base) ? (search_object.relationship_name || :conditions) : :search
73
+ name = search_object.is_a?(Conditions::Base) ? (search_object.object_name || :conditions) : :search
74
74
  args.unshift(search_object).unshift(name)
75
75
  end
76
76
 
@@ -7,6 +7,7 @@ module Searchlogic
7
7
  conditions.delete_if { |condition| condition.blank? }
8
8
  return if conditions.blank?
9
9
  return conditions.first if conditions.size == 1
10
+ options[:scope] = true unless options.key?(:scope)
10
11
 
11
12
  conditions_strs = []
12
13
  conditions_subs = []
@@ -20,14 +21,20 @@ module Searchlogic
20
21
 
21
22
  return if conditions_strs.blank?
22
23
 
23
- join = options[:any] ? "OR" : "AND"
24
- conditions_str = "(#{conditions_strs.join(") #{join} (")})"
24
+ join = options[:any] ? " OR " : " AND "
25
+ conditions_str = options[:scope] ? "(#{conditions_strs.join(")#{join}(")})" : conditions_strs.join(join)
25
26
 
26
27
  return conditions_str if conditions_subs.blank?
27
28
 
28
29
  [conditions_str, *conditions_subs]
29
30
  end
30
31
 
32
+ def scope_condition(condition)
33
+ arr_condition = condition.is_a?(Array) ? condition : [condition]
34
+ arr_condition[0] = "(#{arr_condition[0]})"
35
+ arr_condition.size == 1 ? arr_condition.first : arr_condition
36
+ end
37
+
31
38
  def merge_joins(*joins)
32
39
  joins.delete_if { |join| join.blank? }
33
40
  return if joins.blank?
@@ -67,7 +67,7 @@ module Searchlogic
67
67
 
68
68
  MAJOR = 1
69
69
  MINOR = 5
70
- TINY = 4
70
+ TINY = 6
71
71
 
72
72
  # The current version as a Version instance
73
73
  CURRENT = new(MAJOR, MINOR, TINY)
data/lib/searchlogic.rb CHANGED
@@ -38,6 +38,9 @@ require "searchlogic/search/base"
38
38
  require "searchlogic/search/protection"
39
39
 
40
40
  # Conditions
41
+ require "searchlogic/conditions/any_or_all"
42
+ require "searchlogic/conditions/groups"
43
+ require "searchlogic/conditions/magic_methods"
41
44
  require "searchlogic/conditions/protection"
42
45
  require "searchlogic/conditions/base"
43
46
 
@@ -76,6 +79,9 @@ module Searchlogic
76
79
 
77
80
  module Conditions
78
81
  class Base
82
+ include AnyOrAll
83
+ include Groups
84
+ include MagicMethods
79
85
  include Protection
80
86
  end
81
87