searchlogic 2.3.5 → 2.3.6

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION.yml CHANGED
@@ -1,4 +1,5 @@
1
1
  ---
2
- :patch: 5
3
- :major: 2
4
2
  :minor: 3
3
+ :patch: 6
4
+ :major: 2
5
+ :build:
data/lib/searchlogic.rb CHANGED
@@ -37,4 +37,4 @@ end
37
37
  if defined?(ActionController)
38
38
  require "searchlogic/rails_helpers"
39
39
  ActionController::Base.helper(Searchlogic::RailsHelpers)
40
- end
40
+ end
@@ -13,10 +13,15 @@ module Searchlogic
13
13
  # ActiveRecord hides this internally in a Proc, so we have to try and pull it out with this
14
14
  # method.
15
15
  def named_scope_options(name)
16
+
16
17
  key = scopes.key?(name.to_sym) ? name.to_sym : primary_condition_name(name)
17
18
 
18
19
  if key
19
20
  eval("options", scopes[key].binding)
21
+ elsif name.to_s.downcase.match("_or_")
22
+ condition = find_applied_condition(name)
23
+ newname = name.to_s.gsub(/_or_/, "_#{condition}_or_").to_sym
24
+ named_scope_options(newname) unless name == newname
20
25
  else
21
26
  nil
22
27
  end
@@ -64,4 +64,4 @@ module Searchlogic
64
64
  end
65
65
  end
66
66
  end
67
- end
67
+ end
@@ -8,7 +8,7 @@ module Searchlogic
8
8
 
9
9
  private
10
10
  def association_condition?(name)
11
- !association_condition_details(name).nil?
11
+ !association_condition_details(name).nil? unless name.to_s.downcase.match("_or_")
12
12
  end
13
13
 
14
14
  def method_missing(name, *args, &block)
@@ -20,11 +20,12 @@ module Searchlogic
20
20
  end
21
21
  end
22
22
 
23
- def association_condition_details(name)
23
+ def association_condition_details(name, last_condition = nil)
24
24
  assocs = reflect_on_all_associations.reject { |assoc| assoc.options[:polymorphic] }.sort { |a, b| b.name.to_s.size <=> a.name.to_s.size }
25
25
  return nil if assocs.empty?
26
26
 
27
- if name.to_s =~ /^(#{assocs.collect(&:name).join("|")})_(\w+)$/
27
+ name_with_condition = [name, last_condition].compact.join('_')
28
+ if name_with_condition.to_s =~ /^(#{assocs.collect(&:name).join("|")})_(\w+)$/
28
29
  association_name = $1
29
30
  condition = $2
30
31
  association = reflect_on_association(association_name.to_sym)
@@ -98,4 +99,4 @@ module Searchlogic
98
99
  end
99
100
  end
100
101
  end
101
- end
102
+ end
@@ -40,4 +40,4 @@ module Searchlogic
40
40
  end
41
41
  end
42
42
  end
43
- end
43
+ end
@@ -35,6 +35,11 @@ module Searchlogic
35
35
  :not_blank => [:present]
36
36
  }
37
37
 
38
+ GROUP_CONDITIONS = {
39
+ :in => [],
40
+ :not_in => []
41
+ }
42
+
38
43
  CONDITIONS = {}
39
44
 
40
45
  # Add any / all variations to every comparison and wildcard condition
@@ -46,6 +51,8 @@ module Searchlogic
46
51
 
47
52
  BOOLEAN_CONDITIONS.each { |condition, aliases| CONDITIONS[condition] = aliases }
48
53
 
54
+ GROUP_CONDITIONS.each { |condition, aliases| CONDITIONS[condition] = aliases }
55
+
49
56
  PRIMARY_CONDITIONS = CONDITIONS.keys
50
57
  ALIAS_CONDITIONS = CONDITIONS.values.flatten
51
58
 
@@ -69,9 +76,20 @@ module Searchlogic
69
76
  super
70
77
  end
71
78
  end
79
+
80
+ def find_applied_condition(name)
81
+ if name.to_s =~ /(#{(PRIMARY_CONDITIONS + ALIAS_CONDITIONS).join("|")})$/
82
+ $1
83
+ end
84
+ end
72
85
 
73
- def condition_details(name)
74
- if name.to_s =~ /^(#{column_names.join("|")})_(#{(PRIMARY_CONDITIONS + ALIAS_CONDITIONS).join("|")})$/
86
+ def condition_details(name, *args)
87
+ if args.size > 0 and !args.first.nil?
88
+ assoc = reflect_on_association(args.first.to_sym)
89
+ klass = assoc.klass
90
+ name.to_s =~ /^(#{klass.column_names.join("|")})_(#{(PRIMARY_CONDITIONS + ALIAS_CONDITIONS).join("|")})$/
91
+ {:column => $1, :condition => $2}
92
+ elsif name.to_s =~ /^(#{column_names.join("|")})_(#{(PRIMARY_CONDITIONS + ALIAS_CONDITIONS).join("|")})$/
75
93
  {:column => $1, :condition => $2}
76
94
  end
77
95
  end
@@ -123,6 +141,10 @@ module Searchlogic
123
141
  {:conditions => "#{table_name}.#{column} = '' OR #{table_name}.#{column} IS NULL"}
124
142
  when "not_blank"
125
143
  {:conditions => "#{table_name}.#{column} != '' AND #{table_name}.#{column} IS NOT NULL"}
144
+ when "in"
145
+ scope_options(condition, column_type, "#{table_name}.#{column} IN (?)")
146
+ when "not_in"
147
+ scope_options(condition, column_type, "#{table_name}.#{column} NOT IN (?)")
126
148
  end
127
149
 
128
150
  named_scope("#{column}_#{condition}".to_sym, scope_options)
@@ -67,14 +67,16 @@ module Searchlogic
67
67
  def interpolate_or_conditions(parts)
68
68
  conditions = []
69
69
  last_condition = nil
70
-
70
+
71
71
  parts.reverse.each do |part|
72
72
  if details = condition_details(part)
73
73
  # We are a searchlogic defined scope
74
74
  conditions << "#{details[:column]}_#{details[:condition]}"
75
75
  last_condition = details[:condition]
76
- elsif details = association_condition_details(part)
77
- # pending, need to find the last condition
76
+ elsif association_details = association_condition_details(part, last_condition)
77
+ path = full_association_path(part, last_condition, association_details[:association])
78
+ conditions << "#{path[:path].join("_").to_sym}_#{path[:column]}_#{path[:condition]}"
79
+ last_condition = path[:condition] || nil
78
80
  elsif local_condition?(part)
79
81
  # We are a custom scope
80
82
  conditions << part
@@ -91,7 +93,24 @@ module Searchlogic
91
93
  end
92
94
  end
93
95
 
94
- conditions
96
+ conditions.reverse
97
+ end
98
+
99
+ def full_association_path(part, last_condition, given_assoc)
100
+ path = [given_assoc.to_sym]
101
+ part.sub!(/^#{given_assoc}_/, "")
102
+ klass = self
103
+ while klass = klass.send(:reflect_on_association, given_assoc.to_sym)
104
+ klass = klass.klass
105
+ if details = klass.send(:association_condition_details, part, last_condition)
106
+ path << details[:association]
107
+ part = details[:condition]
108
+ given_assoc = details[:association]
109
+ elsif details = klass.send(:condition_details, part, nil)
110
+ return { :path => path, :column => details[:column], :condition => details[:condition] }
111
+ end
112
+ end
113
+ { :path => path, :column => part, :condition => last_condition }
95
114
  end
96
115
 
97
116
  def create_or_condition(scopes, args)
@@ -109,8 +128,10 @@ module Searchlogic
109
128
  scope.send(scope_name, *args)
110
129
  end
111
130
 
112
- scope.scope(:find).merge(:conditions => "(" + conditions.join(") OR (") + ")")
131
+ options = scope.scope(:find)
132
+ options.delete(:readonly) unless scope.proxy_options.key?(:readonly)
133
+ options.merge(:conditions => "(" + conditions.join(") OR (") + ")")
113
134
  end
114
135
  end
115
136
  end
116
- end
137
+ end
@@ -45,4 +45,4 @@ module Searchlogic
45
45
  end
46
46
  end
47
47
  end
48
- end
48
+ end
@@ -96,7 +96,7 @@ module Searchlogic
96
96
  conditions[condition_name]
97
97
  end
98
98
  else
99
- scope = conditions.inject(klass.scoped(current_scope)) do |scope, condition|
99
+ scope = conditions.inject(klass.scoped(current_scope) || {}) do |scope, condition|
100
100
  scope_name, value = condition
101
101
  scope_name = normalize_scope_name(scope_name)
102
102
  klass.send(scope_name, value) if !klass.respond_to?(scope_name)
@@ -117,7 +117,11 @@ module Searchlogic
117
117
  end
118
118
 
119
119
  def normalize_scope_name(scope_name)
120
- klass.column_names.include?(scope_name.to_s) ? "#{scope_name}_equals".to_sym : scope_name.to_sym
120
+ case
121
+ when klass.scopes.key?(scope_name.to_sym) then scope_name.to_sym
122
+ when klass.column_names.include?(scope_name.to_s) then "#{scope_name}_equals".to_sym
123
+ else scope_name.to_sym
124
+ end
121
125
  end
122
126
 
123
127
  def setter?(name)
@@ -125,8 +129,8 @@ module Searchlogic
125
129
  end
126
130
 
127
131
  def condition_name(name)
128
- condition = name.to_s.match(/(\w+)=?$/)[1]
129
- condition ? condition.to_sym : nil
132
+ condition = name.to_s.match(/(\w+)=?$/)
133
+ condition ? condition[1].to_sym : nil
130
134
  end
131
135
 
132
136
  def scope_name(condition_name)
@@ -138,7 +142,7 @@ module Searchlogic
138
142
  end
139
143
 
140
144
  def cast_type(name)
141
- 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
145
+ 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
142
146
  named_scope_options = klass.named_scope_options(name)
143
147
  arity = klass.named_scope_arity(name)
144
148
  if !arity || arity == 0
@@ -152,6 +156,8 @@ module Searchlogic
152
156
  case value
153
157
  when Array
154
158
  value.collect { |v| type_cast(v, type) }
159
+ when Range
160
+ Range.new(type_cast(value.first, type), type_cast(value.last, type))
155
161
  else
156
162
  # Let's leverage ActiveRecord's type casting, so that casting is consistent
157
163
  # with the other models.
data/searchlogic.gemspec CHANGED
@@ -1,15 +1,15 @@
1
1
  # Generated by jeweler
2
- # DO NOT EDIT THIS FILE
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
4
  # -*- encoding: utf-8 -*-
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{searchlogic}
8
- s.version = "2.3.5"
8
+ s.version = "2.3.6"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Ben Johnson of Binary Logic"]
12
- s.date = %q{2009-09-13}
12
+ s.date = %q{2009-11-05}
13
13
  s.description = %q{Searchlogic makes using ActiveRecord named scopes easier and less repetitive.}
14
14
  s.email = %q{bjohnson@binarylogic.com}
15
15
  s.extra_rdoc_files = [
@@ -82,3 +82,4 @@ Gem::Specification.new do |s|
82
82
  s.add_dependency(%q<activerecord>, [">= 2.0.0"])
83
83
  end
84
84
  end
85
+
@@ -15,7 +15,6 @@ describe "Conditions" do
15
15
  it "should have equals" do
16
16
  (5..7).each { |age| User.create(:age => age) }
17
17
  User.age_equals(6).all.should == User.find_all_by_age(6)
18
- User.age_equals(nil).all.should == User.find_all_by_age(nil)
19
18
  User.age_equals(5..6).all.should == User.find_all_by_age(5..6)
20
19
  User.age_equals([5, 7]).all.should == User.find_all_by_age([5, 7])
21
20
  end
@@ -269,6 +268,18 @@ describe "Conditions" do
269
268
  end
270
269
  end
271
270
 
271
+ context "group conditions" do
272
+ it "should have in" do
273
+ (5..7).each { |age| User.create(:age => age) }
274
+ User.age_in([5,6]).all == User.find(:all, :conditions => ["users.age IN (?)", [5, 6]])
275
+ end
276
+
277
+ it "should have not_in" do
278
+ (5..7).each { |age| User.create(:age => age) }
279
+ User.age_not_in([5,6]).all == User.find(:all, :conditions => ["users.age NOT IN (?)", [5, 6]])
280
+ end
281
+ end
282
+
272
283
  context "searchlogic lambda" do
273
284
  it "should be a string" do
274
285
  User.username_like("test")
@@ -1,16 +1,21 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
2
2
 
3
3
  describe "Or conditions" do
4
+ it "should define a scope by the exact same name as requested by the code" do
5
+ User.name_or_username_like('Test')
6
+ User.respond_to?(:name_or_username_like).should be_true
7
+ end
8
+
4
9
  it "should match username or name" do
5
- User.username_or_name_like("ben").proxy_options.should == {:conditions => "(users.name LIKE '%ben%') OR (users.username LIKE '%ben%')"}
10
+ User.username_or_name_like("ben").proxy_options.should == {:conditions => "(users.username LIKE '%ben%') OR (users.name LIKE '%ben%')"}
6
11
  end
7
12
 
8
13
  it "should use the specified condition" do
9
- User.username_begins_with_or_name_like("ben").proxy_options.should == {:conditions => "(users.name LIKE '%ben%') OR (users.username LIKE 'ben%')"}
14
+ User.username_begins_with_or_name_like("ben").proxy_options.should == {:conditions => "(users.username LIKE 'ben%') OR (users.name LIKE '%ben%')"}
10
15
  end
11
16
 
12
17
  it "should use the last specified condition" do
13
- User.username_or_name_like_or_id_or_age_lt(10).proxy_options.should == {:conditions => "(users.age < 10) OR (users.id < 10) OR (users.name LIKE '%10%') OR (users.username LIKE '%10%')"}
18
+ 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)"}
14
19
  end
15
20
 
16
21
  it "should raise an error on unknown conditions" do
@@ -18,19 +23,31 @@ describe "Or conditions" do
18
23
  end
19
24
 
20
25
  it "should work well with _or_equal_to" do
21
- User.id_less_than_or_equal_to_or_age_gt(10).proxy_options.should == {:conditions => "(users.age > 10) OR (users.id <= 10)"}
26
+ User.id_less_than_or_equal_to_or_age_gt(10).proxy_options.should == {:conditions => "(users.id <= 10) OR (users.age > 10)"}
22
27
  end
23
28
 
24
29
  it "should work well with _or_equal_to_any" do
25
- User.id_less_than_or_equal_to_all_or_age_gt(10).proxy_options.should == {:conditions => "(users.age > 10) OR (users.id <= 10)"}
30
+ User.id_less_than_or_equal_to_all_or_age_gt(10).proxy_options.should == {:conditions => "(users.id <= 10) OR (users.age > 10)"}
26
31
  end
27
32
 
28
33
  it "should work well with _or_equal_to_all" do
29
- User.id_less_than_or_equal_to_any_or_age_gt(10).proxy_options.should == {:conditions => "(users.age > 10) OR (users.id <= 10)"}
34
+ User.id_less_than_or_equal_to_any_or_age_gt(10).proxy_options.should == {:conditions => "(users.id <= 10) OR (users.age > 10)"}
30
35
  end
31
36
 
32
37
  it "should play nice with other scopes" do
33
38
  User.username_begins_with("ben").id_gt(10).age_not_nil.username_or_name_ends_with("ben").scope(:find).should ==
34
- {:conditions => "((users.name LIKE '%ben') OR (users.username LIKE '%ben')) AND ((users.age IS NOT NULL) AND ((users.id > 10) AND (users.username LIKE 'ben%')))"}
39
+ {: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%')))"}
40
+ end
41
+
42
+ it "should play nice with scopes on associations" do
43
+ lambda { User.name_or_company_name_like("ben") }.should_not raise_error(Searchlogic::NamedScopes::OrConditions::NoConditionSpecifiedError)
44
+ User.name_or_company_name_like("ben").proxy_options.should == {:joins => :company, :conditions => "(users.name LIKE '%ben%') OR (companies.name LIKE '%ben%')"}
45
+ User.company_name_or_name_like("ben").proxy_options.should == {:joins => :company, :conditions => "(companies.name LIKE '%ben%') OR (users.name LIKE '%ben%')"}
46
+ User.company_name_or_company_description_like("ben").proxy_options.should == {:joins =>[:company], :conditions => "(companies.name LIKE '%ben%') OR (companies.description LIKE '%ben%')"}
47
+ end
48
+
49
+ it "should play nice with n-depth scopes on associations" do
50
+ User.company_conglomerate_name_or_company_conglomerate_description_like("ben").proxy_options.should ==
51
+ {:joins =>[{:company, :conglomerate}], :conditions => "(conglomerates.name LIKE '%ben%') OR (conglomerates.description LIKE '%ben%')"}
35
52
  end
36
- end
53
+ end
data/spec/search_spec.rb CHANGED
@@ -75,6 +75,15 @@ describe "Search" do
75
75
  search.username.should be_nil
76
76
  end
77
77
 
78
+ it "should use custom scopes before normalizing" do
79
+ User.create(:username => "bjohnson")
80
+ User.named_scope :username, lambda { |value| {:conditions => {:username => value.reverse}} }
81
+ search1 = User.search(:username => "bjohnson")
82
+ search2 = User.search(:username => "nosnhojb")
83
+ search1.count.should == 0
84
+ search2.count.should == 1
85
+ end
86
+
78
87
  it "should ignore blank values in arrays" do
79
88
  search = User.search
80
89
  search.conditions = {"username_equals_any" => [""]}
@@ -95,7 +104,7 @@ describe "Search" do
95
104
  search.username_gt.should == "bjohnson"
96
105
  end
97
106
 
98
- it "should allow chainging conditions" do
107
+ it "should allow chaining conditions" do
99
108
  user = User.create(:username => "bjohnson", :age => 20)
100
109
  User.create(:username => "bjohnson", :age => 5)
101
110
  search = User.search
@@ -103,6 +112,12 @@ describe "Search" do
103
112
  search.all.should == [user]
104
113
  end
105
114
 
115
+ it "should allow chaining conditions with n-depth associations" do
116
+ search = User.search
117
+ search.company_conglomerate_name_or_company_conglomerate_description_like("ben")
118
+ search.proxy_options.should == User.company_conglomerate_name_or_company_conglomerate_description_like("ben").proxy_options
119
+ end
120
+
106
121
  it "should allow setting association conditions" do
107
122
  search = User.search
108
123
  search.orders_total_gt = 10
@@ -237,6 +252,12 @@ describe "Search" do
237
252
  search.total_gt.should == 1.5
238
253
  end
239
254
 
255
+ it "should be a Range given 1..3" do
256
+ search = Order.search
257
+ search.total_eq = (1..3)
258
+ search.total_eq.should == (1..3)
259
+ end
260
+
240
261
  it "should be a Date given 'Jan 1, 2009'" do
241
262
  search = Order.search
242
263
  search.shipped_on_after = "Jan 1, 2009"
@@ -321,4 +342,22 @@ describe "Search" do
321
342
  User.search(:order => "ascend_by_username").proxy_options.should == User.ascend_by_username.proxy_options
322
343
  end
323
344
  end
345
+
346
+ context "method delegation" do
347
+ it "should respond to count" do
348
+ User.create(:username => "bjohnson")
349
+ search1 = User.search(:username => "bjohnson")
350
+ search2 = User.search(:username => "nosnhojb")
351
+ search1.count.should == 1
352
+ search2.count.should == 0
353
+ end
354
+
355
+ it "should respond to empty?" do
356
+ User.create(:username => "bjohnson")
357
+ search1 = User.search(:username => "bjohnson")
358
+ search2 = User.search(:username => "nosnhojb")
359
+ search1.empty?.should == false
360
+ search2.empty?.should == true
361
+ end
362
+ end
324
363
  end
data/spec/spec_helper.rb CHANGED
@@ -15,6 +15,7 @@ ActiveRecord::Schema.define(:version => 1) do
15
15
  t.datetime :created_at
16
16
  t.datetime :updated_at
17
17
  t.string :name
18
+ t.string :description
18
19
  t.integer :users_count, :default => 0
19
20
  end
20
21
 
@@ -56,6 +57,13 @@ ActiveRecord::Schema.define(:version => 1) do
56
57
  t.integer :order_id
57
58
  t.float :price
58
59
  end
60
+
61
+ create_table :conglomerates do |t|
62
+ t.datetime :created_at
63
+ t.datetime :updated_at
64
+ t.string :name
65
+ t.string :description
66
+ end
59
67
  end
60
68
 
61
69
  $LOAD_PATH.unshift(File.dirname(__FILE__))
@@ -65,6 +73,7 @@ require 'searchlogic'
65
73
  Spec::Runner.configure do |config|
66
74
  config.before(:each) do
67
75
  class Company < ActiveRecord::Base
76
+ belongs_to :conglomerate
68
77
  has_many :users, :dependent => :destroy
69
78
  end
70
79
 
@@ -86,6 +95,10 @@ Spec::Runner.configure do |config|
86
95
  class LineItem < ActiveRecord::Base
87
96
  belongs_to :order
88
97
  end
98
+
99
+ class Conglomerate < ActiveRecord::Base
100
+ has_many :companies, :dependent => :destroy
101
+ end
89
102
 
90
103
  Company.destroy_all
91
104
  User.destroy_all
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: searchlogic
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.5
4
+ version: 2.3.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Johnson of Binary Logic
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-09-13 00:00:00 -04:00
12
+ date: 2009-11-05 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency