searchlogic-heroku 2.4.19
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 +35 -0
- data/VERSION.yml +5 -0
- data/init.rb +1 -0
- data/lib/searchlogic.rb +56 -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 +101 -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 +131 -0
- data/lib/searchlogic/named_scopes/association_ordering.rb +44 -0
- data/lib/searchlogic/named_scopes/conditions.rb +226 -0
- data/lib/searchlogic/named_scopes/or_conditions.rb +141 -0
- data/lib/searchlogic/named_scopes/ordering.rb +48 -0
- data/lib/searchlogic/rails_helpers.rb +79 -0
- data/lib/searchlogic/search.rb +251 -0
- data/rails/init.rb +1 -0
- data/searchlogic.gemspec +89 -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 +198 -0
- data/spec/searchlogic/named_scopes/association_ordering_spec.rb +27 -0
- data/spec/searchlogic/named_scopes/conditions_spec.rb +319 -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 +132 -0
- metadata +128 -0
@@ -0,0 +1,251 @@
|
|
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, 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
|
+
attr_accessor :klass, :current_scope, :conditions
|
42
|
+
undef :id if respond_to?(:id)
|
43
|
+
|
44
|
+
# Creates a new search object for the given class. Ex:
|
45
|
+
#
|
46
|
+
# Searchlogic::Search.new(User, {}, {:username_like => "bjohnson"})
|
47
|
+
def initialize(klass, current_scope, conditions = {})
|
48
|
+
self.klass = klass
|
49
|
+
self.current_scope = current_scope
|
50
|
+
@conditions ||= {}
|
51
|
+
self.conditions = conditions if conditions.is_a?(Hash)
|
52
|
+
end
|
53
|
+
|
54
|
+
def clone
|
55
|
+
self.class.new(klass, current_scope && current_scope.clone, conditions.clone)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns a hash of the current conditions set.
|
59
|
+
def conditions
|
60
|
+
mass_conditions.clone.merge(@conditions)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Accepts a hash of conditions.
|
64
|
+
def conditions=(values)
|
65
|
+
values.each do |condition, value|
|
66
|
+
# if a condition name ends with "(1i)", assume it's date / datetime
|
67
|
+
if condition =~ /(.*)\(1i\)$/
|
68
|
+
date_scope_name = $1
|
69
|
+
date_parts = (1..6).to_a.map do |idx|
|
70
|
+
values.delete("#{ date_scope_name }(#{ idx }i)")
|
71
|
+
end.reject{|s| s.blank? }.map{|s| s.to_i }
|
72
|
+
|
73
|
+
# did we get enough info to build a time?
|
74
|
+
if date_parts.length >= 3
|
75
|
+
values[date_scope_name] = Time.zone.local(*date_parts)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
values.each do |condition, value|
|
81
|
+
mass_conditions[condition.to_sym] = value
|
82
|
+
value.delete_if { |v| ignore_value?(v) } if value.is_a?(Array)
|
83
|
+
next if ignore_value?(value)
|
84
|
+
send("#{condition}=", value)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Delete a condition from the search. Since conditions map to named scopes,
|
89
|
+
# if a named scope accepts a parameter there is no way to actually delete
|
90
|
+
# the scope if you do not want it anymore. A nil value might be meaningful
|
91
|
+
# to that scope.
|
92
|
+
def delete(*names)
|
93
|
+
names.each do |name|
|
94
|
+
@conditions.delete(name.to_sym)
|
95
|
+
mass_conditions.delete(name)
|
96
|
+
end
|
97
|
+
self
|
98
|
+
end
|
99
|
+
|
100
|
+
# Returns the column we are currently ordering by
|
101
|
+
def ordering_by
|
102
|
+
order && order.to_s.gsub(/^(ascend|descend)_by_/, '')
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
def method_missing(name, *args, &block)
|
107
|
+
condition_name = condition_name(name)
|
108
|
+
scope_name = scope_name(condition_name)
|
109
|
+
|
110
|
+
if setter?(name)
|
111
|
+
if scope?(scope_name)
|
112
|
+
if args.size == 1
|
113
|
+
write_condition(
|
114
|
+
condition_name,
|
115
|
+
type_cast(
|
116
|
+
args.first,
|
117
|
+
cast_type(scope_name),
|
118
|
+
scope_options(scope_name).respond_to?(:searchlogic_options) ? scope_options(scope_name).searchlogic_options : {}
|
119
|
+
)
|
120
|
+
)
|
121
|
+
else
|
122
|
+
write_condition(condition_name, args)
|
123
|
+
end
|
124
|
+
else
|
125
|
+
raise UnknownConditionError.new(condition_name)
|
126
|
+
end
|
127
|
+
elsif scope?(scope_name) && args.size <= 1
|
128
|
+
if args.size == 0
|
129
|
+
read_condition(condition_name)
|
130
|
+
else
|
131
|
+
send("#{condition_name}=", *args)
|
132
|
+
self
|
133
|
+
end
|
134
|
+
else
|
135
|
+
scope = conditions_array.inject(klass.scoped(current_scope) || {}) do |scope, condition|
|
136
|
+
scope_name, value = condition
|
137
|
+
scope_name = normalize_scope_name(scope_name)
|
138
|
+
klass.send(scope_name, value) if !klass.respond_to?(scope_name)
|
139
|
+
arity = klass.named_scope_arity(scope_name)
|
140
|
+
|
141
|
+
if !arity || arity == 0
|
142
|
+
if value == true
|
143
|
+
scope.send(scope_name)
|
144
|
+
else
|
145
|
+
scope
|
146
|
+
end
|
147
|
+
elsif arity == -1
|
148
|
+
scope.send(scope_name, *(value.is_a?(Array) ? value : [value]))
|
149
|
+
else
|
150
|
+
scope.send(scope_name, value)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
scope.send(name, *args, &block)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# This is here as a hook to allow people to modify the order in which the conditions are called, for whatever reason.
|
158
|
+
def conditions_array
|
159
|
+
@conditions.to_a
|
160
|
+
end
|
161
|
+
|
162
|
+
def normalize_scope_name(scope_name)
|
163
|
+
case
|
164
|
+
when klass.scopes.key?(scope_name.to_sym) then scope_name.to_sym
|
165
|
+
when klass.column_names.include?(scope_name.to_s) then "#{scope_name}_equals".to_sym
|
166
|
+
else scope_name.to_sym
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def setter?(name)
|
171
|
+
!(name.to_s =~ /=$/).nil?
|
172
|
+
end
|
173
|
+
|
174
|
+
def condition_name(name)
|
175
|
+
condition = name.to_s.match(/(\w+)=?$/)
|
176
|
+
condition ? condition[1].to_sym : nil
|
177
|
+
end
|
178
|
+
|
179
|
+
def write_condition(name, value)
|
180
|
+
@conditions[name] = value
|
181
|
+
end
|
182
|
+
|
183
|
+
def read_condition(name)
|
184
|
+
@conditions[name]
|
185
|
+
end
|
186
|
+
|
187
|
+
def mass_conditions
|
188
|
+
@mass_conditions ||= {}
|
189
|
+
end
|
190
|
+
|
191
|
+
def scope_name(condition_name)
|
192
|
+
condition_name && normalize_scope_name(condition_name)
|
193
|
+
end
|
194
|
+
|
195
|
+
def scope?(scope_name)
|
196
|
+
klass.scopes.key?(scope_name) || klass.condition?(scope_name)
|
197
|
+
end
|
198
|
+
|
199
|
+
def scope_options(name)
|
200
|
+
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
|
201
|
+
klass.named_scope_options(name)
|
202
|
+
end
|
203
|
+
|
204
|
+
def cast_type(name)
|
205
|
+
named_scope_options = scope_options(name)
|
206
|
+
arity = klass.named_scope_arity(name)
|
207
|
+
if !arity || arity == 0
|
208
|
+
:boolean
|
209
|
+
else
|
210
|
+
named_scope_options.respond_to?(:searchlogic_options) ? named_scope_options.searchlogic_options[:type] : :string
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def type_cast(value, type, options = {})
|
215
|
+
case value
|
216
|
+
when Array
|
217
|
+
value.collect { |v| type_cast(v, type) }
|
218
|
+
when Range
|
219
|
+
Range.new(type_cast(value.first, type), type_cast(value.last, type))
|
220
|
+
else
|
221
|
+
# Let's leverage ActiveRecord's type casting, so that casting is consistent
|
222
|
+
# with the other models.
|
223
|
+
column_for_type_cast = ::ActiveRecord::ConnectionAdapters::Column.new("", nil)
|
224
|
+
column_for_type_cast.instance_variable_set(:@type, type)
|
225
|
+
casted_value = column_for_type_cast.type_cast(value)
|
226
|
+
|
227
|
+
if Time.zone && casted_value.is_a?(Time)
|
228
|
+
if value.is_a?(String)
|
229
|
+
if options[:skip_conversion]
|
230
|
+
casted_value.utc
|
231
|
+
else
|
232
|
+
(casted_value + (Time.zone.utc_offset * -1)).in_time_zone
|
233
|
+
end
|
234
|
+
else
|
235
|
+
if options[:skip_conversion]
|
236
|
+
casted_value.utc
|
237
|
+
else
|
238
|
+
casted_value.in_time_zone
|
239
|
+
end
|
240
|
+
end
|
241
|
+
else
|
242
|
+
casted_value
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def ignore_value?(value)
|
248
|
+
(value.is_a?(String) && value.blank?) || (value.is_a?(Array) && value.empty?)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "searchlogic"
|
data/searchlogic.gemspec
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{searchlogic}
|
8
|
+
s.version = "2.4.19.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Ben Johnson of Binary Logic", "William Yeung @ Tofugear"]
|
12
|
+
s.date = %q{2010-07-26}
|
13
|
+
s.description = %q{Searchlogic makes using ActiveRecord named scopes easier and less repetitive. Patch to fix heroku form_for issue}
|
14
|
+
s.email = %q{bjohnson@binarylogic.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".gitignore",
|
21
|
+
"LICENSE",
|
22
|
+
"README.rdoc",
|
23
|
+
"Rakefile",
|
24
|
+
"VERSION.yml",
|
25
|
+
"init.rb",
|
26
|
+
"lib/searchlogic.rb",
|
27
|
+
"lib/searchlogic/active_record/association_proxy.rb",
|
28
|
+
"lib/searchlogic/active_record/consistency.rb",
|
29
|
+
"lib/searchlogic/active_record/named_scope_tools.rb",
|
30
|
+
"lib/searchlogic/core_ext/object.rb",
|
31
|
+
"lib/searchlogic/core_ext/proc.rb",
|
32
|
+
"lib/searchlogic/named_scopes/alias_scope.rb",
|
33
|
+
"lib/searchlogic/named_scopes/association_conditions.rb",
|
34
|
+
"lib/searchlogic/named_scopes/association_ordering.rb",
|
35
|
+
"lib/searchlogic/named_scopes/conditions.rb",
|
36
|
+
"lib/searchlogic/named_scopes/or_conditions.rb",
|
37
|
+
"lib/searchlogic/named_scopes/ordering.rb",
|
38
|
+
"lib/searchlogic/rails_helpers.rb",
|
39
|
+
"lib/searchlogic/search.rb",
|
40
|
+
"rails/init.rb",
|
41
|
+
"searchlogic.gemspec",
|
42
|
+
"spec/searchlogic/active_record/association_proxy_spec.rb",
|
43
|
+
"spec/searchlogic/active_record/consistency_spec.rb",
|
44
|
+
"spec/searchlogic/core_ext/object_spec.rb",
|
45
|
+
"spec/searchlogic/core_ext/proc_spec.rb",
|
46
|
+
"spec/searchlogic/named_scopes/alias_scope_spec.rb",
|
47
|
+
"spec/searchlogic/named_scopes/association_conditions_spec.rb",
|
48
|
+
"spec/searchlogic/named_scopes/association_ordering_spec.rb",
|
49
|
+
"spec/searchlogic/named_scopes/conditions_spec.rb",
|
50
|
+
"spec/searchlogic/named_scopes/or_conditions_spec.rb",
|
51
|
+
"spec/searchlogic/named_scopes/ordering_spec.rb",
|
52
|
+
"spec/searchlogic/search_spec.rb",
|
53
|
+
"spec/spec_helper.rb"
|
54
|
+
]
|
55
|
+
s.homepage = %q{http://github.com/goodwill/searchlogic}
|
56
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
57
|
+
s.require_paths = ["lib"]
|
58
|
+
s.rubyforge_project = %q{searchlogic}
|
59
|
+
s.rubygems_version = %q{1.3.6}
|
60
|
+
s.summary = %q{Searchlogic makes using ActiveRecord named scopes easier and less repetitive.}
|
61
|
+
s.test_files = [
|
62
|
+
"spec/searchlogic/active_record/association_proxy_spec.rb",
|
63
|
+
"spec/searchlogic/active_record/consistency_spec.rb",
|
64
|
+
"spec/searchlogic/core_ext/object_spec.rb",
|
65
|
+
"spec/searchlogic/core_ext/proc_spec.rb",
|
66
|
+
"spec/searchlogic/named_scopes/alias_scope_spec.rb",
|
67
|
+
"spec/searchlogic/named_scopes/association_conditions_spec.rb",
|
68
|
+
"spec/searchlogic/named_scopes/association_ordering_spec.rb",
|
69
|
+
"spec/searchlogic/named_scopes/conditions_spec.rb",
|
70
|
+
"spec/searchlogic/named_scopes/or_conditions_spec.rb",
|
71
|
+
"spec/searchlogic/named_scopes/ordering_spec.rb",
|
72
|
+
"spec/searchlogic/search_spec.rb",
|
73
|
+
"spec/spec_helper.rb"
|
74
|
+
]
|
75
|
+
|
76
|
+
if s.respond_to? :specification_version then
|
77
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
78
|
+
s.specification_version = 3
|
79
|
+
|
80
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
81
|
+
s.add_runtime_dependency(%q<activerecord>, [">= 2.0.0"])
|
82
|
+
else
|
83
|
+
s.add_dependency(%q<activerecord>, [">= 2.0.0"])
|
84
|
+
end
|
85
|
+
else
|
86
|
+
s.add_dependency(%q<activerecord>, [">= 2.0.0"])
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
@@ -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
|
@@ -0,0 +1,198 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../../spec_helper")
|
2
|
+
|
3
|
+
describe Searchlogic::NamedScopes::AssociationConditions do
|
4
|
+
it "should create a named scope" do
|
5
|
+
Company.users_username_like("bjohnson").proxy_options.should == User.username_like("bjohnson").proxy_options.merge(:joins => :users)
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should create a deep named scope" do
|
9
|
+
Company.users_orders_total_greater_than(10).proxy_options.should == Order.total_greater_than(10).proxy_options.merge(:joins => {:users => :orders})
|
10
|
+
end
|
11
|
+
|
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
|
+
pending
|
19
|
+
Order.named_scope :big_id, :conditions => "orders.id > 100"
|
20
|
+
Company.users_orders_big_id.proxy_options.should == Order.big_id.proxy_options.merge(:joins => {:users => :orders})
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should allow the use of foreign pre-existing alias scopes" do
|
24
|
+
User.alias_scope :username_has, lambda { |value| User.username_like(value) }
|
25
|
+
Company.users_username_has("bjohnson").proxy_options.should == User.username_has("bjohnson").proxy_options.merge(:joins => :users)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should not raise errors for scopes that don't return anything" do
|
29
|
+
User.alias_scope :blank_scope, lambda { |value| }
|
30
|
+
Company.users_blank_scope("bjohnson").proxy_options.should == {:joins => :users}
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should ignore polymorphic associations" do
|
34
|
+
lambda { Fee.owner_created_at_gt(Time.now) }.should raise_error(NoMethodError)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should not allow named scopes on non existent association columns" do
|
38
|
+
lambda { User.users_whatever_like("bjohnson") }.should raise_error(NoMethodError)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should not allow named scopes on non existent deep association columns" do
|
42
|
+
lambda { User.users_orders_whatever_like("bjohnson") }.should raise_error(NoMethodError)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should allow named scopes to be called multiple times and reflect the value passed" do
|
46
|
+
Company.users_username_like("bjohnson").proxy_options.should == User.username_like("bjohnson").proxy_options.merge(:joins => :users)
|
47
|
+
Company.users_username_like("thunt").proxy_options.should == User.username_like("thunt").proxy_options.merge(:joins => :users)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should allow deep named scopes to be called multiple times and reflect the value passed" do
|
51
|
+
Company.users_orders_total_greater_than(10).proxy_options.should == Order.total_greater_than(10).proxy_options.merge(:joins => {:users => :orders})
|
52
|
+
Company.users_orders_total_greater_than(20).proxy_options.should == Order.total_greater_than(20).proxy_options.merge(:joins => {:users => :orders})
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should have an arity of 1 if the underlying scope has an arity of 1" do
|
56
|
+
Company.users_orders_total_greater_than(10)
|
57
|
+
Company.named_scope_arity("users_orders_total_greater_than").should == Order.named_scope_arity("total_greater_than")
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should have an arity of nil if the underlying scope has an arity of nil" do
|
61
|
+
Company.users_orders_total_null
|
62
|
+
Company.named_scope_arity("users_orders_total_null").should == Order.named_scope_arity("total_null")
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should have an arity of -1 if the underlying scope has an arity of -1" do
|
66
|
+
Company.users_id_equals_any
|
67
|
+
Company.named_scope_arity("users_id_equals_any").should == User.named_scope_arity("id_equals_any")
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should allow aliases" do
|
71
|
+
Company.users_username_contains("bjohnson").proxy_options.should == User.username_contains("bjohnson").proxy_options.merge(:joins => :users)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should allow deep aliases" do
|
75
|
+
Company.users_orders_total_gt(10).proxy_options.should == Order.total_gt(10).proxy_options.merge(:joins => {:users => :orders})
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should include optional associations" do
|
79
|
+
pending # this is a problem with using inner joins and left outer joins
|
80
|
+
Company.create
|
81
|
+
company = Company.create
|
82
|
+
user = company.users.create
|
83
|
+
order = user.orders.create(:total => 20, :taxes => 3)
|
84
|
+
Company.ascend_by_users_orders_total.all.should == Company.all
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should implement exclusive scoping" do
|
88
|
+
scope = Company.users_company_name_like("name").users_company_description_like("description")
|
89
|
+
scope.scope(:find)[:joins].should == [
|
90
|
+
"INNER JOIN \"users\" ON companies.id = users.company_id",
|
91
|
+
"INNER JOIN \"companies\" companies_users ON \"companies_users\".id = \"users\".company_id"
|
92
|
+
]
|
93
|
+
lambda { scope.all }.should_not raise_error
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should not create the same join twice" do
|
97
|
+
scope = Company.users_orders_total_gt(10).users_orders_taxes_lt(5).ascend_by_users_orders_total
|
98
|
+
scope.scope(:find)[:joins].should == [
|
99
|
+
"INNER JOIN \"users\" ON companies.id = users.company_id",
|
100
|
+
"INNER JOIN \"orders\" ON orders.user_id = users.id"
|
101
|
+
]
|
102
|
+
lambda { scope.count }.should_not raise_error
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should not create the same join twice when traveling through the duplicate join" do
|
106
|
+
scope = Company.users_username_like("bjohnson").users_orders_total_gt(100)
|
107
|
+
scope.scope(:find)[:joins].should == [
|
108
|
+
"INNER JOIN \"users\" ON companies.id = users.company_id",
|
109
|
+
"INNER JOIN \"orders\" ON orders.user_id = users.id"
|
110
|
+
]
|
111
|
+
lambda { scope.count }.should_not raise_error
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should not create the same join twice when traveling through the deep duplicate join" do
|
115
|
+
scope = Company.users_orders_total_gt(100).users_orders_line_items_price_gt(20)
|
116
|
+
scope.scope(:find)[:joins].should == [
|
117
|
+
"INNER JOIN \"users\" ON companies.id = users.company_id",
|
118
|
+
"INNER JOIN \"orders\" ON orders.user_id = users.id",
|
119
|
+
"INNER JOIN \"line_items\" ON line_items.order_id = orders.id"
|
120
|
+
]
|
121
|
+
lambda { scope.all }.should_not raise_error
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should allow the use of :include when a join was created" do
|
125
|
+
company = Company.create
|
126
|
+
user = company.users.create
|
127
|
+
order = user.orders.create(:total => 20, :taxes => 3)
|
128
|
+
Company.users_orders_total_gt(10).users_orders_taxes_lt(5).ascend_by_users_orders_total.all(:include => :users).should == Company.all
|
129
|
+
end
|
130
|
+
|
131
|
+
it "should allow the use of deep :include when a join was created" do
|
132
|
+
company = Company.create
|
133
|
+
user = company.users.create
|
134
|
+
order = user.orders.create(:total => 20, :taxes => 3)
|
135
|
+
Company.users_orders_total_gt(10).users_orders_taxes_lt(5).ascend_by_users_orders_total.all(:include => {:users => :orders}).should == Company.all
|
136
|
+
end
|
137
|
+
|
138
|
+
it "should allow the use of :include when traveling through the duplicate join" do
|
139
|
+
company = Company.create
|
140
|
+
user = company.users.create(:username => "bjohnson")
|
141
|
+
order = user.orders.create(:total => 20, :taxes => 3)
|
142
|
+
Company.users_username_like("bjohnson").users_orders_taxes_lt(5).ascend_by_users_orders_total.all(:include => :users).should == Company.all
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should allow the use of deep :include when traveling through the duplicate join" do
|
146
|
+
company = Company.create
|
147
|
+
user = company.users.create(:username => "bjohnson")
|
148
|
+
order = user.orders.create(:total => 20, :taxes => 3)
|
149
|
+
Company.users_orders_taxes_lt(50).ascend_by_users_orders_total.all(:include => {:users => :orders}).should == Company.all
|
150
|
+
end
|
151
|
+
|
152
|
+
it "should automatically add string joins if the association condition is using strings" do
|
153
|
+
User.named_scope(:orders_big_id, :joins => User.inner_joins(:orders))
|
154
|
+
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 "]}
|
155
|
+
end
|
156
|
+
|
157
|
+
it "should order the join statements ascending by the fieldnames so that we don't get double joins where the only difference is that the order of the fields is different" do
|
158
|
+
company = Company.create
|
159
|
+
user = company.users.create(:company_id => company.id)
|
160
|
+
company.users.company_id_eq(company.id).should == [user]
|
161
|
+
end
|
162
|
+
|
163
|
+
it "should sanitize the scope on a foreign model instead of passing the raw options back to the original" do
|
164
|
+
Company.named_scope(:users_count_10, :conditions => {:users_count => 10})
|
165
|
+
User.company_users_count_10.proxy_options.should == {:conditions => "\"companies\".\"users_count\" = 10", :joins => :company}
|
166
|
+
end
|
167
|
+
|
168
|
+
it "should delegate to polymorphic relationships" do
|
169
|
+
Audit.auditable_user_type_name_like("ben").proxy_options.should == {
|
170
|
+
:conditions => ["users.name LIKE ?", "%ben%"],
|
171
|
+
:joins => "INNER JOIN \"users\" ON \"users\".id = \"audits\".auditable_id AND \"audits\".auditable_type = 'User'"
|
172
|
+
}
|
173
|
+
end
|
174
|
+
|
175
|
+
it "should delegate to polymorphic relationships (with a lazy split on _type_)" do
|
176
|
+
Audit.auditable_user_type_some_type_id_like("ben").proxy_options.should == {
|
177
|
+
:conditions => ["users.some_type_id LIKE ?", "%ben%"],
|
178
|
+
:joins => "INNER JOIN \"users\" ON \"users\".id = \"audits\".auditable_id AND \"audits\".auditable_type = 'User'"
|
179
|
+
}
|
180
|
+
end
|
181
|
+
|
182
|
+
it "should deep delegate to polymorphic relationships" do
|
183
|
+
Audit.auditable_user_type_company_name_like("company").proxy_options.should == {
|
184
|
+
:conditions => ["companies.name LIKE ?", "%company%"],
|
185
|
+
:joins => ["INNER JOIN \"users\" ON \"users\".id = \"audits\".auditable_id AND \"audits\".auditable_type = 'User'", " INNER JOIN \"companies\" ON \"companies\".id = \"users\".company_id "]
|
186
|
+
}
|
187
|
+
end
|
188
|
+
|
189
|
+
it "should allow any on a has_many relationship" do
|
190
|
+
company1 = Company.create
|
191
|
+
user1 = company1.users.create
|
192
|
+
company2 = Company.create
|
193
|
+
user2 = company2.users.create
|
194
|
+
user3 = company2.users.create
|
195
|
+
|
196
|
+
Company.users_id_equals_any([user2.id, user3.id]).all(:select => "DISTINCT companies.*").should == [company2]
|
197
|
+
end
|
198
|
+
end
|