activerecord-polytypes 0.1.1 → 0.1.3
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/lib/activerecord-polytypes/version.rb +1 -1
- data/lib/activerecord-polytypes.rb +92 -17
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 233089223a03ffa632ef2091a59fed1f359871068c8dcda702827efa25533920
|
4
|
+
data.tar.gz: 12bcd3a00158a38887c60d95682535b9c8b0f2828e3c9b9bbf9bc4be34485221
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6c350a0b52209ac0da0b7f93e04d795ee9b6fa0e142dd40b8846a5e9fb5a28446b51dfd6a3676579a4bb2e87c179c7aaade3ecd2ee4f3e6965fb65538b566933
|
7
|
+
data.tar.gz: 2400da0809d720bd6e1c42448d18af1d3bd2cff5db3cc5ca5e94d98780d2c7c1b25477ffb1d970ef6c854c8151e00258b16736e6557e05701b7dc54e41281c3b
|
@@ -193,14 +193,14 @@ module ActiveRecordPolytypes
|
|
193
193
|
|
194
194
|
case_components_by_type[association.name] = "WHEN #{association.table_name}.#{association.join_primary_key} IS NOT NULL THEN '#{subtype_class_name}'"
|
195
195
|
join_components_by_type[association.name] = if association.belongs_to?
|
196
|
-
"LEFT
|
196
|
+
["LEFT", association.table_name, "%s JOIN %s ON #{table_name}.#{association.foreign_key} = #{association.table_name}.#{association.join_primary_key}"]
|
197
197
|
else
|
198
|
-
"LEFT
|
198
|
+
["LEFT", association.table_name, "%s JOIN %s ON #{table_name}.#{association.association_primary_key} = #{association.table_name}.#{association.join_primary_key}"]
|
199
199
|
end
|
200
200
|
end
|
201
201
|
|
202
202
|
# Define a scope `with_subtypes` that enriches the base query with subtype information.
|
203
|
-
scope :with_subtypes, ->(*typenames){
|
203
|
+
scope :with_subtypes, ->(*typenames, **join_sources){
|
204
204
|
select_components, case_components, join_components = typenames.map do |typename|
|
205
205
|
[
|
206
206
|
select_components_by_type[typename],
|
@@ -209,10 +209,13 @@ module ActiveRecordPolytypes
|
|
209
209
|
]
|
210
210
|
end.transpose
|
211
211
|
|
212
|
-
|
212
|
+
from(<<~SQL)
|
213
213
|
(
|
214
214
|
SELECT #{table_name}.*,#{select_components * ","}, CASE #{case_components * " "} ELSE '#{name}' END AS type
|
215
|
-
FROM #{table_name} #{
|
215
|
+
FROM #{table_name} #{typenames.map do |typename|
|
216
|
+
join_type, join_source, join_string, = join_components_by_type[typename]
|
217
|
+
join_string % join_sources.fetch(typename, [join_type, join_source])
|
218
|
+
end * " "}
|
216
219
|
) #{table_name}
|
217
220
|
SQL
|
218
221
|
}
|
@@ -230,14 +233,21 @@ module ActiveRecordPolytypes
|
|
230
233
|
# Define a new class inherited from the current class acting as the subtype.
|
231
234
|
subtype_class = supertype_type.const_set(base_type.name, Class.new(subtype_class))
|
232
235
|
subtype_class.class_eval do
|
233
|
-
attr_reader :inner
|
234
|
-
|
235
236
|
# Only include records of this subtype in the default scope.
|
236
|
-
default_scope ->{
|
237
|
+
default_scope ->{
|
238
|
+
with_subtypes(association.name, **{
|
239
|
+
association.name => ["INNER", "#{association.table_name}"]
|
240
|
+
})
|
241
|
+
}
|
237
242
|
# Define callbacks and methods for initializing and saving the inner object.
|
238
|
-
after_initialize :
|
239
|
-
|
240
|
-
|
243
|
+
after_initialize :inner
|
244
|
+
if association.belongs_to?
|
245
|
+
before_save :save_inner_object_if_changed
|
246
|
+
else
|
247
|
+
after_save :save_inner_object_if_changed
|
248
|
+
end
|
249
|
+
|
250
|
+
after_save :reload_inner!, if: :previously_new_record?
|
241
251
|
|
242
252
|
# Define attributes and delegation methods for columns inherited from the base type.
|
243
253
|
base_type.reflect_on_all_associations.each do |assoc|
|
@@ -258,6 +268,26 @@ module ActiveRecordPolytypes
|
|
258
268
|
self.send(assoc.macro, assoc.name, scope, **assoc.options.except(:inverse_of, :destroy, :as), primary_key: "#{association.name}_#{base_type.primary_key}", foreign_key: assoc.foreign_key, class_name: "::#{assoc.class_name}")
|
259
269
|
end
|
260
270
|
end
|
271
|
+
|
272
|
+
base_type.enum_index&.each do |_, kwargs, _|
|
273
|
+
kwargs = kwargs.dup
|
274
|
+
enum_type = kwargs.keys.first
|
275
|
+
enum_values = kwargs.delete(enum_type)
|
276
|
+
namespaced_attribute = "#{association.name}_#{enum_type}"
|
277
|
+
attribute namespaced_attribute, :integer
|
278
|
+
kwargs.merge!(namespaced_attribute => enum_values)
|
279
|
+
self.enum(**kwargs)
|
280
|
+
end
|
281
|
+
|
282
|
+
base_type.scope_index&.each do |args, kwargs, blk|
|
283
|
+
self.scope(args[0], proc do |*scope_args|
|
284
|
+
inner_scope = base_type.instance_exec(*scope_args, &args[1]).to_sql
|
285
|
+
unscope(:from).with_subtypes(association.name, **{
|
286
|
+
association.name => ["INNER", "(#{inner_scope}) #{association.table_name}"]
|
287
|
+
})
|
288
|
+
end)
|
289
|
+
end
|
290
|
+
|
261
291
|
base_type.columns.each do |column|
|
262
292
|
column_name = "#{association.name}_#{column.name}"
|
263
293
|
attribute column_name
|
@@ -280,8 +310,13 @@ module ActiveRecordPolytypes
|
|
280
310
|
end
|
281
311
|
end
|
282
312
|
|
313
|
+
def inner
|
314
|
+
@inner ||= initialize_inner_object
|
315
|
+
end
|
316
|
+
|
283
317
|
# Initialize the inner object based on the association's attributes or build a new association instance.
|
284
318
|
define_method :initialize_inner_object do
|
319
|
+
return if @inner
|
285
320
|
# Prepare attributes for instantiation.
|
286
321
|
@inner_attributes ||= base_type.columns.each_with_object({}) do |c, attrs|
|
287
322
|
attrs[c.name.to_s] = self["#{association.name}_#{c.name}"]
|
@@ -304,13 +339,14 @@ module ActiveRecordPolytypes
|
|
304
339
|
# Override `as_json` to include attributes from both the outer and inner objects.
|
305
340
|
define_method :as_json do |options={}|
|
306
341
|
only = base_type.column_names + ["type"] + (options || {}).fetch(:only,[])
|
307
|
-
outer = super(**(options || {}), only:
|
342
|
+
outer = super(**(options || {}), only:)
|
308
343
|
@inner.as_json(options).merge(outer)
|
309
344
|
end
|
310
345
|
|
311
346
|
# Save the inner object if it has changed before saving the outer object.
|
312
347
|
def save_inner_object_if_changed
|
313
|
-
@inner.save if @inner.changed?
|
348
|
+
@inner.save if @inner.changed? || @inner.new_record?
|
349
|
+
self.errors.merge!(@inner.errors)
|
314
350
|
end
|
315
351
|
|
316
352
|
# Check if an attribute exists in either the outer or inner object.
|
@@ -318,14 +354,30 @@ module ActiveRecordPolytypes
|
|
318
354
|
super || @inner._has_attribute?(attribute)
|
319
355
|
end
|
320
356
|
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
357
|
+
define_method :_assign_attribute do |name, value|
|
358
|
+
inner.has_attribute?(name) ? inner.send(:_assign_attribute, name, value) : super(name, value)
|
359
|
+
end
|
360
|
+
|
361
|
+
define_method :update_column do |key, value|
|
362
|
+
return super if self.class.column_names.include?(key.to_s)
|
363
|
+
return inner.update_column(key, value) if inner.class.column_names.include?(key.to_s)
|
364
|
+
key = key.to_s.gsub(%r{^#{association.name}_}, '')
|
365
|
+
return inner.update_column(key.to_sym, value) if inner.class.column_names.include?(key)
|
366
|
+
end
|
367
|
+
|
368
|
+
define_method :reload_inner! do
|
369
|
+
inner.reload if inner.persisted?
|
325
370
|
# Update attributes from the reloaded inner object.
|
326
371
|
base_type.columns.each_with_object({}) do |c, attrs|
|
327
372
|
self["#{association.name}_#{c.name}"] = @inner[c.name.to_s]
|
328
373
|
end
|
374
|
+
end
|
375
|
+
|
376
|
+
# Reload both the outer and inner objects to ensure consistency.
|
377
|
+
define_method :reload do
|
378
|
+
super()
|
379
|
+
reload_inner!
|
380
|
+
|
329
381
|
self
|
330
382
|
end
|
331
383
|
end
|
@@ -333,7 +385,30 @@ module ActiveRecordPolytypes
|
|
333
385
|
end
|
334
386
|
end
|
335
387
|
|
388
|
+
module ActiveRecordPolytypeInterceptors
|
389
|
+
module ClassMethods
|
390
|
+
attr_accessor :enum_index, :scope_index
|
391
|
+
|
392
|
+
def enum(*args, **kwargs, &blk)
|
393
|
+
@enum_index ||= []
|
394
|
+
@enum_index << [args.deep_dup, kwargs.deep_dup, blk]
|
395
|
+
puts "Defining enum: #{args} #{kwargs} #{blk} for #{self.name}. #{@enum_index}"
|
396
|
+
super
|
397
|
+
end
|
398
|
+
|
399
|
+
def scope(*args, **kwargs, &blk)
|
400
|
+
@scope_index ||= []
|
401
|
+
@scope_index << [args.dup, kwargs.dup, blk]
|
402
|
+
super
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
def self.prepended(mod)
|
407
|
+
mod.singleton_class.prepend(ClassMethods)
|
408
|
+
end
|
409
|
+
end
|
336
410
|
# Hook into ActiveSupport's on_load mechanism to automatically include this functionality into ActiveRecord.
|
337
411
|
ActiveSupport.on_load(:active_record) do
|
338
412
|
include ActiveRecordPolytypes
|
413
|
+
prepend ActiveRecordPolytypeInterceptors
|
339
414
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord-polytypes
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Wouter Coppieters
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-04-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|