Module: ConcernsOnRails::Models::Duplicable
- Extended by:
- ActiveSupport::Concern
- Defined in:
- lib/concerns_on_rails/models/duplicable.rb
Overview
Concern-aware deep copy ("clone this invoice/template"). A bare
ActiveRecord dup copies identity-bearing columns — the slug, the API
token, the invoice number, the audit trail, even created_at (which AR
preserves on save when present) — so naive copies collide with unique
indexes or lie about their history. Duplicable knows its sibling
concerns and blanks exactly those columns, letting each concern
regenerate fresh values on save.
class Invoice < ApplicationRecord
include ConcernsOnRails::Models::Duplicable
include ConcernsOnRails::Models::Sequenceable
has_many :line_items
sequenceable_by :sequence, into: :number, prefix: "INV-"
duplicable_by associations: %i[line_items],
reset: %i[issued_at],
suffix: { title: " (copy)" }
end
copy = invoice.duplicate # unsaved deep copy
copy = invoice.duplicate!(title: "Q3") # saved, with overrides
What gets blanked automatically (identity, not business state):
* created_at / updated_at (AR keeps a present created_at on save)
* Sluggable slug — regenerated from the source field on save
* Tokenizable / Hashable columns — regenerated on create
* Sequenceable sequence + into: columns — next number on create
* Auditable trail column — a copy inherits no history (its own
creation is then audited normally, like any create)
* SoftDeletable timestamp — a copy of trash is a live record
* Lockable attempts (0) / locked_at (nil) — a copy starts unlocked
Business state (Publishable/Stateable/Activatable/...) is a judgment
call, so it is NOT auto-reset — list those columns in reset:.
Associations (associations: allow-list, declared before the macro):
* has_many / has_one — children are deep-copied. A child whose class
also includes Duplicable is copied via ITS OWN `duplicate` (own
resets, own nested associations) — recursive graphs stay declarative.
* has_and_belongs_to_many — the copy links to the SAME records (join
rows are duplicated, the associated records are not).
* has_many :through — rejected; duplicate the direct association.
* belongs_to — rejected; a copy shares its parent by keeping the FK.
duplicate returns an UNSAVED record with unsaved children (persisted
together by duplicate! / save! via autosave). Override
on_duplicate(copy) for custom tweaks — it runs last, before return.
Defined Under Namespace
Modules: ClassMethods
Constant Summary collapse
- LABEL =
"ConcernsOnRails::Models::Duplicable".freeze
- SUPPORTED_MACROS =
%i[has_many has_one has_and_belongs_to_many].freeze
- TIMESTAMP_COLUMNS =
%w[created_at updated_at].freeze
Instance Method Summary collapse
-
#duplicate(overrides = {}) ⇒ Object
Unsaved deep copy: attributes via
dup, identity columns blanked,reset:columns blanked,suffix:strings appended,overridesassigned, allow-listed associations copied, thenon_duplicate. -
#duplicate!(overrides = {}) ⇒ Object
Persisted deep copy — the copy and its copied children save together (autosave) inside one transaction.
-
#on_duplicate(_copy) ⇒ Object
Override point — receives the UNSAVED copy as the last step of
duplicate, so tweaks apply before any save.
Instance Method Details
#duplicate(overrides = {}) ⇒ Object
Unsaved deep copy: attributes via dup, identity columns blanked,
reset: columns blanked, suffix: strings appended, overrides
assigned, allow-listed associations copied, then on_duplicate.
116 117 118 119 120 121 122 123 124 |
# File 'lib/concerns_on_rails/models/duplicable.rb', line 116 def duplicate(overrides = {}) copy = dup duplicable_reset_attributes(copy) duplicable_apply_suffixes(copy) overrides.each { |attribute, value| copy.public_send("#{attribute}=", value) } duplicable_copy_associations(copy) on_duplicate(copy) copy end |
#duplicate!(overrides = {}) ⇒ Object
Persisted deep copy — the copy and its copied children save together (autosave) inside one transaction. Returns the saved copy.
128 129 130 131 132 |
# File 'lib/concerns_on_rails/models/duplicable.rb', line 128 def duplicate!(overrides = {}) copy = duplicate(overrides) transaction { copy.save! } copy end |
#on_duplicate(_copy) ⇒ Object
Override point — receives the UNSAVED copy as the last step of
duplicate, so tweaks apply before any save.
111 |
# File 'lib/concerns_on_rails/models/duplicable.rb', line 111 def on_duplicate(_copy); end |