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 +13 -0
- data/VERSION.yml +2 -2
- data/lib/searchlogic/active_record/named_scopes.rb +21 -1
- data/lib/searchlogic/named_scopes/association_conditions.rb +40 -20
- data/lib/searchlogic/named_scopes/association_ordering.rb +5 -4
- data/lib/searchlogic/named_scopes/or_conditions.rb +3 -3
- data/searchlogic.gemspec +2 -2
- data/spec/named_scopes/association_conditions_spec.rb +8 -1
- data/spec/spec_helper.rb +9 -0
- metadata +2 -2
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
@@ -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
|
-
#
|
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
|
-
|
25
|
-
|
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
|
-
|
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 =>
|
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,
|
42
|
-
|
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(
|
46
|
-
|
47
|
-
scope =
|
48
|
-
scope_options =
|
49
|
-
arity =
|
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 =
|
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
|
-
|
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
|
33
|
-
|
34
|
-
|
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.
|
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.
|
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.
|
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-
|
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 => "\"
|
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.
|
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-
|
12
|
+
date: 2010-01-23 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|