searchlogic 2.3.5 → 2.3.6

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/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