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.
data/README.md DELETED
@@ -1,85 +0,0 @@
1
- [![Build Status](https://travis-ci.org/appfolio/store_base_sti_class.svg?branch=master)](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,8 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activerecord", "4.2.11"
6
- gem "sqlite3", "~> 1.3.0"
7
-
8
- gemspec path: "../"
@@ -1,8 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activerecord", "5.0.7"
6
- gem "sqlite3", "~> 1.3.0"
7
-
8
- gemspec path: "../"
@@ -1,8 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activerecord", "5.1.6"
6
- gem "sqlite3", "~> 1.3.0"
7
-
8
- gemspec path: "../"
@@ -1,8 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activerecord", "5.2.3"
6
- gem "sqlite3", "~> 1.3.0"
7
-
8
- gemspec path: "../"
@@ -1,8 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activerecord", "5.2.4"
6
- gem "sqlite3", "~> 1.3.0"
7
-
8
- gemspec path: "../"
@@ -1,8 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activerecord", "6.0.1"
6
- gem "sqlite3", "~> 1.4.0"
7
-
8
- gemspec path: "../"
@@ -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