store_base_sti_class 2.0.1 → 3.0.0
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.
- checksums.yaml +4 -4
- data/LICENSE.txt +1 -1
- data/lib/store_base_sti_class/version.rb +1 -1
- data/lib/store_base_sti_class.rb +3 -11
- data/lib/{store_base_sti_class_for_5_2.rb → store_base_sti_class_for_6_1.rb} +22 -42
- data/lib/store_base_sti_class_for_7_0.rb +156 -0
- data/store_base_sti_class.gemspec +25 -24
- metadata +28 -78
- data/Appraisals +0 -15
- data/Gemfile +0 -3
- data/Gemfile.lock +0 -46
- data/README.md +0 -85
- data/Rakefile +0 -23
- data/gemfiles/rails_4.2.11.gemfile +0 -8
- data/gemfiles/rails_5.0.7.gemfile +0 -8
- data/gemfiles/rails_5.1.6.gemfile +0 -8
- data/gemfiles/rails_5.2.3.gemfile +0 -8
- data/gemfiles/rails_5.2.4.gemfile +0 -8
- data/gemfiles/rails_6.0.1.gemfile +0 -8
- data/lib/store_base_sti_class_for_4_2.rb +0 -360
- data/lib/store_base_sti_class_for_5_0.rb +0 -420
- data/lib/store_base_sti_class_for_5_1.rb +0 -367
data/README.md
DELETED
@@ -1,85 +0,0 @@
|
|
1
|
-
[](https://travis-ci.org/appfolio/store_base_sti_class)
|
2
|
-
## Description
|
3
|
-
|
4
|
-
Given the following class definitions:
|
5
|
-
|
6
|
-
```ruby
|
7
|
-
class Address
|
8
|
-
belongs_to :addressable, :polymorphic => true
|
9
|
-
end
|
10
|
-
|
11
|
-
class Person
|
12
|
-
has_many :addresses, :as => addressable
|
13
|
-
end
|
14
|
-
|
15
|
-
class Vendor < Person
|
16
|
-
end
|
17
|
-
```
|
18
|
-
|
19
|
-
and given the following code:
|
20
|
-
|
21
|
-
```ruby
|
22
|
-
vendor = Vendor.create(...)
|
23
|
-
address = vendor.addresses.create(...)
|
24
|
-
|
25
|
-
p vendor
|
26
|
-
p address
|
27
|
-
```
|
28
|
-
|
29
|
-
will output:
|
30
|
-
|
31
|
-
```ruby
|
32
|
-
#<Vendor id: 1, type: "Vendor" ...>
|
33
|
-
#<Address id: 1, addressable_id: 1, addressable_type: 'Person' ...>
|
34
|
-
```
|
35
|
-
|
36
|
-
Notice that addressable_type column is Person even though the actual class is Vendor.
|
37
|
-
|
38
|
-
Normally, this isn't a problem, however, it can have negative performance
|
39
|
-
characteristics in certain circumstances. The most obvious one is that a join
|
40
|
-
with persons or an extra query is required to find out the actual type of
|
41
|
-
addressable.
|
42
|
-
|
43
|
-
This gem adds the ActiveRecord::Base.store_base_sti_class configuration
|
44
|
-
option. It defaults to true for backwards compatibility. Setting it to false
|
45
|
-
will alter ActiveRecord's behavior to store the actual class in polymorphic
|
46
|
-
_type columns when STI is used.
|
47
|
-
|
48
|
-
In the example above, if the ActiveRecord::Base.store_base_sti_class is false, the output will be,
|
49
|
-
|
50
|
-
```
|
51
|
-
#<Vendor id: 1, type: "Vendor" ...>
|
52
|
-
#<Address id: 1, addressable_id: 1, addressable_type: 'Vendor' ...>
|
53
|
-
```
|
54
|
-
|
55
|
-
## Usage
|
56
|
-
|
57
|
-
Add the following line to your Gemfile,
|
58
|
-
|
59
|
-
```ruby
|
60
|
-
gem 'store_base_sti_class'
|
61
|
-
```
|
62
|
-
|
63
|
-
then bundle install. Once you have the gem installed, add the following to one
|
64
|
-
of the initializers (or make a new one) in config/initializers,
|
65
|
-
|
66
|
-
ActiveRecord::Base.store_base_sti_class = false
|
67
|
-
|
68
|
-
When changing this behavior, you will have write a migration to update all of
|
69
|
-
your existing _type columns accordingly. You may also need to change your
|
70
|
-
application if it explicitly relies on the _type columns.
|
71
|
-
|
72
|
-
## Notes
|
73
|
-
|
74
|
-
This gem incorporates work from:
|
75
|
-
|
76
|
-
- https://github.com/codepodu/store_base_sti_class_for_4_0
|
77
|
-
|
78
|
-
It currently works with ActiveRecord 4.2.x through 6.0.x. If you need support
|
79
|
-
for ActiveRecord 3.x, use a pre-1.0 version of the gem, or ActiveRecord < 4.2
|
80
|
-
use a pre-2.0 version of the gem.
|
81
|
-
|
82
|
-
## Copyright
|
83
|
-
|
84
|
-
Copyright (c) 2011-2019 AppFolio, inc. See LICENSE.txt for
|
85
|
-
further details.
|
data/Rakefile
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'bundler'
|
3
|
-
|
4
|
-
require 'appraisal'
|
5
|
-
|
6
|
-
begin
|
7
|
-
Bundler.setup(:default, :development)
|
8
|
-
rescue Bundler::BundlerError => e
|
9
|
-
$stderr.puts e.message
|
10
|
-
$stderr.puts "Run `bundle install` to install missing gems"
|
11
|
-
exit e.status_code
|
12
|
-
end
|
13
|
-
require 'rake'
|
14
|
-
|
15
|
-
require 'rake/testtask'
|
16
|
-
Rake::TestTask.new(:test) do |test|
|
17
|
-
test.libs << 'lib' << 'test'
|
18
|
-
test.pattern = 'test/**/test_*.rb'
|
19
|
-
test.verbose = true
|
20
|
-
end
|
21
|
-
|
22
|
-
task :default => :test
|
23
|
-
|
@@ -1,360 +0,0 @@
|
|
1
|
-
require 'active_record/associations/join_dependency/join_part'
|
2
|
-
|
3
|
-
if ActiveRecord::VERSION::STRING =~ /^4\.2/
|
4
|
-
module ActiveRecord
|
5
|
-
|
6
|
-
class Base
|
7
|
-
class_attribute :store_base_sti_class
|
8
|
-
self.store_base_sti_class = true
|
9
|
-
end
|
10
|
-
|
11
|
-
module Associations
|
12
|
-
class Association
|
13
|
-
|
14
|
-
def creation_attributes
|
15
|
-
attributes = {}
|
16
|
-
|
17
|
-
if (reflection.has_one? || reflection.collection?) && !options[:through]
|
18
|
-
attributes[reflection.foreign_key] = owner[reflection.active_record_primary_key]
|
19
|
-
|
20
|
-
if reflection.options[:as]
|
21
|
-
# START PATCH
|
22
|
-
# original:
|
23
|
-
# attributes[reflection.type] = owner.class.base_class.name
|
24
|
-
|
25
|
-
attributes[reflection.type] = ActiveRecord::Base.store_base_sti_class ? owner.class.base_class.name : owner.class.name
|
26
|
-
# END PATCH
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
attributes
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
class JoinDependency # :nodoc:
|
35
|
-
class JoinAssociation < JoinPart # :nodoc:
|
36
|
-
def join_constraints(foreign_table, foreign_klass, node, join_type, tables, scope_chain, chain)
|
37
|
-
joins = []
|
38
|
-
bind_values = []
|
39
|
-
tables = tables.reverse
|
40
|
-
|
41
|
-
scope_chain_index = 0
|
42
|
-
scope_chain = scope_chain.reverse
|
43
|
-
|
44
|
-
# The chain starts with the target table, but we want to end with it here (makes
|
45
|
-
# more sense in this context), so we reverse
|
46
|
-
chain.reverse_each do |reflection|
|
47
|
-
table = tables.shift
|
48
|
-
klass = reflection.klass
|
49
|
-
|
50
|
-
join_keys = reflection.join_keys(klass)
|
51
|
-
key = join_keys.key
|
52
|
-
foreign_key = join_keys.foreign_key
|
53
|
-
|
54
|
-
constraint = build_constraint(klass, table, key, foreign_table, foreign_key)
|
55
|
-
|
56
|
-
scope_chain_items = scope_chain[scope_chain_index].map do |item|
|
57
|
-
if item.is_a?(Relation)
|
58
|
-
item
|
59
|
-
else
|
60
|
-
ActiveRecord::Relation.create(klass, table).instance_exec(node, &item)
|
61
|
-
end
|
62
|
-
end
|
63
|
-
scope_chain_index += 1
|
64
|
-
|
65
|
-
scope_chain_items.concat [klass.send(:build_default_scope, ActiveRecord::Relation.create(klass, table))].compact
|
66
|
-
|
67
|
-
rel = scope_chain_items.inject(scope_chain_items.shift) do |left, right|
|
68
|
-
left.merge right
|
69
|
-
end
|
70
|
-
|
71
|
-
if rel && !rel.arel.constraints.empty?
|
72
|
-
bind_values.concat rel.bind_values
|
73
|
-
constraint = constraint.and rel.arel.constraints
|
74
|
-
end
|
75
|
-
|
76
|
-
if reflection.type
|
77
|
-
# START PATCH
|
78
|
-
# original:
|
79
|
-
# value = foreign_klass.base_class.name
|
80
|
-
value = ActiveRecord::Base.store_base_sti_class ? foreign_klass.base_class.name : foreign_klass.name
|
81
|
-
# END PATCH
|
82
|
-
column = klass.columns_hash[reflection.type.to_s]
|
83
|
-
|
84
|
-
substitute = klass.connection.substitute_at(column)
|
85
|
-
bind_values.push [column, value]
|
86
|
-
constraint = constraint.and table[reflection.type].eq substitute
|
87
|
-
end
|
88
|
-
|
89
|
-
joins << table.create_join(table, table.create_on(constraint), join_type)
|
90
|
-
|
91
|
-
# The current table in this iteration becomes the foreign table in the next
|
92
|
-
foreign_table, foreign_klass = table, klass
|
93
|
-
end
|
94
|
-
|
95
|
-
JoinInformation.new joins, bind_values
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
class BelongsToPolymorphicAssociation
|
101
|
-
private
|
102
|
-
|
103
|
-
def replace_keys(record)
|
104
|
-
super
|
105
|
-
|
106
|
-
# START PATCH
|
107
|
-
# original:
|
108
|
-
# owner[reflection.foreign_type] = record.class.base_class.name
|
109
|
-
|
110
|
-
owner[reflection.foreign_type] = ActiveRecord::Base.store_base_sti_class ? record.class.base_class.name : record.class.name
|
111
|
-
|
112
|
-
# END PATCH
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
class Preloader
|
117
|
-
class Association
|
118
|
-
|
119
|
-
def build_scope
|
120
|
-
scope = klass.unscoped
|
121
|
-
|
122
|
-
values = reflection_scope.values
|
123
|
-
reflection_binds = reflection_scope.bind_values
|
124
|
-
preload_values = preload_scope.values
|
125
|
-
preload_binds = preload_scope.bind_values
|
126
|
-
|
127
|
-
scope.where_values = Array(values[:where]) + Array(preload_values[:where])
|
128
|
-
scope.references_values = Array(values[:references]) + Array(preload_values[:references])
|
129
|
-
scope.bind_values = (reflection_binds + preload_binds)
|
130
|
-
|
131
|
-
scope._select! preload_values[:select] || values[:select] || table[Arel.star]
|
132
|
-
scope.includes! preload_values[:includes] || values[:includes]
|
133
|
-
scope.joins! preload_values[:joins] || values[:joins]
|
134
|
-
scope.order! preload_values[:order] || values[:order]
|
135
|
-
|
136
|
-
if preload_values[:readonly] || values[:readonly]
|
137
|
-
scope.readonly!
|
138
|
-
end
|
139
|
-
|
140
|
-
if options[:as]
|
141
|
-
# START PATCH
|
142
|
-
# original:
|
143
|
-
# scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name })
|
144
|
-
|
145
|
-
scope.where!(klass.table_name => { reflection.type => ActiveRecord::Base.store_base_sti_class ? model.base_class.sti_name : model.sti_name })
|
146
|
-
|
147
|
-
# END PATCH
|
148
|
-
end
|
149
|
-
|
150
|
-
scope.unscope_values = Array(values[:unscope])
|
151
|
-
klass.default_scoped.merge(scope)
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
module ThroughAssociation
|
156
|
-
private
|
157
|
-
|
158
|
-
def through_scope
|
159
|
-
scope = through_reflection.klass.unscoped
|
160
|
-
|
161
|
-
if options[:source_type]
|
162
|
-
# BEGIN PATCH
|
163
|
-
# original: scope.where! reflection.foreign_type => options[:source_type]
|
164
|
-
|
165
|
-
adjusted_foreign_type = if ActiveRecord::Base.store_base_sti_class
|
166
|
-
options[:source_type]
|
167
|
-
else
|
168
|
-
([options[:source_type].constantize] + options[:source_type].constantize.descendants).map(&:to_s)
|
169
|
-
end
|
170
|
-
|
171
|
-
scope.where! reflection.foreign_type => adjusted_foreign_type
|
172
|
-
|
173
|
-
# END PATCH
|
174
|
-
else
|
175
|
-
unless reflection_scope.where_values.empty?
|
176
|
-
scope.includes_values = Array(reflection_scope.values[:includes] || options[:source])
|
177
|
-
scope.where_values = reflection_scope.values[:where]
|
178
|
-
scope.bind_values = reflection_scope.bind_values
|
179
|
-
end
|
180
|
-
|
181
|
-
scope.references! reflection_scope.values[:references]
|
182
|
-
scope = scope.order reflection_scope.values[:order] if scope.eager_loading?
|
183
|
-
end
|
184
|
-
|
185
|
-
scope
|
186
|
-
end
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
class AssociationScope
|
191
|
-
def self.get_bind_values(owner, chain)
|
192
|
-
binds = []
|
193
|
-
last_reflection = chain.last
|
194
|
-
|
195
|
-
binds << last_reflection.join_id_for(owner)
|
196
|
-
if last_reflection.type
|
197
|
-
# START PATCH
|
198
|
-
# original: binds << owner.class.base_class.name
|
199
|
-
binds << (ActiveRecord::Base.store_base_sti_class ? owner.class.base_class.name : owner.class.name)
|
200
|
-
# END PATCH
|
201
|
-
end
|
202
|
-
|
203
|
-
chain.each_cons(2).each do |reflection, next_reflection|
|
204
|
-
if reflection.type
|
205
|
-
# START PATCH
|
206
|
-
# original: binds << next_reflection.klass.base_class.name
|
207
|
-
binds << (ActiveRecord::Base.store_base_sti_class ? next_reflection.klass.base_class.name : next_reflection.klass.name)
|
208
|
-
# END PATCH
|
209
|
-
end
|
210
|
-
end
|
211
|
-
binds
|
212
|
-
end
|
213
|
-
|
214
|
-
def next_chain_scope(scope, table, reflection, tracker, assoc_klass, foreign_table, next_reflection)
|
215
|
-
join_keys = reflection.join_keys(assoc_klass)
|
216
|
-
key = join_keys.key
|
217
|
-
foreign_key = join_keys.foreign_key
|
218
|
-
|
219
|
-
constraint = table[key].eq(foreign_table[foreign_key])
|
220
|
-
|
221
|
-
if reflection.type
|
222
|
-
# BEGIN PATCH
|
223
|
-
# original:
|
224
|
-
# value = next_reflection.klass.base_class.name
|
225
|
-
# bind_val = bind scope, table.table_name, reflection.type, value, tracker
|
226
|
-
# scope = scope.where(table[reflection.type].eq(bind_val))
|
227
|
-
if ActiveRecord::Base.store_base_sti_class
|
228
|
-
value = next_reflection.klass.base_class.name
|
229
|
-
bind_val = bind scope, table.table_name, reflection.type, value, tracker
|
230
|
-
scope = scope.where(table[reflection.type].eq(bind_val))
|
231
|
-
else
|
232
|
-
value = next_reflection.klass.name
|
233
|
-
klass = next_reflection.klass
|
234
|
-
scope = scope.where(table[reflection.type].in(([klass] + klass.descendants).map(&:name)))
|
235
|
-
end
|
236
|
-
# END PATCH
|
237
|
-
|
238
|
-
end
|
239
|
-
|
240
|
-
scope = scope.joins(join(foreign_table, constraint))
|
241
|
-
end
|
242
|
-
|
243
|
-
def last_chain_scope(scope, table, reflection, owner, tracker, assoc_klass)
|
244
|
-
join_keys = reflection.join_keys(assoc_klass)
|
245
|
-
key = join_keys.key
|
246
|
-
foreign_key = join_keys.foreign_key
|
247
|
-
|
248
|
-
bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key], tracker
|
249
|
-
scope = scope.where(table[key].eq(bind_val))
|
250
|
-
|
251
|
-
if reflection.type
|
252
|
-
# BEGIN PATCH
|
253
|
-
# original: owner.class.base_class.name
|
254
|
-
value = ActiveRecord::Base.store_base_sti_class ? owner.class.base_class.name : owner.class.name
|
255
|
-
# END PATCH
|
256
|
-
bind_val = bind scope, table.table_name, reflection.type, value, tracker
|
257
|
-
scope = scope.where(table[reflection.type].eq(bind_val))
|
258
|
-
else
|
259
|
-
scope
|
260
|
-
end
|
261
|
-
end
|
262
|
-
|
263
|
-
end
|
264
|
-
|
265
|
-
module ThroughAssociation
|
266
|
-
def construct_join_attributes(*records)
|
267
|
-
ensure_mutable
|
268
|
-
|
269
|
-
if source_reflection.association_primary_key(reflection.klass) == reflection.klass.primary_key
|
270
|
-
join_attributes = { source_reflection.name => records }
|
271
|
-
else
|
272
|
-
join_attributes = {
|
273
|
-
source_reflection.foreign_key =>
|
274
|
-
records.map { |record|
|
275
|
-
record.send(source_reflection.association_primary_key(reflection.klass))
|
276
|
-
}
|
277
|
-
}
|
278
|
-
end
|
279
|
-
|
280
|
-
if options[:source_type]
|
281
|
-
|
282
|
-
# START PATCH
|
283
|
-
# original:
|
284
|
-
# join_attributes[source_reflection.foreign_type] =
|
285
|
-
# records.map { |record| record.class.base_class.name }
|
286
|
-
|
287
|
-
join_attributes[source_reflection.foreign_type] =
|
288
|
-
records.map { |record| ActiveRecord::Base.store_base_sti_class ? record.class.base_class.name : record.class.name }
|
289
|
-
|
290
|
-
# END PATCH
|
291
|
-
end
|
292
|
-
|
293
|
-
if records.count == 1
|
294
|
-
Hash[join_attributes.map { |k, v| [k, v.first] }]
|
295
|
-
else
|
296
|
-
join_attributes
|
297
|
-
end
|
298
|
-
end
|
299
|
-
|
300
|
-
end
|
301
|
-
|
302
|
-
class HasManyThroughAssociation
|
303
|
-
|
304
|
-
def build_through_record(record)
|
305
|
-
@through_records[record.object_id] ||= begin
|
306
|
-
ensure_mutable
|
307
|
-
|
308
|
-
through_record = through_association.build(*options_for_through_record)
|
309
|
-
through_record.send("#{source_reflection.name}=", record)
|
310
|
-
|
311
|
-
# START PATCH
|
312
|
-
if ActiveRecord::Base.store_base_sti_class
|
313
|
-
if options[:source_type]
|
314
|
-
through_record.send("#{source_reflection.foreign_type}=", options[:source_type])
|
315
|
-
end
|
316
|
-
end
|
317
|
-
# END PATCH
|
318
|
-
|
319
|
-
through_record
|
320
|
-
end
|
321
|
-
end
|
322
|
-
end
|
323
|
-
end
|
324
|
-
|
325
|
-
module Reflection
|
326
|
-
class ThroughReflection
|
327
|
-
def scope_chain
|
328
|
-
@scope_chain ||= begin
|
329
|
-
scope_chain = source_reflection.scope_chain.map(&:dup)
|
330
|
-
|
331
|
-
# Add to it the scope from this reflection (if any)
|
332
|
-
scope_chain.first << scope if scope
|
333
|
-
|
334
|
-
through_scope_chain = through_reflection.scope_chain.map(&:dup)
|
335
|
-
|
336
|
-
if options[:source_type]
|
337
|
-
type = foreign_type
|
338
|
-
# START PATCH
|
339
|
-
# original: source_type = options[:source_type]
|
340
|
-
source_type = if ActiveRecord::Base.store_base_sti_class
|
341
|
-
options[:source_type]
|
342
|
-
else
|
343
|
-
([options[:source_type].constantize] + options[:source_type].constantize.descendants).map(&:to_s)
|
344
|
-
end
|
345
|
-
# END PATCH
|
346
|
-
through_scope_chain.first << lambda { |object|
|
347
|
-
where(type => source_type)
|
348
|
-
}
|
349
|
-
end
|
350
|
-
|
351
|
-
# Recursively fill out the rest of the array from the through reflection
|
352
|
-
scope_chain + through_scope_chain
|
353
|
-
end
|
354
|
-
end
|
355
|
-
end
|
356
|
-
|
357
|
-
end
|
358
|
-
end
|
359
|
-
|
360
|
-
end
|