store_base_sti_class 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. data/Appraisals +25 -0
  2. data/CHANGELOG +2 -0
  3. data/Gemfile +9 -0
  4. data/Gemfile.lock +45 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.rdoc +63 -0
  7. data/Rakefile +47 -0
  8. data/VERSION +1 -0
  9. data/db/storebasestiname_unittest.sql +0 -0
  10. data/gemfiles/rails_3.0.10.gemfile +11 -0
  11. data/gemfiles/rails_3.0.10.gemfile.lock +43 -0
  12. data/gemfiles/rails_3.0.11.gemfile +11 -0
  13. data/gemfiles/rails_3.0.11.gemfile.lock +43 -0
  14. data/gemfiles/rails_3.0.12.gemfile +11 -0
  15. data/gemfiles/rails_3.0.12.gemfile.lock +43 -0
  16. data/gemfiles/rails_3.0.3.gemfile +11 -0
  17. data/gemfiles/rails_3.0.3.gemfile.lock +43 -0
  18. data/gemfiles/rails_3.0.4.gemfile +11 -0
  19. data/gemfiles/rails_3.0.4.gemfile.lock +43 -0
  20. data/gemfiles/rails_3.0.5.gemfile +11 -0
  21. data/gemfiles/rails_3.0.5.gemfile.lock +43 -0
  22. data/gemfiles/rails_3.0.6.gemfile +11 -0
  23. data/gemfiles/rails_3.0.6.gemfile.lock +43 -0
  24. data/gemfiles/rails_3.0.7.gemfile +11 -0
  25. data/gemfiles/rails_3.0.7.gemfile.lock +43 -0
  26. data/gemfiles/rails_3.0.8.gemfile +11 -0
  27. data/gemfiles/rails_3.0.8.gemfile.lock +43 -0
  28. data/gemfiles/rails_3.0.9.gemfile +11 -0
  29. data/gemfiles/rails_3.0.9.gemfile.lock +43 -0
  30. data/gemfiles/rails_3.1.0.gemfile +11 -0
  31. data/gemfiles/rails_3.1.0.gemfile.lock +47 -0
  32. data/gemfiles/rails_3.1.1.gemfile +11 -0
  33. data/gemfiles/rails_3.1.1.gemfile.lock +45 -0
  34. data/gemfiles/rails_3.1.2.gemfile +11 -0
  35. data/gemfiles/rails_3.1.2.gemfile.lock +45 -0
  36. data/gemfiles/rails_3.1.3.gemfile +11 -0
  37. data/gemfiles/rails_3.1.3.gemfile.lock +45 -0
  38. data/gemfiles/rails_3.1.4.gemfile +11 -0
  39. data/gemfiles/rails_3.1.4.gemfile.lock +45 -0
  40. data/gemfiles/rails_3.2.0.gemfile +11 -0
  41. data/gemfiles/rails_3.2.0.gemfile.lock +45 -0
  42. data/gemfiles/rails_3.2.1.gemfile +11 -0
  43. data/gemfiles/rails_3.2.1.gemfile.lock +45 -0
  44. data/gemfiles/rails_3.2.2.gemfile +11 -0
  45. data/gemfiles/rails_3.2.2.gemfile.lock +45 -0
  46. data/gemfiles/rails_3.2.3.gemfile +11 -0
  47. data/gemfiles/rails_3.2.3.gemfile.lock +45 -0
  48. data/lib/store_base_sti_class.rb +7 -0
  49. data/lib/store_base_sti_class_for_3_0.rb +470 -0
  50. data/lib/store_base_sti_class_for_3_1_and_above.rb +304 -0
  51. data/store_base_sti_class.gemspec +111 -0
  52. data/storebasestiname_unittest.sql +0 -0
  53. data/test/connection.rb +22 -0
  54. data/test/helper.rb +63 -0
  55. data/test/models.rb +48 -0
  56. data/test/schema.rb +35 -0
  57. data/test/test_store_base_sti_class.rb +161 -0
  58. metadata +205 -0
@@ -0,0 +1,470 @@
1
+ require 'active_record'
2
+
3
+ if ActiveRecord::VERSION::STRING =~ /^3\.0/
4
+ require 'active_record/associations'
5
+ require 'active_record/reflection'
6
+ require 'active_record/association_preload'
7
+ require 'active_record/associations/has_many_association'
8
+ require 'active_record/associations/has_one_association'
9
+ require 'active_record/associations/through_association_scope'
10
+ require 'active_record/associations/association_proxy'
11
+ require 'active_record/associations/belongs_to_polymorphic_association'
12
+
13
+ class ActiveRecord::Base
14
+
15
+ # Determine whether to store the base class or the actual class in polymorhic type columns when using STI
16
+ superclass_delegating_accessor :store_base_sti_class
17
+ self.store_base_sti_class = true
18
+
19
+ class << self
20
+
21
+ def polymorphic_sti_name
22
+ store_base_sti_class ? base_class.sti_name : sti_name
23
+ end
24
+
25
+ def in_or_equals_sti_names
26
+ if store_base_sti_class
27
+ "= #{quote_value(base_class.sti_name)}"
28
+ else
29
+ names = sti_names.map { |name| quote_value(name) }
30
+ names.length > 1 ? "IN (#{names.join(',')})" : "= #{names.first}"
31
+ end
32
+ end
33
+
34
+ def sti_names
35
+ ([self] + descendants).map { |model| model.sti_name }
36
+ end
37
+
38
+ end
39
+
40
+ end
41
+
42
+ module ActiveRecord
43
+ module Reflection
44
+ class AssociationReflection < MacroReflection #:nodoc:
45
+
46
+ def active_record_primary_key
47
+ @active_record_primary_key ||= options[:primary_key] || active_record.primary_key
48
+ end
49
+
50
+ end
51
+ end
52
+ end
53
+
54
+ module ActiveRecord
55
+ module AssociationPreload
56
+ module ClassMethods
57
+
58
+ private
59
+
60
+ def preload_through_records(records, reflection, through_association)
61
+ # p 'preload_through_records'
62
+ through_reflection = reflections[through_association]
63
+ through_primary_key = through_reflection.primary_key_name
64
+
65
+ through_records = []
66
+ if reflection.options[:source_type]
67
+ interface = reflection.source_reflection.options[:foreign_type]
68
+ source_type = reflection.options[:source_type].to_s.constantize
69
+ preload_options = { :conditions => "#{connection.quote_column_name interface} #{source_type.in_or_equals_sti_names}" }
70
+
71
+ records.compact!
72
+ records.first.class.preload_associations(records, through_association, preload_options)
73
+
74
+ # Dont cache the association - we would only be caching a subset
75
+ records.each do |record|
76
+ proxy = record.send(through_association)
77
+
78
+ if proxy.respond_to?(:target)
79
+ through_records.concat Array.wrap(proxy.target)
80
+ proxy.reset
81
+ else # this is a has_one :through reflection
82
+ through_records << proxy if proxy
83
+ end
84
+ end
85
+ else
86
+ options = {}
87
+ options[:include] = reflection.options[:include] || reflection.options[:source] if reflection.options[:conditions] || reflection.options[:order]
88
+ options[:order] = reflection.options[:order]
89
+ options[:conditions] = reflection.options[:conditions]
90
+ records.first.class.preload_associations(records, through_association, options)
91
+
92
+ records.each do |record|
93
+ through_records.concat Array.wrap(record.send(through_association))
94
+ end
95
+ end
96
+ through_records
97
+ end
98
+
99
+ def find_associated_records(ids, reflection, preload_options)
100
+ # p 'find_associated_records'
101
+ options = reflection.options
102
+ table_name = reflection.klass.quoted_table_name
103
+
104
+ if interface = reflection.options[:as]
105
+ conditions = "#{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_id"} #{in_or_equals_for_ids(ids)} and #{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_type"} #{self.in_or_equals_sti_names}"
106
+ else
107
+ foreign_key = reflection.primary_key_name
108
+ conditions = "#{reflection.klass.quoted_table_name}.#{foreign_key} #{in_or_equals_for_ids(ids)}"
109
+ end
110
+
111
+ # p 'append_conditions'
112
+ conditions << append_conditions(reflection, preload_options)
113
+
114
+ find_options = {
115
+ :select => preload_options[:select] || options[:select] || Arel::SqlLiteral.new("#{table_name}.*"),
116
+ :include => preload_options[:include] || options[:include],
117
+ :joins => options[:joins],
118
+ :group => preload_options[:group] || options[:group],
119
+ :order => preload_options[:order] || options[:order]
120
+ }
121
+
122
+ # p 'associated_records'
123
+ associated_records(ids) do |some_ids|
124
+ reflection.klass.scoped.apply_finder_options(find_options.merge(:conditions => [conditions, some_ids])).to_a
125
+ end
126
+ end
127
+
128
+ end
129
+ end
130
+ end
131
+
132
+ module ActiveRecord
133
+ module Associations
134
+ class HasManyAssociation < AssociationCollection
135
+
136
+ protected
137
+
138
+ def construct_sql
139
+ # p 'construct_sql :has_many'
140
+
141
+ case
142
+ when @reflection.options[:finder_sql]
143
+ @finder_sql = interpolate_and_sanitize_sql(@reflection.options[:finder_sql])
144
+
145
+ when @reflection.options[:as]
146
+ @finder_sql =
147
+ "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND " +
148
+ "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type #{@owner.class.in_or_equals_sti_names}"
149
+ @finder_sql << " AND (#{conditions})" if conditions
150
+
151
+ else
152
+ @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
153
+ @finder_sql << " AND (#{conditions})" if conditions
154
+ end
155
+
156
+ construct_counter_sql
157
+ end
158
+
159
+ end
160
+ end
161
+ end
162
+
163
+ module ActiveRecord
164
+ module Associations
165
+ class HasOneAssociation < AssociationProxy
166
+
167
+ protected
168
+
169
+ def construct_sql
170
+ # p 'construct_sql :has_one'
171
+
172
+ case
173
+ when @reflection.options[:as]
174
+ @finder_sql =
175
+ "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND " +
176
+ "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type #{@owner.class.in_or_equals_sti_names}"
177
+ else
178
+ @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
179
+ end
180
+ @finder_sql << " AND (#{conditions})" if conditions
181
+ end
182
+
183
+ end
184
+ end
185
+ end
186
+
187
+ module ActiveRecord
188
+ module Associations
189
+ module ThroughAssociationScope
190
+
191
+ protected
192
+
193
+ def construct_quoted_owner_attributes(reflection)
194
+ # p 'construct_quoted_owner_attributes'
195
+
196
+ if as = reflection.options[:as]
197
+ {
198
+ "#{as}_id" => owner_quoted_id,
199
+ "#{as}_type" => @owner.class.quote_value(@owner.class.polymorphic_sti_name)
200
+ }
201
+ elsif reflection.macro == :belongs_to
202
+ { reflection.klass.primary_key => @owner.class.quote_value(@owner[reflection.primary_key_name]) }
203
+ else
204
+ { reflection.primary_key_name => owner_quoted_id }
205
+ end
206
+ end
207
+
208
+ def construct_joins(custom_joins = nil)
209
+ # p 'construct_joins'
210
+
211
+ polymorphic_join = nil
212
+ if @reflection.source_reflection.macro == :belongs_to
213
+ reflection_primary_key = @reflection.klass.primary_key
214
+ source_primary_key = @reflection.source_reflection.primary_key_name
215
+ if @reflection.options[:source_type]
216
+ source_type = @reflection.options[:source_type].to_s.constantize
217
+ polymorphic_join = "AND %s.%s #{source_type.in_or_equals_sti_names}" % [
218
+ @reflection.through_reflection.quoted_table_name,
219
+ "#{@reflection.source_reflection.options[:foreign_type]}"
220
+ ]
221
+ end
222
+ else
223
+ reflection_primary_key = @reflection.source_reflection.primary_key_name
224
+ source_primary_key = @reflection.through_reflection.klass.primary_key
225
+ if @reflection.source_reflection.options[:as]
226
+ polymorphic_join = "AND %s.%s #{@reflection.through_reflection.klass.in_or_equals_sti_names}" % [
227
+ @reflection.quoted_table_name,
228
+ "#{@reflection.source_reflection.options[:as]}_type"
229
+ ]
230
+ end
231
+ end
232
+
233
+ "INNER JOIN %s ON %s.%s = %s.%s %s #{@reflection.options[:joins]} #{custom_joins}" % [
234
+ @reflection.through_reflection.quoted_table_name,
235
+ @reflection.quoted_table_name, reflection_primary_key,
236
+ @reflection.through_reflection.quoted_table_name, source_primary_key,
237
+ polymorphic_join
238
+ ]
239
+ end
240
+
241
+ def construct_owner_attributes(reflection)
242
+ # p 'construct_owner_attributes'
243
+
244
+ if as = reflection.options[:as]
245
+ { "#{as}_id" => @owner.id,
246
+ "#{as}_type" => @owner.class.polymorphic_sti_name }
247
+ else
248
+ { reflection.primary_key_name => @owner.id }
249
+ end
250
+ end
251
+
252
+ end
253
+ end
254
+ end
255
+
256
+ module ActiveRecord
257
+ module Associations
258
+ class AssociationProxy
259
+
260
+ protected
261
+
262
+ def set_belongs_to_association_for(record)
263
+ # p 'set_belongs_to_association_for'
264
+
265
+ if @reflection.options[:as]
266
+ record["#{@reflection.options[:as]}_id"] = @owner.id unless @owner.new_record?
267
+ record["#{@reflection.options[:as]}_type"] = @owner.class.polymorphic_sti_name
268
+ else
269
+ unless @owner.new_record?
270
+ primary_key = @reflection.options[:primary_key] || :id
271
+ record[@reflection.primary_key_name] = @owner.send(primary_key)
272
+ end
273
+ end
274
+ end
275
+
276
+ end
277
+ end
278
+ end
279
+
280
+ module ActiveRecord
281
+ module Associations
282
+ class BelongsToPolymorphicAssociation < AssociationProxy
283
+
284
+ def replace(record)
285
+ # p 'replace'
286
+
287
+ if record.nil?
288
+ @target = @owner[@reflection.primary_key_name] = @owner[@reflection.options[:foreign_type]] = nil
289
+ else
290
+ @target = (AssociationProxy === record ? record.target : record)
291
+
292
+ @owner[@reflection.primary_key_name] = record_id(record)
293
+ @owner[@reflection.options[:foreign_type]] = record.class.polymorphic_sti_name
294
+
295
+ @updated = true
296
+ end
297
+
298
+ set_inverse_instance(record, @owner)
299
+ loaded
300
+ record
301
+ end
302
+
303
+ end
304
+ end
305
+ end
306
+
307
+ module ActiveRecord
308
+ module Associations
309
+ module ThroughAssociationScope
310
+
311
+ protected
312
+
313
+ def construct_quoted_owner_attributes(reflection)
314
+ # p 'construct_quoted_owner_attributes'
315
+ if as = reflection.options[:as]
316
+ {
317
+ "#{as}_id" => owner_quoted_id,
318
+ "#{as}_type" => @owner.class.quote_value(@owner.class.polymorphic_sti_name)
319
+ }
320
+ elsif reflection.macro == :belongs_to
321
+ { reflection.klass.primary_key => @owner.class.quote_value(@owner[reflection.primary_key_name]) }
322
+ else
323
+ { reflection.primary_key_name => owner_quoted_id }
324
+ end
325
+ end
326
+
327
+ def construct_owner_attributes(reflection)
328
+ # p 'construct_owner_attributes'
329
+ if as = reflection.options[:as]
330
+ { "#{as}_id" => @owner.id,
331
+ "#{as}_type" => @owner.class.polymorphic_sti_name }
332
+ else
333
+ { reflection.primary_key_name => @owner.id }
334
+ end
335
+ end
336
+
337
+ def construct_join_attributes(associate)
338
+ # p 'construct_join_attributes'
339
+ # TODO: revisit this to allow it for deletion, supposing dependent option is supported
340
+ raise ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(@owner, @reflection) if [:has_one, :has_many].include?(@reflection.source_reflection.macro)
341
+
342
+ join_attributes = construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id)
343
+
344
+ if @reflection.options[:source_type]
345
+ join_attributes.merge!(@reflection.source_reflection.options[:foreign_type] => associate.class.polymorphic_sti_name)
346
+ end
347
+
348
+ if @reflection.through_reflection.options[:conditions].is_a?(Hash)
349
+ join_attributes.merge!(@reflection.through_reflection.options[:conditions])
350
+ end
351
+
352
+ join_attributes
353
+ end
354
+
355
+ end
356
+ end
357
+ end
358
+
359
+ module ActiveRecord
360
+ module Associations
361
+ module ClassMethods
362
+ class JoinDependency # :nodoc:
363
+ class JoinAssociation < JoinBase # :nodoc:
364
+
365
+ def association_join
366
+ # p 'association_join'
367
+
368
+ return @join if @join
369
+
370
+ aliased_table = Arel::Table.new(table_name, :as => @aliased_table_name,
371
+ :engine => arel_engine,
372
+ :columns => klass.columns)
373
+
374
+ parent_table = Arel::Table.new(parent.table_name, :as => parent.aliased_table_name,
375
+ :engine => arel_engine,
376
+ :columns => parent.active_record.columns)
377
+
378
+ @join = case reflection.macro
379
+ when :has_and_belongs_to_many
380
+ join_table = Arel::Table.new(options[:join_table], :as => aliased_join_table_name, :engine => arel_engine)
381
+ fk = options[:foreign_key] || reflection.active_record.to_s.foreign_key
382
+ klass_fk = options[:association_foreign_key] || klass.to_s.foreign_key
383
+
384
+ [
385
+ join_table[fk].eq(parent_table[reflection.active_record.primary_key]),
386
+ aliased_table[klass.primary_key].eq(join_table[klass_fk])
387
+ ]
388
+ when :has_many, :has_one
389
+ if reflection.options[:through]
390
+ join_table = Arel::Table.new(through_reflection.klass.table_name, :as => aliased_join_table_name, :engine => arel_engine)
391
+ jt_as_extra = jt_source_extra = jt_sti_extra = nil
392
+ first_key = second_key = nil
393
+
394
+ if through_reflection.macro == :belongs_to
395
+ jt_primary_key = through_reflection.primary_key_name
396
+ jt_foreign_key = through_reflection.association_primary_key
397
+ else
398
+ jt_primary_key = through_reflection.active_record_primary_key
399
+ jt_foreign_key = through_reflection.primary_key_name
400
+
401
+ if through_reflection.options[:as] # has_many :through against a polymorphic join
402
+ jt_as_extra = join_table[through_reflection.options[:as].to_s + '_type'].in(parent.active_record.sti_names)
403
+ end
404
+ end
405
+
406
+ case source_reflection.macro
407
+ when :has_many
408
+ if source_reflection.options[:as]
409
+ first_key = "#{source_reflection.options[:as]}_id"
410
+ second_key = options[:foreign_key] || primary_key
411
+ else
412
+ first_key = through_reflection.klass.base_class.to_s.foreign_key
413
+ second_key = options[:foreign_key] || primary_key
414
+ end
415
+
416
+ unless through_reflection.klass.descends_from_active_record?
417
+ # there is no test for this condition
418
+ jt_sti_extra = join_table[through_reflection.active_record.inheritance_column].eq(through_reflection.klass.sti_name)
419
+ end
420
+ when :belongs_to
421
+ first_key = primary_key
422
+ if reflection.options[:source_type]
423
+ source_type = reflection.options[:source_type].to_s.constantize
424
+ second_key = source_reflection.association_foreign_key
425
+ jt_source_extra = join_table[reflection.source_reflection.options[:foreign_type]].in(source_type.sti_names)
426
+ else
427
+ second_key = source_reflection.primary_key_name
428
+ end
429
+ end
430
+
431
+ [
432
+ [parent_table[jt_primary_key].eq(join_table[jt_foreign_key]), jt_as_extra, jt_source_extra, jt_sti_extra].reject{|x| x.blank? },
433
+ aliased_table[first_key].eq(join_table[second_key])
434
+ ]
435
+ elsif reflection.options[:as]
436
+ id_rel = aliased_table["#{reflection.options[:as]}_id"].eq(parent_table[parent.primary_key])
437
+ type_rel = aliased_table["#{reflection.options[:as]}_type"].in(parent.active_record.sti_names)
438
+ [id_rel, type_rel]
439
+ else
440
+ foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key
441
+ [aliased_table[foreign_key].eq(parent_table[reflection.options[:primary_key] || parent.primary_key])]
442
+ end
443
+ when :belongs_to
444
+ [aliased_table[options[:primary_key] || reflection.klass.primary_key].eq(parent_table[options[:foreign_key] || reflection.primary_key_name])]
445
+ end
446
+
447
+ unless klass.descends_from_active_record?
448
+ sti_column = aliased_table[klass.inheritance_column]
449
+ sti_condition = sti_column.eq(klass.sti_name)
450
+ klass.descendants.each {|subclass| sti_condition = sti_condition.or(sti_column.eq(subclass.sti_name)) }
451
+
452
+ @join << sti_condition
453
+ end
454
+
455
+ [through_reflection, reflection].each do |ref|
456
+ if ref && ref.options[:conditions]
457
+ @join << process_conditions(ref.options[:conditions], aliased_table_name)
458
+ end
459
+ end
460
+
461
+ @join
462
+ end
463
+
464
+ end
465
+ end
466
+ end
467
+ end
468
+ end
469
+
470
+ end