README.rdoc in kazjote-searchlogic-2.1.9.3 vs README.rdoc in kazjote-searchlogic-2.3.4
- old
+ new
@@ -1,21 +1,20 @@
= Searchlogic
-<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>
+Searchlogic makes using ActiveRecord named scopes easier and less repetitive. It helps keep your code DRY, clean, and simple.
-Searchlogic provides common named scopes and object based searching for ActiveRecord.
-
== Helpful links
* <b>Documentation:</b> http://rdoc.info/projects/binarylogic/searchlogic
* <b>Repository:</b> http://github.com/binarylogic/searchlogic/tree/master
-* <b>Bugs / feature suggestions:</b> http://binarylogic.lighthouseapp.com/projects/16601-searchlogic
+* <b>Issues:</b> http://github.com/binarylogic/searchlogic/issues
* <b>Google group:</b> http://groups.google.com/group/searchlogic
+* <b>Railscast:</b> http://railscasts.com/episodes/176-searchlogic
<b>Before contacting me directly, please read:</b>
-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.
+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.
== Install & use
Install the gem from rubyforge:
@@ -40,40 +39,57 @@
# We have the following model
User(id: integer, created_at: datetime, username: string, age: integer)
# Searchlogic gives you a bunch of named scopes for free:
User.username_equals("bjohnson")
+ User.username_equals(["bjohnson", "thunt"])
+ User.username_equals("a".."b")
User.username_does_not_equal("bjohnson")
User.username_begins_with("bjohnson")
+ User.username_not_begin_with("bjohnson")
User.username_like("bjohnson")
+ User.username_not_like("bjohnson")
User.username_ends_with("bjohnson")
+ User.username_not_end_with("bjohnson")
User.age_greater_than(20)
User.age_greater_than_or_equal_to(20)
User.age_less_than(20)
User.age_less_than_or_equal_to(20)
User.username_null
+ User.username_not_null
User.username_blank
-
- # You can also order by columns
- User.ascend_by_username
- User.descend_by_username
- User.order("ascend_by_username")
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:
- scope = User.username_like("bjohnson").age_greater_than(20).ascend_by_username
+ scope = User.username_like("bjohnson").age_greater_than(20).id_less_than(55)
scope.all
scope.first
scope.count
# etc...
-That's all pretty standard, but here's where Searchlogic starts to get interesting...
+For a complete list of conditions please see the constants in Searchlogic::NamedScopes::Conditions.
-== Search using conditions on associated columns
+== Use condition aliases
-You also get named scopes for any of your associations:
+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:
+ User.username_is(10) # equals
+ User.username_eq(10) # equals
+ User.id_lt(10) # less than
+ User.id_lte(10) # less than or equal to
+ User.id_gt(10) # greater than
+ User.id_gte(10) # greater than or equal to
+ # etc...
+
+== Search using scopes in associated classes
+
+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:
+
+=== Searchlogic provided scopes
+
+Let's take some basic scopes that Searchlogic provides for every model:
+
# We have the following relationships
User.has_many :orders
Order.has_many :line_items
LineItem
@@ -83,12 +99,26 @@
# Order by association columns
User.ascend_by_order_total
User.descend_by_orders_line_items_price
-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:
+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.
+=== Custom associated scopes
+
+Also, these conditions aren't limited to the scopes Searchlogic provides. You can use your own scopes. Like this:
+
+ LineItem.named_scope :expensive, :conditions => "line_items.price > 500"
+
+ User.orders_line_items_expensive
+
+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?
+
+=== Uses :joins not :include
+
+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:
+
Benchmark.bm do |x|
x.report { 10.times { Event.tickets_id_gt(10).all(:include => :tickets) } }
x.report { 10.times { Event.tickets_id_gt(10).all } }
end
user system total real
@@ -97,12 +127,63 @@
If you want to use the :include option, just specify it:
User.orders_line_items_price_greater_than(20).all(:include => {:orders => :line_items})
-Obviously, only do this if you want to actually use the included objects.
+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.
+== Order your search
+
+Just like the various conditions, Searchlogic gives you some very basic scopes for ordering your data:
+
+ User.ascend_by_id
+ User.descend_by_id
+ User.ascend_by_orders_line_items_price
+ # etc...
+
+== Use any or all
+
+Every condition you've seen in this readme also has 2 related conditions that you can use. Example:
+
+ User.username_like_any("bjohnson", "thunt") # will return any users that have either of the strings in their username
+ User.username_like_all("bjohnson", "thunt") # will return any users that have all of the strings in their username
+ User.username_like_any(["bjohnson", "thunt"]) # also accepts an array
+
+This is great for checkbox filters, etc. Where you can pass an array right from your form to this condition.
+
+== Combine scopes with 'OR'
+
+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:
+
+ User.username_or_first_name_like("ben")
+ => "username LIKE '%ben%' OR first_name like'%ben%'"
+
+ User.id_or_age_lt_or_username_or_first_name_begins_with(10)
+ => "id < 10 OR age < 10 OR username LIKE 'ben%' OR first_name like'ben%'"
+
+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:
+
+ User.username_like_or_first_name_like("ben")
+
+You can do:
+
+ User.username_or_first_name_like("ben")
+
+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.
+
+== Create scope procedures
+
+Sometimes you notice a pattern in your application where you are constantly combining certain named scopes. You want to keep the flexibility of being able to mix and match small named scopes, while at the same time being able to call a single scope for a common task. User searchlogic's scpe procedure:
+
+ User.scope_procedure :awesome, lambda { first_name_begins_with("ben").last_name_begins_with("johnson").website_equals("binarylogic.com") }
+
+All that this is doing is creating a class level method, but what is nice about this method is that is more inline with your other named scopes. It also tells searchlogic that this method is 'safe' to use when using the search method. Ex:
+
+ User.search(:awesome => true)
+
+Otherwise searchlogic will ignore the 'awesome' condition because there is no way to tell that its a valid scope. This is a security measure to keep users from passing in a scope with a named like 'destroy_all'.
+
== Make searching and ordering data in your application trivial
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...
search = User.search(:username_like => "bjohnson", :age_less_than => 20)
@@ -176,22 +257,12 @@
- form_for @search do |f|
= f.text_field :username_like
= f.check_box :four_year_olds
= f.submit
-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.
+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.
-== Use any or all
-
-Every condition you've seen in this readme also has 2 related conditions that you can use. Example:
-
- User.username_like_any("bjohnson", "thunt") # will return any users that have either of the strings in their username
- User.username_like_all("bjohnson", "thunt") # will return any users that have all of the strings in their username
- User.username_like_any(["bjohnson", "thunt"]) # also accepts an array
-
-This is great for checkbox filters, etc. Where you can pass an array right from your form to this condition.
-
== Pagination (leverage will_paginate)
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:
User.username_like("bjohnson").age_less_than(20).paginate(:page => params[:page])
@@ -199,11 +270,11 @@
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.
== Conflicts with other gems
-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
+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
User.search
You would do:
@@ -212,9 +283,11 @@
== Under the hood
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:
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.
+
+The search object is just a proxy to your model that only delegates calls that map to named scopes and nothing more. This is obviously done for security reasons. It also helps make form integration easier, by type casting values, and playing nice with form_for. This class is pretty simple as well.
That's about it, the named scope options are pretty bare bones and created just like you would manually.
== Credit
\ No newline at end of file