schof-searchlogic 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +302 -0
- data/MIT-LICENSE +20 -0
- data/Manifest +157 -0
- data/README.rdoc +461 -0
- data/Rakefile +13 -0
- data/TODO.rdoc +4 -0
- data/init.rb +1 -0
- data/lib/searchlogic.rb +100 -0
- data/lib/searchlogic/active_record/associations.rb +52 -0
- data/lib/searchlogic/active_record/base.rb +224 -0
- data/lib/searchlogic/active_record/connection_adapters/mysql_adapter.rb +176 -0
- data/lib/searchlogic/active_record/connection_adapters/postgresql_adapter.rb +172 -0
- data/lib/searchlogic/active_record/connection_adapters/sqlite_adapter.rb +80 -0
- data/lib/searchlogic/condition/base.rb +165 -0
- data/lib/searchlogic/condition/begins_with.rb +17 -0
- data/lib/searchlogic/condition/blank.rb +21 -0
- data/lib/searchlogic/condition/child_of.rb +11 -0
- data/lib/searchlogic/condition/descendant_of.rb +11 -0
- data/lib/searchlogic/condition/ends_with.rb +17 -0
- data/lib/searchlogic/condition/equals.rb +33 -0
- data/lib/searchlogic/condition/greater_than.rb +15 -0
- data/lib/searchlogic/condition/greater_than_or_equal_to.rb +15 -0
- data/lib/searchlogic/condition/inclusive_descendant_of.rb +10 -0
- data/lib/searchlogic/condition/keywords.rb +47 -0
- data/lib/searchlogic/condition/less_than.rb +15 -0
- data/lib/searchlogic/condition/less_than_or_equal_to.rb +15 -0
- data/lib/searchlogic/condition/like.rb +15 -0
- data/lib/searchlogic/condition/nested_set.rb +17 -0
- data/lib/searchlogic/condition/nil.rb +21 -0
- data/lib/searchlogic/condition/not_begin_with.rb +20 -0
- data/lib/searchlogic/condition/not_blank.rb +19 -0
- data/lib/searchlogic/condition/not_end_with.rb +20 -0
- data/lib/searchlogic/condition/not_equal.rb +27 -0
- data/lib/searchlogic/condition/not_have_keywords.rb +20 -0
- data/lib/searchlogic/condition/not_like.rb +20 -0
- data/lib/searchlogic/condition/not_nil.rb +19 -0
- data/lib/searchlogic/condition/sibling_of.rb +14 -0
- data/lib/searchlogic/conditions/any_or_all.rb +42 -0
- data/lib/searchlogic/conditions/base.rb +244 -0
- data/lib/searchlogic/conditions/groups.rb +74 -0
- data/lib/searchlogic/conditions/magic_methods.rb +286 -0
- data/lib/searchlogic/conditions/multiparameter_attributes.rb +105 -0
- data/lib/searchlogic/conditions/protection.rb +36 -0
- data/lib/searchlogic/config.rb +31 -0
- data/lib/searchlogic/config/helpers.rb +338 -0
- data/lib/searchlogic/config/search.rb +53 -0
- data/lib/searchlogic/core_ext/hash.rb +75 -0
- data/lib/searchlogic/core_ext/object.rb +19 -0
- data/lib/searchlogic/helpers/control_types/link.rb +310 -0
- data/lib/searchlogic/helpers/control_types/links.rb +242 -0
- data/lib/searchlogic/helpers/control_types/remote_link.rb +87 -0
- data/lib/searchlogic/helpers/control_types/remote_links.rb +72 -0
- data/lib/searchlogic/helpers/control_types/remote_select.rb +36 -0
- data/lib/searchlogic/helpers/control_types/select.rb +82 -0
- data/lib/searchlogic/helpers/form.rb +208 -0
- data/lib/searchlogic/helpers/utilities.rb +197 -0
- data/lib/searchlogic/modifiers/absolute.rb +15 -0
- data/lib/searchlogic/modifiers/acos.rb +11 -0
- data/lib/searchlogic/modifiers/asin.rb +11 -0
- data/lib/searchlogic/modifiers/atan.rb +11 -0
- data/lib/searchlogic/modifiers/avg.rb +15 -0
- data/lib/searchlogic/modifiers/base.rb +27 -0
- data/lib/searchlogic/modifiers/ceil.rb +15 -0
- data/lib/searchlogic/modifiers/char_length.rb +15 -0
- data/lib/searchlogic/modifiers/cos.rb +15 -0
- data/lib/searchlogic/modifiers/cot.rb +15 -0
- data/lib/searchlogic/modifiers/count.rb +11 -0
- data/lib/searchlogic/modifiers/day_of_month.rb +15 -0
- data/lib/searchlogic/modifiers/day_of_week.rb +15 -0
- data/lib/searchlogic/modifiers/day_of_year.rb +15 -0
- data/lib/searchlogic/modifiers/degrees.rb +11 -0
- data/lib/searchlogic/modifiers/exp.rb +15 -0
- data/lib/searchlogic/modifiers/floor.rb +15 -0
- data/lib/searchlogic/modifiers/hex.rb +11 -0
- data/lib/searchlogic/modifiers/hour.rb +11 -0
- data/lib/searchlogic/modifiers/log.rb +15 -0
- data/lib/searchlogic/modifiers/log10.rb +11 -0
- data/lib/searchlogic/modifiers/log2.rb +11 -0
- data/lib/searchlogic/modifiers/lower.rb +15 -0
- data/lib/searchlogic/modifiers/ltrim.rb +15 -0
- data/lib/searchlogic/modifiers/md5.rb +11 -0
- data/lib/searchlogic/modifiers/microseconds.rb +11 -0
- data/lib/searchlogic/modifiers/milliseconds.rb +11 -0
- data/lib/searchlogic/modifiers/minute.rb +15 -0
- data/lib/searchlogic/modifiers/month.rb +15 -0
- data/lib/searchlogic/modifiers/octal.rb +15 -0
- data/lib/searchlogic/modifiers/radians.rb +11 -0
- data/lib/searchlogic/modifiers/round.rb +11 -0
- data/lib/searchlogic/modifiers/rtrim.rb +15 -0
- data/lib/searchlogic/modifiers/second.rb +15 -0
- data/lib/searchlogic/modifiers/sign.rb +11 -0
- data/lib/searchlogic/modifiers/sin.rb +11 -0
- data/lib/searchlogic/modifiers/square_root.rb +15 -0
- data/lib/searchlogic/modifiers/sum.rb +11 -0
- data/lib/searchlogic/modifiers/tan.rb +15 -0
- data/lib/searchlogic/modifiers/trim.rb +15 -0
- data/lib/searchlogic/modifiers/upper.rb +15 -0
- data/lib/searchlogic/modifiers/week.rb +11 -0
- data/lib/searchlogic/modifiers/year.rb +11 -0
- data/lib/searchlogic/search/base.rb +148 -0
- data/lib/searchlogic/search/conditions.rb +53 -0
- data/lib/searchlogic/search/ordering.rb +244 -0
- data/lib/searchlogic/search/pagination.rb +121 -0
- data/lib/searchlogic/search/protection.rb +89 -0
- data/lib/searchlogic/search/searching.rb +32 -0
- data/lib/searchlogic/shared/utilities.rb +56 -0
- data/lib/searchlogic/shared/virtual_classes.rb +39 -0
- data/lib/searchlogic/version.rb +79 -0
- data/searchlogic.gemspec +41 -0
- data/test/active_record_tests/associations_test.rb +94 -0
- data/test/active_record_tests/base_test.rb +115 -0
- data/test/condition_tests/base_test.rb +54 -0
- data/test/condition_tests/begins_with_test.rb +11 -0
- data/test/condition_tests/blank_test.rb +31 -0
- data/test/condition_tests/child_of_test.rb +17 -0
- data/test/condition_tests/descendant_of_test.rb +12 -0
- data/test/condition_tests/ends_with_test.rb +11 -0
- data/test/condition_tests/equals_test.rb +28 -0
- data/test/condition_tests/greater_than_or_equal_to_test.rb +11 -0
- data/test/condition_tests/greater_than_test.rb +11 -0
- data/test/condition_tests/inclusive_descendant_of_test.rb +12 -0
- data/test/condition_tests/keywords_test.rb +23 -0
- data/test/condition_tests/less_than_or_equal_to_test.rb +11 -0
- data/test/condition_tests/less_than_test.rb +11 -0
- data/test/condition_tests/like_test.rb +11 -0
- data/test/condition_tests/nil_test.rb +31 -0
- data/test/condition_tests/not_begin_with_test.rb +8 -0
- data/test/condition_tests/not_blank_test.rb +8 -0
- data/test/condition_tests/not_end_with_test.rb +8 -0
- data/test/condition_tests/not_equal_test.rb +19 -0
- data/test/condition_tests/not_have_keywords_test.rb +8 -0
- data/test/condition_tests/not_like_test.rb +8 -0
- data/test/condition_tests/not_nil_test.rb +13 -0
- data/test/condition_tests/sibling_of_test.rb +15 -0
- data/test/conditions_tests/any_or_all_test.rb +23 -0
- data/test/conditions_tests/base_test.rb +185 -0
- data/test/conditions_tests/groups_test.rb +68 -0
- data/test/conditions_tests/magic_methods_test.rb +36 -0
- data/test/conditions_tests/multiparameter_attributes_test.rb +15 -0
- data/test/conditions_tests/protection_test.rb +18 -0
- data/test/config_test.rb +23 -0
- data/test/fixtures/accounts.yml +12 -0
- data/test/fixtures/animals.yml +7 -0
- data/test/fixtures/orders.yml +12 -0
- data/test/fixtures/user_groups.yml +5 -0
- data/test/fixtures/users.yml +45 -0
- data/test/libs/awesome_nested_set.rb +545 -0
- data/test/libs/awesome_nested_set/compatability.rb +29 -0
- data/test/libs/awesome_nested_set/helper.rb +40 -0
- data/test/libs/awesome_nested_set/named_scope.rb +140 -0
- data/test/libs/rexml_fix.rb +14 -0
- data/test/modifier_tests/day_of_month_test.rb +16 -0
- data/test/search_tests/base_test.rb +241 -0
- data/test/search_tests/conditions_test.rb +21 -0
- data/test/search_tests/ordering_test.rb +167 -0
- data/test/search_tests/pagination_test.rb +74 -0
- data/test/search_tests/protection_test.rb +26 -0
- data/test/test_helper.rb +116 -0
- 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
|