searchlogic 2.1.13 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +4 -0
- data/README.rdoc +4 -4
- data/VERSION.yml +2 -2
- data/lib/searchlogic.rb +14 -2
- data/lib/searchlogic/active_record/consistency.rb +22 -0
- data/lib/searchlogic/active_record/named_scopes.rb +51 -0
- data/lib/searchlogic/named_scopes/alias_scope.rb +1 -0
- data/lib/searchlogic/named_scopes/association_conditions.rb +19 -67
- data/lib/searchlogic/named_scopes/association_ordering.rb +21 -7
- data/lib/searchlogic/named_scopes/conditions.rb +22 -72
- data/lib/searchlogic/named_scopes/ordering.rb +12 -22
- data/lib/searchlogic/search.rb +2 -2
- data/searchlogic.gemspec +4 -3
- data/spec/named_scopes/alias_scope_spec.rb +4 -0
- data/spec/named_scopes/association_conditions_spec.rb +8 -0
- data/spec/named_scopes/conditions_spec.rb +0 -3
- data/spec/search_spec.rb +7 -0
- data/spec/spec_helper.rb +0 -3
- metadata +4 -3
- data/lib/searchlogic/active_record_consistency.rb +0 -28
data/CHANGELOG.rdoc
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
== 2.2.0 released 2009-07-30
|
2
|
+
|
3
|
+
* Refactored association code to be much simpler and rely on recursion. This allows the underlying class to do most of the work. This also allows calling any named scopes through any level of associations.
|
4
|
+
|
1
5
|
== 2.1.13 released 2009-07-29
|
2
6
|
|
3
7
|
* Applied bug fix from http://github.com/skanev/searchlogic to make #order work with association ordering.
|
data/README.rdoc
CHANGED
@@ -83,7 +83,7 @@ You also get named scopes for any of your associations:
|
|
83
83
|
User.ascend_by_order_total
|
84
84
|
User.descend_by_orders_line_items_price
|
85
85
|
|
86
|
-
Again these are just named scopes. You can chain them together, call methods off of them, etc. What's great about these named scopes is that they do NOT use the :include option, making them <em>much</em> faster. Instead they
|
86
|
+
Again these are just named scopes. You can chain them together, call methods off of them, etc. What's great about these named scopes is that they 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:
|
87
87
|
|
88
88
|
Benchmark.bm do |x|
|
89
89
|
x.report { 10.times { Event.tickets_id_gt(10).all(:include => :tickets) } }
|
@@ -97,7 +97,7 @@ If you want to use the :include option, just specify it:
|
|
97
97
|
|
98
98
|
User.orders_line_items_price_greater_than(20).all(:include => {:orders => :line_items})
|
99
99
|
|
100
|
-
Obviously, only do this if you want to actually use the included objects.
|
100
|
+
Obviously, only do this if you want to actually use the included objects. Including objects into a query can be helpful with performance, especially when solving an N+1 query problem.
|
101
101
|
|
102
102
|
== Make searching and ordering data in your application trivial
|
103
103
|
|
@@ -176,7 +176,7 @@ Now just throw it in your form:
|
|
176
176
|
= f.check_box :four_year_olds
|
177
177
|
= f.submit
|
178
178
|
|
179
|
-
|
179
|
+
This really allows Searchlogic to extend beyond what it provides internally. If Searchlogic doesn't provide a named scope for that crazy edge case that you need, just create your own named scope and use it. The sky is the limit.
|
180
180
|
|
181
181
|
== Use any or all
|
182
182
|
|
@@ -199,7 +199,7 @@ If you don't like will_paginate, use another solution, or roll your own. Paginat
|
|
199
199
|
|
200
200
|
== Conflicts with other gems
|
201
201
|
|
202
|
-
You will notice searchlogic wants to create a method called "search". So do other libraries like thinking
|
202
|
+
You will notice searchlogic wants to create a method called "search". So do other libraries like thinking-sphinx, etc. So searchlogic has a no conflict resolution. If the "search" method is already taken the method will be called "searchlogic" instead. So instead of
|
203
203
|
|
204
204
|
User.search
|
205
205
|
|
data/VERSION.yml
CHANGED
data/lib/searchlogic.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require "searchlogic/core_ext/proc"
|
2
2
|
require "searchlogic/core_ext/object"
|
3
|
-
require "searchlogic/
|
3
|
+
require "searchlogic/active_record/consistency"
|
4
|
+
require "searchlogic/active_record/named_scopes"
|
4
5
|
require "searchlogic/named_scopes/conditions"
|
5
6
|
require "searchlogic/named_scopes/ordering"
|
6
7
|
require "searchlogic/named_scopes/association_conditions"
|
@@ -10,10 +11,21 @@ require "searchlogic/search"
|
|
10
11
|
|
11
12
|
Proc.send(:include, Searchlogic::CoreExt::Proc)
|
12
13
|
Object.send(:include, Searchlogic::CoreExt::Object)
|
14
|
+
|
15
|
+
module ActiveRecord # :nodoc: all
|
16
|
+
class Base
|
17
|
+
class << self
|
18
|
+
include Searchlogic::ActiveRecord::Consistency
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
ActiveRecord::Base.extend(Searchlogic::ActiveRecord::NamedScopes)
|
24
|
+
|
13
25
|
ActiveRecord::Base.extend(Searchlogic::NamedScopes::Conditions)
|
14
|
-
ActiveRecord::Base.extend(Searchlogic::NamedScopes::Ordering)
|
15
26
|
ActiveRecord::Base.extend(Searchlogic::NamedScopes::AssociationConditions)
|
16
27
|
ActiveRecord::Base.extend(Searchlogic::NamedScopes::AssociationOrdering)
|
28
|
+
ActiveRecord::Base.extend(Searchlogic::NamedScopes::Ordering)
|
17
29
|
ActiveRecord::Base.extend(Searchlogic::NamedScopes::AliasScope)
|
18
30
|
ActiveRecord::Base.extend(Searchlogic::Search::Implementation)
|
19
31
|
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Searchlogic
|
2
|
+
module ActiveRecord
|
3
|
+
# Active Record is pretty inconsistent with how their SQL is constructed. This
|
4
|
+
# method attempts to close the gap between the various inconsistencies.
|
5
|
+
module Consistency
|
6
|
+
def self.included(klass)
|
7
|
+
klass.class_eval do
|
8
|
+
alias_method_chain :merge_joins, :searchlogic
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# In AR multiple joins are sometimes in a single join query, and other times they
|
13
|
+
# are not. The merge_joins method in AR should account for this, but it doesn't.
|
14
|
+
# This fixes that problem. This way there is one join per string, which allows
|
15
|
+
# the merge_joins method to delete duplicates.
|
16
|
+
def merge_joins_with_searchlogic(*args)
|
17
|
+
joins = merge_joins_without_searchlogic(*args)
|
18
|
+
joins.collect { |j| j.is_a?(String) ? j.split(" ") : j }.flatten.uniq
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Searchlogic
|
2
|
+
module ActiveRecord
|
3
|
+
# Adds methods that give extra information about a classes named scopes.
|
4
|
+
module NamedScopes
|
5
|
+
# Retrieves the options passed when creating the respective named scope. Ex:
|
6
|
+
#
|
7
|
+
# named_scope :whatever, :conditions => {:column => value}
|
8
|
+
#
|
9
|
+
# This method will return:
|
10
|
+
#
|
11
|
+
# :conditions => {:column => value}
|
12
|
+
#
|
13
|
+
# ActiveRecord hides this internally in a Proc, so we have to try and pull it out with this
|
14
|
+
# method.
|
15
|
+
def named_scope_options(name)
|
16
|
+
key = scopes.key?(name.to_sym) ? name.to_sym : primary_condition_name(name)
|
17
|
+
|
18
|
+
if key
|
19
|
+
eval("options", scopes[key].binding)
|
20
|
+
else
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# The arity for a named scope's proc is important, because we use the arity
|
26
|
+
# to determine if the condition should be ignored when calling the search method.
|
27
|
+
# If the condition is false and the arity is 0, then we skip it all together. Ex:
|
28
|
+
#
|
29
|
+
# User.named_scope :age_is_4, :conditions => {:age => 4}
|
30
|
+
# User.search(:age_is_4 => false) == User.all
|
31
|
+
# User.search(:age_is_4 => true) == User.all(:conditions => {:age => 4})
|
32
|
+
#
|
33
|
+
# We also use it when trying to "copy" the underlying named scope for association
|
34
|
+
# conditions. This way our aliased scope accepts the same number of parameters for
|
35
|
+
# the underlying scope.
|
36
|
+
def named_scope_arity(name)
|
37
|
+
options = named_scope_options(name)
|
38
|
+
options.respond_to?(:arity) ? options.arity : nil
|
39
|
+
end
|
40
|
+
|
41
|
+
# A convenience method for creating inner join sql to that your inner joins
|
42
|
+
# are consistent with how Active Record creates them. Basically a tool for
|
43
|
+
# you to use when writing your own named scopes. This way you know for sure
|
44
|
+
# that duplicate joins will be removed when chaining scopes together that
|
45
|
+
# use the same join.
|
46
|
+
def inner_joins(association_name)
|
47
|
+
ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, association_name, nil).join_associations.collect { |assoc| assoc.association_join }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -3,7 +3,7 @@ module Searchlogic
|
|
3
3
|
# Handles dynamically creating named scopes for associations.
|
4
4
|
module AssociationConditions
|
5
5
|
def condition?(name) # :nodoc:
|
6
|
-
super || association_condition?(name)
|
6
|
+
super || association_condition?(name)
|
7
7
|
end
|
8
8
|
|
9
9
|
def primary_condition_name(name) # :nodoc:
|
@@ -11,40 +11,19 @@ module Searchlogic
|
|
11
11
|
result
|
12
12
|
elsif association_condition?(name)
|
13
13
|
name.to_sym
|
14
|
-
elsif details = association_alias_condition_details(name)
|
15
|
-
"#{details[:association]}_#{details[:column]}_#{primary_condition(details[:condition])}".to_sym
|
16
14
|
else
|
17
15
|
nil
|
18
16
|
end
|
19
17
|
end
|
20
18
|
|
21
|
-
# Is the name of the method a valid name for an association condition?
|
22
|
-
def association_condition?(name)
|
23
|
-
!association_condition_details(name).nil?
|
24
|
-
end
|
25
|
-
|
26
|
-
# Is the named of the method a valid name for an association alias condition?
|
27
|
-
# An alias being "gt" for "greater_than", etc.
|
28
|
-
def association_alias_condition?(name)
|
29
|
-
!association_alias_condition_details(name).nil?
|
30
|
-
end
|
31
|
-
|
32
|
-
# A convenience method for creating inner join sql to that your inner joins
|
33
|
-
# are consistent with how Active Record creates them. Basically a tool for
|
34
|
-
# you to use when writing your own named scopes. This way you know for sure
|
35
|
-
# that duplicate joins will be removed when chaining scopes together that
|
36
|
-
# use the same join.
|
37
|
-
def inner_joins(association_name)
|
38
|
-
ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, association_name, nil).join_associations.collect { |assoc| assoc.association_join }
|
39
|
-
end
|
40
|
-
|
41
19
|
private
|
20
|
+
def association_condition?(name)
|
21
|
+
!association_condition_details(name).nil?
|
22
|
+
end
|
23
|
+
|
42
24
|
def method_missing(name, *args, &block)
|
43
25
|
if details = association_condition_details(name)
|
44
|
-
create_association_condition(details[:association], details[:
|
45
|
-
send(name, *args)
|
46
|
-
elsif details = association_alias_condition_details(name)
|
47
|
-
create_association_alias_condition(details[:association], details[:column], details[:condition], args)
|
26
|
+
create_association_condition(details[:association], details[:condition], args)
|
48
27
|
send(name, *args)
|
49
28
|
else
|
50
29
|
super
|
@@ -52,51 +31,24 @@ module Searchlogic
|
|
52
31
|
end
|
53
32
|
|
54
33
|
def association_condition_details(name)
|
55
|
-
assocs =
|
34
|
+
assocs = reflect_on_all_associations.reject { |assoc| assoc.options[:polymorphic] }
|
56
35
|
return nil if assocs.empty?
|
57
|
-
regexes = [association_searchlogic_regex(assocs, Conditions::PRIMARY_CONDITIONS)]
|
58
|
-
assocs.each do |assoc|
|
59
|
-
scope_names = assoc.klass.scopes.keys + assoc.klass.alias_scopes.keys
|
60
|
-
scope_names.uniq!
|
61
|
-
scope_names.delete(:scoped)
|
62
|
-
next if scope_names.empty?
|
63
|
-
regexes << /^(#{assoc.name})_(#{scope_names.join("|")})$/
|
64
|
-
end
|
65
36
|
|
66
|
-
if
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
def association_alias_condition_details(name)
|
78
|
-
assocs = non_polymorphic_associations
|
79
|
-
return nil if assocs.empty?
|
80
|
-
|
81
|
-
if !local_condition?(name) && name.to_s =~ association_searchlogic_regex(assocs, Conditions::ALIAS_CONDITIONS)
|
82
|
-
{:association => $1, :column => $2, :condition => $3}
|
37
|
+
if name.to_s =~ /^(#{assocs.collect(&:name).join("|")})_(\w+)$/
|
38
|
+
association_name = $1
|
39
|
+
condition = $2
|
40
|
+
association = reflect_on_association(association_name.to_sym)
|
41
|
+
klass = association.klass
|
42
|
+
if klass.condition?(condition)
|
43
|
+
{:association => $1, :condition => $2}
|
44
|
+
else
|
45
|
+
nil
|
46
|
+
end
|
83
47
|
end
|
84
48
|
end
|
85
49
|
|
86
|
-
def
|
87
|
-
|
88
|
-
end
|
89
|
-
|
90
|
-
def association_searchlogic_regex(assocs, condition_names)
|
91
|
-
/^(#{assocs.collect(&:name).join("|")})_(\w+)_(#{condition_names.join("|")})$/
|
92
|
-
end
|
93
|
-
|
94
|
-
def create_association_alias_condition(association, column, condition, args)
|
95
|
-
primary_condition = primary_condition(condition)
|
96
|
-
alias_name = "#{association}_#{column}_#{condition}"
|
97
|
-
primary_name = "#{association}_#{column}_#{primary_condition}"
|
98
|
-
send(primary_name, *args) # go back to method_missing and make sure we create the method
|
99
|
-
(class << self; self; end).class_eval { alias_method alias_name, primary_name }
|
50
|
+
def create_association_condition(association, condition, args)
|
51
|
+
named_scope("#{association}_#{condition}", association_condition_options(association, condition, args))
|
100
52
|
end
|
101
53
|
|
102
54
|
def association_condition_options(association_name, association_condition, args)
|
@@ -2,14 +2,28 @@ module Searchlogic
|
|
2
2
|
module NamedScopes
|
3
3
|
# Handles dynamically creating named scopes for associations.
|
4
4
|
module AssociationOrdering
|
5
|
-
def
|
6
|
-
|
5
|
+
def condition?(name) # :nodoc:
|
6
|
+
super || association_ordering_condition?(name)
|
7
|
+
end
|
8
|
+
|
9
|
+
def primary_condition_name(name) # :nodoc
|
10
|
+
if result = super
|
11
|
+
result
|
12
|
+
elsif association_ordering_condition?(name)
|
13
|
+
name.to_sym
|
14
|
+
else
|
15
|
+
nil
|
16
|
+
end
|
7
17
|
end
|
8
18
|
|
9
19
|
private
|
20
|
+
def association_ordering_condition?(name)
|
21
|
+
!association_ordering_condition_details(name).nil?
|
22
|
+
end
|
23
|
+
|
10
24
|
def method_missing(name, *args, &block)
|
11
25
|
if details = association_ordering_condition_details(name)
|
12
|
-
create_association_ordering_condition(details[:association], details[:order_as], details[:
|
26
|
+
create_association_ordering_condition(details[:association], details[:order_as], details[:condition], args)
|
13
27
|
send(name, *args)
|
14
28
|
else
|
15
29
|
super
|
@@ -18,13 +32,13 @@ module Searchlogic
|
|
18
32
|
|
19
33
|
def association_ordering_condition_details(name)
|
20
34
|
associations = reflect_on_all_associations.collect { |assoc| assoc.name }
|
21
|
-
if
|
22
|
-
{:order_as => $1, :association => $2, :
|
35
|
+
if name.to_s =~ /^(ascend|descend)_by_(#{associations.join("|")})_(\w+)$/
|
36
|
+
{:order_as => $1, :association => $2, :condition => $3}
|
23
37
|
end
|
24
38
|
end
|
25
39
|
|
26
|
-
def create_association_ordering_condition(
|
27
|
-
named_scope("#{order_as}_by_#{
|
40
|
+
def create_association_ordering_condition(association, order_as, condition, args)
|
41
|
+
named_scope("#{order_as}_by_#{association}_#{condition}", association_condition_options(association, "#{order_as}_by_#{condition}", args))
|
28
42
|
end
|
29
43
|
end
|
30
44
|
end
|
@@ -40,42 +40,6 @@ module Searchlogic
|
|
40
40
|
PRIMARY_CONDITIONS = CONDITIONS.keys
|
41
41
|
ALIAS_CONDITIONS = CONDITIONS.values.flatten
|
42
42
|
|
43
|
-
# Retrieves the options passed when creating the respective named scope. Ex:
|
44
|
-
#
|
45
|
-
# named_scope :whatever, :conditions => {:column => value}
|
46
|
-
#
|
47
|
-
# This method will return:
|
48
|
-
#
|
49
|
-
# :conditions => {:column => value}
|
50
|
-
#
|
51
|
-
# ActiveRecord hides this internally in a Proc, so we have to try and pull it out with this
|
52
|
-
# method.
|
53
|
-
def named_scope_options(name)
|
54
|
-
key = scopes.key?(name.to_sym) ? name.to_sym : primary_condition_name(name)
|
55
|
-
|
56
|
-
if key
|
57
|
-
eval("options", scopes[key])
|
58
|
-
else
|
59
|
-
nil
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
# The arity for a named scope's proc is important, because we use the arity
|
64
|
-
# to determine if the condition should be ignored when calling the search method.
|
65
|
-
# If the condition is false and the arity is 0, then we skip it all together. Ex:
|
66
|
-
#
|
67
|
-
# User.named_scope :age_is_4, :conditions => {:age => 4}
|
68
|
-
# User.search(:age_is_4 => false) == User.all
|
69
|
-
# User.search(:age_is_4 => true) == User.all(:conditions => {:age => 4})
|
70
|
-
#
|
71
|
-
# We also use it when trying to "copy" the underlying named scope for association
|
72
|
-
# conditions. This way our aliased scope accepts the same number of parameters for
|
73
|
-
# the underlying scope.
|
74
|
-
def named_scope_arity(name)
|
75
|
-
options = named_scope_options(name)
|
76
|
-
options.respond_to?(:arity) ? options.arity : nil
|
77
|
-
end
|
78
|
-
|
79
43
|
# Returns the primary condition for the given alias. Ex:
|
80
44
|
#
|
81
45
|
# primary_condition(:gt) => :greater_than
|
@@ -90,10 +54,12 @@ module Searchlogic
|
|
90
54
|
# primary_condition_name(:id_gt) => :id_greater_than
|
91
55
|
# primary_condition_name(:id_greater_than) => :id_greater_than
|
92
56
|
def primary_condition_name(name)
|
93
|
-
if
|
94
|
-
name.to_sym
|
95
|
-
|
96
|
-
|
57
|
+
if details = condition_details(name)
|
58
|
+
if PRIMARY_CONDITIONS.include?(name.to_sym)
|
59
|
+
name
|
60
|
+
else
|
61
|
+
"#{details[:column]}_#{primary_condition(details[:condition])}".to_sym
|
62
|
+
end
|
97
63
|
else
|
98
64
|
nil
|
99
65
|
end
|
@@ -101,48 +67,38 @@ module Searchlogic
|
|
101
67
|
|
102
68
|
# Is the name of the method a valid condition that can be dynamically created?
|
103
69
|
def condition?(name)
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
# Is the condition for a local column, not an association
|
108
|
-
def local_condition?(name)
|
109
|
-
primary_condition?(name) || alias_condition?(name)
|
110
|
-
end
|
111
|
-
|
112
|
-
# Is the name of the method a valid condition that can be dynamically created,
|
113
|
-
# AND is it a primary condition (not an alias). "greater_than" not "gt".
|
114
|
-
def primary_condition?(name)
|
115
|
-
!primary_condition_details(name).nil?
|
116
|
-
end
|
117
|
-
|
118
|
-
# Is the name of the method a valid condition that can be dynamically created,
|
119
|
-
# AND is it an alias condition. "gt" not "greater_than".
|
120
|
-
def alias_condition?(name)
|
121
|
-
!alias_condition_details(name).nil?
|
70
|
+
return false if name.blank?
|
71
|
+
scope_names = scopes.keys.reject { |k| k == :scoped }
|
72
|
+
scope_names.include?(name.to_sym) || !condition_details(name).nil?
|
122
73
|
end
|
123
74
|
|
124
75
|
private
|
125
76
|
def method_missing(name, *args, &block)
|
126
|
-
if details =
|
127
|
-
|
128
|
-
send(name, *args)
|
129
|
-
elsif details = alias_condition_details(name)
|
130
|
-
create_alias_condition(details[:column], details[:condition], args)
|
77
|
+
if details = condition_details(name)
|
78
|
+
create_condition(details[:column], details[:condition], args)
|
131
79
|
send(name, *args)
|
132
80
|
else
|
133
81
|
super
|
134
82
|
end
|
135
83
|
end
|
136
84
|
|
137
|
-
def
|
138
|
-
if name.to_s =~ /^(#{column_names.join("|")})_(#{PRIMARY_CONDITIONS.join("|")})$/
|
85
|
+
def condition_details(name)
|
86
|
+
if name.to_s =~ /^(#{column_names.join("|")})_(#{(PRIMARY_CONDITIONS + ALIAS_CONDITIONS).join("|")})$/
|
139
87
|
{:column => $1, :condition => $2}
|
140
88
|
end
|
141
89
|
end
|
142
90
|
|
91
|
+
def create_condition(column, condition, args)
|
92
|
+
if PRIMARY_CONDITIONS.include?(condition.to_sym)
|
93
|
+
create_primary_condition(column, condition)
|
94
|
+
elsif ALIAS_CONDITIONS.include?(condition.to_sym)
|
95
|
+
create_alias_condition(column, condition, args)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
143
99
|
def create_primary_condition(column, condition)
|
144
100
|
column_type = columns_hash[column.to_s].type
|
145
|
-
match_keyword = ActiveRecord::Base.connection.adapter_name == "PostgreSQL" ? "ILIKE" : "LIKE"
|
101
|
+
match_keyword = ::ActiveRecord::Base.connection.adapter_name == "PostgreSQL" ? "ILIKE" : "LIKE"
|
146
102
|
|
147
103
|
scope_options = case condition.to_s
|
148
104
|
when /^equals/
|
@@ -218,12 +174,6 @@ module Searchlogic
|
|
218
174
|
end
|
219
175
|
end
|
220
176
|
|
221
|
-
def alias_condition_details(name)
|
222
|
-
if name.to_s =~ /^(#{column_names.join("|")})_(#{ALIAS_CONDITIONS.join("|")})$/
|
223
|
-
{:column => $1, :condition => $2}
|
224
|
-
end
|
225
|
-
end
|
226
|
-
|
227
177
|
def create_alias_condition(column, condition, args)
|
228
178
|
primary_condition = primary_condition(condition)
|
229
179
|
alias_name = "#{column}_#{condition}"
|
@@ -2,59 +2,49 @@ module Searchlogic
|
|
2
2
|
module NamedScopes
|
3
3
|
# Handles dynamically creating named scopes for orderin by columns.
|
4
4
|
module Ordering
|
5
|
-
def
|
6
|
-
super ||
|
5
|
+
def condition?(name) # :nodoc:
|
6
|
+
super || ordering_condition?(name)
|
7
7
|
end
|
8
8
|
|
9
9
|
def primary_condition_name(name) # :nodoc
|
10
10
|
if result = super
|
11
11
|
result
|
12
|
-
elsif
|
12
|
+
elsif ordering_condition?(name)
|
13
13
|
name.to_sym
|
14
14
|
else
|
15
15
|
nil
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
def order_condition?(name) # :nodoc:
|
20
|
-
!order_condition_details(name).nil?
|
21
|
-
end
|
22
|
-
|
23
|
-
def custom_order_condition?(name) # :nodoc:
|
24
|
-
!custom_order_condition_details(name).nil?
|
25
|
-
end
|
26
|
-
|
27
19
|
private
|
20
|
+
def ordering_condition?(name) # :nodoc:
|
21
|
+
!ordering_condition_details(name).nil?
|
22
|
+
end
|
23
|
+
|
28
24
|
def method_missing(name, *args, &block)
|
29
25
|
if name == :order
|
30
26
|
named_scope name, lambda { |scope_name|
|
31
|
-
return {} if !
|
27
|
+
return {} if !condition?(scope_name)
|
32
28
|
send(scope_name).proxy_options
|
33
29
|
}
|
34
30
|
send(name, *args)
|
35
|
-
elsif details =
|
36
|
-
|
31
|
+
elsif details = ordering_condition_details(name)
|
32
|
+
create_ordering_conditions(details[:column])
|
37
33
|
send(name, *args)
|
38
34
|
else
|
39
35
|
super
|
40
36
|
end
|
41
37
|
end
|
42
38
|
|
43
|
-
def
|
39
|
+
def ordering_condition_details(name)
|
44
40
|
if name.to_s =~ /^(ascend|descend)_by_(#{column_names.join("|")})$/
|
45
41
|
{:order_as => $1, :column => $2}
|
46
42
|
elsif name.to_s =~ /^order$/
|
47
43
|
{}
|
48
44
|
end
|
49
45
|
end
|
50
|
-
|
51
|
-
def custom_order_condition_details(name)
|
52
|
-
if name.to_s =~ /^(ascend|descend)_by_(.+)$/
|
53
|
-
{:order_as => $1, :scope => name.to_sym}
|
54
|
-
end
|
55
|
-
end
|
56
46
|
|
57
|
-
def
|
47
|
+
def create_ordering_conditions(column)
|
58
48
|
named_scope("ascend_by_#{column}".to_sym, {:order => "#{table_name}.#{column} ASC"})
|
59
49
|
named_scope("descend_by_#{column}".to_sym, {:order => "#{table_name}.#{column} DESC"})
|
60
50
|
end
|
data/lib/searchlogic/search.rb
CHANGED
@@ -17,7 +17,7 @@ module Searchlogic
|
|
17
17
|
class Search
|
18
18
|
# Responsible for adding a "search" method into your models.
|
19
19
|
module Implementation
|
20
|
-
# Additional method, gets
|
20
|
+
# Additional method, gets aliased as "search" if that method
|
21
21
|
# is available. A lot of other libraries like to use "search"
|
22
22
|
# as well, so if you have a conflict like this, you can use
|
23
23
|
# this method directly.
|
@@ -141,7 +141,7 @@ module Searchlogic
|
|
141
141
|
else
|
142
142
|
# Let's leverage ActiveRecord's type casting, so that casting is consistent
|
143
143
|
# with the other models.
|
144
|
-
column_for_type_cast = ActiveRecord::ConnectionAdapters::Column.new("", nil)
|
144
|
+
column_for_type_cast = ::ActiveRecord::ConnectionAdapters::Column.new("", nil)
|
145
145
|
column_for_type_cast.instance_variable_set(:@type, type)
|
146
146
|
value = column_for_type_cast.type_cast(value)
|
147
147
|
Time.zone && value.is_a?(Time) ? value.in_time_zone : value
|
data/searchlogic.gemspec
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{searchlogic}
|
5
|
-
s.version = "2.
|
5
|
+
s.version = "2.2.0"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Ben Johnson of Binary Logic"]
|
9
|
-
s.date = %q{2009-07-
|
9
|
+
s.date = %q{2009-07-30}
|
10
10
|
s.description = %q{Searchlogic provides common named scopes and object based searching for ActiveRecord.}
|
11
11
|
s.email = %q{bjohnson@binarylogic.com}
|
12
12
|
s.extra_rdoc_files = [
|
@@ -22,7 +22,8 @@ Gem::Specification.new do |s|
|
|
22
22
|
"VERSION.yml",
|
23
23
|
"init.rb",
|
24
24
|
"lib/searchlogic.rb",
|
25
|
-
"lib/searchlogic/
|
25
|
+
"lib/searchlogic/active_record/consistency.rb",
|
26
|
+
"lib/searchlogic/active_record/named_scopes.rb",
|
26
27
|
"lib/searchlogic/core_ext/object.rb",
|
27
28
|
"lib/searchlogic/core_ext/proc.rb",
|
28
29
|
"lib/searchlogic/named_scopes/alias_scope.rb",
|
@@ -1,6 +1,10 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
|
2
2
|
|
3
3
|
describe "AliasScope" do
|
4
|
+
before(:each) do
|
5
|
+
User.alias_scope :username_has, lambda { |value| User.username_like(value) }
|
6
|
+
end
|
7
|
+
|
4
8
|
it "should allow alias scopes" do
|
5
9
|
User.create(:username => "bjohnson")
|
6
10
|
User.create(:username => "thunt")
|
@@ -10,14 +10,22 @@ describe "Association Conditions" do
|
|
10
10
|
end
|
11
11
|
|
12
12
|
it "should allow the use of foreign pre-existing named scopes" do
|
13
|
+
User.named_scope :uname, lambda { |value| {:conditions => ["users.username = ?", value]} }
|
13
14
|
Company.users_uname("bjohnson").proxy_options.should == User.uname("bjohnson").proxy_options.merge(:joins => :users)
|
14
15
|
end
|
15
16
|
|
17
|
+
it "should allow the use of deep foreign pre-existing named scopes" do
|
18
|
+
Order.named_scope :big_id, :conditions => "orders.id > 100"
|
19
|
+
Company.users_orders_big_id.proxy_options.should == Order.big_id.proxy_options.merge(:joins => {:users => :orders})
|
20
|
+
end
|
21
|
+
|
16
22
|
it "should allow the use of foreign pre-existing alias scopes" do
|
23
|
+
User.alias_scope :username_has, lambda { |value| User.username_like(value) }
|
17
24
|
Company.users_username_has("bjohnson").proxy_options.should == User.username_has("bjohnson").proxy_options.merge(:joins => :users)
|
18
25
|
end
|
19
26
|
|
20
27
|
it "should not raise errors for scopes that don't return anything" do
|
28
|
+
User.alias_scope :blank_scope, lambda { |value| }
|
21
29
|
Company.users_blank_scope("bjohnson").proxy_options.should == {:joins => :users}
|
22
30
|
end
|
23
31
|
|
@@ -203,9 +203,6 @@ describe "Conditions" do
|
|
203
203
|
end
|
204
204
|
|
205
205
|
it "should have is_not" do
|
206
|
-
# This is matching "not" first. How do you give priority in a regex? Because it's matching the
|
207
|
-
# 'not' condition and thinking the column is 'age_is'.
|
208
|
-
pending
|
209
206
|
User.age_is_not(5).proxy_options.should == User.age_does_not_equal(5).proxy_options
|
210
207
|
end
|
211
208
|
|
data/spec/search_spec.rb
CHANGED
@@ -110,12 +110,14 @@ describe "Search" do
|
|
110
110
|
end
|
111
111
|
|
112
112
|
it "should allow setting pre-existing association conditions" do
|
113
|
+
User.named_scope :uname, lambda { |value| {:conditions => ["users.username = ?", value]} }
|
113
114
|
search = Company.search
|
114
115
|
search.users_uname = "bjohnson"
|
115
116
|
search.users_uname.should == "bjohnson"
|
116
117
|
end
|
117
118
|
|
118
119
|
it "should allow setting pre-existing association alias conditions" do
|
120
|
+
User.alias_scope :username_has, lambda { |value| User.username_like(value) }
|
119
121
|
search = Company.search
|
120
122
|
search.users_username_has = "bjohnson"
|
121
123
|
search.users_username_has.should == "bjohnson"
|
@@ -158,6 +160,11 @@ describe "Search" do
|
|
158
160
|
lambda { search.unknown = true }.should raise_error(Searchlogic::Search::UnknownConditionError)
|
159
161
|
end
|
160
162
|
|
163
|
+
it "should not allow setting conditions on sensitive methods" do
|
164
|
+
search = User.search
|
165
|
+
lambda { search.destroy = true }.should raise_error(Searchlogic::Search::UnknownConditionError)
|
166
|
+
end
|
167
|
+
|
161
168
|
it "should not use the ruby implementation of the id method" do
|
162
169
|
search = User.search
|
163
170
|
search.id.should be_nil
|
data/spec/spec_helper.rb
CHANGED
@@ -71,9 +71,6 @@ Spec::Runner.configure do |config|
|
|
71
71
|
class User < ActiveRecord::Base
|
72
72
|
belongs_to :company, :counter_cache => true
|
73
73
|
has_many :orders, :dependent => :destroy
|
74
|
-
named_scope :uname, lambda { |value| {:conditions => ["users.username = ?", value]} }
|
75
|
-
alias_scope :username_has, lambda { |value| username_like(value) }
|
76
|
-
alias_scope :blank_scope, lambda { |value| }
|
77
74
|
end
|
78
75
|
|
79
76
|
class Order < ActiveRecord::Base
|
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.2.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: 2009-07-
|
12
|
+
date: 2009-07-30 00:00:00 -04:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -40,7 +40,8 @@ files:
|
|
40
40
|
- VERSION.yml
|
41
41
|
- init.rb
|
42
42
|
- lib/searchlogic.rb
|
43
|
-
- lib/searchlogic/
|
43
|
+
- lib/searchlogic/active_record/consistency.rb
|
44
|
+
- lib/searchlogic/active_record/named_scopes.rb
|
44
45
|
- lib/searchlogic/core_ext/object.rb
|
45
46
|
- lib/searchlogic/core_ext/proc.rb
|
46
47
|
- lib/searchlogic/named_scopes/alias_scope.rb
|
@@ -1,28 +0,0 @@
|
|
1
|
-
module Searchlogic
|
2
|
-
# Active Record is pretty inconsistent with how their SQL is constructed. This
|
3
|
-
# method attempts to close the gap between the various inconsistencies.
|
4
|
-
module ActiveRecordConsistency
|
5
|
-
def self.included(klass)
|
6
|
-
klass.class_eval do
|
7
|
-
alias_method_chain :merge_joins, :searchlogic
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
|
-
# In AR multiple joins are sometimes in a single join query, and other times they
|
12
|
-
# are not. The merge_joins method in AR should account for this, but it doesn't.
|
13
|
-
# This fixes that problem. This way there is one join per string, which allows
|
14
|
-
# the merge_joins method to delete duplicates.
|
15
|
-
def merge_joins_with_searchlogic(*args)
|
16
|
-
joins = merge_joins_without_searchlogic(*args)
|
17
|
-
joins.collect { |j| j.is_a?(String) ? j.split(" ") : j }.flatten.uniq
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
module ActiveRecord # :nodoc: all
|
23
|
-
class Base
|
24
|
-
class << self
|
25
|
-
include Searchlogic::ActiveRecordConsistency
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|