searchlogic 2.3.16 → 2.4.0
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/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
|