Like has_many_polymorphs but easier. Can handle double polymorphic associations with single table inheritance from the join model.
sudo gem install acts-as-joinable
Dry Assocations with Zero Dependencies
Here's what you would write:
class Content < ActiveRecord::Base joins :assets joins :images joins_one :cover_image, :source => :image end class Page < Content joins :children, :as => :parent, :source => :content joins :parents, :as => :child, :source => :content end class Post < Content joins :parents, :as => :child, :source => :page end class Asset < ActiveRecord::Base joinable end class Image < Asset end
Here's how you'd use it:
page = Page.create!(:title => "Home Page") post = Post.create!(:title => "My first blog post") image = Image.create!(:src => "http://imgur.com/123123.png") page.children << post post.cover_image = image assert_equal post, page.children.first assert_equal image, post.images.first assert_equal image, post.assets.first
You can also create a simple group membership system no problem:
class User < ActiveRecord::Base joins :groups, :as => :child, :context => :membership end class Group < ActiveRecord::Base joins :members, :context => :membership, :source => :user joins_one :admin, :context => :membership, :value => :admin, :source => :user joins :board_of_directors, :context => :membership, :value => :board, :source => :user end basic_member = User.create!(:position => "I'm an employee") board_member = User.create!(:position => "I'm on the board") company = Group.create!(:name => "A Company") company.members << basic_member company.board_of_directors << board_member assert_equal 2, company.members.length assert_equal 1, company.board_of_directors
What's it doing?
First, it has a generic join model. All join models are the same in the end, so there is no need to create extra tables for each. Maybe when you get 10,000,000 records you'll need to start creating tables for specific joins, but you don't need that by default. It increases the complexity of your application unnecessarily which makes it harder to extend and manage.
Instead, this creates a single join model that will solve for most of your cases (if there is a case it doesn't solve for, I'd love to know, it solves all of mine).
Here's the table for the built-in
create_table :relationships do |t| t.references :parent, :polymorphic => true t.references :child, :polymorphic => true t.string :context t.string :value t.integer :position t.timestamps end
The features are:
- Double sided polymorphic associations. Which means you can tie any object to any other object.
- Built-in relationship directionality, similar to a Directed Acyclic Graph. So you can say the
Image, since you usually attach an Image to a Post (not the other way around), so
Post. This means you have some sort of built in hierarchy.
Context. You can create many-to-many relationships between the same models and call them different things. This is roughly equivalent to creating STI join models. This is useful for creating something like organizing
- Position. You can sort the objects by relationship in primitive ways.
You can always add columns to the relationship table, but the foundation is set.
copyright @viatropos 2010
- Handle case where one side has_one and the other has_many