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,461 @@
1
+ = Searchlogic
2
+
3
+ Searchlogic's goal is to keep your application free of searching clutter. It's is an ActiveRecord extension that allows you to perform simple and complex searches with a hash. Why is this great? Because GET and POST parameters are a hash. This means if you can execute a search with a hash, you can execute a search using GET or POST parameters. This makes searching in your application dead simple and clutter free:
4
+
5
+ @search = User.new_search(params[:search])
6
+ @users = @search.all
7
+
8
+ Now think about it this way. How are resources / web services used? Through GET and POST. All of a sudden you have a resource that comes preloaded with a *very* flexible searching functionality, all with 2 lines of code.
9
+
10
+ What about your interface? What is an HTML form's sole purpose? It's to send GET or POST parameters to a URI. Now you can build a form that represents a search. Meaning adding a condition to your search is as easy as adding a field to your form. That's what the @search object above is all about. You can pass that right into your form builder and build a form just like you would for an ActiveRecord object. Now you can use that handy form builder that you like you use and get rid of even more searching clutter in your views.
11
+
12
+ Searching in your application has never been cleaner, more flexible, or easier.
13
+
14
+ These are just the basics of Searchlogic, it can do a lot more, including pagination, ordering data, etc. All of these things are just as simple, if not simpler. Keep reading to find out everything it can do.
15
+
16
+ == Helpful links
17
+
18
+ * <b>Documentation:</b> http://searchlogic.rubyforge.org
19
+ * <b>Easy pagination, ordering, and searching tutorial:</b> http://www.binarylogic.com/2008/9/7/tutorial-pagination-ordering-and-searching-with-searchlogic
20
+ * <b>Live example of the tutorial above (with source):</b> http://searchlogic_example.binarylogic.com
21
+ * <b>Complex searching:</b> http://www.binarylogic.com/2008/11/30/searchlogic-1-5-7-complex-searching-no-longer-a-problem
22
+ * <b>Bugs / feature suggestions:</b> http://binarylogic.lighthouseapp.com/projects/16601-searchlogic
23
+
24
+ == Install and use
25
+
26
+ sudo gem install searchlogic
27
+
28
+ For rails, as a gem (recommended)
29
+
30
+ # config/environment.rb
31
+ config.gem "searchlogic"
32
+
33
+ Or as a plugin (for older versions of rails)
34
+
35
+ script/plugin install git://github.com/binarylogic/searchlogic.git
36
+
37
+ Now try out some of the examples below:
38
+
39
+ <b>For all examples, let's assume the following relationships: User => Orders => Line Items</b>
40
+
41
+ == Simple Searching Example
42
+
43
+ User.all(
44
+ :conditions => {
45
+ :first_name_contains => "Ben", # first_name like '%Ben%'
46
+ :email_ends_with => "binarylogic.com", # email like '%binarylogic.com'
47
+ :created_after => Time.now, # created_at > Time.now
48
+ :hour_of_created_at => 5 # HOUR(created_at) > 5 (depends on DB type)
49
+ },
50
+ :per_page => 20, # limit 20
51
+ :page => 3, # offset 40, which starts us on page 3
52
+ :order_as => "ASC",
53
+ :order_by => {:user_group => :name} # order user_groups.name ASC
54
+ )
55
+
56
+ same as above, but object based
57
+
58
+ search = User.new_search
59
+ search.conditions.first_name_contains = "Ben"
60
+ search.conditions.email_ends_with = "binarylogic.com"
61
+ search.conditions.created_after = Time.now
62
+ search.conditiona.hour_of_created_at = 5
63
+ search.per_page = 20
64
+ search.page = 3
65
+ search.order_as = "ASC"
66
+ search.order_by = {:user_group => :name}
67
+ search.all
68
+
69
+ In both examples, instead of using the "all" method you could use any search method: first, find(:all), find(:first), count, sum, average, etc, just like ActiveRecord.
70
+
71
+ == The beauty of Searchlogic, integration into rails
72
+
73
+ Using Searchlogic in rails is the best part, because rails has all kinds of nifty methods to make dealing with ActiveRecord objects quick and easy, especially with forms. So let's take advantage of them! That's the idea behind this plugin. Searchlogic is searching, ordering, and pagination all rolled into one simple plugin. Take all of that pagination and searching cruft out of your models and controllers, and let Searchlogic handle it. Check it out:
74
+
75
+ # app/controllers/users_controller.rb
76
+ def index
77
+ @search = User.new_search(params[:search])
78
+ @users, @users_count = @search.all, @search.count
79
+ end
80
+
81
+ Now your view:
82
+
83
+ # app/views/users/index.html.haml
84
+ - form_for @search do |f|
85
+ - f.fields_for @search.conditions do |users|
86
+ = users.text_field :first_name_contains
87
+ = users.date_select :created_after
88
+ - users.fields_for users.object.orders do |orders|
89
+ = orders.select :total_gt, (1..100)
90
+ = f.submit "Search"
91
+
92
+ - unless @users_count.zero?
93
+ %table
94
+ %tr
95
+ %th= order_by_link :account => :name
96
+ %th= order_by_link :first_name
97
+ %th= order_by_link :last_name
98
+ %th= order_by_link :email
99
+ - @users.each do |user|
100
+ %tr
101
+ %td= user.account? ? user.account.name : "-"
102
+ %td= user.first_name
103
+ %td= user.last_name
104
+ %td= user.email
105
+
106
+ == Per page: #{per_page_select}
107
+ == Page: #{page_select}
108
+ - else
109
+ No users were found.
110
+
111
+ Things to note in this view:
112
+
113
+ 1. Passing a search object right into form\_for and fields\_for
114
+ 2. The built in conditions for each column and how you can traverse the relationships and set conditions on them
115
+ 3. The order_by_link helper
116
+ 4. The page_select and per_page_select helpers
117
+ 5. All of your search logic is in 1 spot: your view. Nice and DRY.
118
+
119
+ <b>See my tutorial on this example: http://www.binarylogic.com/2008/9/7/tutorial-pagination-ordering-and-searching-with-searchlogic</b>
120
+
121
+ == Exhaustive Example w/ Object Based Searching (great for form_for or fields_for)
122
+
123
+ # Start a new search
124
+ @search = User.new_search(
125
+ :conditions => {
126
+ :first_name_contains => "Ben",
127
+ :age_gt => 18,
128
+ :orders => {:total_lt => 100}
129
+ },
130
+ :per_page => 20,
131
+ :page => 2,
132
+ :order_by => {:orders => :total},
133
+ :order_as => "DESC"
134
+ )
135
+
136
+ # Set local conditions
137
+ @search.conditions.email_ends_with = "binarylogic.com"
138
+
139
+ # Set conditions on relationships
140
+ @search.conditions.oders.line_items.created_after = Time.now # can traverse through all relationships
141
+
142
+ # Set options
143
+ @search.per_page = 50 # overrides the 20 set above
144
+ @search.order_by = [:first_name, {:user_group => :name}] # order by first name and then by the user group's name it belongs to
145
+ @search.order_as = "ASC"
146
+
147
+ # Set ANY of the ActiveRecord options
148
+ @search.group = "last_name"
149
+ @search.readonly = true
150
+ # ... see ActiveRecord documentation
151
+
152
+ # Return results just like ActiveRecord
153
+ @search.all
154
+ @search.first
155
+
156
+ Take the @search object and pass it right into form\_for or fields\_for (see above).
157
+
158
+ == Calculations
159
+
160
+ Using the object from above:
161
+
162
+ @search.average('id')
163
+ @search.count # ignores limit and offset
164
+ @search.maximum('id')
165
+ @search.minimum('id')
166
+ @search.sum('id')
167
+ @search.calculate(:sum, 'id')
168
+ # ...any of the above calculations, see ActiveRecord documentation on calculations
169
+
170
+ Or do it from your model:
171
+
172
+ User.count(:conditions => {:first_name_contains => "Ben"})
173
+ User.sum('id', :conditions => {:first_name_contains => "Ben"})
174
+ # ... all other calcualtions, etc.
175
+
176
+ == Different ways to search, take your pick
177
+
178
+ Any of the options used in the above example can be used in these, but for the sake of brevity I am only using a few:
179
+
180
+ User.all(:conditions => {:age_gt => 18}, :per_page => 20)
181
+
182
+ User.first(:conditions => {:age_gt => 18}, :per_page => 20)
183
+
184
+ User.find(:all, :conditions => {:age_gt => 18}, :per_page => 20)
185
+
186
+ User.find(:first, :conditions => {:age_gt => 18}, :per_page => 20)
187
+
188
+ search = User.new_search(:conditions => {:age_gt => 18}) # build_search is an alias
189
+ search.conditions.first_name_contains = "Ben"
190
+ search.per_page = 20
191
+ search.all
192
+
193
+ search = User.new_search(:conditions => {:age_gt => 18}) do |s|
194
+ s.conditions.first_name_contains = "Ben"
195
+ s.per_page = 20
196
+ end
197
+ search.all
198
+
199
+ == Match ANY or ALL of the conditions
200
+
201
+ As you saw above, the nice thing about Searchlogic is it's integration with forms. I designed the "any" option so that forms can set this as well, just like a condition.
202
+
203
+ @search = User.new_search(:conditions => {:age_gt => 18})
204
+ @search.conditions.first_name_contains = "Ben"
205
+ @search.conditions.any = true # can set this to "true" or "1" or "yes"
206
+ @search.all # will join all conditions with "or" instead of "and"
207
+ # ... all operations above are available
208
+
209
+ What if you want to mix and match?
210
+
211
+ @search = User.new_search(:conditions => {:age_gt => 18})
212
+ @search.conditions.or_first_name_contains = "Ben"
213
+ @search.conditions.or_last_name_contains = "Johnson"
214
+ @search.conditions.and_id_gt = 5 # the and_ is optional, calling just id_gt is the same thing
215
+ @search.all # will join conditions in the orders they were set with their specified join condition
216
+ # => age > 18 OR first_name like '%Ben%' OR lsat_name like '%Johnson%' AND id > 5
217
+
218
+ The order the conditions is set is relevant, as the SQL will be built in the same order.
219
+
220
+ == Grouping conditions
221
+
222
+ In more complex searching situations you might want to group conditions. Just like you use parenthesis in raw SQL. Searchlogic's "group" function is basically a way to implement parenthesis in your conditions. It's simple:
223
+
224
+ Group off an object:
225
+
226
+ search = User.new_search
227
+ search.id_gt = 2
228
+ group1 = search.conditions.group
229
+ group1.first_name_like = "Ben"
230
+ group2 = search.conditions.group
231
+ group2.last_name_like = "Johnson"
232
+ group21 = group2.group
233
+ group21.email_ends_with = "binarylogic.com"
234
+ # => id > 2 AND (first_name like '%Ben%') AND (last_name like '%Johnson%' AND (email like '%binarylogic.com'))
235
+
236
+ Group with a block:
237
+
238
+ search = User.new_search
239
+ search.id_gt = 2
240
+ search.conditions.group do |group|
241
+ group.first_name_like = "Ben"
242
+ end
243
+ search.conditions.or_group do |group|
244
+ group.last_name_like = "Johnson"
245
+ group.group do |sub_group|
246
+ sub_group.email_ends_with = "binarylogic.com"
247
+ end
248
+ end
249
+ # => id > 2 AND (first_name like '%Ben%') OR (last_name like '%Johnson%' AND (email like '%binarylogic.com'))
250
+
251
+ Group with a hash:
252
+
253
+ search = User.new_search(:conditions => [
254
+ {:id_gt => 2},
255
+ {:group => {:first_name_like => "Ben"}},
256
+ {:group => [
257
+ {:last_name_like => "Johnson"},
258
+ {:group => {:email_ends_with => "binarylogic.com"}}
259
+ ]}
260
+ ])
261
+ # => id > 2 AND (first_name like '%Ben%') AND (last_name like '%Johnson%' AND (email like '%binarylogic.com'))
262
+
263
+ I want to end this by saying Searchlogic was never meant to replace SQL, name_scopes, etc. If you need to perform complex searching there is nothing wrong with resorting to a named scope or using the traditional search methods. In fact, search logic plays nice with named_scopes anyways, so you can combine the 2 if needed:
264
+
265
+ @search = User.my_awesome_scope.another_cool_scope.new_search
266
+
267
+ You decide when a named scope makes the most sense. If you are creating a named scope that is specific to the search form you are building, you should consider adding the conditions right into the form. But if you are creating a named scope that you are using throughout your application, then it probably makes sense for it to be a named scope.
268
+
269
+ == Scoped searching
270
+
271
+ @current_user.orders.find(:all, :conditions => {:total_lte => 500})
272
+ @current_user.orders.count(:conditions => {:total_lte => 500})
273
+ @current_user.orders.sum('total', :conditions => {:total_lte => 500})
274
+ @current_user.orders.build_search(:conditions => {:total_lte => 500})
275
+
276
+ == Scope support
277
+
278
+ Not only can you use searchlogic when searching, but you can use it when using scopes.
279
+
280
+ class User < ActiveRecord::Base
281
+ named_scope :sexy, :conditions => {:first_name => "Ben", email_ends_with => "binarylogic.com"}, :per_page => 20
282
+ end
283
+
284
+ or
285
+
286
+ class User < ActiveRecord::Base
287
+ def self.find_sexy
288
+ with_scope(:find => {:conditions => {:first_name => "Ben", email_ends_with => "binarylogic.com"}, :per_page => 20}) do
289
+ all
290
+ end
291
+ end
292
+ end
293
+
294
+ == Protection against SQL injections
295
+
296
+ SQL injections are not fun, so let's make sure they don't happen. That's why searchlogic protects you by default. The new\_search methods protect mass assignments by default (instantiation and search.options = {}). This means that various checks are done to ensure it is not possible to perform any type of SQL injection during mass assignments. But this also limits how you can search, meaning you can't write raw SQL. If you want to be daring and search without protection, all that you have to do is add ! to the end of the method: new\_search!.
297
+
298
+ === Protected from SQL injections
299
+
300
+ search = Account.new_search(params[:search])
301
+
302
+ === *NOT* protected from SQL injections
303
+
304
+ accounts = Account.find(params[:search])
305
+ accounts = Account.all(params[:search])
306
+ account = Account.first(params[:search])
307
+ search = Account.new_search!(params[:search])
308
+
309
+ I'm sure you already knew this, but it's tempting to do this when you can pass the params hash right into these methods.
310
+
311
+ Lesson learned: use new\_search when passing in params as *ANY* of the options.
312
+
313
+ == Available Conditions
314
+
315
+ The conditions are pretty self explanitory, but if you need more information checkout the docs or the source. The code is very simple and self explanatory.
316
+
317
+ === Column conditions
318
+
319
+ Each column can be used with any of the following conditions
320
+
321
+ Name Aliases Description
322
+ :begins_with :starts_with, :sw, :bw, :start col LIKE 'value%'
323
+ :ends_with :ew, :ends, :end col LIKE '%value'
324
+ :equals :is, "" Lets ActiveRecord handle this
325
+ :greater_than :gt, :after col > value
326
+ :greater_than_or_equal_to :at_least, :least, :gte col >= value
327
+ :ilike :icontains, :ihas PostgreSQL specific, makes LIKE case insensitive
328
+ :less_than :lt, :before col < value
329
+ :less_than_or_equal_to :at_most, :most, :lte col <= value
330
+ :keywords :kwords, :kw Splits into each word and omits meaningless words, a true keyword search
331
+ :like :contains, :has col LIKE '%value%'
332
+
333
+ :not_begin_with :not_bw, :not_sw, :not_start_with, :not_start, :beginning_is_not, :beginning_not
334
+ :not_end_with :not_ew, :not_end, :end_is_not, :end_not
335
+ :not_equal :does_not_equal, :is_not, :not
336
+ :not_have_keywords :not_have_keywords, :not_keywords, :not_have_kw, :not_kw, :not_have_kwwords, :not_kwwords
337
+ :not_ilike :not_icontain, :not_ihave
338
+ :not_like :not_contain, :not_have
339
+
340
+ === Class level conditions
341
+
342
+ Each model comes preloaded with class level conditions as well. The difference is that these are not applied to each column, but instead to the model as a whole. Example: search.conditions.child_of = 2
343
+
344
+ Name Description
345
+ :child_of Returns all children of value
346
+ :descendant_of Returns all descendants (children, grandchildren, grandgrandchildren, etc)
347
+ :inclusive_descendant_of Same as above but also includes the root
348
+ :sibling_of Returns all records that have the same parent
349
+
350
+ The above conditions are for a nested set data structure, *not* a tree. Since we need to traverse through the tree a nested set is required for performance reasons. Traversing through a large tree is very bad for performance, a nested set solves this problem.
351
+
352
+ == Modifiers
353
+
354
+ === What are modifiers?
355
+
356
+ ActiveRecord does a great job when it comes to keeping your code database agnostic. But I feel like it neglected searching when it came to that goal. What if you want to find all records that were created after 7am? Depending on your database you would have to do something like the following:
357
+
358
+ MySQL: HOUR(created_at)
359
+ PostgreSQL: date_part('hour', created_at)
360
+ SQLite: strftime('%H', created_at)
361
+
362
+ All of a sudden your app is not database agnostic. Searchlogic to the rescue! Searchlogic creates what I like to call "modifiers" to handle this nonsense for you. A modifier modifies a column. For example, the hour modifier modifies a datetime column to return the hour.
363
+
364
+ The last thing to keep in mind is that <b>not all modifiers are available for every database</b>. MySQL and PostgreSQL support all of these, but SQLite does not. SQLite is nice, in the sense that its really is "lite". The only modifiers it supports are the datetime modifiers. If you want support for the other modifiers you have to write the SQLite function yourself and register the modifier in searchlogic.
365
+
366
+ Here are all of the available modifiers:
367
+
368
+ === Available modifiers
369
+
370
+ Name Aliases Description
371
+ :microsecond :microseconds, :microsecs, :microsec Extracts the microseconds
372
+ :millisecond :milliseconds, :millisecs, :millisec Extracts the milliseconds
373
+ :second :sec Extracts the seconds
374
+ :minute :min Extracts the minute
375
+ :hour Extracts the hour
376
+ :day_of_week :dow Extracts the day of week (1-7)
377
+ :day_of_month :dom Extracts the day of month (1-31)
378
+ :day_of_year :doy Extracts the day of year (1-366)
379
+ :week Extracts the week (1-53), 53rd week can be a "run-over" week to the next year
380
+ :month :mon Extracts the month (1-12)
381
+ :year Extracts the year
382
+
383
+ :md5 Converts to a MD5
384
+ :char_length :length The length of the string (integer)
385
+ :lower :downcase, :lcase Converts the string to all lower case characters
386
+ :ltrin :lstrip Strips off spaces from the beginning of the string
387
+ :trim :strip Strips off spaces from the beginning and end of the string
388
+ :rtrim :rstrip Strips off spaces from the end of the string
389
+ :upper :upcase, :ucase Converts the string to all upper case character
390
+
391
+ :absolute :abs The absolute value (-1 => 1)
392
+ :acos The arc cosine
393
+ :asin The arc sine
394
+ :atan The arc tangent
395
+ :avg :average The average
396
+ :ceil :round_up Rounds up to the nearest int
397
+ :cos :cosine The cosine
398
+ :cot :cotangent The cotangent
399
+ :count The count
400
+ :degrees Converts radians to degrees
401
+ :exp :exponential Returns the value of e (the base of natural logarithms) raised to the power of X
402
+ :floor :round_down Rounds down to the nearest int
403
+ :hex Converts the number to a hex
404
+ :log :ln The natural logarithm
405
+ :log10 Returns the base-10 logarithm
406
+ :log2 Returns the base-2 logarithm
407
+ :octal :oct Return an octal representation of a decimal number
408
+ :radians Converts to radians
409
+ :round Rounds the number
410
+ :sign The sign of the number
411
+ :sin The sine of the number
412
+ :square_root :sqrt, :sq_rt The square root of the number
413
+ :sum The sum of the number
414
+ :tan :tangent The tangent of the number
415
+
416
+ === How to use modifiers
417
+
418
+ Here's what the above table means. Let's take the created_at column. The created_at column is a datetime column, laet's apply modifiers that make sense for a datetime column.
419
+
420
+ search.conditions.second_of_created_at = 2
421
+ search.conditions.sec_of_created_at_greater_than = 3
422
+ search.conditions.year_of_created_at_most = 5
423
+ search.conditions.year_of_created_at_lt = 2000
424
+ # ... any of the modifiers that apply to datetime columns
425
+
426
+ Here's the cool part. Chaining modifiers:
427
+
428
+ search.conditions.ceil_of_cos_of_sec_of_created_at_greater_than = 3
429
+
430
+ As long as the modifier chain makes sense the possibilities are endless.
431
+
432
+ === Modifiers are not indexed
433
+
434
+ Depending on your database you can create complex indexes. But chances are you probably didn't or don't plan to. So keep in mind that once you use a modifier it will not be using an index, meaning the query will be slower. One of the ways to get the best of both worlds is to cache virtual attributes in the database. Checkout my tutorial:
435
+
436
+ http://www.binarylogic.com/2008/10/5/tutorial-caching-virtual-attributes-in-the-database
437
+
438
+ == Roll your own conditions & modifiers
439
+
440
+ For more information on this please see Searchlogic::Conditions::Base
441
+
442
+ == Under the hood
443
+
444
+ I'm a big fan of understanding what I'm using, so here's a quick explanation: The design behind this plugin is pretty simple and I had 1 main rule when developing this:
445
+
446
+ ActiveRecord should never know about Searchlogic
447
+
448
+ What that rule means is that any options you pass when searching get "sanitized" down into options ActiveRecord can understand. Searchlogic serves as a transparent filter between you and ActiveRecord. It doesn't dig into the ActiveRecord internals, it only uses what is publicly available. It jumps in and helps out <em>only</em> when needed, otherwise it sits back and stays completely out of the way.
449
+
450
+ Lastly, Searchlogic is lazy. It only creates objects, methods, and classes when needed. Once it creates them it caches them. For example, all of the nifty conditions are created via meta programming. The first time you execute something like User.new_search all of that method creation gets cached into Searchlogic::Cache::UserSearch. The next time you execute User.new_search it will be over 50 times faster because it uses the cached class.
451
+
452
+ Between that and the extensive tests, this is a solid and fast plugin.
453
+
454
+ == Credits
455
+
456
+ Author: {Ben Johnson}[http://github.com/binarylogic] of {Binary Logic}[http://www.binarylogic.com]
457
+
458
+ Credit to {Zack Ham}[http://github.com/zackham] for helping with feature suggestions.
459
+
460
+
461
+ Copyright (c) 2008 {Ben Johnson}[http://github.com/binarylogic] of {Binary Logic}[http://www.binarylogic.com], released under the MIT license