searchlogic 1.6.6 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (178) hide show
  1. data/.gitignore +6 -0
  2. data/CHANGELOG.rdoc +17 -0
  3. data/{MIT-LICENSE → LICENSE} +2 -2
  4. data/README.rdoc +128 -379
  5. data/Rakefile +56 -20
  6. data/VERSION.yml +4 -0
  7. data/init.rb +1 -1
  8. data/lib/searchlogic.rb +18 -98
  9. data/lib/searchlogic/core_ext/object.rb +33 -13
  10. data/lib/searchlogic/core_ext/proc.rb +11 -0
  11. data/lib/searchlogic/named_scopes/alias_scope.rb +63 -0
  12. data/lib/searchlogic/named_scopes/associations.rb +126 -0
  13. data/lib/searchlogic/named_scopes/conditions.rb +215 -0
  14. data/lib/searchlogic/named_scopes/ordering.rb +53 -0
  15. data/lib/searchlogic/rails_helpers.rb +69 -0
  16. data/lib/searchlogic/search.rb +146 -0
  17. data/rails/init.rb +1 -0
  18. data/searchlogic.gemspec +69 -0
  19. data/spec/core_ext/object_spec.rb +7 -0
  20. data/spec/core_ext/proc_spec.rb +9 -0
  21. data/spec/named_scopes/alias_scope_spec.rb +15 -0
  22. data/spec/named_scopes/associations_spec.rb +120 -0
  23. data/spec/named_scopes/conditions_spec.rb +253 -0
  24. data/spec/named_scopes/ordering_spec.rb +23 -0
  25. data/spec/search_spec.rb +283 -0
  26. data/spec/spec_helper.rb +78 -0
  27. metadata +40 -231
  28. data/Manifest.txt +0 -158
  29. data/TODO.rdoc +0 -4
  30. data/lib/searchlogic/active_record/associations.rb +0 -52
  31. data/lib/searchlogic/active_record/base.rb +0 -224
  32. data/lib/searchlogic/active_record/connection_adapters/mysql_adapter.rb +0 -176
  33. data/lib/searchlogic/active_record/connection_adapters/postgresql_adapter.rb +0 -172
  34. data/lib/searchlogic/active_record/connection_adapters/sqlite_adapter.rb +0 -80
  35. data/lib/searchlogic/condition/base.rb +0 -165
  36. data/lib/searchlogic/condition/begins_with.rb +0 -17
  37. data/lib/searchlogic/condition/blank.rb +0 -24
  38. data/lib/searchlogic/condition/child_of.rb +0 -11
  39. data/lib/searchlogic/condition/descendant_of.rb +0 -11
  40. data/lib/searchlogic/condition/ends_with.rb +0 -17
  41. data/lib/searchlogic/condition/equals.rb +0 -33
  42. data/lib/searchlogic/condition/greater_than.rb +0 -15
  43. data/lib/searchlogic/condition/greater_than_or_equal_to.rb +0 -15
  44. data/lib/searchlogic/condition/inclusive_descendant_of.rb +0 -10
  45. data/lib/searchlogic/condition/keywords.rb +0 -52
  46. data/lib/searchlogic/condition/less_than.rb +0 -15
  47. data/lib/searchlogic/condition/less_than_or_equal_to.rb +0 -15
  48. data/lib/searchlogic/condition/like.rb +0 -15
  49. data/lib/searchlogic/condition/nested_set.rb +0 -17
  50. data/lib/searchlogic/condition/nil.rb +0 -21
  51. data/lib/searchlogic/condition/not_begin_with.rb +0 -20
  52. data/lib/searchlogic/condition/not_blank.rb +0 -19
  53. data/lib/searchlogic/condition/not_end_with.rb +0 -20
  54. data/lib/searchlogic/condition/not_equal.rb +0 -27
  55. data/lib/searchlogic/condition/not_have_keywords.rb +0 -20
  56. data/lib/searchlogic/condition/not_like.rb +0 -20
  57. data/lib/searchlogic/condition/not_nil.rb +0 -19
  58. data/lib/searchlogic/condition/sibling_of.rb +0 -14
  59. data/lib/searchlogic/conditions/any_or_all.rb +0 -42
  60. data/lib/searchlogic/conditions/base.rb +0 -244
  61. data/lib/searchlogic/conditions/groups.rb +0 -74
  62. data/lib/searchlogic/conditions/magic_methods.rb +0 -286
  63. data/lib/searchlogic/conditions/multiparameter_attributes.rb +0 -105
  64. data/lib/searchlogic/conditions/protection.rb +0 -36
  65. data/lib/searchlogic/config.rb +0 -31
  66. data/lib/searchlogic/config/helpers.rb +0 -338
  67. data/lib/searchlogic/config/search.rb +0 -53
  68. data/lib/searchlogic/core_ext/hash.rb +0 -75
  69. data/lib/searchlogic/helpers/control_types/link.rb +0 -310
  70. data/lib/searchlogic/helpers/control_types/links.rb +0 -242
  71. data/lib/searchlogic/helpers/control_types/remote_link.rb +0 -87
  72. data/lib/searchlogic/helpers/control_types/remote_links.rb +0 -72
  73. data/lib/searchlogic/helpers/control_types/remote_select.rb +0 -36
  74. data/lib/searchlogic/helpers/control_types/select.rb +0 -82
  75. data/lib/searchlogic/helpers/form.rb +0 -208
  76. data/lib/searchlogic/helpers/utilities.rb +0 -197
  77. data/lib/searchlogic/modifiers/absolute.rb +0 -15
  78. data/lib/searchlogic/modifiers/acos.rb +0 -11
  79. data/lib/searchlogic/modifiers/asin.rb +0 -11
  80. data/lib/searchlogic/modifiers/atan.rb +0 -11
  81. data/lib/searchlogic/modifiers/avg.rb +0 -15
  82. data/lib/searchlogic/modifiers/base.rb +0 -27
  83. data/lib/searchlogic/modifiers/ceil.rb +0 -15
  84. data/lib/searchlogic/modifiers/char_length.rb +0 -15
  85. data/lib/searchlogic/modifiers/cos.rb +0 -15
  86. data/lib/searchlogic/modifiers/cot.rb +0 -15
  87. data/lib/searchlogic/modifiers/count.rb +0 -11
  88. data/lib/searchlogic/modifiers/day_of_month.rb +0 -15
  89. data/lib/searchlogic/modifiers/day_of_week.rb +0 -15
  90. data/lib/searchlogic/modifiers/day_of_year.rb +0 -15
  91. data/lib/searchlogic/modifiers/degrees.rb +0 -11
  92. data/lib/searchlogic/modifiers/exp.rb +0 -15
  93. data/lib/searchlogic/modifiers/floor.rb +0 -15
  94. data/lib/searchlogic/modifiers/hex.rb +0 -11
  95. data/lib/searchlogic/modifiers/hour.rb +0 -11
  96. data/lib/searchlogic/modifiers/log.rb +0 -15
  97. data/lib/searchlogic/modifiers/log10.rb +0 -11
  98. data/lib/searchlogic/modifiers/log2.rb +0 -11
  99. data/lib/searchlogic/modifiers/lower.rb +0 -15
  100. data/lib/searchlogic/modifiers/ltrim.rb +0 -15
  101. data/lib/searchlogic/modifiers/md5.rb +0 -11
  102. data/lib/searchlogic/modifiers/microseconds.rb +0 -11
  103. data/lib/searchlogic/modifiers/milliseconds.rb +0 -11
  104. data/lib/searchlogic/modifiers/minute.rb +0 -15
  105. data/lib/searchlogic/modifiers/month.rb +0 -15
  106. data/lib/searchlogic/modifiers/octal.rb +0 -15
  107. data/lib/searchlogic/modifiers/radians.rb +0 -11
  108. data/lib/searchlogic/modifiers/round.rb +0 -11
  109. data/lib/searchlogic/modifiers/rtrim.rb +0 -15
  110. data/lib/searchlogic/modifiers/second.rb +0 -15
  111. data/lib/searchlogic/modifiers/sign.rb +0 -11
  112. data/lib/searchlogic/modifiers/sin.rb +0 -11
  113. data/lib/searchlogic/modifiers/square_root.rb +0 -15
  114. data/lib/searchlogic/modifiers/sum.rb +0 -11
  115. data/lib/searchlogic/modifiers/tan.rb +0 -15
  116. data/lib/searchlogic/modifiers/trim.rb +0 -15
  117. data/lib/searchlogic/modifiers/upper.rb +0 -15
  118. data/lib/searchlogic/modifiers/week.rb +0 -11
  119. data/lib/searchlogic/modifiers/year.rb +0 -11
  120. data/lib/searchlogic/search/base.rb +0 -148
  121. data/lib/searchlogic/search/conditions.rb +0 -53
  122. data/lib/searchlogic/search/ordering.rb +0 -244
  123. data/lib/searchlogic/search/pagination.rb +0 -121
  124. data/lib/searchlogic/search/protection.rb +0 -89
  125. data/lib/searchlogic/search/searching.rb +0 -32
  126. data/lib/searchlogic/shared/utilities.rb +0 -57
  127. data/lib/searchlogic/shared/virtual_classes.rb +0 -39
  128. data/lib/searchlogic/version.rb +0 -79
  129. data/test/active_record_tests/associations_test.rb +0 -94
  130. data/test/active_record_tests/base_test.rb +0 -115
  131. data/test/condition_tests/base_test.rb +0 -62
  132. data/test/condition_tests/begins_with_test.rb +0 -11
  133. data/test/condition_tests/blank_test.rb +0 -31
  134. data/test/condition_tests/child_of_test.rb +0 -17
  135. data/test/condition_tests/descendant_of_test.rb +0 -12
  136. data/test/condition_tests/ends_with_test.rb +0 -11
  137. data/test/condition_tests/equals_test.rb +0 -28
  138. data/test/condition_tests/greater_than_or_equal_to_test.rb +0 -11
  139. data/test/condition_tests/greater_than_test.rb +0 -11
  140. data/test/condition_tests/inclusive_descendant_of_test.rb +0 -12
  141. data/test/condition_tests/keywords_test.rb +0 -23
  142. data/test/condition_tests/less_than_or_equal_to_test.rb +0 -11
  143. data/test/condition_tests/less_than_test.rb +0 -11
  144. data/test/condition_tests/like_test.rb +0 -11
  145. data/test/condition_tests/nil_test.rb +0 -31
  146. data/test/condition_tests/not_begin_with_test.rb +0 -8
  147. data/test/condition_tests/not_blank_test.rb +0 -8
  148. data/test/condition_tests/not_end_with_test.rb +0 -8
  149. data/test/condition_tests/not_equal_test.rb +0 -19
  150. data/test/condition_tests/not_have_keywords_test.rb +0 -8
  151. data/test/condition_tests/not_like_test.rb +0 -8
  152. data/test/condition_tests/not_nil_test.rb +0 -13
  153. data/test/condition_tests/sibling_of_test.rb +0 -15
  154. data/test/conditions_tests/any_or_all_test.rb +0 -23
  155. data/test/conditions_tests/base_test.rb +0 -185
  156. data/test/conditions_tests/groups_test.rb +0 -68
  157. data/test/conditions_tests/magic_methods_test.rb +0 -36
  158. data/test/conditions_tests/multiparameter_attributes_test.rb +0 -15
  159. data/test/conditions_tests/protection_test.rb +0 -18
  160. data/test/config_test.rb +0 -23
  161. data/test/fixtures/accounts.yml +0 -12
  162. data/test/fixtures/animals.yml +0 -7
  163. data/test/fixtures/orders.yml +0 -12
  164. data/test/fixtures/user_groups.yml +0 -5
  165. data/test/fixtures/users.yml +0 -45
  166. data/test/libs/awesome_nested_set.rb +0 -545
  167. data/test/libs/awesome_nested_set/.autotest +0 -13
  168. data/test/libs/awesome_nested_set/compatability.rb +0 -29
  169. data/test/libs/awesome_nested_set/helper.rb +0 -40
  170. data/test/libs/awesome_nested_set/named_scope.rb +0 -140
  171. data/test/libs/rexml_fix.rb +0 -14
  172. data/test/modifier_tests/day_of_month_test.rb +0 -16
  173. data/test/search_tests/base_test.rb +0 -241
  174. data/test/search_tests/conditions_test.rb +0 -21
  175. data/test/search_tests/ordering_test.rb +0 -167
  176. data/test/search_tests/pagination_test.rb +0 -74
  177. data/test/search_tests/protection_test.rb +0 -26
  178. data/test/test_helper.rb +0 -122
@@ -0,0 +1,6 @@
1
+ .DS_Store
2
+ *.log
3
+ pkg/*
4
+ coverage/*
5
+ doc/*
6
+ benchmarks/*
@@ -1,3 +1,20 @@
1
+ == 2.0.2
2
+
3
+ * Added a delete method to the Search class to allow the deleting of conditions off of the object.
4
+ * Add alias_scope feature, lets your create "named scopes" that represent a procedure of named scopes, while at the same time telling Searchlogic it is safe to use in the Search class.
5
+ * Use url_for as the default with form_for since rails does some magic to determine the url to use.
6
+
7
+ == 2.0.1 released 2009-06-20
8
+
9
+ * Allow the chaining of conditions off of a search object. Ex: search.username_like("bjohnson").age_gt(20).all
10
+ * Split out left outer join creation into its own method, allowing you to use it in your own named scopes.
11
+ * Make sure the search objects clone properly.
12
+ * named_scope_options should return the proper options for existing named scopes with an arity greater than 0
13
+
14
+ == 2.0.0
15
+
16
+ * Everything, completely rewritten.
17
+
1
18
  == 1.6.6
2
19
 
3
20
  * Fixed time zone issues when searching.
@@ -1,4 +1,4 @@
1
- Copyright (c) 2008 Ben Johnson of Binary Logic (binarylogic.com)
1
+ Copyright (c) 2009 Ben Johnson of Binary Logic
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -17,4 +17,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
17
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
18
  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
19
  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -1,461 +1,210 @@
1
1
  = Searchlogic
2
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:
3
+ <b>Searchlogic has been <em>completely</em> rewritten for v2. It is much simpler and has taken an entirely new approach. To give you an idea, v1 had ~2300 lines of code, v2 has ~420 lines of code.</b>
4
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.
5
+ Searchlogic provides common named scopes and object based searching for ActiveRecord.
15
6
 
16
7
  == Helpful links
17
8
 
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
9
+ * <b>Documentation:</b> http://rdoc.info/projects/binarylogic/searchlogic
10
+ * <b>Repository:</b> http://github.com/binarylogic/searchlogic/tree/master
22
11
  * <b>Bugs / feature suggestions:</b> http://binarylogic.lighthouseapp.com/projects/16601-searchlogic
12
+ * <b>Google group:</b> http://groups.google.com/group/searchlogic
23
13
 
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"
14
+ <b>Before contacting me directly, please read:</b>
32
15
 
33
- Or as a plugin (for older versions of rails)
16
+ If you find a bug or a problem please post it on lighthouse. If you need help with something, please use google groups. I check both regularly and get emails when anything happens, so that is the best place to get help. This also benefits other people in the future with the same questions / problems. Thank you.
34
17
 
35
- script/plugin install git://github.com/binarylogic/searchlogic.git
18
+ == Install & use
36
19
 
37
- Now try out some of the examples below:
20
+ Install the gem from rubyforge:
38
21
 
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
22
+ sudo gem install searchlogic
68
23
 
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.
24
+ Or from github:
70
25
 
71
- == The beauty of Searchlogic, integration into rails
26
+ sudo gem install binarylogic-searchlogic
72
27
 
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:
28
+ Now just include it in your project and you are ready to go. See below for usage examples.
74
29
 
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
30
+ == Search using conditions on columns
80
31
 
81
- Now your view:
32
+ Instead of explaining what Searchlogic can do, let me show you. Let's start at the top:
82
33
 
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"
34
+ # We have the following model
35
+ User(id: integer, created_at: datetime, username: string, age: integer)
91
36
 
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
37
+ # Searchlogic gives you a bunch of named scopes for free:
38
+ User.username_equals("bjohnson")
39
+ User.username_does_not_equal("bjohnson")
40
+ User.username_begins_with("bjohnson")
41
+ User.username_like("bjohnson")
42
+ User.username_ends_with("bjohnson")
43
+ User.age_greater_than(20)
44
+ User.age_greater_than_or_equal_to(20)
45
+ User.age_less_than(20)
46
+ User.age_less_than_or_equal_to(20)
47
+ User.username_null
48
+ User.username_blank
105
49
 
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
50
+ # You can also order by columns
51
+ User.ascend_by_username
52
+ User.descend_by_username
53
+ User.order("ascend_by_username")
159
54
 
160
- Using the object from above:
55
+ Any named scope Searchlogic creates is dynamic and created via method_missing. Meaning it will only create what you need. Also, keep in mind, these are just named scopes, you can chain them, call methods off of them, etc:
161
56
 
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
57
+ scope = User.username_like("bjohnson").age_greater_than(20).ascend_by_username
58
+ scope.all
59
+ scope.first
60
+ scope.count
61
+ # etc...
169
62
 
170
- Or do it from your model:
63
+ That's all pretty standard, but here's where Searchlogic starts to get interesting...
171
64
 
172
- User.count(:conditions => {:first_name_contains => "Ben"})
173
- User.sum('id', :conditions => {:first_name_contains => "Ben"})
174
- # ... all other calcualtions, etc.
65
+ == Search using conditions on associated columns
175
66
 
176
- == Different ways to search, take your pick
67
+ You also get named scopes for any of your associations:
177
68
 
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
69
+ # We have the following relationships
70
+ User.has_many :orders
71
+ Order.has_many :line_items
72
+ LineItem
192
73
 
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
74
+ # Set conditions on association columns
75
+ User.orders_total_greater_than(20)
76
+ User.orders_line_items_price_greater_than(20)
198
77
 
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.
78
+ # Order by association columns
79
+ User.ascend_by_order_total
80
+ User.descend_by_orders_line_items_price
202
81
 
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
82
+ Again these are just named scopes. You can chain them together, call methods off of them, etc. What's great about these named scopes is that they do NOT use the :include option, making them <em>much</em> faster. Instead they create a INNER JOIN and pass it to the :joins option, which is great for performance. To prove my point here is a quick benchmark from an application I am working on:
208
83
 
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
84
+ Benchmark.bm do |x|
85
+ x.report { 10.times { Event.tickets_id_gt(10).all(:include => :tickets) } }
86
+ x.report { 10.times { Event.tickets_id_gt(10).all } }
87
+ end
88
+ user system total real
89
+ 10.120000 0.170000 10.290000 ( 12.625521)
90
+ 2.630000 0.050000 2.680000 ( 3.313754)
217
91
 
218
- The order the conditions is set is relevant, as the SQL will be built in the same order.
92
+ If you want to use the :include option, just specify it:
219
93
 
220
- == Grouping conditions
94
+ User.orders_line_items_price_greater_than(20).all(:include => {:orders => :line_items})
221
95
 
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:
96
+ Obviously, only do this if you want to actually use the included objects.
223
97
 
224
- Group off an object:
98
+ == Make searching and ordering data in your application trivial
225
99
 
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:
100
+ The above is great, but what about tying all of this in with a search form in your application? What would be really nice is if we could use an object that represented a single search. Like this...
237
101
 
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
102
+ search = User.search(:username_like => "bjohnson", :age_less_than => 20)
103
+ search.all
266
104
 
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.
105
+ The above is equivalent to:
268
106
 
269
- == Scoped searching
107
+ User.username_like("bjohnson").age_less_than(20).all
270
108
 
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})
109
+ You can set, read, and chain conditions off of your search too:
275
110
 
276
- == Scope support
111
+ search.username_like => "bjohnson"
112
+ search.age_gt = 2 => 2
113
+ search.id_gt(10).email_begins_with("bjohnson") => <#Searchlogic::Search...>
114
+ search.all => An array of users
115
+ search.count => integer
116
+ # .. etc
277
117
 
278
- Not only can you use searchlogic when searching, but you can use it when using scopes.
118
+ So let's start with the controller...
279
119
 
280
- class User < ActiveRecord::Base
281
- named_scope :sexy, :conditions => {:first_name => "Ben", email_ends_with => "binarylogic.com"}, :per_page => 20
282
- end
120
+ === Your controller
283
121
 
284
- or
122
+ The search class just chains named scopes together for you. What's so great about that? It keeps your controllers extremely simple:
285
123
 
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
124
+ class UsersController < ApplicationController
125
+ def index
126
+ @search = User.search(params[:search])
127
+ @users = @search.all
291
128
  end
292
129
  end
293
130
 
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!.
131
+ It doesn't get any simpler than that.
297
132
 
298
- === Protected from SQL injections
133
+ === Your form
299
134
 
300
- search = Account.new_search(params[:search])
135
+ Adding a search condition is as simple as adding a condition to your form. Remember all of those named scopes above? Just create fields with the same names:
301
136
 
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
137
+ - form_for @search do |f|
138
+ = f.text_field :username_like
139
+ = f.select :age_greater_than, (0..100)
140
+ = f.text_field :orders_total_greater_than
141
+ = f.submit
314
142
 
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.
143
+ When a Searchlogic::Search object is passed to form_for it will add a hidden field for the "order" condition, to preserve the order of the data.
316
144
 
317
- === Column conditions
145
+ === Additional helpers
318
146
 
319
- Each column can be used with any of the following conditions
147
+ There really isn't a big need for helpers in searchlogic, other than helping you order data. If you want to order your search with a link, just specify the name of the column. Ex:
320
148
 
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
149
+ = order @search, :by => :age
150
+ = order @search, :by => :created_at, :as => "Created date"
339
151
 
340
- === Class level conditions
152
+ The first one will create a link that alternates between calling "ascend_by_age" and "descend_by_age". If you wanted to order your data by more than just a column, create your own named scopes: "ascend_by_*" and "descend_by_*". The "order" helper is a very straight forward helper, checkout the docs for some of the options.
341
153
 
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
154
+ <b>This helper is just a convenience method. It's extremely simple and there is nothing wrong with creating your own. If it doesn't do what you want, copy the code, modify it, and create your own. You could even fork the project, modify it there, and use your own gem.</b>
343
155
 
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
156
+ == Use your existing named scopes
349
157
 
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.
158
+ This is one of the big differences between Searchlogic v1 and v2. What about your existing named scopes? Let's say you have this:
351
159
 
352
- == Modifiers
160
+ User.named_scope :four_year_olds, :conditions => {:age => 4}
353
161
 
354
- === What are modifiers?
162
+ Again, these are all just named scopes, use it in the same way:
355
163
 
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:
164
+ User.search(:four_year_olds => true, :username_like => "bjohnson")
357
165
 
358
- MySQL: HOUR(created_at)
359
- PostgreSQL: date_part('hour', created_at)
360
- SQLite: strftime('%H', created_at)
166
+ Notice we pass true as the value. If a named scope does not accept any parameters (arity == 0) you can simply pass it true or false. If you pass false, the named scope will be ignored. If your named scope accepts a parameter, the value will be passed right to the named scope regardless of the value.
361
167
 
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.
168
+ Now just throw it in your form:
363
169
 
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.
170
+ - form_for @search do |f|
171
+ = f.text_field :username_like
172
+ = f.check_box :four_year_olds
173
+ = f.submit
365
174
 
366
- Here are all of the available modifiers:
175
+ What's great about this is that you can do just about anything you want. If Searchlogic doesn't provide a named scope for that crazy edge case that you need, just create your own named scope. The sky is the limit.
367
176
 
368
- === Available modifiers
177
+ == Use any or all
369
178
 
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:
179
+ Every condition you've seen in this readme also has 2 related conditions that you can use. Example:
427
180
 
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.
181
+ User.username_like_any("bjohnson", "thunt") # will return any users that have either of the strings in their username
182
+ User.username_like_all("bjohnson", "thunt") # will return any users that have all of the strings in their username
183
+ User.username_like_any(["bjohnson", "thunt"]) # also accepts an array
431
184
 
432
- === Modifiers are not indexed
185
+ This is great for checkbox filters, etc. Where you can pass an array right from your form to this condition.
433
186
 
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:
187
+ == Pagination (leverage will_paginate)
435
188
 
436
- http://www.binarylogic.com/2008/10/5/tutorial-caching-virtual-attributes-in-the-database
189
+ Instead of recreating the wheel with pagination, Searchlogic works great with will_paginate. All that Searchlogic is doing is creating named scopes, and will_paginate works great with named scopes:
437
190
 
438
- == Roll your own conditions & modifiers
191
+ User.username_like("bjohnson").age_less_than(20).paginate(:page => params[:page])
192
+ User.search(:username_like => "bjohnson", :age_less_than => 20).paginate(:page => params[:page])
439
193
 
440
- For more information on this please see Searchlogic::Conditions::Base
194
+ If you don't like will_paginate, use another solution, or roll your own. Pagination really has nothing to do with searching, and the main goal for Searchlogic v2 was to keep it lean and simple. No reason to recreate the wheel and bloat the library.
441
195
 
442
196
  == Under the hood
443
197
 
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.
198
+ Before I use a library in my application I like to glance at the source and try to at least understand the basics of how it works. If you are like me, a nice little explanation from the author is always helpful:
451
199
 
452
- Between that and the extensive tests, this is a solid and fast plugin.
200
+ Searchlogic utilizes method_missing to create all of these named scopes. When it hits method_missing it creates a named scope to ensure it will never hit method missing for that named scope again. Sort of a caching mechanism. It works in the same fashion as ActiveRecord's "find_by_*" methods. This way only the named scopes you need are created and nothing more.
453
201
 
454
- == Credits
202
+ That's about it, the named scope options are pretty bare bones and created just like you would manually.
455
203
 
456
- Author: {Ben Johnson}[http://github.com/binarylogic] of {Binary Logic}[http://www.binarylogic.com]
204
+ == Credit
457
205
 
458
- Credit to {Zack Ham}[http://github.com/zackham] for helping with feature suggestions.
206
+ Thanks a lot to {Tyler Hunt}[http://github.com/tylerhunt] for helping plan, design, and start the project. He was a big help.
459
207
 
208
+ == Copyright
460
209
 
461
- Copyright (c) 2008 {Ben Johnson}[http://github.com/binarylogic] of {Binary Logic}[http://www.binarylogic.com], released under the MIT license
210
+ Copyright (c) 2009 {Ben Johnson of Binary Logic}[http://www.binarylogic.com], released under the MIT license