searchlogic 1.6.6 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/CHANGELOG.rdoc +17 -0
- data/{MIT-LICENSE → LICENSE} +2 -2
- data/README.rdoc +128 -379
- data/Rakefile +56 -20
- data/VERSION.yml +4 -0
- data/init.rb +1 -1
- data/lib/searchlogic.rb +18 -98
- data/lib/searchlogic/core_ext/object.rb +33 -13
- data/lib/searchlogic/core_ext/proc.rb +11 -0
- data/lib/searchlogic/named_scopes/alias_scope.rb +63 -0
- data/lib/searchlogic/named_scopes/associations.rb +126 -0
- data/lib/searchlogic/named_scopes/conditions.rb +215 -0
- data/lib/searchlogic/named_scopes/ordering.rb +53 -0
- data/lib/searchlogic/rails_helpers.rb +69 -0
- data/lib/searchlogic/search.rb +146 -0
- data/rails/init.rb +1 -0
- data/searchlogic.gemspec +69 -0
- data/spec/core_ext/object_spec.rb +7 -0
- data/spec/core_ext/proc_spec.rb +9 -0
- data/spec/named_scopes/alias_scope_spec.rb +15 -0
- data/spec/named_scopes/associations_spec.rb +120 -0
- data/spec/named_scopes/conditions_spec.rb +253 -0
- data/spec/named_scopes/ordering_spec.rb +23 -0
- data/spec/search_spec.rb +283 -0
- data/spec/spec_helper.rb +78 -0
- metadata +40 -231
- data/Manifest.txt +0 -158
- data/TODO.rdoc +0 -4
- data/lib/searchlogic/active_record/associations.rb +0 -52
- data/lib/searchlogic/active_record/base.rb +0 -224
- data/lib/searchlogic/active_record/connection_adapters/mysql_adapter.rb +0 -176
- data/lib/searchlogic/active_record/connection_adapters/postgresql_adapter.rb +0 -172
- data/lib/searchlogic/active_record/connection_adapters/sqlite_adapter.rb +0 -80
- data/lib/searchlogic/condition/base.rb +0 -165
- data/lib/searchlogic/condition/begins_with.rb +0 -17
- data/lib/searchlogic/condition/blank.rb +0 -24
- data/lib/searchlogic/condition/child_of.rb +0 -11
- data/lib/searchlogic/condition/descendant_of.rb +0 -11
- data/lib/searchlogic/condition/ends_with.rb +0 -17
- data/lib/searchlogic/condition/equals.rb +0 -33
- data/lib/searchlogic/condition/greater_than.rb +0 -15
- data/lib/searchlogic/condition/greater_than_or_equal_to.rb +0 -15
- data/lib/searchlogic/condition/inclusive_descendant_of.rb +0 -10
- data/lib/searchlogic/condition/keywords.rb +0 -52
- data/lib/searchlogic/condition/less_than.rb +0 -15
- data/lib/searchlogic/condition/less_than_or_equal_to.rb +0 -15
- data/lib/searchlogic/condition/like.rb +0 -15
- data/lib/searchlogic/condition/nested_set.rb +0 -17
- data/lib/searchlogic/condition/nil.rb +0 -21
- data/lib/searchlogic/condition/not_begin_with.rb +0 -20
- data/lib/searchlogic/condition/not_blank.rb +0 -19
- data/lib/searchlogic/condition/not_end_with.rb +0 -20
- data/lib/searchlogic/condition/not_equal.rb +0 -27
- data/lib/searchlogic/condition/not_have_keywords.rb +0 -20
- data/lib/searchlogic/condition/not_like.rb +0 -20
- data/lib/searchlogic/condition/not_nil.rb +0 -19
- data/lib/searchlogic/condition/sibling_of.rb +0 -14
- data/lib/searchlogic/conditions/any_or_all.rb +0 -42
- data/lib/searchlogic/conditions/base.rb +0 -244
- data/lib/searchlogic/conditions/groups.rb +0 -74
- data/lib/searchlogic/conditions/magic_methods.rb +0 -286
- data/lib/searchlogic/conditions/multiparameter_attributes.rb +0 -105
- data/lib/searchlogic/conditions/protection.rb +0 -36
- data/lib/searchlogic/config.rb +0 -31
- data/lib/searchlogic/config/helpers.rb +0 -338
- data/lib/searchlogic/config/search.rb +0 -53
- data/lib/searchlogic/core_ext/hash.rb +0 -75
- data/lib/searchlogic/helpers/control_types/link.rb +0 -310
- data/lib/searchlogic/helpers/control_types/links.rb +0 -242
- data/lib/searchlogic/helpers/control_types/remote_link.rb +0 -87
- data/lib/searchlogic/helpers/control_types/remote_links.rb +0 -72
- data/lib/searchlogic/helpers/control_types/remote_select.rb +0 -36
- data/lib/searchlogic/helpers/control_types/select.rb +0 -82
- data/lib/searchlogic/helpers/form.rb +0 -208
- data/lib/searchlogic/helpers/utilities.rb +0 -197
- data/lib/searchlogic/modifiers/absolute.rb +0 -15
- data/lib/searchlogic/modifiers/acos.rb +0 -11
- data/lib/searchlogic/modifiers/asin.rb +0 -11
- data/lib/searchlogic/modifiers/atan.rb +0 -11
- data/lib/searchlogic/modifiers/avg.rb +0 -15
- data/lib/searchlogic/modifiers/base.rb +0 -27
- data/lib/searchlogic/modifiers/ceil.rb +0 -15
- data/lib/searchlogic/modifiers/char_length.rb +0 -15
- data/lib/searchlogic/modifiers/cos.rb +0 -15
- data/lib/searchlogic/modifiers/cot.rb +0 -15
- data/lib/searchlogic/modifiers/count.rb +0 -11
- data/lib/searchlogic/modifiers/day_of_month.rb +0 -15
- data/lib/searchlogic/modifiers/day_of_week.rb +0 -15
- data/lib/searchlogic/modifiers/day_of_year.rb +0 -15
- data/lib/searchlogic/modifiers/degrees.rb +0 -11
- data/lib/searchlogic/modifiers/exp.rb +0 -15
- data/lib/searchlogic/modifiers/floor.rb +0 -15
- data/lib/searchlogic/modifiers/hex.rb +0 -11
- data/lib/searchlogic/modifiers/hour.rb +0 -11
- data/lib/searchlogic/modifiers/log.rb +0 -15
- data/lib/searchlogic/modifiers/log10.rb +0 -11
- data/lib/searchlogic/modifiers/log2.rb +0 -11
- data/lib/searchlogic/modifiers/lower.rb +0 -15
- data/lib/searchlogic/modifiers/ltrim.rb +0 -15
- data/lib/searchlogic/modifiers/md5.rb +0 -11
- data/lib/searchlogic/modifiers/microseconds.rb +0 -11
- data/lib/searchlogic/modifiers/milliseconds.rb +0 -11
- data/lib/searchlogic/modifiers/minute.rb +0 -15
- data/lib/searchlogic/modifiers/month.rb +0 -15
- data/lib/searchlogic/modifiers/octal.rb +0 -15
- data/lib/searchlogic/modifiers/radians.rb +0 -11
- data/lib/searchlogic/modifiers/round.rb +0 -11
- data/lib/searchlogic/modifiers/rtrim.rb +0 -15
- data/lib/searchlogic/modifiers/second.rb +0 -15
- data/lib/searchlogic/modifiers/sign.rb +0 -11
- data/lib/searchlogic/modifiers/sin.rb +0 -11
- data/lib/searchlogic/modifiers/square_root.rb +0 -15
- data/lib/searchlogic/modifiers/sum.rb +0 -11
- data/lib/searchlogic/modifiers/tan.rb +0 -15
- data/lib/searchlogic/modifiers/trim.rb +0 -15
- data/lib/searchlogic/modifiers/upper.rb +0 -15
- data/lib/searchlogic/modifiers/week.rb +0 -11
- data/lib/searchlogic/modifiers/year.rb +0 -11
- data/lib/searchlogic/search/base.rb +0 -148
- data/lib/searchlogic/search/conditions.rb +0 -53
- data/lib/searchlogic/search/ordering.rb +0 -244
- data/lib/searchlogic/search/pagination.rb +0 -121
- data/lib/searchlogic/search/protection.rb +0 -89
- data/lib/searchlogic/search/searching.rb +0 -32
- data/lib/searchlogic/shared/utilities.rb +0 -57
- data/lib/searchlogic/shared/virtual_classes.rb +0 -39
- data/lib/searchlogic/version.rb +0 -79
- data/test/active_record_tests/associations_test.rb +0 -94
- data/test/active_record_tests/base_test.rb +0 -115
- data/test/condition_tests/base_test.rb +0 -62
- data/test/condition_tests/begins_with_test.rb +0 -11
- data/test/condition_tests/blank_test.rb +0 -31
- data/test/condition_tests/child_of_test.rb +0 -17
- data/test/condition_tests/descendant_of_test.rb +0 -12
- data/test/condition_tests/ends_with_test.rb +0 -11
- data/test/condition_tests/equals_test.rb +0 -28
- data/test/condition_tests/greater_than_or_equal_to_test.rb +0 -11
- data/test/condition_tests/greater_than_test.rb +0 -11
- data/test/condition_tests/inclusive_descendant_of_test.rb +0 -12
- data/test/condition_tests/keywords_test.rb +0 -23
- data/test/condition_tests/less_than_or_equal_to_test.rb +0 -11
- data/test/condition_tests/less_than_test.rb +0 -11
- data/test/condition_tests/like_test.rb +0 -11
- data/test/condition_tests/nil_test.rb +0 -31
- data/test/condition_tests/not_begin_with_test.rb +0 -8
- data/test/condition_tests/not_blank_test.rb +0 -8
- data/test/condition_tests/not_end_with_test.rb +0 -8
- data/test/condition_tests/not_equal_test.rb +0 -19
- data/test/condition_tests/not_have_keywords_test.rb +0 -8
- data/test/condition_tests/not_like_test.rb +0 -8
- data/test/condition_tests/not_nil_test.rb +0 -13
- data/test/condition_tests/sibling_of_test.rb +0 -15
- data/test/conditions_tests/any_or_all_test.rb +0 -23
- data/test/conditions_tests/base_test.rb +0 -185
- data/test/conditions_tests/groups_test.rb +0 -68
- data/test/conditions_tests/magic_methods_test.rb +0 -36
- data/test/conditions_tests/multiparameter_attributes_test.rb +0 -15
- data/test/conditions_tests/protection_test.rb +0 -18
- data/test/config_test.rb +0 -23
- data/test/fixtures/accounts.yml +0 -12
- data/test/fixtures/animals.yml +0 -7
- data/test/fixtures/orders.yml +0 -12
- data/test/fixtures/user_groups.yml +0 -5
- data/test/fixtures/users.yml +0 -45
- data/test/libs/awesome_nested_set.rb +0 -545
- data/test/libs/awesome_nested_set/.autotest +0 -13
- data/test/libs/awesome_nested_set/compatability.rb +0 -29
- data/test/libs/awesome_nested_set/helper.rb +0 -40
- data/test/libs/awesome_nested_set/named_scope.rb +0 -140
- data/test/libs/rexml_fix.rb +0 -14
- data/test/modifier_tests/day_of_month_test.rb +0 -16
- data/test/search_tests/base_test.rb +0 -241
- data/test/search_tests/conditions_test.rb +0 -21
- data/test/search_tests/ordering_test.rb +0 -167
- data/test/search_tests/pagination_test.rb +0 -74
- data/test/search_tests/protection_test.rb +0 -26
- data/test/test_helper.rb +0 -122
data/.gitignore
ADDED
data/CHANGELOG.rdoc
CHANGED
@@ -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.
|
data/{MIT-LICENSE → LICENSE}
RENAMED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright (c)
|
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.
|
data/README.rdoc
CHANGED
@@ -1,461 +1,210 @@
|
|
1
1
|
= Searchlogic
|
2
2
|
|
3
|
-
Searchlogic
|
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
|
-
|
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
|
-
*
|
19
|
-
*
|
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
|
-
|
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
|
-
|
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
|
-
|
18
|
+
== Install & use
|
36
19
|
|
37
|
-
|
20
|
+
Install the gem from rubyforge:
|
38
21
|
|
39
|
-
|
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
|
-
|
24
|
+
Or from github:
|
70
25
|
|
71
|
-
|
26
|
+
sudo gem install binarylogic-searchlogic
|
72
27
|
|
73
|
-
|
28
|
+
Now just include it in your project and you are ready to go. See below for usage examples.
|
74
29
|
|
75
|
-
|
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
|
-
|
32
|
+
Instead of explaining what Searchlogic can do, let me show you. Let's start at the top:
|
82
33
|
|
83
|
-
#
|
84
|
-
|
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
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
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
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
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
|
-
|
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
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
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
|
-
|
63
|
+
That's all pretty standard, but here's where Searchlogic starts to get interesting...
|
171
64
|
|
172
|
-
|
173
|
-
User.sum('id', :conditions => {:first_name_contains => "Ben"})
|
174
|
-
# ... all other calcualtions, etc.
|
65
|
+
== Search using conditions on associated columns
|
175
66
|
|
176
|
-
|
67
|
+
You also get named scopes for any of your associations:
|
177
68
|
|
178
|
-
|
179
|
-
|
180
|
-
|
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
|
-
|
194
|
-
|
195
|
-
|
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
|
-
|
200
|
-
|
201
|
-
|
78
|
+
# Order by association columns
|
79
|
+
User.ascend_by_order_total
|
80
|
+
User.descend_by_orders_line_items_price
|
202
81
|
|
203
|
-
|
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
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
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
|
-
|
92
|
+
If you want to use the :include option, just specify it:
|
219
93
|
|
220
|
-
|
94
|
+
User.orders_line_items_price_greater_than(20).all(:include => {:orders => :line_items})
|
221
95
|
|
222
|
-
|
96
|
+
Obviously, only do this if you want to actually use the included objects.
|
223
97
|
|
224
|
-
|
98
|
+
== Make searching and ordering data in your application trivial
|
225
99
|
|
226
|
-
|
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.
|
239
|
-
search.
|
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
|
-
|
105
|
+
The above is equivalent to:
|
268
106
|
|
269
|
-
|
107
|
+
User.username_like("bjohnson").age_less_than(20).all
|
270
108
|
|
271
|
-
|
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
|
-
|
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
|
-
|
118
|
+
So let's start with the controller...
|
279
119
|
|
280
|
-
|
281
|
-
named_scope :sexy, :conditions => {:first_name => "Ben", email_ends_with => "binarylogic.com"}, :per_page => 20
|
282
|
-
end
|
120
|
+
=== Your controller
|
283
121
|
|
284
|
-
|
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
|
287
|
-
def
|
288
|
-
|
289
|
-
|
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
|
-
|
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
|
-
===
|
133
|
+
=== Your form
|
299
134
|
|
300
|
-
|
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
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
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
|
-
|
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
|
-
===
|
145
|
+
=== Additional helpers
|
318
146
|
|
319
|
-
|
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
|
-
|
322
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
160
|
+
User.named_scope :four_year_olds, :conditions => {:age => 4}
|
353
161
|
|
354
|
-
|
162
|
+
Again, these are all just named scopes, use it in the same way:
|
355
163
|
|
356
|
-
|
164
|
+
User.search(:four_year_olds => true, :username_like => "bjohnson")
|
357
165
|
|
358
|
-
|
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
|
-
|
168
|
+
Now just throw it in your form:
|
363
169
|
|
364
|
-
|
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
|
-
|
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
|
-
|
177
|
+
== Use any or all
|
369
178
|
|
370
|
-
|
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
|
-
|
429
|
-
|
430
|
-
|
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
|
-
|
185
|
+
This is great for checkbox filters, etc. Where you can pass an array right from your form to this condition.
|
433
186
|
|
434
|
-
|
187
|
+
== Pagination (leverage will_paginate)
|
435
188
|
|
436
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
202
|
+
That's about it, the named scope options are pretty bare bones and created just like you would manually.
|
455
203
|
|
456
|
-
|
204
|
+
== Credit
|
457
205
|
|
458
|
-
|
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)
|
210
|
+
Copyright (c) 2009 {Ben Johnson of Binary Logic}[http://www.binarylogic.com], released under the MIT license
|