TLDR
There is a high chance of Rails breaking alias_attribute
behaviour when its used to alias an association instead of an attribute. This is an opportunity to share reasons to why Rails may need to provide an alternative such as alias_association
Context
In Rails, we currently have the alias_attribute
method, which provides a way to alias an attribute. This is a powerful feature that allows developers to refer to an attribute by a different name, providing flexibility in how applications design models and interact with data.
However, there’s currently no built-in way to alias an association. This has led some applications to use alias_attribute
as a workaround to alias associations, which is not its intended use. This approach is not officially supported by Rails and could potentially break in future Rails updates with no deprecation cycle.
This lack of a built-in method to alias an association can lead to unnecessary code duplication and can make our models more difficult to understand and maintain.
Blockers
- This proposal currently lacks a strong use-case example. Without a solid case it is going to be hard to justify adding complexity that comes with the feature.
- Even with a strong need the implementation will need to ensure that it doesn’t hurt performance for applications that won’t use the feature
Proposal
I propose that we add a new method to Rails: alias_association
. This method would work similarly to alias_attribute
, but it would be used to alias an association instead of an attribute.
Here’s an example of how it might be used:
class Post < ApplicationRecord
belongs_to :author
alias_association :creator, :author
end
In this example, :creator
is an alias for the :author
association. This means that we can use post.creator to refer to the post’s author, just like we would use post.author
. Along with aliased accessor we will be able to query by the aliased association: Post.where(creator: Author.first).to_a
Acceptance Criteria
-
The
alias_association
method should create an alias for an association. -
The alias should work exactly like the original association. This means that we should be able to call all the same methods on the alias as we can on the original association.
-
The alias_association method should raise an error if the original association does not exist.
-
The alias_association method should not interfere with the original association. This means that we should still be able to use the original association even after creating an alias for it.
Fundamentals
The
alias_association
feature should at least provide the same capabilities asalias_attribute
when used against an association:- Provides getter and setter for the association
- Allows objects to be queried by the aliased association name in .where()
Nice to have:
- We need to make sure that the original foreign key is being set when association is assigned using an aliased setter
- Overall
alias_association
should also define an alias on the original foreign key attribute
Acceptance Criteria tests
This is a script to be used to verify the implementation of the feature against acceptance criteria. The list will be extended with more examples and eventually may be used as a set of tests in the Rails test suite.
# typed: true
# frozen_string_literal: true
require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gem "rails", github: "rails/rails", branch: "main"
gem "sqlite3"
end
require "active_record"
require "minitest/autorun"
require "logger"
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Schema.define do
create_table :posts, force: true do |t|
t.string(:title)
t.text(:body)
t.integer(:author_id)
end
create_table :authors, force: true do |t|
t.string(:name)
end
end
class Post < ActiveRecord::Base
belongs_to :author
# Uncomment to see some tests pass to show which use-cases are supported by `alias_attribute`
alias_attribute :creator, :author
# alias_association :creator, :author
end
class Author < ActiveRecord::Base
has_many :posts
# Uncomment to see some tests pass to show which use-cases are supported by `alias_attribute`
alias_attribute :publications, :posts
# alias_association :publication, :posts
end
bob = Author.create(name: "Bob")
alice = Author.create(name: "Alice")
bob.posts.create(title: "Bob's first post", body: "Bob says hello")
bob.posts.create(title: "Bob's second post", body: "Bob says hey")
alice.posts.create(title: "Alice's first post", body: "Alice says hello")
alice.posts.create(title: "Alice's second post", body: "Alice says hey")
class AliasAssociation < Minitest::Test
def setup
@bob = Author.find_by(name: "Bob")
@bobs_first_post = Post.find_by(title: "Bob's first post")
@bobs_second_post = Post.find_by(title: "Bob's second post")
@alice = Author.find_by(name: "Alice")
@alices_first_post = Post.find_by(title: "Alice's first post")
end
def test_belongs_to_getter
assert_equal(@bob, @bobs_first_post.author)
assert_equal(@bobs_first_post.author, @bobs_first_post.creator)
end
def test_belongs_to_setter
@bobs_first_post.creator = @alice
assert_equal(@alice, @bobs_first_post.author)
assert_equal(@alice, @bobs_first_post.creator)
end
def test_belongs_to_where
bobs_posts = @bob.posts.to_a
refute_empty(bobs_posts)
assert_equal(bobs_posts, Post.where(creator: @bob).to_a)
end
def test_has_many_getter
assert_equal([@bobs_first_post, @bobs_second_post], @bob.posts.to_a)
assert_equal(@bob.posts.to_a, @bob.publications.to_a)
end
def has_many_setter
post = Post.create(title: "Completely new post")
@bob.publications = [post]
@bob.save
bobs_posts = @bob.posts.to_a
assert_equal([post], @bob.posts)
assert_equal([post], @bob.publications)
end
def has_many_shovel
post = Post.create(title: "Additional post")
@bob.publications << post
assert_includes(@bob.posts.to_a, post)
assert_includes(@bob.publications.to_a, post)
end
def has_many_where
bobs_posts = @bob.posts.to_a
refute_empty(bobs_posts)
assert_equal(bobs_posts, Post.where(creator: @bob).to_a)
end
# TODO:
# Add tests for aliased `creator_id` foreign key
end
Request for Feedback
To ensure that alias_association
meets the needs of the Rails community, we’re interested in hearing about your potential use-cases for this feature. If you’ve ever found yourself wishing for a way to alias an association, or if you’ve used alias_attribute or other workarounds to achieve this, we want to hear from you.
Please share:
- Scenarios where you would use
alias_association
- How you’re currently handling these scenarios without
alias_association
- Any other thoughts or ideas you have about this feature
Your input will help us shape alias_association
into a feature that’s useful and intuitive for all Rails developers. Thank you in advance for your feedback!