searchlogic 1.5.4 → 1.5.6

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.
@@ -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