skanev-searchlogic 2.1.8.1

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/Rakefile ADDED
@@ -0,0 +1,43 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "searchlogic"
8
+ gem.summary = "Searchlogic provides common named scopes and object based searching for ActiveRecord."
9
+ gem.description = "Searchlogic provides common named scopes and object based searching for ActiveRecord."
10
+ gem.email = "bjohnson@binarylogic.com"
11
+ gem.homepage = "http://github.com/binarylogic/searchlogic"
12
+ gem.authors = ["Ben Johnson of Binary Logic"]
13
+ gem.rubyforge_project = "searchlogic"
14
+ gem.add_dependency "activerecord", ">= 2.0.0"
15
+ end
16
+ rescue LoadError
17
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
18
+ end
19
+
20
+ require 'spec/rake/spectask'
21
+ Spec::Rake::SpecTask.new(:spec) do |spec|
22
+ spec.libs << 'lib' << 'spec'
23
+ spec.spec_files = FileList['spec/**/*_spec.rb']
24
+ end
25
+
26
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
27
+ spec.libs << 'lib' << 'spec'
28
+ spec.pattern = 'spec/**/*_spec.rb'
29
+ spec.rcov = true
30
+ end
31
+
32
+
33
+ task :default => :spec
34
+
35
+ begin
36
+ require 'rake/contrib/sshpublisher'
37
+ namespace :rubyforge do
38
+ desc "Release gem to RubyForge"
39
+ task :release => ["rubyforge:release:gem"]
40
+ end
41
+ rescue LoadError
42
+ puts "Rake SshDirPublisher is unavailable or your rubyforge environment is not configured."
43
+ end
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 2
3
+ :minor: 1
4
+ :patch: 8
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require "searchlogic"
@@ -0,0 +1,33 @@
1
+ require "searchlogic/core_ext/proc"
2
+ require "searchlogic/core_ext/object"
3
+ require "searchlogic/active_record_consistency"
4
+ require "searchlogic/named_scopes/conditions"
5
+ require "searchlogic/named_scopes/ordering"
6
+ require "searchlogic/named_scopes/association_conditions"
7
+ require "searchlogic/named_scopes/association_ordering"
8
+ require "searchlogic/named_scopes/alias_scope"
9
+ require "searchlogic/search"
10
+
11
+ Proc.send(:include, Searchlogic::CoreExt::Proc)
12
+ Object.send(:include, Searchlogic::CoreExt::Object)
13
+ ActiveRecord::Base.extend(Searchlogic::NamedScopes::Conditions)
14
+ ActiveRecord::Base.extend(Searchlogic::NamedScopes::Ordering)
15
+ ActiveRecord::Base.extend(Searchlogic::NamedScopes::AssociationConditions)
16
+ ActiveRecord::Base.extend(Searchlogic::NamedScopes::AssociationOrdering)
17
+ ActiveRecord::Base.extend(Searchlogic::NamedScopes::AliasScope)
18
+ ActiveRecord::Base.extend(Searchlogic::Search::Implementation)
19
+
20
+ # Try to use the search method, if it's available. Thinking sphinx and other plugins
21
+ # like to use that method as well.
22
+ if !ActiveRecord::Base.respond_to?(:search)
23
+ ActiveRecord::Base.class_eval do
24
+ class << self
25
+ alias_method :search, :searchlogic
26
+ end
27
+ end
28
+ end
29
+
30
+ if defined?(ActionController)
31
+ require "searchlogic/rails_helpers"
32
+ ActionController::Base.helper(Searchlogic::RailsHelpers)
33
+ end
@@ -0,0 +1,27 @@
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 time they
12
+ # are not. The merge_joins method in AR should account for this, but it doesn't.
13
+ # This fixes that problem.
14
+ def merge_joins_with_searchlogic(*args)
15
+ joins = merge_joins_without_searchlogic(*args)
16
+ joins.collect { |j| j.is_a?(String) ? j.split(" ") : j }.flatten.uniq
17
+ end
18
+ end
19
+ end
20
+
21
+ module ActiveRecord # :nodoc: all
22
+ class Base
23
+ class << self
24
+ include Searchlogic::ActiveRecordConsistency
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,39 @@
1
+ module Searchlogic
2
+ module CoreExt
3
+ # Contains extensions for the Object class that Searchlogic uses.
4
+ module Object
5
+ # Searchlogic needs to know the expected type of the condition value so that it can properly cast
6
+ # the value in the Searchlogic::Search object. For example:
7
+ #
8
+ # search = User.search(:id_gt => "1")
9
+ #
10
+ # You would expect this:
11
+ #
12
+ # search.id_gt => 1
13
+ #
14
+ # Not this:
15
+ #
16
+ # search.id_gt => "1"
17
+ #
18
+ # Parameter values from forms are ALWAYS strings, so we have to cast them. Just like ActiveRecord
19
+ # does when you instantiate a new User object.
20
+ #
21
+ # The problem is that ruby has no variable types, so Searchlogic needs to know what type you are expecting
22
+ # for your named scope. So instead of this:
23
+ #
24
+ # named_scope :id_gt, lambda { |value| {:conditions => ["id > ?", value]} }
25
+ #
26
+ # You need to do this:
27
+ #
28
+ # named_scope :id_gt, searchlogic_lambda(:integer) { |value| {:conditions => ["id > ?", value]} }
29
+ #
30
+ # If you are wanting a string, you don't have to do anything, because Searchlogic assumes you are want a string.
31
+ # If you want something else, you need to specify it as I did in the above example.
32
+ def searchlogic_lambda(type = :string, &block)
33
+ proc = lambda(&block)
34
+ proc.searchlogic_arg_type = type
35
+ proc
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,11 @@
1
+ module Searchlogic
2
+ module CoreExt
3
+ module Proc # :nodoc:
4
+ def self.included(klass)
5
+ klass.class_eval do
6
+ attr_accessor :searchlogic_arg_type
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,63 @@
1
+ module Searchlogic
2
+ module NamedScopes
3
+ # Adds the ability to create alias scopes that allow you to alias a named
4
+ # scope or create a named scope procedure, while at the same time letting
5
+ # Searchlogic know that this is a safe method.
6
+ module AliasScope
7
+ # The searchlogic Search class takes a hash and chains the values together as named scopes.
8
+ # For security reasons the only hash keys that are allowed must be mapped to named scopes.
9
+ # You can not pass the name of a class method and expect that to be called. In some instances
10
+ # you might create a class method that essentially aliases a named scope or represents a
11
+ # named scope procedure. Ex:
12
+ #
13
+ # User.named_scope :teenager, :conditions => ["age >= ? AND age <= ?", 13, 19]
14
+ #
15
+ # This is obviously a very basic example, but there is logic that is duplicated here. For
16
+ # more complicated named scopes this might make more sense, but to make my point you could
17
+ # do something like this instead
18
+ #
19
+ # class User
20
+ # def teenager
21
+ # age_gte(13).age_lte(19)
22
+ # end
23
+ # end
24
+ #
25
+ # As I stated above, you could not use this method with the Searchlogic::Search class because
26
+ # there is no way to tell that this is actually a named scope. Instead, Searchlogic lets you
27
+ # do something like this:
28
+ #
29
+ # User.alias_scope :teenager, lambda { age_gte(13).age_lte(19) }
30
+ #
31
+ # It fits in better, at the same time Searchlogic will know this is an acceptable named scope.
32
+ def alias_scope(name, options = nil)
33
+ alias_scopes[name.to_sym] = options
34
+ (class << self; self end).instance_eval do
35
+ define_method name do |*args|
36
+ case options
37
+ when Symbol
38
+ send(options)
39
+ else
40
+ options.call(*args)
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ def alias_scopes # :nodoc:
47
+ @alias_scopes ||= {}
48
+ end
49
+
50
+ def alias_scope?(name) # :nodoc:
51
+ alias_scopes.key?(name.to_sym)
52
+ end
53
+
54
+ def condition?(name) # :nodoc:
55
+ super || alias_scope?(name)
56
+ end
57
+
58
+ def named_scope_options(name) # :nodoc:
59
+ super || alias_scopes[name.to_sym]
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,117 @@
1
+ module Searchlogic
2
+ module NamedScopes
3
+ # Handles dynamically creating named scopes for associations.
4
+ module AssociationConditions
5
+ def condition?(name) # :nodoc:
6
+ super || association_condition?(name) || association_alias_condition?(name)
7
+ end
8
+
9
+ def primary_condition_name(name) # :nodoc:
10
+ if result = super
11
+ result
12
+ elsif association_condition?(name)
13
+ name.to_sym
14
+ elsif details = association_alias_condition_details(name)
15
+ "#{details[:association]}_#{details[:column]}_#{primary_condition(details[:condition])}".to_sym
16
+ else
17
+ nil
18
+ end
19
+ end
20
+
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 ane of the method a valie 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.
34
+ def inner_joins(association_name)
35
+ ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, association_name, nil).join_associations.collect { |assoc| assoc.association_join }
36
+ end
37
+
38
+ private
39
+ def method_missing(name, *args, &block)
40
+ if details = association_condition_details(name)
41
+ create_association_condition(details[:association], details[:column], details[:condition], args)
42
+ send(name, *args)
43
+ elsif details = association_alias_condition_details(name)
44
+ create_association_alias_condition(details[:association], details[:column], details[:condition], args)
45
+ send(name, *args)
46
+ else
47
+ super
48
+ end
49
+ end
50
+
51
+ def association_condition_details(name)
52
+ associations = reflect_on_all_associations.collect { |assoc| assoc.name }
53
+ if !local_condition?(name) && name.to_s =~ /^(#{associations.join("|")})_(\w+)_(#{Conditions::PRIMARY_CONDITIONS.join("|")})$/
54
+ {:association => $1, :column => $2, :condition => $3}
55
+ end
56
+ end
57
+
58
+ def create_association_condition(association_name, column, condition, args)
59
+ named_scope("#{association_name}_#{column}_#{condition}", association_condition_options(association_name, "#{column}_#{condition}", args))
60
+ end
61
+
62
+ def association_alias_condition_details(name)
63
+ associations = reflect_on_all_associations.collect { |assoc| assoc.name }
64
+ if !local_condition?(name) && name.to_s =~ /^(#{associations.join("|")})_(\w+)_(#{Conditions::ALIAS_CONDITIONS.join("|")})$/
65
+ {:association => $1, :column => $2, :condition => $3}
66
+ end
67
+ end
68
+
69
+ def create_association_alias_condition(association, column, condition, args)
70
+ primary_condition = primary_condition(condition)
71
+ alias_name = "#{association}_#{column}_#{condition}"
72
+ primary_name = "#{association}_#{column}_#{primary_condition}"
73
+ send(primary_name, *args) # go back to method_missing and make sure we create the method
74
+ (class << self; self; end).class_eval { alias_method alias_name, primary_name }
75
+ end
76
+
77
+ def association_condition_options(association_name, association_condition, args)
78
+ association = reflect_on_association(association_name.to_sym)
79
+ scope = association.klass.send(association_condition, *args)
80
+ scope_options = association.klass.named_scope_options(association_condition)
81
+ arity = association.klass.named_scope_arity(association_condition)
82
+
83
+ if !arity || arity == 0
84
+ # The underlying condition doesn't require any parameters, so let's just create a simple
85
+ # named scope that is based on a hash.
86
+ options = scope.proxy_options
87
+ options[:joins] = options[:joins].blank? ? association.name : {association.name => options[:joins]}
88
+ options
89
+ else
90
+ # The underlying condition requires parameters, let's match the parameters it requires
91
+ # and pass those onto the named scope. We can't use proxy_options because that returns the
92
+ # result after a value has been passed.
93
+ proc_args = []
94
+ if arity > 0
95
+ arity.times { |i| proc_args << "arg#{i}"}
96
+ else
97
+ positive_arity = arity * -1
98
+ positive_arity.times do |i|
99
+ if i == (positive_arity - 1)
100
+ proc_args << "*arg#{i}"
101
+ else
102
+ proc_args << "arg#{i}"
103
+ end
104
+ end
105
+ end
106
+ eval <<-"end_eval"
107
+ searchlogic_lambda(:#{scope_options.searchlogic_arg_type}) { |#{proc_args.join(",")}|
108
+ options = association.klass.named_scope_options(association_condition).call(#{proc_args.join(",")})
109
+ options[:joins] = options[:joins].blank? ? association.name : {association.name => options[:joins]}
110
+ options
111
+ }
112
+ end_eval
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,32 @@
1
+ module Searchlogic
2
+ module NamedScopes
3
+ # Handles dynamically creating named scopes for associations.
4
+ module AssociationOrdering
5
+
6
+ def association_ordering_condition?(name)
7
+ !association_ordering_condition_details(name).nil?
8
+ end
9
+
10
+ private
11
+ def method_missing(name, *args, &block)
12
+ if details = association_ordering_condition_details(name)
13
+ create_association_ordering_condition(details[:association], details[:order_as], details[:column], args)
14
+ send(name, *args)
15
+ else
16
+ super
17
+ end
18
+ end
19
+
20
+ def association_ordering_condition_details(name)
21
+ associations = reflect_on_all_associations.collect { |assoc| assoc.name }
22
+ if !local_condition?(name) && name.to_s =~ /^(ascend|descend)_by_(#{associations.join("|")})_(\w+)$/
23
+ {:order_as => $1, :association => $2, :column => $3}
24
+ end
25
+ end
26
+
27
+ def create_association_ordering_condition(association_name, order_as, column, args)
28
+ named_scope("#{order_as}_by_#{association_name}_#{column}", association_condition_options(association_name, "#{order_as}_by_#{column}", args))
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,232 @@
1
+ module Searchlogic
2
+ module NamedScopes
3
+ # Handles dynamically creating named scopes for columns.
4
+ module Conditions
5
+ COMPARISON_CONDITIONS = {
6
+ :equals => [:is, :eq],
7
+ :does_not_equal => [:not_equal_to, :is_not, :not, :ne],
8
+ :less_than => [:lt, :before],
9
+ :less_than_or_equal_to => [:lte],
10
+ :greater_than => [:gt, :after],
11
+ :greater_than_or_equal_to => [:gte],
12
+ }
13
+
14
+ WILDCARD_CONDITIONS = {
15
+ :like => [:contains, :includes],
16
+ :not_like => [],
17
+ :begins_with => [:bw],
18
+ :not_begin_with => [:does_not_begin_with],
19
+ :ends_with => [:ew],
20
+ :not_end_with => [:does_not_end_with]
21
+ }
22
+
23
+ BOOLEAN_CONDITIONS = {
24
+ :null => [:nil],
25
+ :not_null => [:not_nil],
26
+ :empty => []
27
+ }
28
+
29
+ CONDITIONS = {}
30
+
31
+ COMPARISON_CONDITIONS.merge(WILDCARD_CONDITIONS).each do |condition, aliases|
32
+ CONDITIONS[condition] = aliases
33
+ CONDITIONS["#{condition}_any".to_sym] = aliases.collect { |a| "#{a}_any".to_sym }
34
+ CONDITIONS["#{condition}_all".to_sym] = aliases.collect { |a| "#{a}_all".to_sym }
35
+ end
36
+
37
+ BOOLEAN_CONDITIONS.each { |condition, aliases| CONDITIONS[condition] = aliases }
38
+
39
+ PRIMARY_CONDITIONS = CONDITIONS.keys
40
+ ALIAS_CONDITIONS = CONDITIONS.values.flatten
41
+
42
+ # Retrieves the options passed when creating the respective named scope. Ex:
43
+ #
44
+ # named_scope :whatever, :conditions => {:column => value}
45
+ #
46
+ # This method will return:
47
+ #
48
+ # :conditions => {:column => value}
49
+ #
50
+ # ActiveRecord hides this internally, so we have to try and pull it out with this
51
+ # method.
52
+ def named_scope_options(name)
53
+ key = scopes.key?(name.to_sym) ? name.to_sym : primary_condition_name(name)
54
+
55
+ if key
56
+ eval("options", scopes[key])
57
+ else
58
+ nil
59
+ end
60
+ end
61
+
62
+ # The arity for a named scope's proc is important, because we use the arity
63
+ # to determine if the condition should be ignored when calling the search method.
64
+ # If the condition is false and the arity is 0, then we skip it all together. Ex:
65
+ #
66
+ # User.named_scope :age_is_4, :conditions => {:age => 4}
67
+ # User.search(:age_is_4 => false) == User.all
68
+ # User.search(:age_is_4 => true) == User.all(:conditions => {:age => 4})
69
+ #
70
+ # We also use it when trying to "copy" the underlying named scope for association
71
+ # conditions.
72
+ def named_scope_arity(name)
73
+ options = named_scope_options(name)
74
+ options.respond_to?(:arity) ? options.arity : nil
75
+ end
76
+
77
+ # Returns the primary condition for the given alias. Ex:
78
+ #
79
+ # primary_condition(:gt) => :greater_than
80
+ def primary_condition(alias_condition)
81
+ CONDITIONS.find { |k, v| k == alias_condition.to_sym || v.include?(alias_condition.to_sym) }.first
82
+ end
83
+
84
+ # Returns the primary name for any condition on a column. You can pass it
85
+ # a primary condition, alias condition, etc, and it will return the proper
86
+ # primary condition name. This helps simply logic throughout Searchlogic. Ex:
87
+ #
88
+ # primary_condition_name(:id_gt) => :id_greater_than
89
+ # primary_condition_name(:id_greater_than) => :id_greater_than
90
+ def primary_condition_name(name)
91
+ if primary_condition?(name)
92
+ name.to_sym
93
+ elsif details = alias_condition_details(name)
94
+ "#{details[:column]}_#{primary_condition(details[:condition])}".to_sym
95
+ else
96
+ nil
97
+ end
98
+ end
99
+
100
+ # Is the name of the method a valid condition that can be dynamically created?
101
+ def condition?(name)
102
+ local_condition?(name)
103
+ end
104
+
105
+ # Is the condition for a local column, not an association
106
+ def local_condition?(name)
107
+ primary_condition?(name) || alias_condition?(name)
108
+ end
109
+
110
+ # Is the name of the method a valid condition that can be dynamically created,
111
+ # AND is it a primary condition (not an alias). "greater_than" not "gt".
112
+ def primary_condition?(name)
113
+ !primary_condition_details(name).nil?
114
+ end
115
+
116
+ # Is the name of the method a valid condition that can be dynamically created,
117
+ # AND is it an alias condition. "gt" not "greater_than".
118
+ def alias_condition?(name)
119
+ !alias_condition_details(name).nil?
120
+ end
121
+
122
+ private
123
+ def method_missing(name, *args, &block)
124
+ if details = primary_condition_details(name)
125
+ create_primary_condition(details[:column], details[:condition])
126
+ send(name, *args)
127
+ elsif details = alias_condition_details(name)
128
+ create_alias_condition(details[:column], details[:condition], args)
129
+ send(name, *args)
130
+ else
131
+ super
132
+ end
133
+ end
134
+
135
+ def primary_condition_details(name)
136
+ if name.to_s =~ /^(#{column_names.join("|")})_(#{PRIMARY_CONDITIONS.join("|")})$/
137
+ {:column => $1, :condition => $2}
138
+ end
139
+ end
140
+
141
+ def create_primary_condition(column, condition)
142
+ column_type = columns_hash[column.to_s].type
143
+ scope_options = case condition.to_s
144
+ when /^equals/
145
+ scope_options(condition, column_type, "#{table_name}.#{column} = ?")
146
+ when /^does_not_equal/
147
+ scope_options(condition, column_type, "#{table_name}.#{column} != ?")
148
+ when /^less_than_or_equal_to/
149
+ scope_options(condition, column_type, "#{table_name}.#{column} <= ?")
150
+ when /^less_than/
151
+ scope_options(condition, column_type, "#{table_name}.#{column} < ?")
152
+ when /^greater_than_or_equal_to/
153
+ scope_options(condition, column_type, "#{table_name}.#{column} >= ?")
154
+ when /^greater_than/
155
+ scope_options(condition, column_type, "#{table_name}.#{column} > ?")
156
+ when /^like/
157
+ scope_options(condition, column_type, "#{table_name}.#{column} LIKE ?", :like)
158
+ when /^not_like/
159
+ scope_options(condition, column_type, "#{table_name}.#{column} NOT LIKE ?", :like)
160
+ when /^begins_with/
161
+ scope_options(condition, column_type, "#{table_name}.#{column} LIKE ?", :begins_with)
162
+ when /^not_begin_with/
163
+ scope_options(condition, column_type, "#{table_name}.#{column} NOT LIKE ?", :begins_with)
164
+ when /^ends_with/
165
+ scope_options(condition, column_type, "#{table_name}.#{column} LIKE ?", :ends_with)
166
+ when /^not_end_with/
167
+ scope_options(condition, column_type, "#{table_name}.#{column} NOT LIKE ?", :ends_with)
168
+ when "null"
169
+ {:conditions => "#{table_name}.#{column} IS NULL"}
170
+ when "not_null"
171
+ {:conditions => "#{table_name}.#{column} IS NOT NULL"}
172
+ when "empty"
173
+ {:conditions => "#{table_name}.#{column} = ''"}
174
+ end
175
+
176
+ named_scope("#{column}_#{condition}".to_sym, scope_options)
177
+ end
178
+
179
+ # This method helps cut down on defining scope options for conditions that allow *_any or *_all conditions.
180
+ # Kepp in mind that the lambdas get cached in a method, so you want to keep the contents of the lambdas as
181
+ # fast as possible, which is why I didn't do the case statement inside of the lambda.
182
+ def scope_options(condition, column_type, sql, value_modifier = nil)
183
+ case condition.to_s
184
+ when /_(any|all)$/
185
+ searchlogic_lambda(column_type) { |*values|
186
+ return {} if values.empty?
187
+ values = values.flatten
188
+
189
+ values_to_sub = nil
190
+ if value_modifier.nil?
191
+ values_to_sub = values
192
+ else
193
+ values_to_sub = values.collect { |value| value_with_modifier(value, value_modifier) }
194
+ end
195
+
196
+ join = $1 == "any" ? " OR " : " AND "
197
+ {:conditions => [values.collect { |value| sql }.join(join), *values_to_sub]}
198
+ }
199
+ else
200
+ searchlogic_lambda(column_type) { |value| {:conditions => [sql, value_with_modifier(value, value_modifier)]} }
201
+ end
202
+ end
203
+
204
+ def value_with_modifier(value, modifier)
205
+ case modifier
206
+ when :like
207
+ "%#{value}%"
208
+ when :begins_with
209
+ "#{value}%"
210
+ when :ends_with
211
+ "%#{value}"
212
+ else
213
+ value
214
+ end
215
+ end
216
+
217
+ def alias_condition_details(name)
218
+ if name.to_s =~ /^(#{column_names.join("|")})_(#{ALIAS_CONDITIONS.join("|")})$/
219
+ {:column => $1, :condition => $2}
220
+ end
221
+ end
222
+
223
+ def create_alias_condition(column, condition, args)
224
+ primary_condition = primary_condition(condition)
225
+ alias_name = "#{column}_#{condition}"
226
+ primary_name = "#{column}_#{primary_condition}"
227
+ send(primary_name, *args) # go back to method_missing and make sure we create the method
228
+ (class << self; self; end).class_eval { alias_method alias_name, primary_name }
229
+ end
230
+ end
231
+ end
232
+ end