joost-searchlogic 2.1.7.1 → 2.2.3.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +47 -0
- data/README.rdoc +4 -6
- data/Rakefile +1 -0
- data/VERSION.yml +1 -1
- data/lib/searchlogic.rb +13 -1
- data/lib/searchlogic/active_record/consistency.rb +22 -0
- data/lib/searchlogic/active_record/named_scopes.rb +51 -0
- data/lib/searchlogic/core_ext/object.rb +4 -2
- data/lib/searchlogic/named_scopes/alias_scope.rb +1 -0
- data/lib/searchlogic/named_scopes/conditions.rb +42 -55
- data/lib/searchlogic/named_scopes/ordering.rb +9 -9
- data/lib/searchlogic/rails_helpers.rb +6 -2
- data/lib/searchlogic/search.rb +4 -4
- data/searchlogic.gemspec +6 -4
- data/spec/named_scopes/alias_scope_spec.rb +4 -0
- data/spec/named_scopes/association_conditions_spec.rb +31 -2
- data/spec/named_scopes/conditions_spec.rb +20 -3
- data/spec/search_spec.rb +30 -1
- data/spec/spec_helper.rb +18 -1
- metadata +7 -5
- data/lib/searchlogic/active_record_consistency.rb +0 -27
data/CHANGELOG.rdoc
CHANGED
@@ -1,5 +1,52 @@
|
|
1
|
+
<<<<<<< HEAD:CHANGELOG.rdoc
|
1
2
|
== 2.1.7
|
2
3
|
|
4
|
+
=======
|
5
|
+
== 2.2.3 released 2009-07-31
|
6
|
+
|
7
|
+
* Fixed bug when an associations named scope joins is a string or an array of strings, the joins we add in automatically should also be a string, not a symbol.
|
8
|
+
|
9
|
+
== 2.2.2 released 2009-07-31
|
10
|
+
|
11
|
+
* Fix bug to give priority to local columns.
|
12
|
+
|
13
|
+
== 2.2.1 released 2009-07-30
|
14
|
+
|
15
|
+
* Use ::ActiveRecord instead of ActiveRecord to avoid a name conflict since ActiveRecord is a module within Searchlogic.
|
16
|
+
|
17
|
+
== 2.2.0 released 2009-07-30
|
18
|
+
|
19
|
+
* Refactored association code to be much simpler and rely on recursion. This allows the underlying class to do most of the work. This also allows calling any named scopes through any level of associations.
|
20
|
+
|
21
|
+
== 2.1.13 released 2009-07-29
|
22
|
+
|
23
|
+
* Applied bug fix from http://github.com/skanev/searchlogic to make #order work with association ordering.
|
24
|
+
* Applied bug fix to allow for custom ordering conditions.
|
25
|
+
|
26
|
+
== 2.1.12 released 2009-07-28
|
27
|
+
|
28
|
+
* Fixed bug when dealing with scopes that return nil.
|
29
|
+
|
30
|
+
== 2.1.11 released 2009-07-28
|
31
|
+
|
32
|
+
* Reworked how alias conditions are created on the fly, uses scope(:find) instead of proxy_options to create the scope. This allows using association alias named scopes.
|
33
|
+
|
34
|
+
== 2.1.10 released 2009-07-28
|
35
|
+
|
36
|
+
* Ignore polymorphic associations when dynamically creating conditions on associations.
|
37
|
+
|
38
|
+
== 2.1.9 released 2009-07-28
|
39
|
+
|
40
|
+
* Fixed bug when cloning with no scope
|
41
|
+
* Allow the call of foreign pre-existing named scopes instead of those generated by searchlogic. Allows you to call named scopes on associations that you define yourself.
|
42
|
+
|
43
|
+
== 2.1.8 released 2009-07-15
|
44
|
+
|
45
|
+
* Added support for not_like, not_begin_with, not_end_with, and not_null
|
46
|
+
|
47
|
+
== 2.1.7 released 2009-07-14
|
48
|
+
|
49
|
+
>>>>>>> searchlogic/master:CHANGELOG.rdoc
|
3
50
|
* Add support for time zones in the Search class when type casting to Time objects.
|
4
51
|
|
5
52
|
== 2.1.6 released 2009-07-13
|
data/README.rdoc
CHANGED
@@ -9,8 +9,6 @@ Changes made:
|
|
9
9
|
|
10
10
|
= Searchlogic
|
11
11
|
|
12
|
-
<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>
|
13
|
-
|
14
12
|
Searchlogic provides common named scopes and object based searching for ActiveRecord.
|
15
13
|
|
16
14
|
== Helpful links
|
@@ -94,7 +92,7 @@ You also get named scopes for any of your associations:
|
|
94
92
|
User.ascend_by_order_total
|
95
93
|
User.descend_by_orders_line_items_price
|
96
94
|
|
97
|
-
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
|
95
|
+
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 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:
|
98
96
|
|
99
97
|
Benchmark.bm do |x|
|
100
98
|
x.report { 10.times { Event.tickets_id_gt(10).all(:include => :tickets) } }
|
@@ -108,7 +106,7 @@ If you want to use the :include option, just specify it:
|
|
108
106
|
|
109
107
|
User.orders_line_items_price_greater_than(20).all(:include => {:orders => :line_items})
|
110
108
|
|
111
|
-
Obviously, only do this if you want to actually use the included objects.
|
109
|
+
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.
|
112
110
|
|
113
111
|
== Make searching and ordering data in your application trivial
|
114
112
|
|
@@ -201,7 +199,7 @@ Now just throw it in your form:
|
|
201
199
|
= f.check_box :four_year_olds
|
202
200
|
= f.submit
|
203
201
|
|
204
|
-
|
202
|
+
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.
|
205
203
|
|
206
204
|
== Use any or all
|
207
205
|
|
@@ -224,7 +222,7 @@ If you don't like will_paginate, use another solution, or roll your own. Paginat
|
|
224
222
|
|
225
223
|
== Conflicts with other gems
|
226
224
|
|
227
|
-
You will notice searchlogic wants to create a method called "search". So do other libraries like thinking
|
225
|
+
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
|
228
226
|
|
229
227
|
User.search
|
230
228
|
|
data/Rakefile
CHANGED
@@ -6,6 +6,7 @@ begin
|
|
6
6
|
Jeweler::Tasks.new do |gem|
|
7
7
|
gem.name = "searchlogic"
|
8
8
|
gem.summary = "Searchlogic provides common named scopes and object based searching for ActiveRecord."
|
9
|
+
gem.description = "Searchlogic provides common named scopes and object based searching for ActiveRecord."
|
9
10
|
gem.email = "bjohnson@binarylogic.com"
|
10
11
|
gem.homepage = "http://github.com/binarylogic/searchlogic"
|
11
12
|
gem.authors = ["Ben Johnson of Binary Logic"]
|
data/VERSION.yml
CHANGED
data/lib/searchlogic.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require "searchlogic/core_ext/proc"
|
2
2
|
require "searchlogic/core_ext/object"
|
3
|
-
require "searchlogic/
|
3
|
+
require "searchlogic/active_record/consistency"
|
4
|
+
require "searchlogic/active_record/named_scopes"
|
4
5
|
require "searchlogic/named_scopes/conditions"
|
5
6
|
require "searchlogic/named_scopes/ordering"
|
6
7
|
require "searchlogic/named_scopes/association_conditions"
|
@@ -10,6 +11,17 @@ require "searchlogic/search"
|
|
10
11
|
|
11
12
|
Proc.send(:include, Searchlogic::CoreExt::Proc)
|
12
13
|
Object.send(:include, Searchlogic::CoreExt::Object)
|
14
|
+
|
15
|
+
module ActiveRecord # :nodoc: all
|
16
|
+
class Base
|
17
|
+
class << self
|
18
|
+
include Searchlogic::ActiveRecord::Consistency
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
ActiveRecord::Base.extend(Searchlogic::ActiveRecord::NamedScopes)
|
24
|
+
|
13
25
|
ActiveRecord::Base.extend(Searchlogic::NamedScopes::Conditions)
|
14
26
|
ActiveRecord::Base.extend(Searchlogic::NamedScopes::Ordering)
|
15
27
|
ActiveRecord::Base.extend(Searchlogic::NamedScopes::AssociationConditions)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Searchlogic
|
2
|
+
module ActiveRecord
|
3
|
+
# Active Record is pretty inconsistent with how their SQL is constructed. This
|
4
|
+
# method attempts to close the gap between the various inconsistencies.
|
5
|
+
module Consistency
|
6
|
+
def self.included(klass)
|
7
|
+
klass.class_eval do
|
8
|
+
alias_method_chain :merge_joins, :searchlogic
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# In AR multiple joins are sometimes in a single join query, and other times they
|
13
|
+
# are not. The merge_joins method in AR should account for this, but it doesn't.
|
14
|
+
# This fixes that problem. This way there is one join per string, which allows
|
15
|
+
# the merge_joins method to delete duplicates.
|
16
|
+
def merge_joins_with_searchlogic(*args)
|
17
|
+
joins = merge_joins_without_searchlogic(*args)
|
18
|
+
joins.collect { |j| j.is_a?(String) ? j.split(" ") : j }.flatten.uniq
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Searchlogic
|
2
|
+
module ActiveRecord
|
3
|
+
# Adds methods that give extra information about a classes named scopes.
|
4
|
+
module NamedScopes
|
5
|
+
# Retrieves the options passed when creating the respective named scope. Ex:
|
6
|
+
#
|
7
|
+
# named_scope :whatever, :conditions => {:column => value}
|
8
|
+
#
|
9
|
+
# This method will return:
|
10
|
+
#
|
11
|
+
# :conditions => {:column => value}
|
12
|
+
#
|
13
|
+
# ActiveRecord hides this internally in a Proc, so we have to try and pull it out with this
|
14
|
+
# method.
|
15
|
+
def named_scope_options(name)
|
16
|
+
key = scopes.key?(name.to_sym) ? name.to_sym : primary_condition_name(name)
|
17
|
+
|
18
|
+
if key
|
19
|
+
eval("options", scopes[key].binding)
|
20
|
+
else
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# The arity for a named scope's proc is important, because we use the arity
|
26
|
+
# to determine if the condition should be ignored when calling the search method.
|
27
|
+
# If the condition is false and the arity is 0, then we skip it all together. Ex:
|
28
|
+
#
|
29
|
+
# User.named_scope :age_is_4, :conditions => {:age => 4}
|
30
|
+
# User.search(:age_is_4 => false) == User.all
|
31
|
+
# User.search(:age_is_4 => true) == User.all(:conditions => {:age => 4})
|
32
|
+
#
|
33
|
+
# We also use it when trying to "copy" the underlying named scope for association
|
34
|
+
# conditions. This way our aliased scope accepts the same number of parameters for
|
35
|
+
# the underlying scope.
|
36
|
+
def named_scope_arity(name)
|
37
|
+
options = named_scope_options(name)
|
38
|
+
options.respond_to?(:arity) ? options.arity : nil
|
39
|
+
end
|
40
|
+
|
41
|
+
# A convenience method for creating inner join sql to that your inner joins
|
42
|
+
# are consistent with how Active Record creates them. Basically a tool for
|
43
|
+
# you to use when writing your own named scopes. This way you know for sure
|
44
|
+
# that duplicate joins will be removed when chaining scopes together that
|
45
|
+
# use the same join.
|
46
|
+
def inner_joins(association_name)
|
47
|
+
::ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, association_name, nil).join_associations.collect { |assoc| assoc.association_join }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -27,8 +27,10 @@ module Searchlogic
|
|
27
27
|
#
|
28
28
|
# named_scope :id_gt, searchlogic_lambda(:integer) { |value| {:conditions => ["id > ?", value]} }
|
29
29
|
#
|
30
|
-
# If you are wanting a string, you don't have to do anything, because Searchlogic assumes you
|
31
|
-
# If you want something else, you need to specify it as I did in the above example.
|
30
|
+
# If you are wanting a string, you don't have to do anything, because Searchlogic assumes you want a string.
|
31
|
+
# If you want something else, you need to specify it as I did in the above example. Comments are appreciated
|
32
|
+
# on this, if you know of a better solution please let me know. But this is the best I could come up with,
|
33
|
+
# without being intrusive and altering default behavior.
|
32
34
|
def searchlogic_lambda(type = :string, &block)
|
33
35
|
proc = lambda(&block)
|
34
36
|
proc.searchlogic_arg_type = type
|
@@ -13,17 +13,22 @@ module Searchlogic
|
|
13
13
|
|
14
14
|
WILDCARD_CONDITIONS = {
|
15
15
|
:like => [:contains, :includes],
|
16
|
+
:not_like => [],
|
16
17
|
:begins_with => [:bw],
|
18
|
+
:not_begin_with => [:does_not_begin_with],
|
17
19
|
:ends_with => [:ew],
|
20
|
+
:not_end_with => [:does_not_end_with]
|
18
21
|
}
|
19
22
|
|
20
23
|
BOOLEAN_CONDITIONS = {
|
21
24
|
:null => [:nil],
|
25
|
+
:not_null => [:not_nil],
|
22
26
|
:empty => []
|
23
27
|
}
|
24
28
|
|
25
29
|
CONDITIONS = {}
|
26
30
|
|
31
|
+
# Add any / all variations to every comparison and wildcard condition
|
27
32
|
COMPARISON_CONDITIONS.merge(WILDCARD_CONDITIONS).each do |condition, aliases|
|
28
33
|
CONDITIONS[condition] = aliases
|
29
34
|
CONDITIONS["#{condition}_any".to_sym] = aliases.collect { |a| "#{a}_any".to_sym }
|
@@ -35,41 +40,6 @@ module Searchlogic
|
|
35
40
|
PRIMARY_CONDITIONS = CONDITIONS.keys
|
36
41
|
ALIAS_CONDITIONS = CONDITIONS.values.flatten
|
37
42
|
|
38
|
-
# Retrieves the options passed when creating the respective named scope. Ex:
|
39
|
-
#
|
40
|
-
# named_scope :whatever, :conditions => {:column => value}
|
41
|
-
#
|
42
|
-
# This method will return:
|
43
|
-
#
|
44
|
-
# :conditions => {:column => value}
|
45
|
-
#
|
46
|
-
# ActiveRecord hides this internally, so we have to try and pull it out with this
|
47
|
-
# method.
|
48
|
-
def named_scope_options(name)
|
49
|
-
key = scopes.key?(name.to_sym) ? name.to_sym : primary_condition_name(name)
|
50
|
-
|
51
|
-
if key
|
52
|
-
eval("options", scopes[key])
|
53
|
-
else
|
54
|
-
nil
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
# The arity for a named scope's proc is important, because we use the arity
|
59
|
-
# to determine if the condition should be ignored when calling the search method.
|
60
|
-
# If the condition is false and the arity is 0, then we skip it all together. Ex:
|
61
|
-
#
|
62
|
-
# User.named_scope :age_is_4, :conditions => {:age => 4}
|
63
|
-
# User.search(:age_is_4 => false) == User.all
|
64
|
-
# User.search(:age_is_4 => true) == User.all(:conditions => {:age => 4})
|
65
|
-
#
|
66
|
-
# We also use it when trying to "copy" the underlying named scope for association
|
67
|
-
# conditions.
|
68
|
-
def named_scope_arity(name)
|
69
|
-
options = named_scope_options(name)
|
70
|
-
options.respond_to?(:arity) ? options.arity : nil
|
71
|
-
end
|
72
|
-
|
73
43
|
# Returns the primary condition for the given alias. Ex:
|
74
44
|
#
|
75
45
|
# primary_condition(:gt) => :greater_than
|
@@ -84,10 +54,12 @@ module Searchlogic
|
|
84
54
|
# primary_condition_name(:id_gt) => :id_greater_than
|
85
55
|
# primary_condition_name(:id_greater_than) => :id_greater_than
|
86
56
|
def primary_condition_name(name)
|
87
|
-
if
|
88
|
-
name.to_sym
|
89
|
-
|
90
|
-
|
57
|
+
if details = condition_details(name)
|
58
|
+
if PRIMARY_CONDITIONS.include?(name.to_sym)
|
59
|
+
name
|
60
|
+
else
|
61
|
+
"#{details[:column]}_#{primary_condition(details[:condition])}".to_sym
|
62
|
+
end
|
91
63
|
else
|
92
64
|
nil
|
93
65
|
end
|
@@ -116,26 +88,39 @@ module Searchlogic
|
|
116
88
|
end
|
117
89
|
|
118
90
|
private
|
91
|
+
def local_condition?(name)
|
92
|
+
return false if name.blank?
|
93
|
+
scope_names = scopes.keys.reject { |k| k == :scoped }
|
94
|
+
scope_names.include?(name.to_sym) || !condition_details(name).nil?
|
95
|
+
end
|
96
|
+
|
119
97
|
def method_missing(name, *args, &block)
|
120
|
-
if details =
|
121
|
-
|
122
|
-
send(name, *args)
|
123
|
-
elsif details = alias_condition_details(name)
|
124
|
-
create_alias_condition(details[:column], details[:condition], args)
|
98
|
+
if details = condition_details(name)
|
99
|
+
create_condition(details[:column], details[:condition], args)
|
125
100
|
send(name, *args)
|
126
101
|
else
|
127
102
|
super
|
128
103
|
end
|
129
104
|
end
|
130
105
|
|
131
|
-
def
|
132
|
-
if name.to_s =~ /^(#{column_names.join("|")})_(#{PRIMARY_CONDITIONS.join("|")})$/
|
106
|
+
def condition_details(name)
|
107
|
+
if name.to_s =~ /^(#{column_names.join("|")})_(#{(PRIMARY_CONDITIONS + ALIAS_CONDITIONS).join("|")})$/
|
133
108
|
{:column => $1, :condition => $2}
|
134
109
|
end
|
135
110
|
end
|
136
111
|
|
112
|
+
def create_condition(column, condition, args)
|
113
|
+
if PRIMARY_CONDITIONS.include?(condition.to_sym)
|
114
|
+
create_primary_condition(column, condition)
|
115
|
+
elsif ALIAS_CONDITIONS.include?(condition.to_sym)
|
116
|
+
create_alias_condition(column, condition, args)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
137
120
|
def create_primary_condition(column, condition)
|
138
121
|
column_type = columns_hash[column.to_s].type
|
122
|
+
match_keyword = ::ActiveRecord::Base.connection.adapter_name == "PostgreSQL" ? "ILIKE" : "LIKE"
|
123
|
+
|
139
124
|
scope_options = case condition.to_s
|
140
125
|
when /^equals/
|
141
126
|
scope_options(condition, column_type, "#{table_name}.#{column} = ?")
|
@@ -150,13 +135,21 @@ module Searchlogic
|
|
150
135
|
when /^greater_than/
|
151
136
|
scope_options(condition, column_type, "#{table_name}.#{column} > ?")
|
152
137
|
when /^like/
|
153
|
-
scope_options(condition, column_type, "#{table_name}.#{column}
|
138
|
+
scope_options(condition, column_type, "#{table_name}.#{column} #{match_keyword} ?", :like)
|
139
|
+
when /^not_like/
|
140
|
+
scope_options(condition, column_type, "#{table_name}.#{column} NOT #{match_keyword} ?", :like)
|
154
141
|
when /^begins_with/
|
155
|
-
scope_options(condition, column_type, "#{table_name}.#{column}
|
142
|
+
scope_options(condition, column_type, "#{table_name}.#{column} #{match_keyword} ?", :begins_with)
|
143
|
+
when /^not_begin_with/
|
144
|
+
scope_options(condition, column_type, "#{table_name}.#{column} NOT #{match_keyword} ?", :begins_with)
|
156
145
|
when /^ends_with/
|
157
|
-
scope_options(condition, column_type, "#{table_name}.#{column}
|
146
|
+
scope_options(condition, column_type, "#{table_name}.#{column} #{match_keyword} ?", :ends_with)
|
147
|
+
when /^not_end_with/
|
148
|
+
scope_options(condition, column_type, "#{table_name}.#{column} NOT #{match_keyword} ?", :ends_with)
|
158
149
|
when "null"
|
159
150
|
{:conditions => "#{table_name}.#{column} IS NULL"}
|
151
|
+
when "not_null"
|
152
|
+
{:conditions => "#{table_name}.#{column} IS NOT NULL"}
|
160
153
|
when "empty"
|
161
154
|
{:conditions => "#{table_name}.#{column} = ''"}
|
162
155
|
end
|
@@ -202,12 +195,6 @@ module Searchlogic
|
|
202
195
|
end
|
203
196
|
end
|
204
197
|
|
205
|
-
def alias_condition_details(name)
|
206
|
-
if name.to_s =~ /^(#{column_names.join("|")})_(#{ALIAS_CONDITIONS.join("|")})$/
|
207
|
-
{:column => $1, :condition => $2}
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
198
|
def create_alias_condition(column, condition, args)
|
212
199
|
primary_condition = primary_condition(condition)
|
213
200
|
alias_name = "#{column}_#{condition}"
|
@@ -9,27 +9,27 @@ module Searchlogic
|
|
9
9
|
def primary_condition_name(name) # :nodoc
|
10
10
|
if result = super
|
11
11
|
result
|
12
|
-
elsif
|
12
|
+
elsif ordering_condition?(name)
|
13
13
|
name.to_sym
|
14
14
|
else
|
15
15
|
nil
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
def order_condition?(name) # :nodoc:
|
20
|
-
!order_condition_details(name).nil?
|
21
|
-
end
|
22
|
-
|
23
19
|
private
|
20
|
+
def ordering_condition?(name) # :nodoc:
|
21
|
+
!ordering_condition_details(name).nil?
|
22
|
+
end
|
23
|
+
|
24
24
|
def method_missing(name, *args, &block)
|
25
25
|
if name == :order
|
26
26
|
named_scope name, lambda { |scope_name|
|
27
|
-
return {} if !
|
27
|
+
return {} if !condition?(scope_name)
|
28
28
|
send(scope_name).proxy_options
|
29
29
|
}
|
30
30
|
send(name, *args)
|
31
|
-
elsif details =
|
32
|
-
|
31
|
+
elsif details = ordering_condition_details(name)
|
32
|
+
create_ordering_conditions(details[:column])
|
33
33
|
send(name, *args)
|
34
34
|
else
|
35
35
|
super
|
@@ -44,7 +44,7 @@ module Searchlogic
|
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
|
-
def
|
47
|
+
def create_ordering_conditions(column)
|
48
48
|
named_scope("ascend_by_#{column}".to_sym, {:order => "#{table_name}.#{column} ASC"})
|
49
49
|
named_scope("descend_by_#{column}".to_sym, {:order => "#{table_name}.#{column} DESC"})
|
50
50
|
end
|
@@ -19,6 +19,7 @@ module ActionView
|
|
19
19
|
# * <tt>:as</tt> - the text used in the link, defaults to whatever is passed to :by
|
20
20
|
# * <tt>:ascend_scope</tt> - what scope to call for ascending the data, defaults to "ascend_by_:by"
|
21
21
|
# * <tt>:descend_scope</tt> - what scope to call for descending the data, defaults to "descend_by_:by"
|
22
|
+
# * <tt>:params</tt> - hash with additional params which will be added to generated url
|
22
23
|
# * <tt>:params_scope</tt> - the name of the params key to scope the order condition by, defaults to :search
|
23
24
|
# * <tt>:default_scope</tt> - either :asc or :desc, defaults to ascend_scope
|
24
25
|
# * <tt>:arrow</tt> - set to true if you want an ascii arrow showing the ordering
|
@@ -45,7 +46,10 @@ module ActionView
|
|
45
46
|
else
|
46
47
|
new_scope = (options[:default_scope] == :desc) ? options[:descend_scope] : options[:ascend_scope]
|
47
48
|
end
|
48
|
-
|
49
|
+
url_options = {
|
50
|
+
options[:params_scope] => search.conditions.merge( { :order => new_scope } )
|
51
|
+
}.deep_merge(options[:params] || {})
|
52
|
+
link_to options[:as], url_for(url_options), html_options
|
49
53
|
end
|
50
54
|
|
51
55
|
# Automatically makes the form method :get if a Searchlogic::Search and sets
|
@@ -73,4 +77,4 @@ module ActionView
|
|
73
77
|
end
|
74
78
|
|
75
79
|
end
|
76
|
-
end
|
80
|
+
end
|
data/lib/searchlogic/search.rb
CHANGED
@@ -17,7 +17,7 @@ module Searchlogic
|
|
17
17
|
class Search
|
18
18
|
# Responsible for adding a "search" method into your models.
|
19
19
|
module Implementation
|
20
|
-
# Additional method, gets
|
20
|
+
# Additional method, gets aliased as "search" if that method
|
21
21
|
# is available. A lot of other libraries like to use "search"
|
22
22
|
# as well, so if you have a conflict like this, you can use
|
23
23
|
# this method directly.
|
@@ -51,7 +51,7 @@ module Searchlogic
|
|
51
51
|
end
|
52
52
|
|
53
53
|
def clone
|
54
|
-
self.class.new(klass, current_scope.clone, conditions.clone)
|
54
|
+
self.class.new(klass, current_scope && current_scope.clone, conditions.clone)
|
55
55
|
end
|
56
56
|
|
57
57
|
# Returns a hash of the current conditions set.
|
@@ -141,11 +141,11 @@ module Searchlogic
|
|
141
141
|
else
|
142
142
|
# Let's leverage ActiveRecord's type casting, so that casting is consistent
|
143
143
|
# with the other models.
|
144
|
-
column_for_type_cast = ActiveRecord::ConnectionAdapters::Column.new("", nil)
|
144
|
+
column_for_type_cast = ::ActiveRecord::ConnectionAdapters::Column.new("", nil)
|
145
145
|
column_for_type_cast.instance_variable_set(:@type, type)
|
146
146
|
value = column_for_type_cast.type_cast(value)
|
147
147
|
Time.zone && value.is_a?(Time) ? value.in_time_zone : value
|
148
148
|
end
|
149
149
|
end
|
150
150
|
end
|
151
|
-
end
|
151
|
+
end
|
data/searchlogic.gemspec
CHANGED
@@ -2,11 +2,12 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{searchlogic}
|
5
|
-
s.version = "2.
|
5
|
+
s.version = "2.2.3.1"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Ben Johnson of Binary Logic"]
|
9
|
-
s.date = %q{2009-07-
|
9
|
+
s.date = %q{2009-07-31}
|
10
|
+
s.description = %q{Searchlogic provides common named scopes and object based searching for ActiveRecord.}
|
10
11
|
s.email = %q{bjohnson@binarylogic.com}
|
11
12
|
s.extra_rdoc_files = [
|
12
13
|
"LICENSE",
|
@@ -21,7 +22,8 @@ Gem::Specification.new do |s|
|
|
21
22
|
"VERSION.yml",
|
22
23
|
"init.rb",
|
23
24
|
"lib/searchlogic.rb",
|
24
|
-
"lib/searchlogic/
|
25
|
+
"lib/searchlogic/active_record/consistency.rb",
|
26
|
+
"lib/searchlogic/active_record/named_scopes.rb",
|
25
27
|
"lib/searchlogic/core_ext/object.rb",
|
26
28
|
"lib/searchlogic/core_ext/proc.rb",
|
27
29
|
"lib/searchlogic/named_scopes/alias_scope.rb",
|
@@ -47,7 +49,7 @@ Gem::Specification.new do |s|
|
|
47
49
|
s.rdoc_options = ["--charset=UTF-8"]
|
48
50
|
s.require_paths = ["lib"]
|
49
51
|
s.rubyforge_project = %q{searchlogic}
|
50
|
-
s.rubygems_version = %q{1.3.
|
52
|
+
s.rubygems_version = %q{1.3.5}
|
51
53
|
s.summary = %q{Searchlogic provides common named scopes and object based searching for ActiveRecord.}
|
52
54
|
s.test_files = [
|
53
55
|
"spec/core_ext/object_spec.rb",
|
@@ -1,6 +1,10 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
|
2
2
|
|
3
3
|
describe "AliasScope" do
|
4
|
+
before(:each) do
|
5
|
+
User.alias_scope :username_has, lambda { |value| User.username_like(value) }
|
6
|
+
end
|
7
|
+
|
4
8
|
it "should allow alias scopes" do
|
5
9
|
User.create(:username => "bjohnson")
|
6
10
|
User.create(:username => "thunt")
|
@@ -9,11 +9,35 @@ describe "Association Conditions" do
|
|
9
9
|
Company.users_orders_total_greater_than(10).proxy_options.should == Order.total_greater_than(10).proxy_options.merge(:joins => {:users => :orders})
|
10
10
|
end
|
11
11
|
|
12
|
-
it "should
|
12
|
+
it "should allow the use of foreign pre-existing named scopes" do
|
13
|
+
User.named_scope :uname, lambda { |value| {:conditions => ["users.username = ?", value]} }
|
14
|
+
Company.users_uname("bjohnson").proxy_options.should == User.uname("bjohnson").proxy_options.merge(:joins => :users)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should allow the use of deep foreign pre-existing named scopes" do
|
18
|
+
Order.named_scope :big_id, :conditions => "orders.id > 100"
|
19
|
+
Company.users_orders_big_id.proxy_options.should == Order.big_id.proxy_options.merge(:joins => {:users => :orders})
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should allow the use of foreign pre-existing alias scopes" do
|
23
|
+
User.alias_scope :username_has, lambda { |value| User.username_like(value) }
|
24
|
+
Company.users_username_has("bjohnson").proxy_options.should == User.username_has("bjohnson").proxy_options.merge(:joins => :users)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should not raise errors for scopes that don't return anything" do
|
28
|
+
User.alias_scope :blank_scope, lambda { |value| }
|
29
|
+
Company.users_blank_scope("bjohnson").proxy_options.should == {:joins => :users}
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should ignore polymorphic associations" do
|
33
|
+
lambda { Fee.owner_created_at_gt(Time.now) }.should raise_error(NoMethodError)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should not allow named scopes on non existent association columns" do
|
13
37
|
lambda { User.users_whatever_like("bjohnson") }.should raise_error(NoMethodError)
|
14
38
|
end
|
15
39
|
|
16
|
-
it "should not
|
40
|
+
it "should not allow named scopes on non existent deep association columns" do
|
17
41
|
lambda { User.users_orders_whatever_like("bjohnson") }.should raise_error(NoMethodError)
|
18
42
|
end
|
19
43
|
|
@@ -101,4 +125,9 @@ describe "Association Conditions" do
|
|
101
125
|
order = user.orders.create(:total => 20, :taxes => 3)
|
102
126
|
Company.users_orders_taxes_lt(50).ascend_by_users_orders_total.all(:include => {:users => :orders}).should == Company.all
|
103
127
|
end
|
128
|
+
|
129
|
+
it "should automatically add string joins if the association condition is using strings" do
|
130
|
+
User.named_scope(:orders_big_id, :joins => User.inner_joins(:orders))
|
131
|
+
Company.users_orders_big_id.proxy_options.should == {:joins=>[" INNER JOIN \"users\" ON users.company_id = companies.id ", " INNER JOIN \"orders\" ON orders.user_id = users.id "]}
|
132
|
+
end
|
104
133
|
end
|
@@ -49,15 +49,30 @@ describe "Conditions" do
|
|
49
49
|
User.username_like("john").all.should == User.find_all_by_username("bjohnson")
|
50
50
|
end
|
51
51
|
|
52
|
+
it "should have not like" do
|
53
|
+
%w(bjohnson thunt).each { |username| User.create(:username => username) }
|
54
|
+
User.username_not_like("john").all.should == User.find_all_by_username("thunt")
|
55
|
+
end
|
56
|
+
|
52
57
|
it "should have begins with" do
|
53
58
|
%w(bjohnson thunt).each { |username| User.create(:username => username) }
|
54
59
|
User.username_begins_with("bj").all.should == User.find_all_by_username("bjohnson")
|
55
60
|
end
|
56
61
|
|
62
|
+
it "should have not begin with" do
|
63
|
+
%w(bjohnson thunt).each { |username| User.create(:username => username) }
|
64
|
+
User.username_not_begin_with("bj").all.should == User.find_all_by_username("thunt")
|
65
|
+
end
|
66
|
+
|
57
67
|
it "should have ends with" do
|
58
68
|
%w(bjohnson thunt).each { |username| User.create(:username => username) }
|
59
69
|
User.username_ends_with("son").all.should == User.find_all_by_username("bjohnson")
|
60
70
|
end
|
71
|
+
|
72
|
+
it "should have not end with" do
|
73
|
+
%w(bjohnson thunt).each { |username| User.create(:username => username) }
|
74
|
+
User.username_not_end_with("son").all.should == User.find_all_by_username("thunt")
|
75
|
+
end
|
61
76
|
end
|
62
77
|
|
63
78
|
context "boolean conditions" do
|
@@ -66,6 +81,11 @@ describe "Conditions" do
|
|
66
81
|
User.username_null.all.should == User.find_all_by_username(nil)
|
67
82
|
end
|
68
83
|
|
84
|
+
it "should have not null" do
|
85
|
+
["bjohnson", nil].each { |username| User.create(:username => username) }
|
86
|
+
User.username_not_null.all.should == User.find_all_by_username("bjohnson")
|
87
|
+
end
|
88
|
+
|
69
89
|
it "should have empty" do
|
70
90
|
["bjohnson", ""].each { |username| User.create(:username => username) }
|
71
91
|
User.username_empty.all.should == User.find_all_by_username("")
|
@@ -183,9 +203,6 @@ describe "Conditions" do
|
|
183
203
|
end
|
184
204
|
|
185
205
|
it "should have is_not" do
|
186
|
-
# This is matching "not" first. How do you give priority in a regex? Because it's matching the
|
187
|
-
# 'not' condition and thinking the column is 'age_is'.
|
188
|
-
pending
|
189
206
|
User.age_is_not(5).proxy_options.should == User.age_does_not_equal(5).proxy_options
|
190
207
|
end
|
191
208
|
|
data/spec/search_spec.rb
CHANGED
@@ -40,6 +40,16 @@ describe "Search" do
|
|
40
40
|
search1.all.should == [user2]
|
41
41
|
end
|
42
42
|
|
43
|
+
it "should clone properly without scope" do
|
44
|
+
user1 = User.create(:age => 5)
|
45
|
+
user2 = User.create(:age => 25)
|
46
|
+
search1 = User.search(:age_gt => 10)
|
47
|
+
search2 = search1.clone
|
48
|
+
search2.age_gt = 1
|
49
|
+
search2.all.should == User.all
|
50
|
+
search1.all.should == [user2]
|
51
|
+
end
|
52
|
+
|
43
53
|
it "should delete the condition" do
|
44
54
|
search = User.search(:username_like => "bjohnson")
|
45
55
|
search.delete("username_like")
|
@@ -99,6 +109,20 @@ describe "Search" do
|
|
99
109
|
search.orders_total_gt.should == 10
|
100
110
|
end
|
101
111
|
|
112
|
+
it "should allow setting pre-existing association conditions" do
|
113
|
+
User.named_scope :uname, lambda { |value| {:conditions => ["users.username = ?", value]} }
|
114
|
+
search = Company.search
|
115
|
+
search.users_uname = "bjohnson"
|
116
|
+
search.users_uname.should == "bjohnson"
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should allow setting pre-existing association alias conditions" do
|
120
|
+
User.alias_scope :username_has, lambda { |value| User.username_like(value) }
|
121
|
+
search = Company.search
|
122
|
+
search.users_username_has = "bjohnson"
|
123
|
+
search.users_username_has.should == "bjohnson"
|
124
|
+
end
|
125
|
+
|
102
126
|
it "should allow using custom conditions" do
|
103
127
|
User.named_scope(:four_year_olds, { :conditions => { :age => 4 } })
|
104
128
|
search = User.search
|
@@ -136,6 +160,11 @@ describe "Search" do
|
|
136
160
|
lambda { search.unknown = true }.should raise_error(Searchlogic::Search::UnknownConditionError)
|
137
161
|
end
|
138
162
|
|
163
|
+
it "should not allow setting conditions on sensitive methods" do
|
164
|
+
search = User.search
|
165
|
+
lambda { search.destroy = true }.should raise_error(Searchlogic::Search::UnknownConditionError)
|
166
|
+
end
|
167
|
+
|
139
168
|
it "should not use the ruby implementation of the id method" do
|
140
169
|
search = User.search
|
141
170
|
search.id.should be_nil
|
@@ -292,4 +321,4 @@ describe "Search" do
|
|
292
321
|
User.search(:order => "ascend_by_username").proxy_options.should == User.ascend_by_username.proxy_options
|
293
322
|
end
|
294
323
|
end
|
295
|
-
end
|
324
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -26,6 +26,12 @@ ActiveRecord::Schema.define(:version => 1) do
|
|
26
26
|
t.integer :age
|
27
27
|
end
|
28
28
|
|
29
|
+
create_table :carts do |t|
|
30
|
+
t.datetime :created_at
|
31
|
+
t.datetime :updated_at
|
32
|
+
t.integer :user_id
|
33
|
+
end
|
34
|
+
|
29
35
|
create_table :orders do |t|
|
30
36
|
t.datetime :created_at
|
31
37
|
t.datetime :updated_at
|
@@ -35,6 +41,14 @@ ActiveRecord::Schema.define(:version => 1) do
|
|
35
41
|
t.float :total
|
36
42
|
end
|
37
43
|
|
44
|
+
create_table :fees do |t|
|
45
|
+
t.datetime :created_at
|
46
|
+
t.datetime :updated_at
|
47
|
+
t.string :owner_type
|
48
|
+
t.integer :owner_id
|
49
|
+
t.float :cost
|
50
|
+
end
|
51
|
+
|
38
52
|
create_table :line_items do |t|
|
39
53
|
t.datetime :created_at
|
40
54
|
t.datetime :updated_at
|
@@ -56,7 +70,6 @@ Spec::Runner.configure do |config|
|
|
56
70
|
class User < ActiveRecord::Base
|
57
71
|
belongs_to :company, :counter_cache => true
|
58
72
|
has_many :orders, :dependent => :destroy
|
59
|
-
alias_scope :username_has, lambda { |value| username_like(value) }
|
60
73
|
end
|
61
74
|
|
62
75
|
class Order < ActiveRecord::Base
|
@@ -64,6 +77,10 @@ Spec::Runner.configure do |config|
|
|
64
77
|
has_many :line_items, :dependent => :destroy
|
65
78
|
end
|
66
79
|
|
80
|
+
class Fee < ActiveRecord::Base
|
81
|
+
belongs_to :owner, :polymorphic => true
|
82
|
+
end
|
83
|
+
|
67
84
|
class LineItem < ActiveRecord::Base
|
68
85
|
belongs_to :order
|
69
86
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: joost-searchlogic
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.2.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Johnson of Binary Logic
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-07-
|
12
|
+
date: 2009-07-31 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -22,7 +22,7 @@ dependencies:
|
|
22
22
|
- !ruby/object:Gem::Version
|
23
23
|
version: 2.0.0
|
24
24
|
version:
|
25
|
-
description:
|
25
|
+
description: Searchlogic provides common named scopes and object based searching for ActiveRecord.
|
26
26
|
email: bjohnson@binarylogic.com
|
27
27
|
executables: []
|
28
28
|
|
@@ -40,7 +40,8 @@ files:
|
|
40
40
|
- VERSION.yml
|
41
41
|
- init.rb
|
42
42
|
- lib/searchlogic.rb
|
43
|
-
- lib/searchlogic/
|
43
|
+
- lib/searchlogic/active_record/consistency.rb
|
44
|
+
- lib/searchlogic/active_record/named_scopes.rb
|
44
45
|
- lib/searchlogic/core_ext/object.rb
|
45
46
|
- lib/searchlogic/core_ext/proc.rb
|
46
47
|
- lib/searchlogic/named_scopes/alias_scope.rb
|
@@ -63,6 +64,7 @@ files:
|
|
63
64
|
- spec/spec_helper.rb
|
64
65
|
has_rdoc: false
|
65
66
|
homepage: http://github.com/binarylogic/searchlogic
|
67
|
+
licenses:
|
66
68
|
post_install_message:
|
67
69
|
rdoc_options:
|
68
70
|
- --charset=UTF-8
|
@@ -83,7 +85,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
83
85
|
requirements: []
|
84
86
|
|
85
87
|
rubyforge_project: searchlogic
|
86
|
-
rubygems_version: 1.
|
88
|
+
rubygems_version: 1.3.5
|
87
89
|
signing_key:
|
88
90
|
specification_version: 3
|
89
91
|
summary: Searchlogic provides common named scopes and object based searching for ActiveRecord.
|
@@ -1,27 +0,0 @@
|
|
1
|
-
module Searchlogic
|
2
|
-
# Active Record is pretty inconsistent with how their SQL is constructed. This
|
3
|
-
# method attempts to close the gap between the various inconsistencies.
|
4
|
-
module ActiveRecordConsistency
|
5
|
-
def self.included(klass)
|
6
|
-
klass.class_eval do
|
7
|
-
alias_method_chain :merge_joins, :searchlogic
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
|
-
# In AR multiple joins are sometimes in a single join query, and other time they
|
12
|
-
# are not. The merge_joins method in AR should account for this, but it doesn't.
|
13
|
-
# This fixes that problem.
|
14
|
-
def merge_joins_with_searchlogic(*args)
|
15
|
-
joins = merge_joins_without_searchlogic(*args)
|
16
|
-
joins.collect { |j| j.is_a?(String) ? j.split(" ") : j }.flatten.uniq
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
module ActiveRecord # :nodoc: all
|
22
|
-
class Base
|
23
|
-
class << self
|
24
|
-
include Searchlogic::ActiveRecordConsistency
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|