I am a Sr. Software Developer at Oracle Cloud. The opinions expressed here are my own and not necessarily those of my employer.
Polymorphic has_and_belongs_to_many relationships
When we first start designing Rails (or other framework) appliciations we encounter typical has_many and belongs_to relationships. User has_many Articles and Article belongs_to User.
class User
has_many :articles
end
class Article
belongs_to :user
end
A more complex design is has_and_belongs_to_many. User can have many Articles and Article can be owned by many Users. Using Mongoid we can model it like this:
class User
has_and_belongs_to_many :articles
end
# { "_id" : ObjectId("..."), "article_ids" : [ObjectId("...")], ... }
class Article
has_and_belongs_to_many :users
end
# { "_id" : ObjectId("..."), "user_ids" : [ObjectId("...")], ... }
Relationship IDs are stored in arrays on both sides. If you are using a SQL DB you will need to create article_user mapping table.
Another advanced use case is polymorphic associations. Here is a good RailsCast.
class User
has_many :articles, as: :author
end
class Organization
has_many :articles, as: :author
end
class Article
belongs_to :author, polymorphic: true
# has author_id and author_type (User or Organization)
end
I recently had to combine both polymorphic and has_and_belongs_to_many relationship between different models in our Mongo DB. The challenge is that when I store relationship IDs in array with the record, I cannot define the relationship_type. Solution is to go with mapping table and define polymorphic relationship there.
# extract common code for User and Organization to app/models/concerns/
module ArticleAuthorMap
extend ActiveSupport::Concern
included do
has_many :article_authors, as: :author
end
def articles
article_authors.map(&:article)
end
end
class User
include ArticleAuthorMap
end
class Organization
include ArticleAuthorMap
end
class Article
has_many :article_authors
def authors
article_authors.map(&:author)
end
end
class ArticleAuthor
belongs_to :article
belongs_to :author, polymorphic: true
end
You can do article.authors, user.articles and organization.articles. In retrospect the solution was fairly straightforward but I could not find any examples online so I decided to write my own blog post.