Skip to content
Newer
Older
100644 285 lines (179 sloc) 12.9 KB
a9cbc43 @binarylogic Add v2 files
authored
1 = Searchlogic
2
26bc920 @binarylogic Readme changes
authored
3 Searchlogic makes using ActiveRecord named scopes easier and less repetitive. It helps keep your code DRY, clean, and simple.
a9cbc43 @binarylogic Add v2 files
authored
4
8752f2b @binarylogic Update readme and start using github to host gems
authored
5 == Helpful links
6
7 * <b>Documentation:</b> http://rdoc.info/projects/binarylogic/searchlogic
8 * <b>Repository:</b> http://github.com/binarylogic/searchlogic/tree/master
ad3b20d @binarylogic Fix url typo
authored
9 * <b>Issues:</b> http://github.com/binarylogic/searchlogic/issues
8752f2b @binarylogic Update readme and start using github to host gems
authored
10 * <b>Google group:</b> http://groups.google.com/group/searchlogic
ce3fa7e @binarylogic Add railscast link
authored
11 * <b>Railscast:</b> http://railscasts.com/episodes/176-searchlogic
8752f2b @binarylogic Update readme and start using github to host gems
authored
12
13 <b>Before contacting me directly, please read:</b>
51d707c @binarylogic Update README
authored
14
c8547d5 @binarylogic Update readme, switched from lighthouse to github
authored
15 If you find a bug or a problem please post it in the issues section. 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.
51d707c @binarylogic Update README
authored
16
8752f2b @binarylogic Update readme and start using github to host gems
authored
17 == Install & use
51d707c @binarylogic Update README
authored
18
a77b72d @binarylogic Added inner_join convenience method.
authored
19 Install the gem from rubyforge:
20
21 sudo gem install searchlogic
22
23 Or from github:
51d707c @binarylogic Update README
authored
24
a77b72d @binarylogic Added inner_join convenience method.
authored
25 sudo gem install binarylogic-searchlogic
8752f2b @binarylogic Update readme and start using github to host gems
authored
26
a77b72d @binarylogic Added inner_join convenience method.
authored
27 Now just include it in your project and you are ready to go.
8752f2b @binarylogic Update readme and start using github to host gems
authored
28
a77b72d @binarylogic Added inner_join convenience method.
authored
29 You can also install this as a plugin:
51d707c @binarylogic Update README
authored
30
a77b72d @binarylogic Added inner_join convenience method.
authored
31 script/plugin install git://github.com/binarylogic/searchlogic.git
32
33 See below for usage examples.
51d707c @binarylogic Update README
authored
34
7db0934 @binarylogic Some readme cleanup
authored
35 == Search using conditions on columns
36
a9cbc43 @binarylogic Add v2 files
authored
37 Instead of explaining what Searchlogic can do, let me show you. Let's start at the top:
38
39 # We have the following model
40 User(id: integer, created_at: datetime, username: string, age: integer)
41
42 # Searchlogic gives you a bunch of named scopes for free:
43 User.username_equals("bjohnson")
24bc371 @binarylogic Added feature for combining conditions with OR and as well as some se…
authored
44 User.username_equals(["bjohnson", "thunt"])
45 User.username_equals("a".."b")
a9cbc43 @binarylogic Add v2 files
authored
46 User.username_does_not_equal("bjohnson")
47 User.username_begins_with("bjohnson")
24bc371 @binarylogic Added feature for combining conditions with OR and as well as some se…
authored
48 User.username_not_begin_with("bjohnson")
a9cbc43 @binarylogic Add v2 files
authored
49 User.username_like("bjohnson")
24bc371 @binarylogic Added feature for combining conditions with OR and as well as some se…
authored
50 User.username_not_like("bjohnson")
a9cbc43 @binarylogic Add v2 files
authored
51 User.username_ends_with("bjohnson")
24bc371 @binarylogic Added feature for combining conditions with OR and as well as some se…
authored
52 User.username_not_end_with("bjohnson")
a9cbc43 @binarylogic Add v2 files
authored
53 User.age_greater_than(20)
54 User.age_greater_than_or_equal_to(20)
55 User.age_less_than(20)
56 User.age_less_than_or_equal_to(20)
57 User.username_null
24bc371 @binarylogic Added feature for combining conditions with OR and as well as some se…
authored
58 User.username_not_null
a9cbc43 @binarylogic Add v2 files
authored
59 User.username_blank
60
9744f24 @binarylogic Added any or all named scopes. See README.
authored
61 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:
a9cbc43 @binarylogic Add v2 files
authored
62
24bc371 @binarylogic Added feature for combining conditions with OR and as well as some se…
authored
63 scope = User.username_like("bjohnson").age_greater_than(20).id_less_than(55)
a9cbc43 @binarylogic Add v2 files
authored
64 scope.all
65 scope.first
66 scope.count
67 # etc...
68
24bc371 @binarylogic Added feature for combining conditions with OR and as well as some se…
authored
69 For a complete list of conditions please see the constants in Searchlogic::NamedScopes::Conditions.
70
71 == Use condition aliases
72
73 Typing out 'greater_than_or_equal_to' is not fun. Instead Searchlogic provides various aliases for the conditions. For a complete list please see Searchlogic::NamedScopes::Conditions. But they are pretty straightforward:
74
26bc920 @binarylogic Readme changes
authored
75 User.username_is(10) # equals
76 User.username_eq(10) # equals
77 User.id_lt(10) # less than
78 User.id_lte(10) # less than or equal to
79 User.id_gt(10) # greater than
80 User.id_gte(10) # greater than or equal to
24bc371 @binarylogic Added feature for combining conditions with OR and as well as some se…
authored
81 # etc...
82
83 == Search using scopes in associated classes
0e5912a @binarylogic Ignore blank values when settings conditions via a Hash on the Search…
authored
84
24bc371 @binarylogic Added feature for combining conditions with OR and as well as some se…
authored
85 This is my favorite part of Searchlogic. You can dynamically call scopes on associated classes and Searchlogic will take care of creating the necessary joins for you. This is REALY nice for keeping your code DRY. The best way to explain this is to show you:
a9cbc43 @binarylogic Add v2 files
authored
86
1ed8434 @binarylogic Readme updates
authored
87 === Searchlogic provided scopes
88
26bc920 @binarylogic Readme changes
authored
89 Let's take some basic scopes that Searchlogic provides for every model:
a9cbc43 @binarylogic Add v2 files
authored
90
91 # We have the following relationships
92 User.has_many :orders
93 Order.has_many :line_items
94 LineItem
95
96 # Set conditions on association columns
97 User.orders_total_greater_than(20)
98 User.orders_line_items_price_greater_than(20)
99
100 # Order by association columns
101 User.ascend_by_order_total
102 User.descend_by_orders_line_items_price
103
24bc371 @binarylogic Added feature for combining conditions with OR and as well as some se…
authored
104 This is recursive, you can travel through your associations simply by typing it in the name of the method. Again these are just named scopes. You can chain them together, call methods off of them, etc.
105
1ed8434 @binarylogic Readme updates
authored
106 === Custom associated scopes
107
24bc371 @binarylogic Added feature for combining conditions with OR and as well as some se…
authored
108 Also, these conditions aren't limited to the scopes Searchlogic provides. You can use your own scopes. Like this:
109
110 LineItem.named_scope :expensive, :conditions => "line_items.price > 500"
111
1ed8434 @binarylogic Readme updates
authored
112 User.orders_line_items_expensive
24bc371 @binarylogic Added feature for combining conditions with OR and as well as some se…
authored
113
114 As I stated above, Searchlogic will take care of creating the necessary joins for you. This is REALLY nice when trying to keep your code DRY, because if you wanted to use a scope like this in your User model you would have to copy over the conditions. Now you have 2 named scopes that are essentially doing the same thing. Why do that when you can dynamically access that scope using this feature?
115
1ed8434 @binarylogic Readme updates
authored
116 === Uses :joins not :include
117
24bc371 @binarylogic Added feature for combining conditions with OR and as well as some se…
authored
118 Another thing to note is that the joins created by Searchlogic do NOT use the :include option, making them <em>much</em> faster. Instead they leverage the :joins option, which is great for performance. To prove my point here is a quick benchmark from an application I am working on:
0e5912a @binarylogic Ignore blank values when settings conditions via a Hash on the Search…
authored
119
120 Benchmark.bm do |x|
121 x.report { 10.times { Event.tickets_id_gt(10).all(:include => :tickets) } }
85fdcc6 @binarylogic Clean up how the SearchProxy calls scopes with no arity
authored
122 x.report { 10.times { Event.tickets_id_gt(10).all } }
0e5912a @binarylogic Ignore blank values when settings conditions via a Hash on the Search…
authored
123 end
124 user system total real
125 10.120000 0.170000 10.290000 ( 12.625521)
126 2.630000 0.050000 2.680000 ( 3.313754)
127
128 If you want to use the :include option, just specify it:
a9cbc43 @binarylogic Add v2 files
authored
129
130 User.orders_line_items_price_greater_than(20).all(:include => {:orders => :line_items})
131
74d5631 @binarylogic * Refactored association code to be much simpler and rely on recursio…
authored
132 Obviously, only do this if you want to actually use the included objects. Including objects into a query can be helpful with performance, especially when solving an N+1 query problem.
a9cbc43 @binarylogic Add v2 files
authored
133
24bc371 @binarylogic Added feature for combining conditions with OR and as well as some se…
authored
134 == Order your search
135
136 Just like the various conditions, Searchlogic gives you some very basic scopes for ordering your data:
137
138 User.ascend_by_id
139 User.descend_by_id
140 User.ascend_by_orders_line_items_price
141 # etc...
142
143 == Use any or all
144
145 Every condition you've seen in this readme also has 2 related conditions that you can use. Example:
146
147 User.username_like_any("bjohnson", "thunt") # will return any users that have either of the strings in their username
148 User.username_like_all("bjohnson", "thunt") # will return any users that have all of the strings in their username
149 User.username_like_any(["bjohnson", "thunt"]) # also accepts an array
150
151 This is great for checkbox filters, etc. Where you can pass an array right from your form to this condition.
152
153 == Combine scopes with 'OR'
154
155 In the same fashion that Searchlogic provides a tool for accessing scopes in associated classes, it also provides a tool for combining scopes with 'OR'. As we all know, when scopes are combined they are joined with 'AND', but sometimes you need to combine scopes with 'OR'. Searchlogic solves this problem:
156
157 User.username_or_first_name_like("ben")
158 => "username LIKE '%ben%' OR first_name like'%ben%'"
159
160 User.id_or_age_lt_or_username_or_first_name_begins_with(10)
161 => "id < 10 OR age < 10 OR username LIKE 'ben%' OR first_name like'ben%'"
162
163 Notice you don't have to specify the explicit condition (like, gt, lt, begins with, etc.). You just need to eventually specify it. If you specify a column it will just use the next condition specified. So instead of:
164
165 User.username_like_or_first_name_like("ben")
166
167 You can do:
168
169 User.username_or_first_name_like("ben")
170
171 Again, these just map to named scopes. Use Searchlogic's dynamic scopes, use scopes on associations, use your own custom scopes. As long as it maps to a named scope it will join the conditions with 'OR'. There are no limitations.
172
7776b27 @binarylogic Some readme cleanup
authored
173 == Make searching and ordering data in your application trivial
a9cbc43 @binarylogic Add v2 files
authored
174
8563a4d @binarylogic * Split out left outer join creation into its own method, allowing yo…
authored
175 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...
a9cbc43 @binarylogic Add v2 files
authored
176
8563a4d @binarylogic * Split out left outer join creation into its own method, allowing yo…
authored
177 search = User.search(:username_like => "bjohnson", :age_less_than => 20)
178 search.all
a9cbc43 @binarylogic Add v2 files
authored
179
180 The above is equivalent to:
181
8563a4d @binarylogic * Split out left outer join creation into its own method, allowing yo…
authored
182 User.username_like("bjohnson").age_less_than(20).all
183
184 You can set, read, and chain conditions off of your search too:
185
186 search.username_like => "bjohnson"
187 search.age_gt = 2 => 2
188 search.id_gt(10).email_begins_with("bjohnson") => <#Searchlogic::Search...>
189 search.all => An array of users
190 search.count => integer
191 # .. etc
a9cbc43 @binarylogic Add v2 files
authored
192
a77b72d @binarylogic Added inner_join convenience method.
authored
193 So let's start with the controller...
194
195 === Your controller
196
197 The search class just chains named scopes together for you. What's so great about that? It keeps your controllers extremely simple:
a9cbc43 @binarylogic Add v2 files
authored
198
199 class UsersController < ApplicationController
200 def index
201 @search = User.search(params[:search])
202 @users = @search.all
203 end
204 end
205
a77b72d @binarylogic Added inner_join convenience method.
authored
206 It doesn't get any simpler than that.
207
208 === Your form
209
210 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:
a9cbc43 @binarylogic Add v2 files
authored
211
212 - form_for @search do |f|
213 = f.text_field :username_like
214 = f.select :age_greater_than, (0..100)
215 = f.text_field :orders_total_greater_than
216 = f.submit
217
a77b72d @binarylogic Added inner_join convenience method.
authored
218 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.
219
220 === Additional helpers
221
222 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:
0f4b5d6 @binarylogic Add in some basic helpers
authored
223
224 = order @search, :by => :age
a77b72d @binarylogic Added inner_join convenience method.
authored
225 = order @search, :by => :created_at, :as => "Created date"
226
227 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.
0f4b5d6 @binarylogic Add in some basic helpers
authored
228
a77b72d @binarylogic Added inner_join convenience method.
authored
229 <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>
0f4b5d6 @binarylogic Add in some basic helpers
authored
230
a9cbc43 @binarylogic Add v2 files
authored
231 == Use your existing named scopes
232
233 This is one of the big differences between Searchlogic v1 and v2. What about your existing named scopes? Let's say you have this:
234
235 User.named_scope :four_year_olds, :conditions => {:age => 4}
236
237 Again, these are all just named scopes, use it in the same way:
238
239 User.search(:four_year_olds => true, :username_like => "bjohnson")
240
241 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.
242
243 Now just throw it in your form:
244
245 - form_for @search do |f|
246 = f.text_field :username_like
247 = f.check_box :four_year_olds
248 = f.submit
249
74d5631 @binarylogic * Refactored association code to be much simpler and rely on recursio…
authored
250 This really allows Searchlogic to extend beyond what it provides internally. If Searchlogic doesn't provide a named scope for that crazy edge case that you need, just create your own named scope and use it. The sky is the limit.
a9cbc43 @binarylogic Add v2 files
authored
251
252 == Pagination (leverage will_paginate)
253
254 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:
255
256 User.username_like("bjohnson").age_less_than(20).paginate(:page => params[:page])
7db0934 @binarylogic Some readme cleanup
authored
257 User.search(:username_like => "bjohnson", :age_less_than => 20).paginate(:page => params[:page])
a9cbc43 @binarylogic Add v2 files
authored
258
259 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.
260
a60fc65 @binarylogic * Added a no conflic resolution for other libraries already using the…
authored
261 == Conflicts with other gems
262
74d5631 @binarylogic * Refactored association code to be much simpler and rely on recursio…
authored
263 You will notice searchlogic wants to create a method called "search". So do other libraries like thinking-sphinx, etc. So searchlogic has a no conflict resolution. If the "search" method is already taken the method will be called "searchlogic" instead. So instead of
a60fc65 @binarylogic * Added a no conflic resolution for other libraries already using the…
authored
264
265 User.search
266
267 You would do:
268
269 User.searchlogic
270
a9cbc43 @binarylogic Add v2 files
authored
271 == Under the hood
272
273 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:
274
275 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.
276
764f859 @binarylogic Use inner joins for conditions instead of left outer
authored
277 That's about it, the named scope options are pretty bare bones and created just like you would manually.
a9cbc43 @binarylogic Add v2 files
authored
278
d8f61c4 @binarylogic Update readme
authored
279 == Credit
280
7db0934 @binarylogic Some readme cleanup
authored
281 Thanks a lot to {Tyler Hunt}[http://github.com/tylerhunt] for helping plan, design, and start the project. He was a big help.
d8f61c4 @binarylogic Update readme
authored
282
a9cbc43 @binarylogic Add v2 files
authored
283 == Copyright
284
285 Copyright (c) 2009 {Ben Johnson of Binary Logic}[http://www.binarylogic.com], released under the MIT license
Something went wrong with that request. Please try again.