Module: ConcernsOnRails::Models::Aliasable
- Extended by:
- ActiveSupport::Concern
- Defined in:
- lib/concerns_on_rails/models/aliasable.rb
Overview
Alias an existing ActiveRecord association under a second name with FULL support — read, write/assign, build/create, and the query side (joins / includes / preload / eager_load / where hash conditions) —not just a delegated reader. Rails’ alias_attribute covers columns only; there is no built-in way to alias an association.
class Book < ApplicationRecord
include ConcernsOnRails::Aliasable
belongs_to :author
has_many :chapters
alias_association :writer, :author # alias_method order: new, old
alias_association :sections, :chapters
# Options:
alias_association :penman, :author, only: :reader # no writer/build_/create_
alias_association :parts, :chapters, except: :ids # skip part_ids/part_ids=
alias_association :owner, :author, deprecated: "use #author" # warns on use
alias_association :maker, :author, alias_foreign_key: true # maker_id -> author_id
end
book.writer # same cached object as book.author
book.writer = user # assigns through the original association
book.build_writer(...) # build_/create_/create_!/reload_ (singular)
book.sections << chapter # the same CollectionProxy as book.chapters
book.section_ids # ids reader/writer (collection)
Book.joins(:writer) # INNER JOIN "authors"
Book.joins(:sections).where(sections: { title: "Intro" })
Notes:
* Declare alias_association AFTER the source association — it raises
"does not exist" when the source has not been defined yet.
* One loaded cache under two names: record.association(:alias) IS
record.association(:source), and only the source macro installs
callbacks — dependent:, counter_cache, autosave and validations
run exactly once.
* Query SQL: a bare joins(:sections) joins "chapters" directly; when
paired with where(sections: {...}) Rails aliases the join as
"sections" (INNER JOIN "chapters" "sections"). A where-hash key
must match the name you joined under (same rule as stock Rails):
joins(:sections).where(sections: {...}) works,
joins(:chapters).where(sections: {...}) does not.
* The belongs_to foreign-key attribute is NOT aliased — pair with
Rails' alias_attribute (e.g. :writer_id, :author_id) if needed.
* has_and_belongs_to_many cannot be aliased — use has_many :through.
* has_many/has_one :through CAN be aliased (the copy pins `source:`
so it is not re-derived from the alias name). One caveat: when the
alias is declared before the through model's class has loaded AND
the through model defines the source under a different name (e.g.
belongs_to :author behind has_many :authors), declare `source:`
explicitly on the original association.
* Subclasses inherit aliases. If a subclass redefines the source
association, re-declare the alias there (re-declaring with the SAME
source is allowed and idempotent) so the query side picks up the
new reflection. Repointing an existing alias at a DIFFERENT source
raises.
* only:/except: narrow the generated methods by group — :reader,
:writer, :build, :reload (singular), :ids (collection). Groups that
do not apply to the reflection type are ignored; the query side
(joins/includes/where-hash) is always registered.
* deprecated: true (or a String hint) makes every generated delegator
warn through ConcernsOnRails.deprecator before delegating — the
gradual-rename story: point the OLD name at the new association and
deprecate it. The query side and alias_foreign_key attribute
aliases do not warn.
* alias_foreign_key: true (belongs_to only) also aliases the FK
attribute via Rails' alias_attribute (<alias>_id, plus <alias>_type
when polymorphic).
Defined Under Namespace
Modules: ClassMethods
Constant Summary collapse
- LABEL =
"ConcernsOnRails::Models::Aliasable".freeze
- SINGULAR_STEM_GROUPS =
Method stems per reflection type, keyed by the only:/except: group names; %s is the association name. The reload_/reset_ stems vary across the supported Rails range and build_/create_ are skipped by Rails for polymorphic belongs_to, so each SOURCE method is existence-checked before its delegator is made. The :ids pair is handled separately (it singularizes the association name).
{ reader: ["%s"].freeze, writer: ["%s="].freeze, build: ["build_%s", "create_%s", "create_%s!"].freeze, reload: ["reload_%s", "reset_%s"].freeze }.freeze
- COLLECTION_STEM_GROUPS =
{ reader: ["%s"].freeze, writer: ["%s="].freeze }.freeze
- METHOD_GROUPS =
%i[reader writer build reload ids].freeze
Instance Method Summary collapse
-
#association(name) ⇒ Object
Route the alias to the source association proxy so record.association(:alias) IS record.association(:source) — one loaded cache.
Instance Method Details
#association(name) ⇒ Object
Route the alias to the source association proxy so record.association(:alias) IS record.association(:source) — one loaded cache. Load-bearing for the preloader, which assigns loaded records via record.association(reflection.name) using the alias’s renamed reflection.
400 401 402 |
# File 'lib/concerns_on_rails/models/aliasable.rb', line 400 def association(name) super(self.class.aliasable_aliases[name.to_sym] || name) end |