rd_searchlogic 3.0.0.rc
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/.gitignore +7 -0
- data/LICENSE +20 -0
- data/README.rdoc +308 -0
- data/Rakefile +42 -0
- data/VERSION.yml +5 -0
- data/init.rb +1 -0
- data/lib/searchlogic/active_record/association_proxy.rb +19 -0
- data/lib/searchlogic/active_record/consistency.rb +49 -0
- data/lib/searchlogic/active_record/named_scope_tools.rb +102 -0
- data/lib/searchlogic/core_ext/object.rb +43 -0
- data/lib/searchlogic/core_ext/proc.rb +17 -0
- data/lib/searchlogic/named_scopes/alias_scope.rb +67 -0
- data/lib/searchlogic/named_scopes/association_conditions.rb +163 -0
- data/lib/searchlogic/named_scopes/association_ordering.rb +44 -0
- data/lib/searchlogic/named_scopes/conditions.rb +232 -0
- data/lib/searchlogic/named_scopes/or_conditions.rb +141 -0
- data/lib/searchlogic/named_scopes/ordering.rb +74 -0
- data/lib/searchlogic/rails_helpers.rb +79 -0
- data/lib/searchlogic/search.rb +259 -0
- data/lib/searchlogic.rb +89 -0
- data/rails/init.rb +1 -0
- data/spec/searchlogic/active_record/association_proxy_spec.rb +23 -0
- data/spec/searchlogic/active_record/consistency_spec.rb +28 -0
- data/spec/searchlogic/core_ext/object_spec.rb +9 -0
- data/spec/searchlogic/core_ext/proc_spec.rb +8 -0
- data/spec/searchlogic/named_scopes/alias_scope_spec.rb +23 -0
- data/spec/searchlogic/named_scopes/association_conditions_spec.rb +221 -0
- data/spec/searchlogic/named_scopes/association_ordering_spec.rb +29 -0
- data/spec/searchlogic/named_scopes/conditions_spec.rb +321 -0
- data/spec/searchlogic/named_scopes/or_conditions_spec.rb +66 -0
- data/spec/searchlogic/named_scopes/ordering_spec.rb +34 -0
- data/spec/searchlogic/search_spec.rb +459 -0
- data/spec/spec_helper.rb +146 -0
- metadata +123 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
module Searchlogic
|
2
|
+
module NamedScopes
|
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.
|
9
|
+
module Ordering
|
10
|
+
def condition?(name) # :nodoc:
|
11
|
+
super || ordering_condition?(name)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.included(base)
|
15
|
+
#this overrides the 'new' method in any class including Ordering -- then when called, new overrides the 'order' method
|
16
|
+
#overriding is not a simple matter in Ruby because includes put the modules below the class in the superclass sequence
|
17
|
+
#this keeps the code cleaner by avoiding alias_method_chain (monkey patching)
|
18
|
+
(class << base; self; end).send(:include, ClassOverrideMethods)
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassOverrideMethods
|
22
|
+
def new(*args)
|
23
|
+
_inst = super
|
24
|
+
#this overrides the 'order' method
|
25
|
+
(class << _inst; self; end).send(:include, Searchlogic::NamedScopes::Ordering::InstanceOverrideMethods)
|
26
|
+
_inst
|
27
|
+
end
|
28
|
+
end
|
29
|
+
module InstanceOverrideMethods
|
30
|
+
def order(*args)
|
31
|
+
if condition?(args[0])
|
32
|
+
send(args[0])
|
33
|
+
else
|
34
|
+
super
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def ordering_condition?(name) # :nodoc:
|
41
|
+
!ordering_condition_details(name).nil?
|
42
|
+
end
|
43
|
+
|
44
|
+
def method_missing(name, *args, &block)
|
45
|
+
if name == :order
|
46
|
+
scope name, lambda { |scope_name|
|
47
|
+
return {} if !condition?(scope_name)
|
48
|
+
send(scope_name).proxy_options
|
49
|
+
}
|
50
|
+
send(name, *args)
|
51
|
+
end
|
52
|
+
if details = ordering_condition_details(name)
|
53
|
+
create_ordering_conditions(details[:column])
|
54
|
+
send(name, *args)
|
55
|
+
else
|
56
|
+
super
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def ordering_condition_details(name)
|
61
|
+
if name.to_s =~ /^(ascend|descend)_by_(#{column_names.join("|")})$/
|
62
|
+
{:order_as => $1, :column => $2}
|
63
|
+
elsif name.to_s =~ /^order$/
|
64
|
+
{}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def create_ordering_conditions(column)
|
69
|
+
scope("ascend_by_#{column}".to_sym, {:order => "#{table_name}.#{column} ASC"})
|
70
|
+
scope("descend_by_#{column}".to_sym, {:order => "#{table_name}.#{column} DESC"})
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Searchlogic
|
2
|
+
module RailsHelpers
|
3
|
+
# Creates a link that alternates between acending and descending. It basically
|
4
|
+
# alternates between calling 2 named scopes: "ascend_by_*" and "descend_by_*"
|
5
|
+
#
|
6
|
+
# By default Searchlogic gives you these named scopes for all of your columns, but
|
7
|
+
# if you wanted to create your own, it will work with those too.
|
8
|
+
#
|
9
|
+
# Examples:
|
10
|
+
#
|
11
|
+
# order @search, :by => :username
|
12
|
+
# order @search, :by => :created_at, :as => "Created"
|
13
|
+
#
|
14
|
+
# This helper accepts the following options:
|
15
|
+
#
|
16
|
+
# * <tt>:by</tt> - the name of the named scope. This helper will prepend this value with "ascend_by_" and "descend_by_"
|
17
|
+
# * <tt>:as</tt> - the text used in the link, defaults to whatever is passed to :by
|
18
|
+
# * <tt>:ascend_scope</tt> - what scope to call for ascending the data, defaults to "ascend_by_:by"
|
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
|
21
|
+
# * <tt>:params_scope</tt> - the name of the params key to scope the order condition by, defaults to :search
|
22
|
+
def order(search, options = {}, html_options = {})
|
23
|
+
options[:params_scope] ||= :search
|
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
|
28
|
+
options[:ascend_scope] ||= "ascend_by_#{options[:by]}"
|
29
|
+
options[:descend_scope] ||= "descend_by_#{options[:by]}"
|
30
|
+
ascending = search.order.to_s == options[:ascend_scope]
|
31
|
+
new_scope = ascending ? options[:descend_scope] : options[:ascend_scope]
|
32
|
+
selected = [options[:ascend_scope], options[:descend_scope]].include?(search.order.to_s)
|
33
|
+
if selected
|
34
|
+
css_classes = html_options[:class] ? html_options[:class].split(" ") : []
|
35
|
+
if ascending
|
36
|
+
options[:as] = raw("▲ #{options[:as]}")
|
37
|
+
css_classes << "ascending"
|
38
|
+
else
|
39
|
+
options[:as] = raw("▼ #{options[:as]}")
|
40
|
+
css_classes << "descending"
|
41
|
+
end
|
42
|
+
html_options[:class] = css_classes.join(" ")
|
43
|
+
end
|
44
|
+
url_options = {
|
45
|
+
options[:params_scope] => search.conditions.merge( { :order => new_scope } )
|
46
|
+
}.deep_merge(options[:params] || {})
|
47
|
+
|
48
|
+
options[:as] = raw(options[:as]) if defined?(RailsXss)
|
49
|
+
|
50
|
+
link_to options[:as], url_for(url_options), html_options
|
51
|
+
end
|
52
|
+
|
53
|
+
# Automatically makes the form method :get if a Searchlogic::Search and sets
|
54
|
+
# the params scope to :search
|
55
|
+
def form_for(*args, &block)
|
56
|
+
if search_obj = args.find { |arg| arg.is_a?(Searchlogic::Search) }
|
57
|
+
options = args.extract_options!
|
58
|
+
options[:html] ||= {}
|
59
|
+
options[:html][:method] ||= :get
|
60
|
+
options[:url] ||= url_for
|
61
|
+
args.unshift(:search) if args.first == search_obj
|
62
|
+
args << options
|
63
|
+
end
|
64
|
+
super
|
65
|
+
end
|
66
|
+
|
67
|
+
# Automatically adds an "order" hidden field in your form to preserve how the data
|
68
|
+
# is being ordered.
|
69
|
+
def fields_for(*args, &block)
|
70
|
+
if search_obj = args.find { |arg| arg.is_a?(Searchlogic::Search) }
|
71
|
+
args.unshift(:search) if args.first == search_obj
|
72
|
+
concat(content_tag("div", hidden_field_tag("#{args.first}[order]", search_obj.order)))
|
73
|
+
super
|
74
|
+
else
|
75
|
+
super
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,259 @@
|
|
1
|
+
module Searchlogic
|
2
|
+
# A class that acts like a model, creates attr_accessors for named_scopes, and then
|
3
|
+
# chains together everything when an "action" method is called. It basically makes
|
4
|
+
# implementing search forms in your application effortless:
|
5
|
+
#
|
6
|
+
# search = User.search
|
7
|
+
# search.username_like = "bjohnson"
|
8
|
+
# search.all
|
9
|
+
#
|
10
|
+
# Is equivalent to:
|
11
|
+
#
|
12
|
+
# User.search(:username_like => "bjohnson").all
|
13
|
+
#
|
14
|
+
# Is equivalent to:
|
15
|
+
#
|
16
|
+
# User.username_like("bjohnson").all
|
17
|
+
class Search
|
18
|
+
# Responsible for adding a "search" method into your models.
|
19
|
+
module Implementation
|
20
|
+
# Additional method, gets aliased as "search" if that method
|
21
|
+
# is available. A lot of other libraries like to use "search"
|
22
|
+
# as well, so if you have a conflict like this, you can use
|
23
|
+
# this method directly.
|
24
|
+
def searchlogic(conditions = {})
|
25
|
+
Search.new(self, self, conditions) #scope(:find), conditions)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Is an invalid condition is used this error will be raised. Ex:
|
30
|
+
#
|
31
|
+
# User.search(:unkown => true)
|
32
|
+
#
|
33
|
+
# Where unknown is not a valid named scope for the User model.
|
34
|
+
class UnknownConditionError < StandardError
|
35
|
+
def initialize(condition)
|
36
|
+
msg = "The #{condition} is not a valid condition. You may only use conditions that map to a named scope"
|
37
|
+
super(msg)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def do_search
|
42
|
+
@current_scope
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_accessor :klass, :current_scope, :conditions
|
46
|
+
undef :id if respond_to?(:id)
|
47
|
+
|
48
|
+
# Creates a new search object for the given class. Ex:
|
49
|
+
#
|
50
|
+
# Searchlogic::Search.new(User, {}, {:username_like => "bjohnson"})
|
51
|
+
def initialize(klass, current_scope, conditions = {})
|
52
|
+
self.klass = klass
|
53
|
+
self.current_scope = current_scope
|
54
|
+
@conditions ||= {}
|
55
|
+
self.conditions = conditions if conditions.is_a?(Hash)
|
56
|
+
end
|
57
|
+
|
58
|
+
def clone
|
59
|
+
self.class.new(klass, current_scope && current_scope.clone, conditions.clone)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns a hash of the current conditions set.
|
63
|
+
def conditions
|
64
|
+
mass_conditions.clone.merge(@conditions)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Accepts a hash of conditions.
|
68
|
+
def conditions=(values)
|
69
|
+
values.each do |condition, value|
|
70
|
+
# if a condition name ends with "(1i)", assume it's date / datetime
|
71
|
+
if condition =~ /(.*)\(1i\)$/
|
72
|
+
date_scope_name = $1
|
73
|
+
date_parts = (1..6).to_a.map do |idx|
|
74
|
+
values.delete("#{ date_scope_name }(#{ idx }i)")
|
75
|
+
end.reject{|s| s.blank? }.map{|s| s.to_i }
|
76
|
+
|
77
|
+
# did we get enough info to build a time?
|
78
|
+
if date_parts.length >= 3
|
79
|
+
values[date_scope_name] = Time.zone.local(*date_parts)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
values.each do |condition, value|
|
85
|
+
mass_conditions[condition.to_sym] = value
|
86
|
+
value.delete_if { |v| ignore_value?(v) } if value.is_a?(Array)
|
87
|
+
next if ignore_value?(value)
|
88
|
+
# value = Time.zone.parse(value) if value =~ /[\d\/.-]{10}/
|
89
|
+
@current_scope = @current_scope.send(condition, value)
|
90
|
+
send("#{condition}=", value)
|
91
|
+
@conditions[condition.to_sym] = value
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Delete a condition from the search. Since conditions map to named scopes,
|
96
|
+
# if a named scope accepts a parameter there is no way to actually delete
|
97
|
+
# the scope if you do not want it anymore. A nil value might be meaningful
|
98
|
+
# to that scope.
|
99
|
+
def delete(*names)
|
100
|
+
names.each do |name|
|
101
|
+
@conditions.delete(name.to_sym)
|
102
|
+
mass_conditions.delete(name)
|
103
|
+
end
|
104
|
+
self
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns the column we are currently ordering by
|
108
|
+
def ordering_by
|
109
|
+
order && order.to_s.gsub(/^(ascend|descend)_by_/, '')
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
def method_missing(name, *args, &block)
|
114
|
+
condition_name = condition_name(name)
|
115
|
+
scope_name = scope_name(condition_name)
|
116
|
+
|
117
|
+
|
118
|
+
if setter?(name)
|
119
|
+
if scope?(scope_name)
|
120
|
+
if args.size == 1
|
121
|
+
write_condition(
|
122
|
+
condition_name,
|
123
|
+
type_cast(
|
124
|
+
args.first,
|
125
|
+
cast_type(scope_name),
|
126
|
+
scope_options(scope_name).respond_to?(:searchlogic_options) ? scope_options(scope_name).searchlogic_options : {}
|
127
|
+
)
|
128
|
+
)
|
129
|
+
else
|
130
|
+
write_condition(condition_name, args)
|
131
|
+
end
|
132
|
+
else
|
133
|
+
raise UnknownConditionError.new(condition_name)
|
134
|
+
end
|
135
|
+
elsif scope?(scope_name) && args.size <= 1
|
136
|
+
if args.size == 0
|
137
|
+
read_condition(condition_name)
|
138
|
+
else
|
139
|
+
send("#{condition_name}=", *args)
|
140
|
+
self
|
141
|
+
end
|
142
|
+
else
|
143
|
+
scope = conditions_array.inject(klass.scoped(current_scope) || {}) do |scope, condition|
|
144
|
+
scope_name, value = condition
|
145
|
+
scope_name = normalize_scope_name(scope_name)
|
146
|
+
klass.send(scope_name, value) if !klass.respond_to?(scope_name)
|
147
|
+
arity = klass.named_scope_arity(scope_name)
|
148
|
+
|
149
|
+
if !arity || arity == 0
|
150
|
+
if value == true
|
151
|
+
scope.send(scope_name)
|
152
|
+
else
|
153
|
+
scope
|
154
|
+
end
|
155
|
+
elsif arity == -1
|
156
|
+
scope.send(scope_name, *(value.is_a?(Array) ? value : [value]))
|
157
|
+
else
|
158
|
+
scope.send(scope_name, value)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
scope.send(name, *args, &block)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# This is here as a hook to allow people to modify the order in which the conditions are called, for whatever reason.
|
166
|
+
def conditions_array
|
167
|
+
@conditions.to_a
|
168
|
+
end
|
169
|
+
|
170
|
+
def normalize_scope_name(scope_name)
|
171
|
+
case
|
172
|
+
when klass.scopes.key?(scope_name.to_sym) then scope_name.to_sym
|
173
|
+
when klass.column_names.include?(scope_name.to_s) then "#{scope_name}_equals".to_sym
|
174
|
+
else scope_name.to_sym
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def setter?(name)
|
179
|
+
!(name.to_s =~ /=$/).nil?
|
180
|
+
end
|
181
|
+
|
182
|
+
def condition_name(name)
|
183
|
+
condition = name.to_s.match(/(\w+)=?$/)
|
184
|
+
condition ? condition[1].to_sym : nil
|
185
|
+
end
|
186
|
+
|
187
|
+
def write_condition(name, value)
|
188
|
+
@conditions[name] = value
|
189
|
+
end
|
190
|
+
|
191
|
+
def read_condition(name)
|
192
|
+
@conditions[name]
|
193
|
+
end
|
194
|
+
|
195
|
+
def mass_conditions
|
196
|
+
@mass_conditions ||= {}
|
197
|
+
end
|
198
|
+
|
199
|
+
def scope_name(condition_name)
|
200
|
+
condition_name && normalize_scope_name(condition_name)
|
201
|
+
end
|
202
|
+
|
203
|
+
def scope?(scope_name)
|
204
|
+
klass.scopes.key?(scope_name) || klass.condition?(scope_name)
|
205
|
+
end
|
206
|
+
|
207
|
+
def scope_options(name)
|
208
|
+
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_scope_options
|
209
|
+
klass.named_scope_options(name)
|
210
|
+
end
|
211
|
+
|
212
|
+
def cast_type(name)
|
213
|
+
named_scope_options = scope_options(name)
|
214
|
+
arity = klass.named_scope_arity(name)
|
215
|
+
if !arity || arity == 0
|
216
|
+
:boolean
|
217
|
+
else
|
218
|
+
named_scope_options.respond_to?(:searchlogic_options) ? named_scope_options.searchlogic_options[:type] : :string
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def type_cast(value, type, options = {})
|
223
|
+
case value
|
224
|
+
when Array
|
225
|
+
value.collect { |v| type_cast(v, type) }
|
226
|
+
when Range
|
227
|
+
Range.new(type_cast(value.first, type), type_cast(value.last, type))
|
228
|
+
else
|
229
|
+
# Let's leverage ActiveRecord's type casting, so that casting is consistent
|
230
|
+
# with the other models.
|
231
|
+
column_for_type_cast = ::ActiveRecord::ConnectionAdapters::Column.new("", nil)
|
232
|
+
column_for_type_cast.instance_variable_set(:@type, type)
|
233
|
+
casted_value = column_for_type_cast.type_cast(value)
|
234
|
+
|
235
|
+
if Time.zone && casted_value.is_a?(Time)
|
236
|
+
if value.is_a?(String)
|
237
|
+
if options[:skip_conversion]
|
238
|
+
casted_value.utc
|
239
|
+
else
|
240
|
+
(casted_value + (Time.zone.utc_offset * -1)).in_time_zone
|
241
|
+
end
|
242
|
+
else
|
243
|
+
if options[:skip_conversion]
|
244
|
+
casted_value.utc
|
245
|
+
else
|
246
|
+
casted_value.in_time_zone
|
247
|
+
end
|
248
|
+
end
|
249
|
+
else
|
250
|
+
casted_value
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def ignore_value?(value)
|
256
|
+
(value.is_a?(String) && value.blank?) || (value.is_a?(Array) && value.empty?)
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
data/lib/searchlogic.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
require "searchlogic/core_ext/proc"
|
2
|
+
require "searchlogic/core_ext/object"
|
3
|
+
require "searchlogic/active_record/association_proxy"
|
4
|
+
require "searchlogic/active_record/consistency"
|
5
|
+
require "searchlogic/active_record/named_scope_tools"
|
6
|
+
require "searchlogic/named_scopes/conditions"
|
7
|
+
require "searchlogic/named_scopes/ordering"
|
8
|
+
require "searchlogic/named_scopes/association_conditions"
|
9
|
+
require "searchlogic/named_scopes/association_ordering"
|
10
|
+
require "searchlogic/named_scopes/alias_scope"
|
11
|
+
require "searchlogic/named_scopes/or_conditions"
|
12
|
+
require "searchlogic/search"
|
13
|
+
|
14
|
+
ActiveRecord::Relation.send(:include, Searchlogic::CoreExt::Proc)
|
15
|
+
Proc.send(:include, Searchlogic::CoreExt::Proc)
|
16
|
+
Object.send(:include, Searchlogic::CoreExt::Object)
|
17
|
+
|
18
|
+
module ActiveRecord # :nodoc: all
|
19
|
+
module Associations
|
20
|
+
class AssociationProxy
|
21
|
+
include Searchlogic::ActiveRecord::AssociationProxy
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# FIX: removed this to get tests running
|
26
|
+
# class Base
|
27
|
+
# class << self; include Searchlogic::ActiveRecord::Consistency; end
|
28
|
+
# end
|
29
|
+
end
|
30
|
+
|
31
|
+
m = Searchlogic::NamedScopes
|
32
|
+
[
|
33
|
+
Searchlogic::ActiveRecord::NamedScopeTools,
|
34
|
+
m::Conditions,
|
35
|
+
m::AssociationConditions,
|
36
|
+
m::AssociationOrdering,
|
37
|
+
m::Ordering,
|
38
|
+
# m::AliasScope
|
39
|
+
# m::OrConditions
|
40
|
+
].each do |_module|
|
41
|
+
ActiveRecord::Base.extend(_module)
|
42
|
+
ActiveRecord::Relation.send(:include, _module)
|
43
|
+
end
|
44
|
+
#ActiveRecord::Base.extend(Searchlogic::ActiveRecord::NamedScopeTools)
|
45
|
+
#ActiveRecord::Base.extend(Searchlogic::NamedScopes::Conditions)
|
46
|
+
|
47
|
+
#ActiveRecord::Base.extend(Searchlogic::NamedScopes::AssociationConditions)
|
48
|
+
#ActiveRecord::Base.extend(Searchlogic::NamedScopes::AssociationOrdering)
|
49
|
+
#ActiveRecord::Base.extend(Searchlogic::NamedScopes::Ordering)
|
50
|
+
#ActiveRecord::Base.extend(Searchlogic::NamedScopes::AliasScope)
|
51
|
+
#ActiveRecord::Base.extend(Searchlogic::NamedScopes::OrConditions)
|
52
|
+
ActiveRecord::Base.extend(Searchlogic::Search::Implementation)
|
53
|
+
|
54
|
+
|
55
|
+
# Try to use the search method, if it's available. Thinking sphinx and other plugins
|
56
|
+
# like to use that method as well.
|
57
|
+
if !ActiveRecord::Base.respond_to?(:search)
|
58
|
+
ActiveRecord::Base.class_eval { class << self; alias_method :search, :searchlogic; end }
|
59
|
+
end
|
60
|
+
|
61
|
+
if defined?(ActionController)
|
62
|
+
require "searchlogic/rails_helpers"
|
63
|
+
ActionController::Base.helper(Searchlogic::RailsHelpers)
|
64
|
+
end
|
65
|
+
|
66
|
+
#FIX: for bug in active_record 3.0.0.beta3 -- left over from Rails 2.3.x, and wasn't removed from attribute_condition method in ActiveRecord::Base
|
67
|
+
module ActiveRecord
|
68
|
+
module NamedScope
|
69
|
+
class Scope; end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
#FIX: proxy_options is used extensively throughout SL testing to determine that no conditions, ordering, etc. were set
|
74
|
+
#module ActiveRecord
|
75
|
+
# class Relation
|
76
|
+
# def proxy_options
|
77
|
+
# [:where_clauses, :order_clauses].inject({}) do |hash, method|
|
78
|
+
# clauses = send(method)
|
79
|
+
# unless clauses.empty?
|
80
|
+
# case method
|
81
|
+
# when :where_clauses then hash[:conditions] = clauses
|
82
|
+
# when :order_clauses then hash[:order] = clauses
|
83
|
+
# end
|
84
|
+
# end
|
85
|
+
# hash
|
86
|
+
# end
|
87
|
+
# end
|
88
|
+
# end
|
89
|
+
#end
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "searchlogic"
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../../spec_helper")
|
2
|
+
|
3
|
+
describe "Searchlogic::ActiveRecord::AssociationProxy" do
|
4
|
+
it "should call location conditions" do
|
5
|
+
company = Company.create
|
6
|
+
user = company.users.create(:username => "bjohnson")
|
7
|
+
company.users.send(:username_like, "bjohnson").should == [user]
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should call ordering conditions" do
|
11
|
+
company = Company.create
|
12
|
+
user = company.users.create(:username => "bjohnson")
|
13
|
+
company.users.send(:ascend_by_username).should == [user]
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should call 'or' conditions" do
|
17
|
+
company = Company.create
|
18
|
+
user = company.users.create(:username => "bjohnson")
|
19
|
+
company.users.send(:username_or_some_type_id_like, "bjohnson").should == [user]
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../../spec_helper")
|
2
|
+
|
3
|
+
describe Searchlogic::ActiveRecord::Consistency do
|
4
|
+
it "should merge joins with consistent conditions" do
|
5
|
+
user_group = UserGroup.create
|
6
|
+
user_group.users.user_groups_name_like("name").user_groups_id_gt(10).scope(:find)[:joins].should == [
|
7
|
+
"INNER JOIN \"user_groups_users\" ON \"user_groups_users\".user_id = \"users\".id",
|
8
|
+
"INNER JOIN \"user_groups\" ON \"user_groups\".id = \"user_groups_users\".user_group_id"
|
9
|
+
]
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should respect parenthesis when reordering conditions" do
|
13
|
+
joins = [
|
14
|
+
"INNER JOIN \"table\" ON (\"b\".user_id = \"a\".id)",
|
15
|
+
"INNER JOIN \"table\" ON (\"b\".id = \"a\".user_group_id)"
|
16
|
+
]
|
17
|
+
ActiveRecord::Base.send(:merge_joins, joins).should == [
|
18
|
+
"INNER JOIN \"table\" ON \"a\".id = \"b\".user_id",
|
19
|
+
"INNER JOIN \"table\" ON \"a\".user_group_id = \"b\".id"
|
20
|
+
]
|
21
|
+
end
|
22
|
+
|
23
|
+
it "shuold not convert joins to strings when delegating via associations" do
|
24
|
+
User.alias_scope :has_id_gt, lambda { User.id_gt(10).has_name.orders_id_gt(10) }
|
25
|
+
User.alias_scope :has_name, lambda { User.orders_created_at_after(Time.now).name_equals("ben").username_equals("ben") }
|
26
|
+
Company.users_has_id_gt.proxy_options[:joins].should == {:users=>[:orders]}
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../../spec_helper")
|
2
|
+
|
3
|
+
describe Searchlogic::CoreExt::Object do
|
4
|
+
it "should accept and pass the argument to the searchlogic_options" do
|
5
|
+
proc = searchlogic_lambda(:integer, :test => :value) {}
|
6
|
+
proc.searchlogic_options[:type].should == :integer
|
7
|
+
proc.searchlogic_options[:test].should == :value
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../../spec_helper")
|
2
|
+
|
3
|
+
describe Searchlogic::NamedScopes::AliasScope do
|
4
|
+
before(:each) do
|
5
|
+
User.alias_scope :username_has, lambda { |value| User.username_like(value) }
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should allow alias scopes" do
|
9
|
+
User.create(:username => "bjohnson")
|
10
|
+
User.create(:username => "thunt")
|
11
|
+
User.username_has("bjohnson").all.should == User.find_all_by_username("bjohnson")
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should allow alias scopes from the search object" do
|
15
|
+
search = User.search
|
16
|
+
search.username_has = "bjohnson"
|
17
|
+
search.username_has.should == "bjohnson"
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should inherit alias scopes from superclasses" do
|
21
|
+
Class.new(User).alias_scope?("username_has").should be_true
|
22
|
+
end
|
23
|
+
end
|