Skip to content

Commit b03754f

Browse files
committed
merge or conditions creates optional joins
1 parent 52275b9 commit b03754f

File tree

12 files changed

+80
-53
lines changed

12 files changed

+80
-53
lines changed

.ruby-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1.9.3-p448

Gemfile.lock

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: .
33
specs:
4-
searchlogic (2.5.13)
4+
searchlogic (2.5.12)
55
activerecord (~> 2.3.12)
66
activesupport (~> 2.3.12)
77

@@ -14,15 +14,23 @@ GEM
1414
appraisal (0.4.1)
1515
bundler
1616
rake
17-
rake (10.0.4)
17+
coderay (1.1.0)
18+
method_source (0.8.2)
19+
pry (0.9.12.3)
20+
coderay (~> 1.0)
21+
method_source (~> 0.8)
22+
slop (~> 3.4)
23+
rake (10.1.0)
1824
rspec (1.3.2)
19-
sqlite3 (1.3.7)
25+
slop (3.4.7)
26+
sqlite3 (1.3.8)
2027

2128
PLATFORMS
2229
ruby
2330

2431
DEPENDENCIES
2532
appraisal (= 0.4.1)
33+
pry
2634
rspec (~> 1.3.1)
2735
searchlogic!
2836
sqlite3

lib/searchlogic.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
require 'active_support'
12
require 'active_record'
23
require "searchlogic/version"
34
require "searchlogic/core_ext/proc"

lib/searchlogic/named_scopes/association_conditions.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def association_condition_options(association, association_condition, poly_class
6262
raise ArgumentError.new("The #{klass} class does not respond to the #{association_condition} scope") if !klass.respond_to?(association_condition)
6363
arity = klass.named_scope_arity(association_condition)
6464

65-
if !arity
65+
if !arity || arity == 0
6666
# The underlying condition doesn't require any parameters, so let's just create a simple
6767
# named scope that is based on a hash.
6868
options = {}

lib/searchlogic/named_scopes/or_conditions.rb

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -128,13 +128,41 @@ def create_or_condition(scopes)
128128
end
129129

130130
def merge_scopes_with_or(scopes)
131-
scopes_options = scopes.collect { |scope| scope.scope(:find) }
132-
conditions = scopes_options.reject { |o| o[:conditions].nil? }.collect { |o| sanitize_sql(o[:conditions]) }
133-
scope = scopes_options.inject(scoped({})) { |current_scope, options| current_scope.scoped(options) }
134-
options = {}
135-
in_searchlogic_delegation { options = scope.scope(:find) }
136-
options.delete(:readonly) unless scopes.any? { |scope| scope.proxy_options.key?(:readonly) }
137-
options.merge(:conditions => "(" + conditions.join(") OR (") + ")")
131+
options = scopes_options(scopes)
132+
merged_options = merge_options(options)
133+
merged_options.delete(:readonly)
134+
if !merged_options[:joins].blank?
135+
merged_options[:joins] = convert_joins_to_optional(merged_options[:joins])
136+
else
137+
merged_options.delete(:joins)
138+
end
139+
conditions = normalized_conditions(options)
140+
if conditions.any?
141+
merged_options[:conditions] = "(" + conditions.join(") OR (") + ")"
142+
end
143+
merged_options
144+
end
145+
146+
def scopes_options(scopes)
147+
scopes.collect { |scope| with_exclusive_scope { scope.scope(:find) } }
148+
end
149+
150+
def convert_joins_to_optional(joins)
151+
joins ||= []
152+
153+
(joins || []).collect { |join| join.gsub(/INNER JOIN/, 'LEFT OUTER JOIN') }
154+
end
155+
156+
def merge_options(options)
157+
with_exclusive_scope do
158+
options.inject(scoped({:joins => "", :conditions => ""})) do |current_scope, option|
159+
current_scope.scoped(option)
160+
end.scope(:find)
161+
end
162+
end
163+
164+
def normalized_conditions(options)
165+
options.collect { |option| option[:conditions] && sanitize_sql(option[:conditions]) }.compact
138166
end
139167
end
140168
end

lib/searchlogic/search/method_missing.rb

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,12 @@ def method_missing(name, *args, &block)
1515
if setter?(name)
1616
if scope?(scope_name)
1717
if args.size == 1
18-
options = scope_options(scope_name).respond_to?(:searchlogic_options) ? scope_options(scope_name).searchlogic_options : {}
19-
options[:cast_single_val_to_array] = cast_single_val_to_array?(condition_name)
20-
2118
write_condition(
2219
condition_name,
2320
type_cast(
2421
args.first,
2522
cast_type(scope_name),
26-
options
23+
scope_options(scope_name).respond_to?(:searchlogic_options) ? scope_options(scope_name).searchlogic_options : {}
2724
)
2825
)
2926
else
@@ -103,24 +100,19 @@ def type_cast(value, type, options = {})
103100
casted_value = column_for_type_cast.type_cast(value)
104101

105102
if Time.zone && casted_value.is_a?(Time)
106-
casted_value = if value.is_a?(String)
103+
if value.is_a?(String)
107104
# if its a string, we should assume the user means the local time
108105
# we need to update the object to include the proper time zone without changing
109106
# the time
110107
(casted_value + (Time.zone.utc_offset * -1)).in_time_zone(Time.zone)
111108
else
112109
casted_value.in_time_zone
113110
end
111+
else
112+
casted_value
114113
end
115-
116-
options[:cast_single_val_to_array] ? [casted_value] : casted_value
117114
end
118115
end
119-
120-
# For *_equals_any conditions, cast single values to an array, ex: 5 to [5] or 'ben' to ['ben']
121-
def cast_single_val_to_array?(condition_name)
122-
condition_name =~ /^\w+_equals_any/
123-
end
124116
end
125117
end
126118
end

lib/searchlogic/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module Searchlogic
2-
VERSION = "2.5.14"
2+
VERSION = "2.5.12"
33
end

searchlogic-2.5.14.gem

34.5 KB
Binary file not shown.

searchlogic.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Gem::Specification.new do |s|
1717
s.add_development_dependency 'rspec', '~> 1.3.1'
1818
s.add_development_dependency 'sqlite3'
1919
s.add_development_dependency 'appraisal', '0.4.1'
20+
s.add_development_dependency 'pry'
2021

2122

2223
s.files = `git ls-files`.split("\n")

spec/searchlogic/named_scopes/or_conditions_spec.rb

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,58 +5,58 @@
55
User.name_or_username_like('Test')
66
User.respond_to?(:name_or_username_like).should be_true
77
end
8-
8+
99
it "should match username or name" do
1010
User.username_or_name_like("ben").proxy_options.should == {:conditions => "(users.username LIKE '%ben%') OR (users.name LIKE '%ben%')"}
1111
end
12-
12+
1313
it "should use the specified condition" do
1414
User.username_begins_with_or_name_like("ben").proxy_options.should == {:conditions => "(users.username LIKE 'ben%') OR (users.name LIKE '%ben%')"}
1515
end
16-
16+
1717
it "should use the last specified condition" do
1818
User.username_or_name_like_or_id_or_age_lt(10).proxy_options.should == {:conditions => "(users.username LIKE '%10%') OR (users.name LIKE '%10%') OR (users.id < 10) OR (users.age < 10)"}
1919
end
20-
20+
2121
it "should raise an error on unknown conditions" do
2222
lambda { User.usernme_begins_with_or_name_like("ben") }.should raise_error(Searchlogic::NamedScopes::OrConditions::UnknownConditionError)
2323
end
24-
24+
2525
it "should work well with _or_equal_to" do
2626
User.id_less_than_or_equal_to_or_age_gt(10).proxy_options.should == {:conditions => "(users.id <= 10) OR (users.age > 10)"}
2727
end
28-
28+
2929
it "should work well with _or_equal_to_any" do
3030
User.id_less_than_or_equal_to_all_or_age_gt(10).proxy_options.should == {:conditions => "(users.id <= 10) OR (users.age > 10)"}
3131
end
32-
32+
3333
it "should work well with _or_equal_to_all" do
3434
User.id_less_than_or_equal_to_any_or_age_gt(10).proxy_options.should == {:conditions => "(users.id <= 10) OR (users.age > 10)"}
3535
end
36-
36+
3737
it "should play nice with other scopes" do
3838
User.username_begins_with("ben").id_gt(10).age_not_nil.username_or_name_ends_with("ben").scope(:find).should ==
3939
{:conditions => "((users.username LIKE '%ben') OR (users.name LIKE '%ben')) AND ((users.age IS NOT NULL) AND ((users.id > 10) AND (users.username LIKE 'ben%')))"}
4040
end
41-
41+
4242
it "should work with boolean conditions" do
4343
User.male_or_name_eq("susan").proxy_options.should == {:conditions => %Q{("users"."male" = 't') OR (users.name = 'susan')}}
4444
User.not_male_or_name_eq("susan").proxy_options.should == {:conditions => %Q{("users"."male" = 'f') OR (users.name = 'susan')}}
4545
lambda { User.male_or_name_eq("susan").all }.should_not raise_error
4646
end
47-
47+
4848
it "should play nice with scopes on associations" do
4949
lambda { User.name_or_company_name_like("ben") }.should_not raise_error(Searchlogic::NamedScopes::OrConditions::NoConditionSpecifiedError)
50-
User.name_or_company_name_like("ben").proxy_options.should == {:joins => :company, :conditions => "(users.name LIKE '%ben%') OR (companies.name LIKE '%ben%')"}
51-
User.company_name_or_name_like("ben").proxy_options.should == {:joins => :company, :conditions => "(companies.name LIKE '%ben%') OR (users.name LIKE '%ben%')"}
52-
User.company_name_or_company_description_like("ben").proxy_options.should == {:joins =>[:company], :conditions => "(companies.name LIKE '%ben%') OR (companies.description LIKE '%ben%')"}
53-
Cart.user_company_name_or_user_company_name_like("ben").proxy_options.should == {:joins => {:user=>:company}, :conditions => "(companies.name LIKE '%ben%') OR (companies.name LIKE '%ben%')"}
50+
User.name_or_company_name_like("ben").proxy_options.should == {:joins => ["LEFT OUTER JOIN \"companies\" ON \"companies\".id = \"users\".company_id"], :conditions => "(users.name LIKE '%ben%') OR (companies.name LIKE '%ben%')"}
51+
User.company_name_or_name_like("ben").proxy_options.should == {:joins => ["LEFT OUTER JOIN \"companies\" ON \"companies\".id = \"users\".company_id"], :conditions => "(companies.name LIKE '%ben%') OR (users.name LIKE '%ben%')"}
52+
User.company_name_or_company_description_like("ben").proxy_options.should == {:joins => ["LEFT OUTER JOIN \"companies\" ON \"companies\".id = \"users\".company_id"], :conditions => "(companies.name LIKE '%ben%') OR (companies.description LIKE '%ben%')"}
53+
Cart.user_company_name_or_user_company_name_like("ben").proxy_options.should == {:joins => ["LEFT OUTER JOIN \"users\" ON \"carts\".user_id = \"users\".id", "LEFT OUTER JOIN \"companies\" ON \"companies\".id = \"users\".company_id"], :conditions => "(companies.name LIKE '%ben%') OR (companies.name LIKE '%ben%')"}
5454
end
55-
55+
5656
it "should raise an error on missing condition" do
5757
lambda { User.id_or_age(123) }.should raise_error(Searchlogic::NamedScopes::OrConditions::NoConditionSpecifiedError)
5858
end
59-
59+
6060
it "should not get confused by the 'or' in find_or_create_by_* methods" do
6161
User.create(:name => "Fred")
6262
User.find_or_create_by_name("Fred").should be_a_kind_of User
@@ -66,12 +66,19 @@
6666
User.create(:name => "Fred", :username => "fredb")
6767
User.find_or_create_by_name_and_username("Fred", "fredb").should be_a_kind_of User
6868
end
69-
69+
7070
it "should work with User.search(conditions) method" do
7171
User.search(:username_or_name_like => 'ben').proxy_options.should == {:conditions => "(users.username LIKE '%ben%') OR (users.name LIKE '%ben%')"}
7272
end
7373

7474
it "should convert types properly when used with User.search(conditions) method" do
7575
User.search(:id_or_age_lte => '10').proxy_options.should == {:conditions => "(users.id <= 10) OR (users.age <= 10)"}
7676
end
77+
78+
it "should converts inner joins to left out joins" do
79+
scopes = []
80+
scopes << User.orders_id_equals(1)
81+
scopes << User.carts_id_equals(1)
82+
User.send(:merge_scopes_with_or, scopes).should == {:conditions=>"(orders.id = 1) OR (carts.id = 1)", :joins=>["LEFT OUTER JOIN \"carts\" ON carts.user_id = users.id", "LEFT OUTER JOIN \"orders\" ON orders.user_id = users.id"]}
83+
end
7784
end

0 commit comments

Comments
 (0)