/
associations.rb
131 lines (118 loc) · 6.01 KB
/
associations.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
module Searchlogic
module NamedScopes
# Handles dynamically creating named scopes for associations.
module Associations
def condition?(name) # :nodoc:
super || association_condition?(name) || association_alias_condition?(name)
end
def primary_condition_name(name) # :nodoc:
if result = super
result
elsif association_condition?(name)
name.to_sym
elsif details = association_alias_condition_details(name)
"#{details[:association]}_#{details[:column]}_#{primary_condition(details[:condition])}".to_sym
else
nil
end
end
# Is the name of the method a valid name for an association condition?
def association_condition?(name)
!association_condition_details(name).nil?
end
# Is the ane of the method a valie name for an association alias condition?
# An alias being "gt" for "greater_than", etc.
def association_alias_condition?(name)
!association_alias_condition_details(name).nil?
end
# A convenience method for creating inner join sql to that your inner joins
# are consistent with how Active Record creates them.
def inner_joins(association_name)
ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, association_name, nil).join_associations.collect { |assoc| assoc.association_join }
end
private
def method_missing(name, *args, &block)
if details = association_condition_details(name)
create_association_condition(details[:association], details[:column], details[:condition], args)
send(name, *args)
elsif details = association_alias_condition_details(name)
create_association_alias_condition(details[:association], details[:column], details[:condition], args)
send(name, *args)
elsif details = association_ordering_condition_details(name)
create_association_ordering_condition(details[:association], details[:order_as], details[:column], args)
send(name, *args)
else
super
end
end
def association_condition_details(name)
associations = reflect_on_all_associations.collect { |assoc| assoc.name }
if name.to_s =~ /^(#{associations.join("|")})_(\w+)_(#{Conditions::PRIMARY_CONDITIONS.join("|")})$/
{:association => $1, :column => $2, :condition => $3}
end
end
def create_association_condition(association_name, column, condition, args)
named_scope("#{association_name}_#{column}_#{condition}", association_condition_options(association_name, "#{column}_#{condition}", args))
end
def association_alias_condition_details(name)
associations = reflect_on_all_associations.collect { |assoc| assoc.name }
if name.to_s =~ /^(#{associations.join("|")})_(\w+)_(#{Conditions::ALIAS_CONDITIONS.join("|")})$/
{:association => $1, :column => $2, :condition => $3}
end
end
def create_association_alias_condition(association, column, condition, args)
primary_condition = primary_condition(condition)
alias_name = "#{association}_#{column}_#{condition}"
primary_name = "#{association}_#{column}_#{primary_condition}"
send(primary_name, *args) # go back to method_missing and make sure we create the method
(class << self; self; end).class_eval { alias_method alias_name, primary_name }
end
def association_ordering_condition_details(name)
associations = reflect_on_all_associations.collect { |assoc| assoc.name }
if name.to_s =~ /^(ascend|descend)_by_(#{associations.join("|")})_(\w+)$/
{:order_as => $1, :association => $2, :column => $3}
end
end
def create_association_ordering_condition(association_name, order_as, column, args)
named_scope("#{order_as}_by_#{association_name}_#{column}", association_condition_options(association_name, "#{order_as}_by_#{column}", args))
end
def association_condition_options(association_name, association_condition, args)
association = reflect_on_association(association_name.to_sym)
scope = association.klass.send(association_condition, *args)
scope_options = association.klass.named_scope_options(association_condition)
arity = association.klass.named_scope_arity(association_condition)
if !arity || arity == 0
# The underlying condition doesn't require any parameters, so let's just create a simple
# named scope that is based on a hash.
options = scope.proxy_options
options[:joins] = options[:joins].blank? ? association.name : {association.name => options[:joins]}
options
else
# The underlying condition requires parameters, let's match the parameters it requires
# and pass those onto the named scope. We can't use proxy_options because that returns the
# result after a value has been passed.
proc_args = []
if arity > 0
arity.times { |i| proc_args << "arg#{i}"}
else
positive_arity = arity * -1
positive_arity.times do |i|
if i == (positive_arity - 1)
proc_args << "*arg#{i}"
else
proc_args << "arg#{i}"
end
end
end
eval <<-"end_eval"
searchlogic_lambda(:#{scope_options.searchlogic_arg_type}) { |#{proc_args.join(",")}|
options = association.klass.named_scope_options(association_condition).call(#{proc_args.join(",")})
options[:joins] = options[:joins].blank? ? association.name : {association.name => options[:joins]}
options
}
end_eval
end
end
end
end
end