store_base_sti_class 0.0.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.
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