squeel_rbg 0.8.2

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.
Files changed (80) hide show
  1. data/.gitignore +4 -0
  2. data/.yardopts +3 -0
  3. data/Gemfile +13 -0
  4. data/LICENSE +20 -0
  5. data/README.md +398 -0
  6. data/Rakefile +19 -0
  7. data/lib/core_ext/hash.rb +13 -0
  8. data/lib/core_ext/symbol.rb +39 -0
  9. data/lib/squeel/adapters/active_record/3.0/association_preload.rb +15 -0
  10. data/lib/squeel/adapters/active_record/3.0/compat.rb +142 -0
  11. data/lib/squeel/adapters/active_record/3.0/context.rb +66 -0
  12. data/lib/squeel/adapters/active_record/3.0/join_association.rb +54 -0
  13. data/lib/squeel/adapters/active_record/3.0/join_dependency.rb +84 -0
  14. data/lib/squeel/adapters/active_record/3.0/relation.rb +327 -0
  15. data/lib/squeel/adapters/active_record/context.rb +66 -0
  16. data/lib/squeel/adapters/active_record/join_association.rb +44 -0
  17. data/lib/squeel/adapters/active_record/join_dependency.rb +83 -0
  18. data/lib/squeel/adapters/active_record/preloader.rb +21 -0
  19. data/lib/squeel/adapters/active_record/relation.rb +351 -0
  20. data/lib/squeel/adapters/active_record.rb +28 -0
  21. data/lib/squeel/configuration.rb +54 -0
  22. data/lib/squeel/constants.rb +24 -0
  23. data/lib/squeel/context.rb +67 -0
  24. data/lib/squeel/dsl.rb +86 -0
  25. data/lib/squeel/nodes/aliasing.rb +13 -0
  26. data/lib/squeel/nodes/and.rb +9 -0
  27. data/lib/squeel/nodes/as.rb +14 -0
  28. data/lib/squeel/nodes/binary.rb +32 -0
  29. data/lib/squeel/nodes/function.rb +66 -0
  30. data/lib/squeel/nodes/join.rb +113 -0
  31. data/lib/squeel/nodes/key_path.rb +192 -0
  32. data/lib/squeel/nodes/nary.rb +45 -0
  33. data/lib/squeel/nodes/not.rb +9 -0
  34. data/lib/squeel/nodes/operation.rb +32 -0
  35. data/lib/squeel/nodes/operators.rb +43 -0
  36. data/lib/squeel/nodes/or.rb +9 -0
  37. data/lib/squeel/nodes/order.rb +53 -0
  38. data/lib/squeel/nodes/predicate.rb +71 -0
  39. data/lib/squeel/nodes/predicate_operators.rb +29 -0
  40. data/lib/squeel/nodes/stub.rb +125 -0
  41. data/lib/squeel/nodes/unary.rb +28 -0
  42. data/lib/squeel/nodes.rb +17 -0
  43. data/lib/squeel/predicate_methods.rb +14 -0
  44. data/lib/squeel/version.rb +3 -0
  45. data/lib/squeel/visitors/attribute_visitor.rb +191 -0
  46. data/lib/squeel/visitors/base.rb +112 -0
  47. data/lib/squeel/visitors/predicate_visitor.rb +319 -0
  48. data/lib/squeel/visitors/symbol_visitor.rb +48 -0
  49. data/lib/squeel/visitors.rb +3 -0
  50. data/lib/squeel.rb +28 -0
  51. data/lib/squeel_rbg.rb +5 -0
  52. data/spec/blueprints/articles.rb +5 -0
  53. data/spec/blueprints/comments.rb +5 -0
  54. data/spec/blueprints/notes.rb +3 -0
  55. data/spec/blueprints/people.rb +4 -0
  56. data/spec/blueprints/tags.rb +3 -0
  57. data/spec/console.rb +22 -0
  58. data/spec/core_ext/symbol_spec.rb +75 -0
  59. data/spec/helpers/squeel_helper.rb +21 -0
  60. data/spec/spec_helper.rb +66 -0
  61. data/spec/squeel/adapters/active_record/context_spec.rb +44 -0
  62. data/spec/squeel/adapters/active_record/join_association_spec.rb +18 -0
  63. data/spec/squeel/adapters/active_record/join_dependency_spec.rb +66 -0
  64. data/spec/squeel/adapters/active_record/relation_spec.rb +627 -0
  65. data/spec/squeel/dsl_spec.rb +92 -0
  66. data/spec/squeel/nodes/function_spec.rb +149 -0
  67. data/spec/squeel/nodes/join_spec.rb +47 -0
  68. data/spec/squeel/nodes/key_path_spec.rb +100 -0
  69. data/spec/squeel/nodes/operation_spec.rb +149 -0
  70. data/spec/squeel/nodes/operators_spec.rb +87 -0
  71. data/spec/squeel/nodes/order_spec.rb +30 -0
  72. data/spec/squeel/nodes/predicate_operators_spec.rb +88 -0
  73. data/spec/squeel/nodes/predicate_spec.rb +50 -0
  74. data/spec/squeel/nodes/stub_spec.rb +198 -0
  75. data/spec/squeel/visitors/attribute_visitor_spec.rb +142 -0
  76. data/spec/squeel/visitors/predicate_visitor_spec.rb +342 -0
  77. data/spec/squeel/visitors/symbol_visitor_spec.rb +42 -0
  78. data/spec/support/schema.rb +104 -0
  79. data/squeel.gemspec +43 -0
  80. metadata +246 -0
@@ -0,0 +1,66 @@
1
+ require 'squeel/context'
2
+
3
+ module Squeel
4
+ module Adapters
5
+ module ActiveRecord
6
+ class Context < ::Squeel::Context
7
+ # Because the AR::Associations namespace is insane
8
+ JoinBase = ::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinBase
9
+
10
+ def initialize(object)
11
+ super
12
+ @base = object.join_base
13
+ @engine = @base.arel_engine
14
+ @arel_visitor = Arel::Visitors.visitor_for @engine
15
+ @default_table = Arel::Table.new(@base.table_name, :as => @base.aliased_table_name, :engine => @engine)
16
+ end
17
+
18
+ def find(object, parent = @base)
19
+ if JoinBase === parent
20
+ object = object.to_sym if String === object
21
+ case object
22
+ when Symbol, Nodes::Stub
23
+ @object.join_associations.detect { |j|
24
+ j.reflection.name == object.to_sym && j.parent == parent
25
+ }
26
+ when Nodes::Join
27
+ @object.join_associations.detect { |j|
28
+ j.reflection.name == object.name && j.parent == parent &&
29
+ (object.polymorphic? ? j.reflection.klass == object._klass : true)
30
+ }
31
+ else
32
+ @object.join_associations.detect { |j|
33
+ j.reflection == object && j.parent == parent
34
+ }
35
+ end
36
+ end
37
+ end
38
+
39
+ def traverse(keypath, parent = @base, include_endpoint = false)
40
+ parent = @base if keypath.absolute?
41
+ keypath.path.each do |key|
42
+ parent = find(key, parent) || key
43
+ end
44
+ parent = find(keypath.endpoint, parent) if include_endpoint
45
+
46
+ parent
47
+ end
48
+
49
+ private
50
+
51
+ def get_table(object)
52
+ if [Symbol, String, Nodes::Stub].include?(object.class)
53
+ Arel::Table.new(object.to_sym, :engine => @engine)
54
+ elsif Nodes::Join === object
55
+ object._klass ? object._klass.arel_table : Arel::Table.new(object._name, :engine => @engine)
56
+ elsif object.respond_to?(:aliased_table_name)
57
+ Arel::Table.new(object.table_name, :as => object.aliased_table_name, :engine => @engine)
58
+ else
59
+ raise ArgumentError, "Unable to get table for #{object}"
60
+ end
61
+ end
62
+
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,54 @@
1
+ require 'active_record'
2
+
3
+ module Squeel
4
+ module Adapters
5
+ module ActiveRecord
6
+ class JoinAssociation < ::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation
7
+
8
+ def initialize(reflection, join_dependency, parent = nil, polymorphic_class = nil)
9
+ if polymorphic_class && ::ActiveRecord::Base > polymorphic_class
10
+ swapping_reflection_klass(reflection, polymorphic_class) do |reflection|
11
+ super(reflection, join_dependency, parent)
12
+ end
13
+ else
14
+ super(reflection, join_dependency, parent)
15
+ end
16
+ end
17
+
18
+ def swapping_reflection_klass(reflection, klass)
19
+ reflection = reflection.clone
20
+ original_polymorphic = reflection.options.delete(:polymorphic)
21
+ reflection.instance_variable_set(:@klass, klass)
22
+ yield reflection
23
+ ensure
24
+ reflection.options[:polymorphic] = original_polymorphic
25
+ end
26
+
27
+ def ==(other)
28
+ super && active_record == other.active_record
29
+ end
30
+
31
+ def association_join
32
+ return @join if @Join
33
+
34
+ @join = super
35
+
36
+ if reflection.macro == :belongs_to && reflection.options[:polymorphic]
37
+ aliased_table = Arel::Table.new(table_name, :as => @aliased_table_name,
38
+ :engine => arel_engine,
39
+ :columns => klass.columns)
40
+
41
+ parent_table = Arel::Table.new(parent.table_name, :as => parent.aliased_table_name,
42
+ :engine => arel_engine,
43
+ :columns => parent.active_record.columns)
44
+
45
+ @join << parent_table[reflection.options[:foreign_type]].eq(reflection.klass.name)
46
+ end
47
+
48
+ @join
49
+ end
50
+
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,84 @@
1
+ require 'active_record'
2
+
3
+ module Squeel
4
+ module Adapters
5
+ module ActiveRecord
6
+ module JoinDependency
7
+
8
+ # Yes, I'm using alias_method_chain here. No, I don't feel too
9
+ # bad about it. JoinDependency, or, to call it by its full proper
10
+ # name, ::ActiveRecord::Associations::JoinDependency, is one of the
11
+ # most "for internal use only" chunks of ActiveRecord.
12
+ def self.included(base)
13
+ base.class_eval do
14
+ alias_method_chain :build, :squeel
15
+ alias_method_chain :graft, :squeel
16
+ alias :join_parts :joins
17
+ end
18
+ end
19
+
20
+ def graft_with_squeel(*associations)
21
+ associations.each do |association|
22
+ unless join_associations.detect {|a| association == a}
23
+ if association.reflection.options[:polymorphic]
24
+ build(Nodes::Join.new(association.reflection.name, association.join_type, association.reflection.klass),
25
+ association.find_parent_in(self) || join_base, association.join_type)
26
+ else
27
+ build(association.reflection.name, association.find_parent_in(self) || join_base, association.join_type)
28
+ end
29
+ end
30
+ end
31
+ self
32
+ end
33
+
34
+ def build_with_squeel(associations, parent = nil, join_type = Arel::InnerJoin)
35
+ associations = associations.symbol if Nodes::Stub === associations
36
+
37
+ case associations
38
+ when Nodes::Join
39
+ parent ||= @joins.last
40
+ reflection = parent.reflections[associations._name] or
41
+ raise ::ActiveRecord::ConfigurationError, "Association named '#{ associations._name }' was not found; perhaps you misspelled it?"
42
+
43
+ unless join_association = find_join_association_respecting_polymorphism(reflection, parent, associations._klass)
44
+ @reflections << reflection
45
+ join_association = build_join_association_respecting_polymorphism(reflection, parent, associations._klass)
46
+ join_association.join_type = associations._type
47
+ @joins << join_association
48
+ cache_joined_association(join_association)
49
+ end
50
+
51
+ join_association
52
+ when Nodes::KeyPath
53
+ parent ||= @joins.last
54
+ associations.path_with_endpoint.each do |key|
55
+ parent = build(key, parent, join_type)
56
+ end
57
+ parent
58
+ else
59
+ build_without_squeel(associations, parent, join_type)
60
+ end
61
+ end
62
+
63
+ def find_join_association_respecting_polymorphism(reflection, parent, klass)
64
+ if association = find_join_association(reflection, parent)
65
+ unless reflection.options[:polymorphic]
66
+ association
67
+ else
68
+ association if association.active_record == klass
69
+ end
70
+ end
71
+ end
72
+
73
+ def build_join_association_respecting_polymorphism(reflection, parent, klass)
74
+ if reflection.options[:polymorphic] && klass
75
+ JoinAssociation.new(reflection, self, parent, klass)
76
+ else
77
+ JoinAssociation.new(reflection, self, parent)
78
+ end
79
+ end
80
+
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,327 @@
1
+ require 'active_record'
2
+
3
+ module Squeel
4
+ module Adapters
5
+ module ActiveRecord
6
+ module Relation
7
+
8
+ JoinAssociation = ::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation
9
+ JoinDependency = ::ActiveRecord::Associations::ClassMethods::JoinDependency
10
+
11
+ attr_writer :join_dependency
12
+ private :join_dependency=
13
+
14
+ # Returns a JoinDependency for the current relation.
15
+ #
16
+ # We don't need to clear out @join_dependency by overriding #reset, because
17
+ # the default #reset already does this, despite never setting it anywhere that
18
+ # I can find. Serendipity, I say!
19
+ def join_dependency
20
+ @join_dependency ||= (build_join_dependency(table, @joins_values) && @join_dependency)
21
+ end
22
+
23
+ def predicate_visitor
24
+ Visitors::PredicateVisitor.new(
25
+ Context.new(join_dependency)
26
+ )
27
+ end
28
+
29
+ def attribute_visitor
30
+ Visitors::AttributeVisitor.new(
31
+ Context.new(join_dependency)
32
+ )
33
+ end
34
+
35
+ # We need to be able to support merging two relations that have a different
36
+ # base class. Stock ActiveRecord doesn't have to do anything too special, because
37
+ # it's already created predicates out of the where_values by now, and they're
38
+ # already bound to the proper table.
39
+ #
40
+ # Squeel, on the other hand, needs to do its best to ensure the predicates are still
41
+ # winding up against the proper table. Merging relations is a really nifty shortcut
42
+ # but another little corner of ActiveRecord where the magic quickly fades. :(
43
+ def merge(r, association_name = nil)
44
+ if association_name || relation_with_different_base?(r)
45
+ r = r.clone
46
+ association_name ||= infer_association_for_relation_merge(r)
47
+ prepare_relation_for_association_merge!(r, association_name)
48
+ self.joins_values += [association_name] if reflect_on_association(association_name)
49
+ end
50
+
51
+ super(r)
52
+ end
53
+
54
+ def relation_with_different_base?(r)
55
+ ::ActiveRecord::Relation === r &&
56
+ base_class.name != r.klass.base_class.name
57
+ end
58
+
59
+ def infer_association_for_relation_merge(r)
60
+ default_association = reflect_on_all_associations.detect {|a| a.class_name == r.klass.name}
61
+ default_association ? default_association.name : r.table_name.to_sym
62
+ end
63
+
64
+ def prepare_relation_for_association_merge!(r, association_name)
65
+ r.where_values.map! {|w| Squeel::Visitors::PredicateVisitor.can_accept?(w) ? {association_name => w} : w}
66
+ r.having_values.map! {|h| Squeel::Visitors::PredicateVisitor.can_accept?(h) ? {association_name => h} : h}
67
+ r.joins_values.map! {|j| [Symbol, Hash, Nodes::Stub, Nodes::Join].include?(j.class) ? {association_name => j} : j}
68
+ end
69
+
70
+ def build_arel
71
+ arel = table
72
+
73
+ arel = build_join_dependency(arel, @joins_values) unless @joins_values.empty?
74
+
75
+ predicate_viz = predicate_visitor
76
+ attribute_viz = attribute_visitor
77
+
78
+ arel = collapse_wheres(arel, predicate_viz.accept((@where_values - ['']).uniq))
79
+
80
+ arel = arel.having(*predicate_viz.accept(@having_values.uniq.reject{|h| h.blank?})) unless @having_values.empty?
81
+
82
+ arel = arel.take(connection.sanitize_limit(@limit_value)) if @limit_value
83
+ arel = arel.skip(@offset_value) if @offset_value
84
+
85
+ arel = arel.group(*attribute_viz.accept(@group_values.uniq.reject{|g| g.blank?})) unless @group_values.empty?
86
+
87
+ arel = arel.order(*attribute_viz.accept(@order_values.uniq.reject{|o| o.blank?})) unless @order_values.empty?
88
+
89
+ arel = build_select(arel, attribute_viz.accept(@select_values.uniq))
90
+
91
+ arel = arel.from(@from_value) if @from_value
92
+ arel = arel.lock(@lock_value) if @lock_value
93
+
94
+ arel
95
+ end
96
+
97
+ def build_join_dependency(relation, joins)
98
+ association_joins = []
99
+
100
+ joins = joins.map {|j| j.respond_to?(:strip) ? j.strip : j}.uniq
101
+
102
+ joins.each do |join|
103
+ association_joins << join if [Hash, Array, Symbol, Nodes::Stub, Nodes::Join, Nodes::KeyPath].include?(join.class) && !array_of_strings?(join)
104
+ end
105
+
106
+ stashed_association_joins = joins.grep(::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation)
107
+
108
+ non_association_joins = (joins - association_joins - stashed_association_joins)
109
+ custom_joins = custom_join_sql(*non_association_joins)
110
+
111
+ self.join_dependency = JoinDependency.new(@klass, association_joins, custom_joins)
112
+
113
+ join_dependency.graft(*stashed_association_joins)
114
+
115
+ @implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?
116
+
117
+ to_join = []
118
+
119
+ join_dependency.join_associations.each do |association|
120
+ if (association_relation = association.relation).is_a?(Array)
121
+ to_join << [association_relation.first, association.join_type, association.association_join.first]
122
+ to_join << [association_relation.last, association.join_type, association.association_join.last]
123
+ else
124
+ to_join << [association_relation, association.join_type, association.association_join]
125
+ end
126
+ end
127
+
128
+ to_join.uniq.each do |left, join_type, right|
129
+ relation = relation.join(left, join_type).on(*right)
130
+ end
131
+
132
+ relation = relation.join(custom_joins)
133
+ end
134
+
135
+ def includes(*args)
136
+ if block_given? && args.empty?
137
+ super(DSL.eval &Proc.new)
138
+ else
139
+ super
140
+ end
141
+ end
142
+
143
+ def preload(*args)
144
+ if block_given? && args.empty?
145
+ super(DSL.eval &Proc.new)
146
+ else
147
+ super
148
+ end
149
+ end
150
+
151
+ def eager_load(*args)
152
+ if block_given? && args.empty?
153
+ super(DSL.eval &Proc.new)
154
+ else
155
+ super
156
+ end
157
+ end
158
+
159
+ def select(value = Proc.new)
160
+ if block_given? && Proc === value
161
+ if value.arity > 0
162
+ to_a.select {|*block_args| value.call(*block_args)}
163
+ else
164
+ relation = clone
165
+ relation.select_values += Array.wrap(DSL.eval &value)
166
+ relation
167
+ end
168
+ else
169
+ super
170
+ end
171
+ end
172
+
173
+ def group(*args)
174
+ if block_given? && args.empty?
175
+ super(DSL.eval &Proc.new)
176
+ else
177
+ super
178
+ end
179
+ end
180
+
181
+ def order(*args)
182
+ if block_given? && args.empty?
183
+ super(DSL.eval &Proc.new)
184
+ else
185
+ super
186
+ end
187
+ end
188
+
189
+ def reorder(*args)
190
+ if block_given? && args.empty?
191
+ super(DSL.eval &Proc.new)
192
+ else
193
+ super
194
+ end
195
+ end
196
+
197
+ def joins(*args)
198
+ if block_given? && args.empty?
199
+ super(DSL.eval &Proc.new)
200
+ else
201
+ super
202
+ end
203
+ end
204
+
205
+ def where(opts = Proc.new, *rest)
206
+ if block_given? && Proc === opts
207
+ super(DSL.eval &opts)
208
+ else
209
+ super
210
+ end
211
+ end
212
+
213
+ def having(*args)
214
+ if block_given? && args.empty?
215
+ super(DSL.eval &Proc.new)
216
+ else
217
+ super
218
+ end
219
+ end
220
+
221
+ def build_where(opts, other = [])
222
+ case opts
223
+ when String, Array
224
+ super
225
+ else # Let's prevent PredicateBuilder from doing its thing
226
+ [opts, *other].map do |arg|
227
+ case arg
228
+ when Array # Just in case there's an array in there somewhere
229
+ @klass.send(:sanitize_sql, arg)
230
+ when Hash
231
+ @klass.send(:expand_hash_conditions_for_aggregates, arg)
232
+ else
233
+ arg
234
+ end
235
+ end
236
+ end
237
+ end
238
+
239
+ def collapse_wheres(arel, wheres)
240
+ wheres = [wheres] unless Array === wheres
241
+ binaries = wheres.grep(Arel::Nodes::Binary)
242
+
243
+ groups = binaries.group_by {|b| [b.class, b.left]}
244
+
245
+ groups.each do |_, bins|
246
+ arel = arel.where(bins.inject(&:and))
247
+ end
248
+
249
+ (wheres - binaries).each do |where|
250
+ where = Arel.sql(where) if String === where
251
+ arel = arel.where(Arel::Nodes::Grouping.new(where))
252
+ end
253
+
254
+ arel
255
+ end
256
+
257
+ def find_equality_predicates(nodes)
258
+ nodes.map { |node|
259
+ case node
260
+ when Arel::Nodes::Equality
261
+ node if node.left.relation.name == table_name
262
+ when Arel::Nodes::Grouping
263
+ find_equality_predicates([node.expr])
264
+ when Arel::Nodes::And
265
+ find_equality_predicates(node.children)
266
+ else
267
+ nil
268
+ end
269
+ }.compact.flatten
270
+ end
271
+
272
+ # Simulate the logic that occurs in #to_a
273
+ #
274
+ # This will let us get a dump of the SQL that will be run against the
275
+ # DB for debug purposes without actually running the query.
276
+ def debug_sql
277
+ if eager_loading?
278
+ including = (@eager_load_values + @includes_values).uniq
279
+ join_dependency = JoinDependency.new(@klass, including, nil)
280
+ construct_relation_for_association_find(join_dependency).to_sql
281
+ else
282
+ arel.to_sql
283
+ end
284
+ end
285
+
286
+ ### ZOMG ALIAS_METHOD_CHAIN IS BELOW. HIDE YOUR EYES!
287
+ # ...
288
+ # ...
289
+ # ...
290
+ # Since you're still looking, let me explain this horrible
291
+ # transgression you see before you.
292
+ # You see, Relation#where_values_hash is defined on the
293
+ # ActiveRecord::Relation class. Since it's defined there, but
294
+ # I would very much like to modify its behavior, I have three
295
+ # choices.
296
+ #
297
+ # 1. Inherit from ActiveRecord::Relation in a Squeel::Relation
298
+ # class, and make an attempt to usurp all of the various calls
299
+ # to methods on ActiveRecord::Relation by doing some really
300
+ # evil stuff with constant reassignment, all for the sake of
301
+ # being able to use super().
302
+ #
303
+ # 2. Submit a patch to Rails core, breaking this method off into
304
+ # another module, all for my own selfish desire to use super()
305
+ # while mucking about in Rails internals.
306
+ #
307
+ # 3. Use alias_method_chain, and say 10 hail Hanssons as penance.
308
+ #
309
+ # I opted to go with #3. Except for the hail Hansson thing.
310
+ # Unless you're DHH, in which case, I totally said them.
311
+
312
+ def self.included(base)
313
+ base.class_eval do
314
+ alias_method_chain :where_values_hash, :squeel
315
+ end
316
+ end
317
+
318
+ def where_values_hash_with_squeel
319
+ equalities = find_equality_predicates(predicate_visitor.accept(@where_values))
320
+
321
+ Hash[equalities.map { |where| [where.left.name, where.right] }]
322
+ end
323
+
324
+ end
325
+ end
326
+ end
327
+ end
@@ -0,0 +1,66 @@
1
+ require 'squeel/context'
2
+
3
+ module Squeel
4
+ module Adapters
5
+ module ActiveRecord
6
+ class Context < ::Squeel::Context
7
+ # Because the AR::Associations namespace is insane
8
+ JoinPart = ::ActiveRecord::Associations::JoinDependency::JoinPart
9
+
10
+ def initialize(object)
11
+ super
12
+ @base = object.join_base
13
+ @engine = @base.arel_engine
14
+ @arel_visitor = Arel::Visitors.visitor_for @engine
15
+ @default_table = Arel::Table.new(@base.table_name, :as => @base.aliased_table_name, :engine => @engine)
16
+ end
17
+
18
+ def find(object, parent = @base)
19
+ if JoinPart === parent
20
+ object = object.to_sym if String === object
21
+ case object
22
+ when Symbol, Nodes::Stub
23
+ @object.join_associations.detect { |j|
24
+ j.reflection.name == object.to_sym && j.parent == parent
25
+ }
26
+ when Nodes::Join
27
+ @object.join_associations.detect { |j|
28
+ j.reflection.name == object.name && j.parent == parent &&
29
+ (object.polymorphic? ? j.reflection.klass == object._klass : true)
30
+ }
31
+ else
32
+ @object.join_associations.detect { |j|
33
+ j.reflection == object && j.parent == parent
34
+ }
35
+ end
36
+ end
37
+ end
38
+
39
+ def traverse(keypath, parent = @base, include_endpoint = false)
40
+ parent = @base if keypath.absolute?
41
+ keypath.path.each do |key|
42
+ parent = find(key, parent) || key
43
+ end
44
+ parent = find(keypath.endpoint, parent) if include_endpoint
45
+
46
+ parent
47
+ end
48
+
49
+ private
50
+
51
+ def get_table(object)
52
+ if [Symbol, String, Nodes::Stub].include?(object.class)
53
+ Arel::Table.new(object.to_sym, :engine => @engine)
54
+ elsif Nodes::Join === object
55
+ object._klass ? object._klass.arel_table : Arel::Table.new(object._name, :engine => @engine)
56
+ elsif object.respond_to?(:aliased_table_name)
57
+ Arel::Table.new(object.table_name, :as => object.aliased_table_name, :engine => @engine)
58
+ else
59
+ raise ArgumentError, "Unable to get table for #{object}"
60
+ end
61
+ end
62
+
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,44 @@
1
+ require 'active_record'
2
+
3
+ module Squeel
4
+ module Adapters
5
+ module ActiveRecord
6
+ class JoinAssociation < ::ActiveRecord::Associations::JoinDependency::JoinAssociation
7
+
8
+ def initialize(reflection, join_dependency, parent = nil, polymorphic_class = nil)
9
+ if polymorphic_class && ::ActiveRecord::Base > polymorphic_class
10
+ swapping_reflection_klass(reflection, polymorphic_class) do |reflection|
11
+ super(reflection, join_dependency, parent)
12
+ end
13
+ else
14
+ super(reflection, join_dependency, parent)
15
+ end
16
+ end
17
+
18
+ def swapping_reflection_klass(reflection, klass)
19
+ reflection = reflection.clone
20
+ original_polymorphic = reflection.options.delete(:polymorphic)
21
+ reflection.instance_variable_set(:@klass, klass)
22
+ yield reflection
23
+ ensure
24
+ reflection.options[:polymorphic] = original_polymorphic
25
+ end
26
+
27
+ def ==(other)
28
+ super && active_record == other.active_record
29
+ end
30
+
31
+ def build_constraint(reflection, table, key, foreign_table, foreign_key)
32
+ if reflection.options[:polymorphic]
33
+ super.and(
34
+ foreign_table[reflection.foreign_type].eq(reflection.klass.name)
35
+ )
36
+ else
37
+ super
38
+ end
39
+ end
40
+
41
+ end
42
+ end
43
+ end
44
+ end