schof-searchlogic 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (159) hide show
  1. data/CHANGELOG.rdoc +302 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Manifest +157 -0
  4. data/README.rdoc +461 -0
  5. data/Rakefile +13 -0
  6. data/TODO.rdoc +4 -0
  7. data/init.rb +1 -0
  8. data/lib/searchlogic.rb +100 -0
  9. data/lib/searchlogic/active_record/associations.rb +52 -0
  10. data/lib/searchlogic/active_record/base.rb +224 -0
  11. data/lib/searchlogic/active_record/connection_adapters/mysql_adapter.rb +176 -0
  12. data/lib/searchlogic/active_record/connection_adapters/postgresql_adapter.rb +172 -0
  13. data/lib/searchlogic/active_record/connection_adapters/sqlite_adapter.rb +80 -0
  14. data/lib/searchlogic/condition/base.rb +165 -0
  15. data/lib/searchlogic/condition/begins_with.rb +17 -0
  16. data/lib/searchlogic/condition/blank.rb +21 -0
  17. data/lib/searchlogic/condition/child_of.rb +11 -0
  18. data/lib/searchlogic/condition/descendant_of.rb +11 -0
  19. data/lib/searchlogic/condition/ends_with.rb +17 -0
  20. data/lib/searchlogic/condition/equals.rb +33 -0
  21. data/lib/searchlogic/condition/greater_than.rb +15 -0
  22. data/lib/searchlogic/condition/greater_than_or_equal_to.rb +15 -0
  23. data/lib/searchlogic/condition/inclusive_descendant_of.rb +10 -0
  24. data/lib/searchlogic/condition/keywords.rb +47 -0
  25. data/lib/searchlogic/condition/less_than.rb +15 -0
  26. data/lib/searchlogic/condition/less_than_or_equal_to.rb +15 -0
  27. data/lib/searchlogic/condition/like.rb +15 -0
  28. data/lib/searchlogic/condition/nested_set.rb +17 -0
  29. data/lib/searchlogic/condition/nil.rb +21 -0
  30. data/lib/searchlogic/condition/not_begin_with.rb +20 -0
  31. data/lib/searchlogic/condition/not_blank.rb +19 -0
  32. data/lib/searchlogic/condition/not_end_with.rb +20 -0
  33. data/lib/searchlogic/condition/not_equal.rb +27 -0
  34. data/lib/searchlogic/condition/not_have_keywords.rb +20 -0
  35. data/lib/searchlogic/condition/not_like.rb +20 -0
  36. data/lib/searchlogic/condition/not_nil.rb +19 -0
  37. data/lib/searchlogic/condition/sibling_of.rb +14 -0
  38. data/lib/searchlogic/conditions/any_or_all.rb +42 -0
  39. data/lib/searchlogic/conditions/base.rb +244 -0
  40. data/lib/searchlogic/conditions/groups.rb +74 -0
  41. data/lib/searchlogic/conditions/magic_methods.rb +286 -0
  42. data/lib/searchlogic/conditions/multiparameter_attributes.rb +105 -0
  43. data/lib/searchlogic/conditions/protection.rb +36 -0
  44. data/lib/searchlogic/config.rb +31 -0
  45. data/lib/searchlogic/config/helpers.rb +338 -0
  46. data/lib/searchlogic/config/search.rb +53 -0
  47. data/lib/searchlogic/core_ext/hash.rb +75 -0
  48. data/lib/searchlogic/core_ext/object.rb +19 -0
  49. data/lib/searchlogic/helpers/control_types/link.rb +310 -0
  50. data/lib/searchlogic/helpers/control_types/links.rb +242 -0
  51. data/lib/searchlogic/helpers/control_types/remote_link.rb +87 -0
  52. data/lib/searchlogic/helpers/control_types/remote_links.rb +72 -0
  53. data/lib/searchlogic/helpers/control_types/remote_select.rb +36 -0
  54. data/lib/searchlogic/helpers/control_types/select.rb +82 -0
  55. data/lib/searchlogic/helpers/form.rb +208 -0
  56. data/lib/searchlogic/helpers/utilities.rb +197 -0
  57. data/lib/searchlogic/modifiers/absolute.rb +15 -0
  58. data/lib/searchlogic/modifiers/acos.rb +11 -0
  59. data/lib/searchlogic/modifiers/asin.rb +11 -0
  60. data/lib/searchlogic/modifiers/atan.rb +11 -0
  61. data/lib/searchlogic/modifiers/avg.rb +15 -0
  62. data/lib/searchlogic/modifiers/base.rb +27 -0
  63. data/lib/searchlogic/modifiers/ceil.rb +15 -0
  64. data/lib/searchlogic/modifiers/char_length.rb +15 -0
  65. data/lib/searchlogic/modifiers/cos.rb +15 -0
  66. data/lib/searchlogic/modifiers/cot.rb +15 -0
  67. data/lib/searchlogic/modifiers/count.rb +11 -0
  68. data/lib/searchlogic/modifiers/day_of_month.rb +15 -0
  69. data/lib/searchlogic/modifiers/day_of_week.rb +15 -0
  70. data/lib/searchlogic/modifiers/day_of_year.rb +15 -0
  71. data/lib/searchlogic/modifiers/degrees.rb +11 -0
  72. data/lib/searchlogic/modifiers/exp.rb +15 -0
  73. data/lib/searchlogic/modifiers/floor.rb +15 -0
  74. data/lib/searchlogic/modifiers/hex.rb +11 -0
  75. data/lib/searchlogic/modifiers/hour.rb +11 -0
  76. data/lib/searchlogic/modifiers/log.rb +15 -0
  77. data/lib/searchlogic/modifiers/log10.rb +11 -0
  78. data/lib/searchlogic/modifiers/log2.rb +11 -0
  79. data/lib/searchlogic/modifiers/lower.rb +15 -0
  80. data/lib/searchlogic/modifiers/ltrim.rb +15 -0
  81. data/lib/searchlogic/modifiers/md5.rb +11 -0
  82. data/lib/searchlogic/modifiers/microseconds.rb +11 -0
  83. data/lib/searchlogic/modifiers/milliseconds.rb +11 -0
  84. data/lib/searchlogic/modifiers/minute.rb +15 -0
  85. data/lib/searchlogic/modifiers/month.rb +15 -0
  86. data/lib/searchlogic/modifiers/octal.rb +15 -0
  87. data/lib/searchlogic/modifiers/radians.rb +11 -0
  88. data/lib/searchlogic/modifiers/round.rb +11 -0
  89. data/lib/searchlogic/modifiers/rtrim.rb +15 -0
  90. data/lib/searchlogic/modifiers/second.rb +15 -0
  91. data/lib/searchlogic/modifiers/sign.rb +11 -0
  92. data/lib/searchlogic/modifiers/sin.rb +11 -0
  93. data/lib/searchlogic/modifiers/square_root.rb +15 -0
  94. data/lib/searchlogic/modifiers/sum.rb +11 -0
  95. data/lib/searchlogic/modifiers/tan.rb +15 -0
  96. data/lib/searchlogic/modifiers/trim.rb +15 -0
  97. data/lib/searchlogic/modifiers/upper.rb +15 -0
  98. data/lib/searchlogic/modifiers/week.rb +11 -0
  99. data/lib/searchlogic/modifiers/year.rb +11 -0
  100. data/lib/searchlogic/search/base.rb +148 -0
  101. data/lib/searchlogic/search/conditions.rb +53 -0
  102. data/lib/searchlogic/search/ordering.rb +244 -0
  103. data/lib/searchlogic/search/pagination.rb +121 -0
  104. data/lib/searchlogic/search/protection.rb +89 -0
  105. data/lib/searchlogic/search/searching.rb +32 -0
  106. data/lib/searchlogic/shared/utilities.rb +56 -0
  107. data/lib/searchlogic/shared/virtual_classes.rb +39 -0
  108. data/lib/searchlogic/version.rb +79 -0
  109. data/searchlogic.gemspec +41 -0
  110. data/test/active_record_tests/associations_test.rb +94 -0
  111. data/test/active_record_tests/base_test.rb +115 -0
  112. data/test/condition_tests/base_test.rb +54 -0
  113. data/test/condition_tests/begins_with_test.rb +11 -0
  114. data/test/condition_tests/blank_test.rb +31 -0
  115. data/test/condition_tests/child_of_test.rb +17 -0
  116. data/test/condition_tests/descendant_of_test.rb +12 -0
  117. data/test/condition_tests/ends_with_test.rb +11 -0
  118. data/test/condition_tests/equals_test.rb +28 -0
  119. data/test/condition_tests/greater_than_or_equal_to_test.rb +11 -0
  120. data/test/condition_tests/greater_than_test.rb +11 -0
  121. data/test/condition_tests/inclusive_descendant_of_test.rb +12 -0
  122. data/test/condition_tests/keywords_test.rb +23 -0
  123. data/test/condition_tests/less_than_or_equal_to_test.rb +11 -0
  124. data/test/condition_tests/less_than_test.rb +11 -0
  125. data/test/condition_tests/like_test.rb +11 -0
  126. data/test/condition_tests/nil_test.rb +31 -0
  127. data/test/condition_tests/not_begin_with_test.rb +8 -0
  128. data/test/condition_tests/not_blank_test.rb +8 -0
  129. data/test/condition_tests/not_end_with_test.rb +8 -0
  130. data/test/condition_tests/not_equal_test.rb +19 -0
  131. data/test/condition_tests/not_have_keywords_test.rb +8 -0
  132. data/test/condition_tests/not_like_test.rb +8 -0
  133. data/test/condition_tests/not_nil_test.rb +13 -0
  134. data/test/condition_tests/sibling_of_test.rb +15 -0
  135. data/test/conditions_tests/any_or_all_test.rb +23 -0
  136. data/test/conditions_tests/base_test.rb +185 -0
  137. data/test/conditions_tests/groups_test.rb +68 -0
  138. data/test/conditions_tests/magic_methods_test.rb +36 -0
  139. data/test/conditions_tests/multiparameter_attributes_test.rb +15 -0
  140. data/test/conditions_tests/protection_test.rb +18 -0
  141. data/test/config_test.rb +23 -0
  142. data/test/fixtures/accounts.yml +12 -0
  143. data/test/fixtures/animals.yml +7 -0
  144. data/test/fixtures/orders.yml +12 -0
  145. data/test/fixtures/user_groups.yml +5 -0
  146. data/test/fixtures/users.yml +45 -0
  147. data/test/libs/awesome_nested_set.rb +545 -0
  148. data/test/libs/awesome_nested_set/compatability.rb +29 -0
  149. data/test/libs/awesome_nested_set/helper.rb +40 -0
  150. data/test/libs/awesome_nested_set/named_scope.rb +140 -0
  151. data/test/libs/rexml_fix.rb +14 -0
  152. data/test/modifier_tests/day_of_month_test.rb +16 -0
  153. data/test/search_tests/base_test.rb +241 -0
  154. data/test/search_tests/conditions_test.rb +21 -0
  155. data/test/search_tests/ordering_test.rb +167 -0
  156. data/test/search_tests/pagination_test.rb +74 -0
  157. data/test/search_tests/protection_test.rb +26 -0
  158. data/test/test_helper.rb +116 -0
  159. metadata +385 -0
@@ -0,0 +1,244 @@
1
+ module Searchlogic
2
+ module Conditions # :nodoc:
3
+ # = Conditions
4
+ #
5
+ # Represents a collection of conditions and performs various tasks on that collection. For information on each condition see Searchlogic::Condition.
6
+ # Each condition has its own file and class and the source for each condition is pretty self explanatory.
7
+ class Base
8
+ include Shared::Utilities
9
+ include Shared::VirtualClasses
10
+
11
+ attr_accessor :object_name
12
+
13
+ class << self
14
+ # Registers a condition as an available condition for a column or a class. MySQL supports a "sounds like" function. I want to use it, so let's add it.
15
+ #
16
+ # === Example
17
+ #
18
+ # # config/initializers/searchlogic.rb
19
+ # # Actual function for MySQL databases only
20
+ # class SoundsLike < Searchlogic::Condition::Base
21
+ # # The name of the conditions. By default its the name of the class, if you want alternate or alias conditions just add them on.
22
+ # # If you don't want to add aliases you don't even need to define this method
23
+ # def self.condition_names_for_column
24
+ # super + ["similar_to", "sounds"]
25
+ # end
26
+ #
27
+ # # You can return an array or a string. NOT a hash, because all of these conditions
28
+ # # need to eventually get merged together. The array or string can be anything you would put in
29
+ # # the :conditions option for ActiveRecord::Base.find(). Also notice the column_sql variable. This is essentail
30
+ # # for applying modifiers and should be used in your conditions wherever you want the column.
31
+ # def to_conditions(value)
32
+ # ["#{column_sql} SOUNDS LIKE ?", value]
33
+ # end
34
+ # end
35
+ #
36
+ # Searchlogic::Conditions::Base.register_condition(SoundsLike)
37
+ def register_condition(condition_class)
38
+ raise(ArgumentError, "You can only register conditions that extend Searchlogic::Condition::Base") unless condition_class.ancestors.include?(Searchlogic::Condition::Base)
39
+ conditions << condition_class unless conditions.include?(condition_class)
40
+ end
41
+
42
+ # A list of available condition type classes
43
+ def conditions
44
+ @@conditions ||= []
45
+ end
46
+
47
+ # Registers a modifier as an available modifier for each column.
48
+ #
49
+ # === Example
50
+ #
51
+ # # config/initializers/searchlogic.rb
52
+ # class Ceil < Searchlogic::Modifiers::Base
53
+ # # The name of the modifier. By default its the name of the class, if you want alternate or alias modifiers just add them on.
54
+ # # If you don't want to add aliases you don't even need to define this method
55
+ # def self.modifier_names
56
+ # super + ["round_up"]
57
+ # end
58
+ #
59
+ # # The name of the method in the connection adapters (see below). By default its the name of your class suffixed with "_sql".
60
+ # # So in this example it would be "ceil_sql". Unless you want to change that you don't need to define this method.
61
+ # def self.adapter_method_name
62
+ # super
63
+ # end
64
+ #
65
+ # # This is the type of value returned from the modifier. This is neccessary for typcasting values for the modifier when
66
+ # # applied to a column
67
+ # def self.return_type
68
+ # :integer
69
+ # end
70
+ # end
71
+ #
72
+ # Searchlogic::Seearch::Conditions.register_modifiers(Ceil)
73
+ #
74
+ # Now here's the fun part, applying this modifier to each connection adapter. Some databases call modifiers differently. If they all apply them the same you can
75
+ # add in the function to ActiveRecord::ConnectionAdapters::AbstractAdapter, otherwise you need to add them to each
76
+ # individually: ActiveRecord::ConnectionAdapters::MysqlAdapter, ActiveRecord::ConnectionAdapters::PostgreSQLAdapter, ActiveRecord::ConnectionAdapters::SQLiteAdapter
77
+ #
78
+ # Do this by includine a model with your method. The name of your method, by default, is: #{modifier_name}_sql. So in the example above it would be "ceil_sql"
79
+ #
80
+ # module CeilAdapterMethod
81
+ # def ceil_sql(column_name)
82
+ # "CEIL(#{column_name})"
83
+ # end
84
+ # end
85
+ #
86
+ # ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:include, CeilAdapterMethod)
87
+ # # ... include for the rest of the adapters
88
+ def register_modifier(modifier_class)
89
+ raise(ArgumentError, "You can only register conditions that extend Searchlogic::Modifiers::Base") unless modifier_class.ancestors.include?(Searchlogic::Modifiers::Base)
90
+ modifiers << modifier_class unless modifiers.include?(modifier_class)
91
+ end
92
+
93
+ # A list of available modifier classes
94
+ def modifiers
95
+ @@modifiers ||= []
96
+ end
97
+
98
+ def needed?(model_class, conditions) # :nodoc:
99
+ return false if conditions.blank?
100
+
101
+ if conditions.is_a?(Hash)
102
+ return true if conditions[:any]
103
+ stringified_conditions = conditions.stringify_keys
104
+ stringified_conditions.keys.each { |condition| return false if condition.include?(".") } # setting conditions on associations, which is just another way of writing SQL, and we ignore SQL
105
+
106
+ column_names = model_class.column_names
107
+ stringified_conditions.keys.each do |condition|
108
+ return true unless column_names.include?(condition)
109
+ end
110
+ end
111
+
112
+ false
113
+ end
114
+ end
115
+
116
+ # Initializes a conditions object, accepts a hash of conditions as the single parameter
117
+ def initialize(init_conditions = {})
118
+ self.conditions = init_conditions
119
+ end
120
+
121
+ # A list of joins to use when searching, includes relationships
122
+ def auto_joins
123
+ j = []
124
+ association_objects.each do |association|
125
+ next if association.conditions.blank?
126
+ association_joins = association.auto_joins
127
+ j << (association_joins.blank? ? association.object_name : {association.object_name => association_joins})
128
+ end
129
+ j.blank? ? nil : (j.size == 1 ? j.first : j)
130
+ end
131
+
132
+ # Provides a much more informative and easier to understand inspection of the object
133
+ def inspect
134
+ "#<#{klass}Conditions#{conditions.blank? ? "" : " #{conditions.inspect}"}>"
135
+ end
136
+
137
+ # Sanitizes the conditions down into conditions that ActiveRecord::Base.find can understand.
138
+ def sanitize
139
+ return @conditions if @conditions # return the conditions if the user set them with a string, aka sql conditions
140
+ joined_conditions = nil
141
+ objects.each do |object|
142
+ sanitized_conditions = group?(object) ? scope_condition(object.sanitize) : object.sanitize
143
+ joined_conditions = merge_conditions(joined_conditions, sanitized_conditions, :any => join_object_with_any?(object))
144
+ end
145
+ joined_conditions
146
+ end
147
+
148
+ # Allows you to set the conditions via a hash.
149
+ def conditions=(value)
150
+ case value
151
+ when Array
152
+ value.each { |v| self.conditions = v }
153
+ when Hash
154
+ remove_conditions_from_protected_assignement(value).each do |condition, condition_value|
155
+ next if [:conditions].include?(condition.to_sym) # protect sensitive methods
156
+
157
+ # delete all blanks from mass assignments, forms submit blanks, blanks are meaningless
158
+ # equals condition thinks everything is meaningful, and arrays can be pased
159
+ new_condition_value = nil
160
+ case condition_value
161
+ when Array
162
+ new_condition_value = condition_value.reject { |v| v == "" }
163
+ next if new_condition_value.empty?
164
+ new_condition_value = new_condition_value.first if new_condition_value.size == 1
165
+ else
166
+ next if condition_value == ""
167
+ new_condition_value = condition_value
168
+ end
169
+
170
+ send("#{condition}=", new_condition_value)
171
+ end
172
+ else
173
+ reset!
174
+ @conditions = value
175
+ end
176
+ end
177
+
178
+ # All of the active conditions (conditions that have been set)
179
+ def conditions
180
+ return @conditions if @conditions
181
+
182
+ conditions_hash = {}
183
+
184
+ association_objects.each do |association_object|
185
+ relationship_conditions = association_object.conditions
186
+ next if relationship_conditions.blank?
187
+ conditions_hash[association_object.object_name] = relationship_conditions
188
+ end
189
+
190
+ condition_objects.each do |condition_object|
191
+ next if condition_object.value_is_meaningless?
192
+ conditions_hash[condition_object.object_name] = condition_object.value
193
+ end
194
+
195
+ conditions_hash
196
+ end
197
+
198
+ # Resets all of the conditions, including conditions set on associations
199
+ def reset!
200
+ objects.each { |object| eval("@#{object.object_name} = nil") }
201
+ objects.clear
202
+ end
203
+
204
+ private
205
+ def association_objects
206
+ objects.select { |object| association?(object) }
207
+ end
208
+
209
+ def association?(object)
210
+ object.class < Base && object.class != self.class
211
+ end
212
+
213
+ def condition_objects
214
+ objects.select { |object| condition?(object) }
215
+ end
216
+
217
+ def condition?(object)
218
+ object.class < Condition::Base
219
+ end
220
+
221
+ def objects
222
+ @objects ||= []
223
+ end
224
+
225
+ def join_object_with_any?(object)
226
+ return any? if !any.nil?
227
+ if condition?(object) || group?(object)
228
+ object.explicit_any?
229
+ elsif association?(object)
230
+ object.send(:join_object_with_any?, object.send(:objects).first)
231
+ end
232
+ end
233
+
234
+ def remove_conditions_from_protected_assignement(conditions)
235
+ return conditions if klass.accessible_conditions.nil? && klass.protected_conditions.nil?
236
+ if klass.accessible_conditions
237
+ conditions.reject { |condition, value| !klass.accessible_conditions.include?(condition.to_s) }
238
+ elsif klass.protected_conditions
239
+ conditions.reject { |condition, value| klass.protected_conditions.include?(condition.to_s) }
240
+ end
241
+ end
242
+ end
243
+ end
244
+ end
@@ -0,0 +1,74 @@
1
+ module Searchlogic
2
+ module Conditions
3
+ # = Groups
4
+ #
5
+ # Allows you to group conditions, similar to how you would group conditions with parenthesis in an SQL statement. See the "Group conditions" section in the READM for examples.
6
+ module Groups
7
+ def self.included(klass)
8
+ klass.class_eval do
9
+ alias_method_chain :auto_joins, :groups
10
+ end
11
+ end
12
+
13
+ def auto_joins_with_groups
14
+ auto_joins = auto_joins_without_groups
15
+ auto_joins = auto_joins.is_a?(Array) ? auto_joins : [auto_joins].compact
16
+
17
+ group_objects.each do |group|
18
+ next if group.conditions.blank?
19
+ group_joins = group.auto_joins
20
+ next if group_joins.blank?
21
+ group_joins = group_joins.is_a?(Array) ? group_joins : [group_joins]
22
+ auto_joins += group_joins
23
+ end
24
+
25
+ auto_joins.blank? ? nil : (auto_joins.size == 1 ? auto_joins.first : auto_joins)
26
+ end
27
+
28
+ # Creates a new group object to set condition off of. See examples at top of class on how to use this.
29
+ def group(conditions = nil, &block)
30
+ obj = self.class.new
31
+ obj.conditions = conditions unless conditions.nil?
32
+ yield obj if block_given?
33
+ objects << obj
34
+ obj
35
+ end
36
+ alias_method :group=, :group
37
+
38
+ def and_group(*args, &block)
39
+ obj = group(*args, &block)
40
+ obj.explicit_any = false
41
+ obj
42
+ end
43
+ alias_method :and_group=, :and_group
44
+
45
+ def or_group(*args, &block)
46
+ obj = group(*args, &block)
47
+ obj.explicit_any = true
48
+ obj
49
+ end
50
+ alias_method :or_group=, :or_group
51
+
52
+ def explicit_any=(value) # :nodoc:
53
+ @explicit_any = value
54
+ end
55
+
56
+ def explicit_any # :nodoc
57
+ @explicit_any
58
+ end
59
+
60
+ def explicit_any? # :nodoc:
61
+ ["true", "1", "yes"].include? explicit_any.to_s
62
+ end
63
+
64
+ private
65
+ def group_objects
66
+ objects.select { |object| group?(object) }
67
+ end
68
+
69
+ def group?(object)
70
+ object.class == self.class
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,286 @@
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_class_level_conditions, :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
+ add_class_level_conditions!
49
+ initialize_without_magic_methods(*args)
50
+ end
51
+
52
+ private
53
+ def add_associations!
54
+ return true if self.class.added_associations
55
+
56
+ klass.reflect_on_all_associations.each do |association|
57
+ next if !association.options[:finder_sql].nil? # associations with finder_sql should not be added since conditions can not be chained to them, etc.
58
+ self.class.class_eval <<-"end_eval", __FILE__, __LINE__
59
+ def #{association.name}
60
+ return @#{association.name} unless @#{association.name}.nil?
61
+ @#{association.name} = Searchlogic::Conditions::Base.create_virtual_class(#{association.class_name}).new
62
+ @#{association.name}.object_name = :#{association.name}
63
+ @#{association.name}.protect = protect
64
+ objects << @#{association.name}
65
+ @#{association.name}
66
+ end
67
+
68
+ def #{association.name}=(conditions)
69
+ @conditions = nil
70
+ #{association.name}.conditions = conditions
71
+ end
72
+
73
+ def reset_#{association.name}!
74
+ objects.delete(#{association.name})
75
+ @#{association.name} = nil
76
+ end
77
+ end_eval
78
+ end
79
+
80
+ self.class.added_associations = true
81
+ end
82
+
83
+ def add_column_equals_conditions!
84
+ return true if self.class.added_column_equals_conditions
85
+ klass.column_names.each { |name| setup_condition(name) }
86
+ self.class.added_column_equals_conditions = true
87
+ end
88
+
89
+ def add_class_level_conditions!
90
+ return true if self.class.added_class_level_conditions
91
+ class_level_conditions = self.class.conditions.select { |condition_class| !condition_class.condition_names_for_model.blank? }
92
+ class_level_conditions.each do |condition_class|
93
+ condition_class.condition_names_for_model.each_with_index do |condition_name, index|
94
+ if index == 0
95
+ add_condition!(condition_class, condition_name, :column => klass.columns_hash[klass.primary_key])
96
+ else
97
+ add_condition_alias!(condition_name, condition_class.condition_names_for_model.first)
98
+ end
99
+ end
100
+ end
101
+ self.class.added_class_level_conditions = true
102
+ end
103
+
104
+ def sanitize_method_name(name)
105
+ name.gsub("=", "").gsub(/^(and|or)_/, "")
106
+ end
107
+
108
+ def extract_column_and_condition_from_method_name(name)
109
+ name_parts = sanitize_method_name(name).split("_")
110
+
111
+ condition_parts = []
112
+ column = nil
113
+ while column.nil? && name_parts.size > 0
114
+ possible_column_name = name_parts.join("_")
115
+
116
+ self.class.column_details.each do |column_detail|
117
+ if column_detail[:column].name == possible_column_name || column_detail[:aliases].include?(possible_column_name)
118
+ column = column_detail
119
+ break
120
+ end
121
+ end
122
+
123
+ condition_parts << name_parts.pop if !column
124
+ end
125
+
126
+ return if column.nil?
127
+
128
+ condition_name = condition_parts.reverse.join("_")
129
+ condition = nil
130
+
131
+ # Find the real condition
132
+ self.class.conditions.each do |condition_klass|
133
+ if condition_klass.condition_names_for_column.include?(condition_name)
134
+ condition = condition_klass
135
+ break
136
+ end
137
+ end
138
+
139
+ [column, condition]
140
+ end
141
+
142
+ def breakdown_method_name(name)
143
+ column_detail, condition_klass = extract_column_and_condition_from_method_name(name)
144
+ if !column_detail.nil? && !condition_klass.nil?
145
+ # There were no modifiers
146
+ return [[], column_detail, condition_klass]
147
+ else
148
+ # There might be modifiers
149
+ name_parts = name.split("_of_")
150
+ column_detail, condition_klass = extract_column_and_condition_from_method_name(name_parts.pop)
151
+ if !column_detail.nil? && !condition_klass.nil?
152
+ # There were modifiers, lets get their real names
153
+ modifier_klasses = []
154
+ name_parts.each do |modifier_name|
155
+ size_before = modifier_klasses.size
156
+ self.class.modifiers.each do |modifier_klass|
157
+ if modifier_klass.modifier_names.include?(modifier_name)
158
+ modifier_klasses << modifier_klass
159
+ break
160
+ end
161
+ end
162
+ return if modifier_klasses.size == size_before # there was an invalid modifer, return nil for everything and let it act as a nomethoderror
163
+ end
164
+
165
+ return [modifier_klasses, column_detail, condition_klass]
166
+ end
167
+ end
168
+
169
+ nil
170
+ end
171
+
172
+ def build_method_name(modifier_klasses, column_name, condition_name)
173
+ modifier_name_parts = []
174
+ modifier_klasses.each { |modifier_klass| modifier_name_parts << modifier_klass.modifier_names.first }
175
+ method_name_parts = []
176
+ method_name_parts << modifier_name_parts.join("_of_") + "_of" unless modifier_name_parts.blank?
177
+ method_name_parts << column_name
178
+ method_name_parts << condition_name unless condition_name.blank?
179
+ method_name_parts.join("_").underscore
180
+ end
181
+
182
+ def method_missing_with_magic_methods(name, *args, &block)
183
+ if setup_condition(name)
184
+ send(name, *args, &block)
185
+ else
186
+ method_missing_without_magic_methods(name, *args, &block)
187
+ end
188
+ end
189
+
190
+ def setup_condition(name)
191
+ modifier_klasses, column_detail, condition_klass = breakdown_method_name(name.to_s)
192
+ if !column_detail.nil? && !condition_klass.nil?
193
+ method_name = build_method_name(modifier_klasses, column_detail[:column].name, condition_klass.condition_names_for_column.first)
194
+
195
+ if !added_condition?(method_name)
196
+ column_type = column_sql = nil
197
+ if !modifier_klasses.blank?
198
+ # Find the column type
199
+ column_type = modifier_klasses.first.return_type
200
+
201
+ # Build the column sql
202
+ column_sql = "{table}.{column}"
203
+ modifier_klasses.each do |modifier_klass|
204
+ next unless klass.connection.respond_to?(modifier_klass.adapter_method_name)
205
+ column_sql = klass.connection.send(modifier_klass.adapter_method_name, column_sql)
206
+ end
207
+ end
208
+
209
+ add_condition!(condition_klass, method_name, :column => column_detail[:column], :column_type => column_type, :column_sql_format => column_sql)
210
+
211
+ ([column_detail[:column].name] + column_detail[:aliases]).each do |column_name|
212
+ condition_klass.condition_names_for_column.each do |condition_name|
213
+ alias_method_name = build_method_name(modifier_klasses, column_name, condition_name)
214
+ add_condition_alias!(alias_method_name, method_name) unless added_condition?(alias_method_name)
215
+ end
216
+ end
217
+ end
218
+
219
+ alias_method_name = sanitize_method_name(name.to_s)
220
+ add_condition_alias!(alias_method_name, method_name) unless added_condition?(alias_method_name)
221
+
222
+ return true
223
+ end
224
+
225
+ false
226
+ end
227
+
228
+ def add_condition!(condition, name, options = {})
229
+ options[:column] = options[:column].name if options[:column]
230
+
231
+ self.class.class_eval <<-"end_eval", __FILE__, __LINE__
232
+ def #{name}_object
233
+ return @#{name} unless @#{name}.nil?
234
+ @#{name} = #{condition.name}.new(klass, #{options.inspect})
235
+ @#{name}.object_name = :#{name}
236
+ objects << @#{name}
237
+ @#{name}
238
+ end
239
+
240
+ def #{name}
241
+ #{name}_object.value
242
+ end
243
+ alias_method :and_#{name}, :#{name}
244
+ alias_method :or_#{name}, :#{name}
245
+
246
+ def #{name}=(value)
247
+ @conditions = nil
248
+ #{name}_object.value = value
249
+ value
250
+ end
251
+
252
+ def and_#{name}=(value)
253
+ #{name}_object.explicit_any = false
254
+ self.#{name} = value
255
+ end
256
+
257
+ def or_#{name}=(value)
258
+ #{name}_object.explicit_any = true
259
+ self.#{name} = value
260
+ end
261
+
262
+ def reset_#{name}!
263
+ objects.delete(#{name}_object)
264
+ @#{name} = nil
265
+ end
266
+ end_eval
267
+ end
268
+
269
+ def added_condition?(name)
270
+ respond_to?("#{name}_object")
271
+ end
272
+
273
+ def add_condition_alias!(alias_name, name)
274
+ self.class.class_eval do
275
+ alias_method "#{alias_name}_object", "#{name}_object"
276
+ alias_method alias_name, name
277
+ alias_method "#{alias_name}=", "#{name}="
278
+ alias_method "and_#{alias_name}=", "and_#{name}="
279
+ alias_method "or_#{alias_name}=", "or_#{name}="
280
+ alias_method "reset_#{alias_name}!", "reset_#{name}!"
281
+ end
282
+ end
283
+ end
284
+ end
285
+ end
286
+ end