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