store_base_sti_class 2.0.1 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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