kazjote-searchlogic 2.1.9.3 → 2.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.rdoc +58 -1
- data/README.rdoc +101 -28
- data/Rakefile +4 -12
- data/VERSION.yml +2 -2
- data/lib/searchlogic/active_record/consistency.rb +22 -0
- data/lib/searchlogic/active_record/named_scopes.rb +60 -0
- data/lib/searchlogic/core_ext/object.rb +4 -2
- data/lib/searchlogic/named_scopes/alias_scope.rb +21 -17
- data/lib/searchlogic/named_scopes/association_conditions.rb +61 -77
- data/lib/searchlogic/named_scopes/association_ordering.rb +22 -6
- data/lib/searchlogic/named_scopes/conditions.rb +74 -103
- data/lib/searchlogic/named_scopes/or_conditions.rb +116 -0
- data/lib/searchlogic/named_scopes/ordering.rb +17 -22
- data/lib/searchlogic/rails_helpers.rb +5 -1
- data/lib/searchlogic/search.rb +47 -26
- data/lib/searchlogic.rb +14 -7
- data/searchlogic.gemspec +13 -6
- data/spec/named_scopes/alias_scope_spec.rb +4 -0
- data/spec/named_scopes/association_conditions_spec.rb +32 -2
- data/spec/named_scopes/association_ordering_spec.rb +8 -0
- data/spec/named_scopes/conditions_spec.rb +21 -3
- data/spec/named_scopes/or_conditions_spec.rb +36 -0
- data/spec/named_scopes/ordering_spec.rb +7 -0
- data/spec/search_spec.rb +35 -1
- data/spec/spec_helper.rb +31 -9
- metadata +10 -7
- data/lib/searchlogic/active_record_consistency.rb +0 -27
@@ -1,11 +1,27 @@
|
|
1
1
|
module Searchlogic
|
2
2
|
module NamedScopes
|
3
|
-
# Handles dynamically creating named scopes for associations
|
3
|
+
# Handles dynamically creating order named scopes for associations:
|
4
|
+
#
|
5
|
+
# User.has_many :orders
|
6
|
+
# Order.has_many :line_items
|
7
|
+
# LineItem
|
8
|
+
#
|
9
|
+
# User.ascend_by_orders_line_items_id
|
10
|
+
#
|
11
|
+
# See the README for a more detailed explanation.
|
4
12
|
module AssociationOrdering
|
13
|
+
def condition?(name) # :nodoc:
|
14
|
+
super || association_ordering_condition?(name)
|
15
|
+
end
|
16
|
+
|
5
17
|
private
|
18
|
+
def association_ordering_condition?(name)
|
19
|
+
!association_ordering_condition_details(name).nil?
|
20
|
+
end
|
21
|
+
|
6
22
|
def method_missing(name, *args, &block)
|
7
23
|
if details = association_ordering_condition_details(name)
|
8
|
-
create_association_ordering_condition(details[:association], details[:order_as], details[:
|
24
|
+
create_association_ordering_condition(details[:association], details[:order_as], details[:condition], args)
|
9
25
|
send(name, *args)
|
10
26
|
else
|
11
27
|
super
|
@@ -14,13 +30,13 @@ module Searchlogic
|
|
14
30
|
|
15
31
|
def association_ordering_condition_details(name)
|
16
32
|
associations = reflect_on_all_associations.collect { |assoc| assoc.name }
|
17
|
-
if
|
18
|
-
{:order_as => $1, :association => $2, :
|
33
|
+
if name.to_s =~ /^(ascend|descend)_by_(#{associations.join("|")})_(\w+)$/
|
34
|
+
{:order_as => $1, :association => $2, :condition => $3}
|
19
35
|
end
|
20
36
|
end
|
21
37
|
|
22
|
-
def create_association_ordering_condition(
|
23
|
-
named_scope("#{order_as}_by_#{
|
38
|
+
def create_association_ordering_condition(association, order_as, condition, args)
|
39
|
+
named_scope("#{order_as}_by_#{association}_#{condition}", association_condition_options(association, "#{order_as}_by_#{condition}", args))
|
24
40
|
end
|
25
41
|
end
|
26
42
|
end
|
@@ -1,6 +1,13 @@
|
|
1
1
|
module Searchlogic
|
2
2
|
module NamedScopes
|
3
|
-
# Handles dynamically creating named scopes for columns.
|
3
|
+
# Handles dynamically creating named scopes for columns. It allows you to do things like:
|
4
|
+
#
|
5
|
+
# User.first_name_like("ben")
|
6
|
+
# User.id_lt(10)
|
7
|
+
#
|
8
|
+
# Notice the constants in this class, they define which conditions Searchlogic provides.
|
9
|
+
#
|
10
|
+
# See the README for a more detailed explanation.
|
4
11
|
module Conditions
|
5
12
|
COMPARISON_CONDITIONS = {
|
6
13
|
:equals => [:is, :eq],
|
@@ -23,11 +30,14 @@ module Searchlogic
|
|
23
30
|
BOOLEAN_CONDITIONS = {
|
24
31
|
:null => [:nil],
|
25
32
|
:not_null => [:not_nil],
|
26
|
-
:empty => []
|
33
|
+
:empty => [],
|
34
|
+
:blank => [],
|
35
|
+
:not_blank => [:present]
|
27
36
|
}
|
28
37
|
|
29
38
|
CONDITIONS = {}
|
30
39
|
|
40
|
+
# Add any / all variations to every comparison and wildcard condition
|
31
41
|
COMPARISON_CONDITIONS.merge(WILDCARD_CONDITIONS).each do |condition, aliases|
|
32
42
|
CONDITIONS[condition] = aliases
|
33
43
|
CONDITIONS["#{condition}_any".to_sym] = aliases.collect { |a| "#{a}_any".to_sym }
|
@@ -39,112 +49,48 @@ module Searchlogic
|
|
39
49
|
PRIMARY_CONDITIONS = CONDITIONS.keys
|
40
50
|
ALIAS_CONDITIONS = CONDITIONS.values.flatten
|
41
51
|
|
42
|
-
# Retrieves the options passed when creating the respective named scope. Ex:
|
43
|
-
#
|
44
|
-
# named_scope :whatever, :conditions => {:column => value}
|
45
|
-
#
|
46
|
-
# This method will return:
|
47
|
-
#
|
48
|
-
# :conditions => {:column => value}
|
49
|
-
#
|
50
|
-
# ActiveRecord hides this internally, so we have to try and pull it out with this
|
51
|
-
# method.
|
52
|
-
def named_scope_options(name)
|
53
|
-
key = scopes.key?(name.to_sym) ? name.to_sym : primary_condition_name(name)
|
54
|
-
|
55
|
-
if key
|
56
|
-
eval("options", scopes[key])
|
57
|
-
else
|
58
|
-
nil
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
# The arity for a named scope's proc is important, because we use the arity
|
63
|
-
# to determine if the condition should be ignored when calling the search method.
|
64
|
-
# If the condition is false and the arity is 0, then we skip it all together. Ex:
|
65
|
-
#
|
66
|
-
# User.named_scope :age_is_4, :conditions => {:age => 4}
|
67
|
-
# User.search(:age_is_4 => false) == User.all
|
68
|
-
# User.search(:age_is_4 => true) == User.all(:conditions => {:age => 4})
|
69
|
-
#
|
70
|
-
# We also use it when trying to "copy" the underlying named scope for association
|
71
|
-
# conditions.
|
72
|
-
def named_scope_arity(name)
|
73
|
-
options = named_scope_options(name)
|
74
|
-
options.respond_to?(:arity) ? options.arity : nil
|
75
|
-
end
|
76
|
-
|
77
|
-
# Returns the primary condition for the given alias. Ex:
|
78
|
-
#
|
79
|
-
# primary_condition(:gt) => :greater_than
|
80
|
-
def primary_condition(alias_condition)
|
81
|
-
CONDITIONS.find { |k, v| k == alias_condition.to_sym || v.include?(alias_condition.to_sym) }.first
|
82
|
-
end
|
83
|
-
|
84
|
-
# Returns the primary name for any condition on a column. You can pass it
|
85
|
-
# a primary condition, alias condition, etc, and it will return the proper
|
86
|
-
# primary condition name. This helps simply logic throughout Searchlogic. Ex:
|
87
|
-
#
|
88
|
-
# primary_condition_name(:id_gt) => :id_greater_than
|
89
|
-
# primary_condition_name(:id_greater_than) => :id_greater_than
|
90
|
-
def primary_condition_name(name)
|
91
|
-
if primary_condition?(name)
|
92
|
-
name.to_sym
|
93
|
-
elsif details = alias_condition_details(name)
|
94
|
-
"#{details[:column]}_#{primary_condition(details[:condition])}".to_sym
|
95
|
-
else
|
96
|
-
nil
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
52
|
# Is the name of the method a valid condition that can be dynamically created?
|
101
53
|
def condition?(name)
|
102
54
|
local_condition?(name)
|
103
55
|
end
|
104
56
|
|
105
|
-
# Is the condition for a local column, not an association
|
106
|
-
def local_condition?(name)
|
107
|
-
primary_condition?(name) || alias_condition?(name)
|
108
|
-
end
|
109
|
-
|
110
|
-
# Is the name of the method a valid condition that can be dynamically created,
|
111
|
-
# AND is it a primary condition (not an alias). "greater_than" not "gt".
|
112
|
-
def primary_condition?(name)
|
113
|
-
!primary_condition_details(name).nil?
|
114
|
-
end
|
115
|
-
|
116
|
-
# Is the name of the method a valid condition that can be dynamically created,
|
117
|
-
# AND is it an alias condition. "gt" not "greater_than".
|
118
|
-
def alias_condition?(name)
|
119
|
-
!alias_condition_details(name).nil?
|
120
|
-
end
|
121
|
-
|
122
57
|
private
|
58
|
+
def local_condition?(name)
|
59
|
+
return false if name.blank?
|
60
|
+
scope_names = scopes.keys.reject { |k| k == :scoped }
|
61
|
+
scope_names.include?(name.to_sym) || !condition_details(name).nil?
|
62
|
+
end
|
63
|
+
|
123
64
|
def method_missing(name, *args, &block)
|
124
|
-
if details =
|
125
|
-
|
126
|
-
send(name, *args)
|
127
|
-
elsif details = alias_condition_details(name)
|
128
|
-
create_alias_condition(details[:column], details[:condition], args)
|
65
|
+
if details = condition_details(name)
|
66
|
+
create_condition(details[:column], details[:condition], args)
|
129
67
|
send(name, *args)
|
130
68
|
else
|
131
69
|
super
|
132
70
|
end
|
133
71
|
end
|
134
72
|
|
135
|
-
def
|
136
|
-
if name.to_s =~ /^(#{column_names.join("|")})_(#{PRIMARY_CONDITIONS.join("|")})$/
|
73
|
+
def condition_details(name)
|
74
|
+
if name.to_s =~ /^(#{column_names.join("|")})_(#{(PRIMARY_CONDITIONS + ALIAS_CONDITIONS).join("|")})$/
|
137
75
|
{:column => $1, :condition => $2}
|
138
76
|
end
|
139
77
|
end
|
140
78
|
|
79
|
+
def create_condition(column, condition, args)
|
80
|
+
if PRIMARY_CONDITIONS.include?(condition.to_sym)
|
81
|
+
create_primary_condition(column, condition)
|
82
|
+
elsif ALIAS_CONDITIONS.include?(condition.to_sym)
|
83
|
+
create_alias_condition(column, condition, args)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
141
87
|
def create_primary_condition(column, condition)
|
142
88
|
column_type = columns_hash[column.to_s].type
|
143
|
-
match_keyword = ActiveRecord::Base.connection.adapter_name == "PostgreSQL" ?
|
144
|
-
|
89
|
+
match_keyword = ::ActiveRecord::Base.connection.adapter_name == "PostgreSQL" ? "ILIKE" : "LIKE"
|
90
|
+
|
145
91
|
scope_options = case condition.to_s
|
146
92
|
when /^equals/
|
147
|
-
scope_options(condition, column_type, "#{table_name}.#{column}
|
93
|
+
scope_options(condition, column_type, lambda { |a| attribute_condition("#{table_name}.#{column}", a) })
|
148
94
|
when /^does_not_equal/
|
149
95
|
scope_options(condition, column_type, "#{table_name}.#{column} != ?")
|
150
96
|
when /^less_than_or_equal_to/
|
@@ -173,6 +119,10 @@ module Searchlogic
|
|
173
119
|
{:conditions => "#{table_name}.#{column} IS NOT NULL"}
|
174
120
|
when "empty"
|
175
121
|
{:conditions => "#{table_name}.#{column} = ''"}
|
122
|
+
when "blank"
|
123
|
+
{:conditions => "#{table_name}.#{column} = '' OR #{table_name}.#{column} IS NULL"}
|
124
|
+
when "not_blank"
|
125
|
+
{:conditions => "#{table_name}.#{column} != '' AND #{table_name}.#{column} IS NOT NULL"}
|
176
126
|
end
|
177
127
|
|
178
128
|
named_scope("#{column}_#{condition}".to_sym, scope_options)
|
@@ -186,20 +136,22 @@ module Searchlogic
|
|
186
136
|
when /_(any|all)$/
|
187
137
|
searchlogic_lambda(column_type) { |*values|
|
188
138
|
return {} if values.empty?
|
189
|
-
values
|
190
|
-
|
191
|
-
values_to_sub = nil
|
192
|
-
if value_modifier.nil?
|
193
|
-
values_to_sub = values
|
194
|
-
else
|
195
|
-
values_to_sub = values.collect { |value| value_with_modifier(value, value_modifier) }
|
196
|
-
end
|
139
|
+
values.flatten!
|
140
|
+
values.collect! { |value| value_with_modifier(value, value_modifier) }
|
197
141
|
|
198
142
|
join = $1 == "any" ? " OR " : " AND "
|
199
|
-
|
143
|
+
scope_sql = values.collect { |value| sql.is_a?(Proc) ? sql.call(value) : sql }.join(join)
|
144
|
+
|
145
|
+
{:conditions => [scope_sql, *expand_range_bind_variables(values)]}
|
200
146
|
}
|
201
147
|
else
|
202
|
-
searchlogic_lambda(column_type) { |
|
148
|
+
searchlogic_lambda(column_type) { |*values|
|
149
|
+
values.collect! { |value| value_with_modifier(value, value_modifier) }
|
150
|
+
|
151
|
+
scope_sql = sql.is_a?(Proc) ? sql.call(*values) : sql
|
152
|
+
|
153
|
+
{:conditions => [scope_sql, *expand_range_bind_variables(values)]}
|
154
|
+
}
|
203
155
|
end
|
204
156
|
end
|
205
157
|
|
@@ -216,12 +168,6 @@ module Searchlogic
|
|
216
168
|
end
|
217
169
|
end
|
218
170
|
|
219
|
-
def alias_condition_details(name)
|
220
|
-
if name.to_s =~ /^(#{column_names.join("|")})_(#{ALIAS_CONDITIONS.join("|")})$/
|
221
|
-
{:column => $1, :condition => $2}
|
222
|
-
end
|
223
|
-
end
|
224
|
-
|
225
171
|
def create_alias_condition(column, condition, args)
|
226
172
|
primary_condition = primary_condition(condition)
|
227
173
|
alias_name = "#{column}_#{condition}"
|
@@ -229,6 +175,31 @@ module Searchlogic
|
|
229
175
|
send(primary_name, *args) # go back to method_missing and make sure we create the method
|
230
176
|
(class << self; self; end).class_eval { alias_method alias_name, primary_name }
|
231
177
|
end
|
178
|
+
|
179
|
+
# Returns the primary condition for the given alias. Ex:
|
180
|
+
#
|
181
|
+
# primary_condition(:gt) => :greater_than
|
182
|
+
def primary_condition(alias_condition)
|
183
|
+
CONDITIONS.find { |k, v| k == alias_condition.to_sym || v.include?(alias_condition.to_sym) }.first
|
184
|
+
end
|
185
|
+
|
186
|
+
# Returns the primary name for any condition on a column. You can pass it
|
187
|
+
# a primary condition, alias condition, etc, and it will return the proper
|
188
|
+
# primary condition name. This helps simply logic throughout Searchlogic. Ex:
|
189
|
+
#
|
190
|
+
# primary_condition_name(:id_gt) => :id_greater_than
|
191
|
+
# primary_condition_name(:id_greater_than) => :id_greater_than
|
192
|
+
def primary_condition_name(name)
|
193
|
+
if details = condition_details(name)
|
194
|
+
if PRIMARY_CONDITIONS.include?(name.to_sym)
|
195
|
+
name
|
196
|
+
else
|
197
|
+
"#{details[:column]}_#{primary_condition(details[:condition])}".to_sym
|
198
|
+
end
|
199
|
+
else
|
200
|
+
nil
|
201
|
+
end
|
202
|
+
end
|
232
203
|
end
|
233
204
|
end
|
234
205
|
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module Searchlogic
|
2
|
+
module NamedScopes
|
3
|
+
# Handles dynamically creating named scopes for 'OR' conditions. Please see the README for a more
|
4
|
+
# detailed explanation.
|
5
|
+
module OrConditions
|
6
|
+
class NoConditionSpecifiedError < StandardError; end
|
7
|
+
class UnknownConditionError < StandardError; end
|
8
|
+
|
9
|
+
def condition?(name) # :nodoc:
|
10
|
+
super || or_condition?(name)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
def or_condition?(name)
|
15
|
+
!or_conditions(name).nil?
|
16
|
+
end
|
17
|
+
|
18
|
+
def method_missing(name, *args, &block)
|
19
|
+
if conditions = or_conditions(name)
|
20
|
+
create_or_condition(conditions, args)
|
21
|
+
(class << self; self; end).class_eval { alias_method name, conditions.join("_or_") } if !respond_to?(name)
|
22
|
+
send(name, *args)
|
23
|
+
else
|
24
|
+
super
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def or_conditions(name)
|
29
|
+
# First determine if we should even work on the name, we want to be as quick as possible
|
30
|
+
# with this.
|
31
|
+
if (parts = split_or_condition(name)).size > 1
|
32
|
+
conditions = interpolate_or_conditions(parts)
|
33
|
+
if conditions.any?
|
34
|
+
conditions
|
35
|
+
else
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def split_or_condition(name)
|
42
|
+
parts = name.to_s.split("_or_")
|
43
|
+
new_parts = []
|
44
|
+
parts.each do |part|
|
45
|
+
if part =~ /^equal_to(_any|_all)?$/
|
46
|
+
new_parts << new_parts.pop + "_or_equal_to"
|
47
|
+
else
|
48
|
+
new_parts << part
|
49
|
+
end
|
50
|
+
end
|
51
|
+
new_parts
|
52
|
+
end
|
53
|
+
|
54
|
+
# The purpose of this method is to convert the method name parts into actual condition names.
|
55
|
+
#
|
56
|
+
# Example:
|
57
|
+
#
|
58
|
+
# ["first_name", "last_name_like"]
|
59
|
+
# => ["first_name_like", "last_name_like"]
|
60
|
+
#
|
61
|
+
# ["id_gt", "first_name_begins_with", "last_name", "middle_name_like"]
|
62
|
+
# => ["id_gt", "first_name_begins_with", "last_name_like", "middle_name_like"]
|
63
|
+
#
|
64
|
+
# Basically if a column is specified without a condition the next condition in the list
|
65
|
+
# is what will be used. Once we are able to get a consistent list of conditions we can easily
|
66
|
+
# create a scope for it.
|
67
|
+
def interpolate_or_conditions(parts)
|
68
|
+
conditions = []
|
69
|
+
last_condition = nil
|
70
|
+
|
71
|
+
parts.reverse.each do |part|
|
72
|
+
if details = condition_details(part)
|
73
|
+
# We are a searchlogic defined scope
|
74
|
+
conditions << "#{details[:column]}_#{details[:condition]}"
|
75
|
+
last_condition = details[:condition]
|
76
|
+
elsif details = association_condition_details(part)
|
77
|
+
# pending, need to find the last condition
|
78
|
+
elsif local_condition?(part)
|
79
|
+
# We are a custom scope
|
80
|
+
conditions << part
|
81
|
+
elsif column_names.include?(part)
|
82
|
+
# we are a column, use the last condition
|
83
|
+
if last_condition.nil?
|
84
|
+
raise NoConditionSpecifiedError.new("The '#{part}' column doesn't know which condition to use, if you use an exact column " +
|
85
|
+
"name you need to specify a condition sometime after (ex: id_or_created_at_lt), where id would use the 'lt' condition.")
|
86
|
+
end
|
87
|
+
|
88
|
+
conditions << "#{part}_#{last_condition}"
|
89
|
+
else
|
90
|
+
raise UnknownConditionError.new("The condition '#{part}' is not a valid condition, we could not find any scopes that match this.")
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
conditions
|
95
|
+
end
|
96
|
+
|
97
|
+
def create_or_condition(scopes, args)
|
98
|
+
named_scope scopes.join("_or_"), lambda { |*args|
|
99
|
+
merge_scopes_with_or(scopes.collect { |scope| [scope, *args] })
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
def merge_scopes_with_or(scopes)
|
104
|
+
scopes_options = scopes.collect { |scope, *args| send(scope, *args).proxy_options }
|
105
|
+
conditions = scopes_options.reject { |o| o[:conditions].nil? }.collect { |o| sanitize_sql(o[:conditions]) }
|
106
|
+
|
107
|
+
scope = scopes.inject(scoped({})) do |scope, info|
|
108
|
+
scope_name, *args = info
|
109
|
+
scope.send(scope_name, *args)
|
110
|
+
end
|
111
|
+
|
112
|
+
scope.scope(:find).merge(:conditions => "(" + conditions.join(") OR (") + ")")
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -1,42 +1,37 @@
|
|
1
1
|
module Searchlogic
|
2
2
|
module NamedScopes
|
3
|
-
# Handles dynamically creating named scopes for
|
3
|
+
# Handles dynamically creating named scopes for ordering by columns. Example:
|
4
|
+
#
|
5
|
+
# User.ascend_by_id
|
6
|
+
# User.descend_by_username
|
7
|
+
#
|
8
|
+
# See the README for a more detailed explanation.
|
4
9
|
module Ordering
|
5
|
-
def
|
6
|
-
super ||
|
7
|
-
end
|
8
|
-
|
9
|
-
def primary_condition_name(name) # :nodoc
|
10
|
-
if result = super
|
11
|
-
result
|
12
|
-
elsif order_condition?(name)
|
13
|
-
name.to_sym
|
14
|
-
else
|
15
|
-
nil
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def order_condition?(name) # :nodoc:
|
20
|
-
!order_condition_details(name).nil?
|
10
|
+
def condition?(name) # :nodoc:
|
11
|
+
super || ordering_condition?(name)
|
21
12
|
end
|
22
13
|
|
23
14
|
private
|
15
|
+
def ordering_condition?(name) # :nodoc:
|
16
|
+
!ordering_condition_details(name).nil?
|
17
|
+
end
|
18
|
+
|
24
19
|
def method_missing(name, *args, &block)
|
25
20
|
if name == :order
|
26
21
|
named_scope name, lambda { |scope_name|
|
27
|
-
return {} if !
|
22
|
+
return {} if !condition?(scope_name)
|
28
23
|
send(scope_name).proxy_options
|
29
24
|
}
|
30
25
|
send(name, *args)
|
31
|
-
elsif details =
|
32
|
-
|
26
|
+
elsif details = ordering_condition_details(name)
|
27
|
+
create_ordering_conditions(details[:column])
|
33
28
|
send(name, *args)
|
34
29
|
else
|
35
30
|
super
|
36
31
|
end
|
37
32
|
end
|
38
33
|
|
39
|
-
def
|
34
|
+
def ordering_condition_details(name)
|
40
35
|
if name.to_s =~ /^(ascend|descend)_by_(#{column_names.join("|")})$/
|
41
36
|
{:order_as => $1, :column => $2}
|
42
37
|
elsif name.to_s =~ /^order$/
|
@@ -44,7 +39,7 @@ module Searchlogic
|
|
44
39
|
end
|
45
40
|
end
|
46
41
|
|
47
|
-
def
|
42
|
+
def create_ordering_conditions(column)
|
48
43
|
named_scope("ascend_by_#{column}".to_sym, {:order => "#{table_name}.#{column} ASC"})
|
49
44
|
named_scope("descend_by_#{column}".to_sym, {:order => "#{table_name}.#{column} DESC"})
|
50
45
|
end
|
@@ -17,10 +17,14 @@ module Searchlogic
|
|
17
17
|
# * <tt>:as</tt> - the text used in the link, defaults to whatever is passed to :by
|
18
18
|
# * <tt>:ascend_scope</tt> - what scope to call for ascending the data, defaults to "ascend_by_:by"
|
19
19
|
# * <tt>:descend_scope</tt> - what scope to call for descending the data, defaults to "descend_by_:by"
|
20
|
+
# * <tt>:params</tt> - hash with additional params which will be added to generated url
|
20
21
|
# * <tt>:params_scope</tt> - the name of the params key to scope the order condition by, defaults to :search
|
21
22
|
def order(search, options = {}, html_options = {})
|
22
23
|
options[:params_scope] ||= :search
|
23
|
-
options[:as]
|
24
|
+
if !options[:as]
|
25
|
+
id = options[:by].to_s.downcase == "id"
|
26
|
+
options[:as] = id ? options[:by].to_s.upcase : options[:by].to_s.humanize
|
27
|
+
end
|
24
28
|
options[:ascend_scope] ||= "ascend_by_#{options[:by]}"
|
25
29
|
options[:descend_scope] ||= "descend_by_#{options[:by]}"
|
26
30
|
ascending = search.order.to_s == options[:ascend_scope]
|
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.
|
@@ -25,7 +25,7 @@ module Searchlogic
|
|
25
25
|
Search.new(self, scope(:find), conditions)
|
26
26
|
end
|
27
27
|
end
|
28
|
-
|
28
|
+
|
29
29
|
# Is an invalid condition is used this error will be raised. Ex:
|
30
30
|
#
|
31
31
|
# User.search(:unkown => true)
|
@@ -37,28 +37,32 @@ module Searchlogic
|
|
37
37
|
super(msg)
|
38
38
|
end
|
39
39
|
end
|
40
|
-
|
41
|
-
attr_accessor :klass, :current_scope, :conditions
|
40
|
+
|
41
|
+
attr_accessor :klass, :current_scope, :conditions, :default_order
|
42
42
|
undef :id if respond_to?(:id)
|
43
|
-
|
43
|
+
|
44
44
|
# Creates a new search object for the given class. Ex:
|
45
45
|
#
|
46
46
|
# Searchlogic::Search.new(User, {}, {:username_like => "bjohnson"})
|
47
47
|
def initialize(klass, current_scope, conditions = {})
|
48
48
|
self.klass = klass
|
49
|
-
|
49
|
+
if current_scope
|
50
|
+
current_scope = current_scope.dup
|
51
|
+
self.default_order = current_scope.delete(:order)
|
52
|
+
self.current_scope = current_scope
|
53
|
+
end
|
50
54
|
self.conditions = conditions if conditions.is_a?(Hash)
|
51
55
|
end
|
52
|
-
|
56
|
+
|
53
57
|
def clone
|
54
58
|
self.class.new(klass, current_scope && current_scope.clone, conditions.clone)
|
55
59
|
end
|
56
|
-
|
60
|
+
|
57
61
|
# Returns a hash of the current conditions set.
|
58
62
|
def conditions
|
59
63
|
@conditions ||= {}
|
60
64
|
end
|
61
|
-
|
65
|
+
|
62
66
|
# Accepts a hash of conditions.
|
63
67
|
def conditions=(values)
|
64
68
|
values.each do |condition, value|
|
@@ -67,7 +71,7 @@ module Searchlogic
|
|
67
71
|
send("#{condition}=", value)
|
68
72
|
end
|
69
73
|
end
|
70
|
-
|
74
|
+
|
71
75
|
# Delete a condition from the search. Since conditions map to named scopes,
|
72
76
|
# if a named scope accepts a parameter there is no way to actually delete
|
73
77
|
# the scope if you do not want it anymore. A nil value might be meaningful
|
@@ -76,23 +80,24 @@ module Searchlogic
|
|
76
80
|
names.each { |name| @conditions.delete(name.to_sym) }
|
77
81
|
self
|
78
82
|
end
|
79
|
-
|
83
|
+
|
80
84
|
private
|
81
85
|
def method_missing(name, *args, &block)
|
82
|
-
|
83
|
-
|
84
|
-
|
86
|
+
condition_name = condition_name(name)
|
87
|
+
scope_name = scope_name(condition_name)
|
88
|
+
|
89
|
+
if setter?(name)
|
85
90
|
if scope?(scope_name)
|
86
|
-
conditions[
|
91
|
+
conditions[condition_name] = type_cast(args.first, cast_type(scope_name))
|
87
92
|
else
|
88
|
-
raise UnknownConditionError.new(
|
93
|
+
raise UnknownConditionError.new(condition_name)
|
89
94
|
end
|
90
|
-
elsif scope?(
|
95
|
+
elsif scope?(scope_name)
|
91
96
|
if args.size > 0
|
92
|
-
send("#{
|
97
|
+
send("#{condition_name}=", *args)
|
93
98
|
self
|
94
99
|
else
|
95
|
-
conditions[
|
100
|
+
conditions[condition_name]
|
96
101
|
end
|
97
102
|
else
|
98
103
|
scope = conditions.inject(klass.scoped(current_scope)) do |scope, condition|
|
@@ -100,7 +105,7 @@ module Searchlogic
|
|
100
105
|
scope_name = normalize_scope_name(scope_name)
|
101
106
|
klass.send(scope_name, value) if !klass.respond_to?(scope_name)
|
102
107
|
arity = klass.named_scope_arity(scope_name)
|
103
|
-
|
108
|
+
|
104
109
|
if !arity || arity == 0
|
105
110
|
if value == true
|
106
111
|
scope.send(scope_name)
|
@@ -111,18 +116,33 @@ module Searchlogic
|
|
111
116
|
scope.send(scope_name, value)
|
112
117
|
end
|
113
118
|
end
|
119
|
+
scope = scope.scoped(:order => default_order) if
|
120
|
+
default_order && !scope.proxy_options.has_key?(:order)
|
114
121
|
scope.send(name, *args, &block)
|
115
122
|
end
|
116
123
|
end
|
117
|
-
|
124
|
+
|
118
125
|
def normalize_scope_name(scope_name)
|
119
126
|
klass.column_names.include?(scope_name.to_s) ? "#{scope_name}_equals".to_sym : scope_name.to_sym
|
120
127
|
end
|
121
|
-
|
128
|
+
|
129
|
+
def setter?(name)
|
130
|
+
!(name.to_s =~ /=$/).nil?
|
131
|
+
end
|
132
|
+
|
133
|
+
def condition_name(name)
|
134
|
+
condition = name.to_s.match(/(\w+)=?$/)[1]
|
135
|
+
condition ? condition.to_sym : nil
|
136
|
+
end
|
137
|
+
|
138
|
+
def scope_name(condition_name)
|
139
|
+
condition_name && normalize_scope_name(condition_name)
|
140
|
+
end
|
141
|
+
|
122
142
|
def scope?(scope_name)
|
123
143
|
klass.scopes.key?(scope_name) || klass.condition?(scope_name)
|
124
144
|
end
|
125
|
-
|
145
|
+
|
126
146
|
def cast_type(name)
|
127
147
|
klass.send(name, nil) if !klass.respond_to?(name) # We need to set up the named scope if it doesn't exist, so we can get a value for named_ssope_options
|
128
148
|
named_scope_options = klass.named_scope_options(name)
|
@@ -133,7 +153,7 @@ module Searchlogic
|
|
133
153
|
named_scope_options.respond_to?(:searchlogic_arg_type) ? named_scope_options.searchlogic_arg_type : :string
|
134
154
|
end
|
135
155
|
end
|
136
|
-
|
156
|
+
|
137
157
|
def type_cast(value, type)
|
138
158
|
case value
|
139
159
|
when Array
|
@@ -141,11 +161,12 @@ module Searchlogic
|
|
141
161
|
else
|
142
162
|
# Let's leverage ActiveRecord's type casting, so that casting is consistent
|
143
163
|
# with the other models.
|
144
|
-
column_for_type_cast = ActiveRecord::ConnectionAdapters::Column.new("", nil)
|
164
|
+
column_for_type_cast = ::ActiveRecord::ConnectionAdapters::Column.new("", nil)
|
145
165
|
column_for_type_cast.instance_variable_set(:@type, type)
|
146
166
|
value = column_for_type_cast.type_cast(value)
|
147
167
|
Time.zone && value.is_a?(Time) ? value.in_time_zone : value
|
148
168
|
end
|
149
169
|
end
|
150
170
|
end
|
151
|
-
end
|
171
|
+
end
|
172
|
+
|