searchlogic 2.3.16 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -113,6 +113,19 @@ Also, these conditions aren't limited to the scopes Searchlogic provides. You ca
113
113
 
114
114
  As I stated above, Searchlogic will take care of creating the necessary joins for you. This is REALLY nice when trying to keep your code DRY, because if you wanted to use a scope like this in your User model you would have to copy over the conditions. Now you have 2 named scopes that are essentially doing the same thing. Why do that when you can dynamically access that scope using this feature?
115
115
 
116
+ === Polymorphic associations
117
+
118
+ Polymorphic associations are tough because ActiveRecord doesn't support them with the :joins or :include options. Searchlogic checks for a specific syntax and takes care of this for you. Ex:
119
+
120
+ Audit.belongs_to :auditable, :polymorphic => true
121
+ User.has_many :audits, :as => :auditable
122
+
123
+ Audit.auditable_user_type_username_equals("ben")
124
+
125
+ The above will take care of creating the inner join on the polymorphic association so that it only looks for type 'User'. On the surface it works the same as a non polymorphic association. The syntax difference being that you need to call the association and then specify the type:
126
+
127
+ [polymorphic association name]_[association type]_type
128
+
116
129
  === Uses :joins not :include
117
130
 
118
131
  Another thing to note is that the joins created by Searchlogic do NOT use the :include option, making them <em>much</em> faster. Instead they leverage the :joins option, which is great for performance. To prove my point here is a quick benchmark from an application I am working on:
data/VERSION.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  ---
2
2
  :major: 2
3
- :minor: 3
3
+ :minor: 4
4
4
  :build:
5
- :patch: 16
5
+ :patch: 0
@@ -51,7 +51,27 @@ module Searchlogic
51
51
  ::ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, association_name, nil).join_associations.collect { |assoc| assoc.association_join }
52
52
  end
53
53
 
54
- # See inner_joins, except this creates LEFT OUTER joins.
54
+ # A convenience methods to create a join on a polymorphic associations target.
55
+ # Ex:
56
+ #
57
+ # Audit.belong_to :auditable, :polymorphic => true
58
+ # User.has_many :audits, :as => :auditable
59
+ #
60
+ # Audit.inner_polymorphic_join(:user, :as => :auditable) # =>
61
+ # "INNER JOINER users ON users.id = audits.auditable_id AND audits.auditable_type = 'User'"
62
+ #
63
+ # This is used internally by searchlogic to handle accessing conditions on polymorphic associations.
64
+ def inner_polymorphic_join(target, options = {})
65
+ options[:on] ||= table_name
66
+ options[:on_table_name] ||= connection.quote_table_name(options[:on])
67
+ options[:target_table] ||= connection.quote_table_name(target.to_s.pluralize)
68
+ options[:as] ||= "owner"
69
+ postgres = ::ActiveRecord::Base.connection.adapter_name == "PostgreSQL"
70
+ "INNER JOIN #{options[:target_table]} ON #{options[:target_table]}.id = #{options[:on_table_name]}.#{options[:as]}_id AND " +
71
+ "#{options[:on_table_name]}.#{options[:as]}_type = #{postgres ? "E" : ""}'#{target.to_s.camelize}'"
72
+ end
73
+
74
+ # See inner_joins. Does the same thing except creates LEFT OUTER joins.
55
75
  def left_outer_joins(association_name)
56
76
  ::ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, association_name, nil).join_associations.collect { |assoc| assoc.association_join }
57
77
  end
@@ -13,7 +13,7 @@ module Searchlogic
13
13
 
14
14
  def method_missing(name, *args, &block)
15
15
  if !local_condition?(name) && details = association_condition_details(name)
16
- create_association_condition(details[:association], details[:condition], args)
16
+ create_association_condition(details[:association], details[:condition], args, details[:poly_class])
17
17
  send(name, *args)
18
18
  else
19
19
  super
@@ -21,38 +21,52 @@ module Searchlogic
21
21
  end
22
22
 
23
23
  def association_condition_details(name, last_condition = nil)
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
- return nil if assocs.empty?
26
-
24
+ non_poly_assocs = reflect_on_all_associations.reject { |assoc| assoc.options[:polymorphic] }.sort { |a, b| b.name.to_s.size <=> a.name.to_s.size }
25
+ poly_assocs = reflect_on_all_associations.reject { |assoc| !assoc.options[:polymorphic] }.sort { |a, b| b.name.to_s.size <=> a.name.to_s.size }
26
+ return nil if non_poly_assocs.empty? && poly_assocs.empty?
27
+
27
28
  name_with_condition = [name, last_condition].compact.join('_')
28
- if name_with_condition.to_s =~ /^(#{assocs.collect(&:name).join("|")})_(\w+)$/
29
+
30
+ association_name = nil
31
+ poly_type = nil
32
+ condition = nil
33
+
34
+ if name_with_condition.to_s =~ /^(#{non_poly_assocs.collect(&:name).join("|")})_(\w+)$/
29
35
  association_name = $1
30
36
  condition = $2
37
+ elsif name_with_condition.to_s =~ /^(#{poly_assocs.collect(&:name).join("|")})_(\w+)_type_(\w+)$/
38
+ association_name = $1
39
+ poly_type = $2
40
+ condition = $3
41
+ end
42
+
43
+ if association_name && condition
31
44
  association = reflect_on_association(association_name.to_sym)
32
- klass = association.klass
45
+ klass = poly_type ? poly_type.camelcase.constantize : association.klass
33
46
  if klass.condition?(condition)
34
- {:association => $1, :condition => $2}
47
+ {:association => association, :poly_class => poly_type && klass, :condition => condition}
35
48
  else
36
49
  nil
37
50
  end
38
51
  end
39
52
  end
40
53
 
41
- def create_association_condition(association, condition, args)
42
- named_scope("#{association}_#{condition}", association_condition_options(association, condition, args))
54
+ def create_association_condition(association, condition_name, args, poly_class = nil)
55
+ name = [association.name, poly_class && "#{poly_class.name.underscore}_type", condition_name].compact.join("_")
56
+ named_scope(name, association_condition_options(association, condition_name, args, poly_class))
43
57
  end
44
58
 
45
- def association_condition_options(association_name, association_condition, args)
46
- association = reflect_on_association(association_name.to_sym)
47
- scope = association.klass.send(association_condition, *args)
48
- scope_options = association.klass.named_scope_options(association_condition)
49
- arity = association.klass.named_scope_arity(association_condition)
59
+ def association_condition_options(association, association_condition, args, poly_class = nil)
60
+ klass = poly_class ? poly_class : association.klass
61
+ scope = klass.send(association_condition, *args)
62
+ scope_options = klass.named_scope_options(association_condition)
63
+ arity = klass.named_scope_arity(association_condition)
50
64
 
51
65
  if !arity || arity == 0
52
66
  # The underlying condition doesn't require any parameters, so let's just create a simple
53
67
  # named scope that is based on a hash.
54
68
  options = scope.scope(:find)
55
- prepare_named_scope_options(options, association)
69
+ prepare_named_scope_options(options, association, poly_class)
56
70
  options
57
71
  else
58
72
  proc_args = arity_args(arity)
@@ -60,9 +74,9 @@ module Searchlogic
60
74
 
61
75
  eval <<-"end_eval"
62
76
  searchlogic_lambda(:#{arg_type}) { |#{proc_args.join(",")}|
63
- scope = association.klass.send(association_condition, #{proc_args.join(",")})
77
+ scope = klass.send(association_condition, #{proc_args.join(",")})
64
78
  options = scope ? scope.scope(:find) : {}
65
- prepare_named_scope_options(options, association)
79
+ prepare_named_scope_options(options, association, poly_class)
66
80
  options
67
81
  }
68
82
  end_eval
@@ -88,13 +102,19 @@ module Searchlogic
88
102
  args
89
103
  end
90
104
 
91
- def prepare_named_scope_options(options, association)
105
+ def prepare_named_scope_options(options, association, poly_class = nil)
92
106
  options.delete(:readonly) # AR likes to set :readonly to true when using the :joins option, we don't want that
93
107
 
94
- options[:conditions] = association.klass.sanitize_sql_for_conditions(options[:conditions]) if options[:conditions].is_a?(Hash)
108
+ klass = poly_class || association.klass
109
+ # sanitize the conditions locally so we get the right table name, otherwise the conditions will be evaluated on the original model
110
+ options[:conditions] = klass.sanitize_sql_for_conditions(options[:conditions]) if options[:conditions].is_a?(Hash)
111
+
112
+ poly_join = poly_class && inner_polymorphic_join(poly_class.name.underscore, :as => association.name)
95
113
 
96
114
  if options[:joins].is_a?(String) || array_of_strings?(options[:joins])
97
- options[:joins] = [inner_joins(association.name), options[:joins]].flatten
115
+ options[:joins] = [poly_class ? poly_join : inner_joins(association.name), options[:joins]].flatten
116
+ elsif poly_class
117
+ options[:joins] = options[:joins].blank? ? poly_join : [poly_join, inner_joins(options[:joins])]
98
118
  else
99
119
  options[:joins] = options[:joins].blank? ? association.name : {association.name => options[:joins]}
100
120
  end
@@ -29,14 +29,15 @@ module Searchlogic
29
29
  end
30
30
 
31
31
  def association_ordering_condition_details(name)
32
- associations = reflect_on_all_associations.collect { |assoc| assoc.name }
33
- if name.to_s =~ /^(ascend|descend)_by_(#{associations.join("|")})_(\w+)$/
34
- {:order_as => $1, :association => $2, :condition => $3}
32
+ associations = reflect_on_all_associations
33
+ association_names = associations.collect { |assoc| assoc.name }
34
+ if name.to_s =~ /^(ascend|descend)_by_(#{association_names.join("|")})_(\w+)$/
35
+ {:order_as => $1, :association => associations.find { |a| a.name == $2.to_sym }, :condition => $3}
35
36
  end
36
37
  end
37
38
 
38
39
  def create_association_ordering_condition(association, order_as, condition, args)
39
- named_scope("#{order_as}_by_#{association}_#{condition}", association_condition_options(association, "#{order_as}_by_#{condition}", args))
40
+ named_scope("#{order_as}_by_#{association.name}_#{condition}", association_condition_options(association, "#{order_as}_by_#{condition}", args))
40
41
  end
41
42
  end
42
43
  end
@@ -101,10 +101,10 @@ module Searchlogic
101
101
  end
102
102
 
103
103
  def full_association_path(part, last_condition, given_assoc)
104
- path = [given_assoc.to_sym]
105
- part.sub!(/^#{given_assoc}_/, "")
104
+ path = [given_assoc.name]
105
+ part.sub!(/^#{given_assoc.name}_/, "")
106
106
  klass = self
107
- while klass = klass.send(:reflect_on_association, given_assoc.to_sym)
107
+ while klass = klass.send(:reflect_on_association, given_assoc.name)
108
108
  klass = klass.klass
109
109
  if details = klass.send(:association_condition_details, part, last_condition)
110
110
  path << details[:association]
data/searchlogic.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{searchlogic}
8
- s.version = "2.3.16"
8
+ s.version = "2.4.0"
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{2010-01-22}
12
+ s.date = %q{2010-01-23}
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 = [
@@ -140,6 +140,13 @@ describe "Association Conditions" do
140
140
 
141
141
  it "should sanitize the scope on a foreign model instead of passing the raw options back to the original" do
142
142
  Company.named_scope(:users_count_10, :conditions => {:users_count => 10})
143
- User.company_users_count_10.proxy_options.should == {:conditions => "\"users\".\"users_count\" = 10", :joins => :company}
143
+ User.company_users_count_10.proxy_options.should == {:conditions => "\"companies\".\"users_count\" = 10", :joins => :company}
144
+ end
145
+
146
+ it "should polymorph" do
147
+ Audit.auditable_user_type_name_like("ben").proxy_options.should == {
148
+ :conditions => ["users.name LIKE ?", "%ben%"],
149
+ :joins => "INNER JOIN \"users\" ON \"users\".id = \"audits\".auditable_id AND \"audits\".auditable_type = 'User'"
150
+ }
144
151
  end
145
152
  end
data/spec/spec_helper.rb CHANGED
@@ -11,6 +11,11 @@ ActiveRecord::Base.configurations = true
11
11
 
12
12
  ActiveRecord::Schema.verbose = false
13
13
  ActiveRecord::Schema.define(:version => 1) do
14
+ create_table :audits do |t|
15
+ t.string :auditable_type
16
+ t.integer :auditable_id
17
+ end
18
+
14
19
  create_table :companies do |t|
15
20
  t.datetime :created_at
16
21
  t.datetime :updated_at
@@ -66,6 +71,10 @@ require 'searchlogic'
66
71
 
67
72
  Spec::Runner.configure do |config|
68
73
  config.before(:each) do
74
+ class Audit < ActiveRecord::Base
75
+ belongs_to :auditable, :polymorphic => true
76
+ end
77
+
69
78
  class Company < ActiveRecord::Base
70
79
  has_many :users, :dependent => :destroy
71
80
  end
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.16
4
+ version: 2.4.0
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: 2010-01-22 00:00:00 -05:00
12
+ date: 2010-01-23 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency