ActsAsJoinable
Like has_many_polymorphs but easier. Can handle double polymorphic associations with single table inheritance from the join model.
Install
sudo gem install acts-as-joinable
Usage
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 Relationship
model:
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.
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
Post
isparent
ofImage
, since you usually attach an Image to a Post (not the other way around), soImage
ischild
ofPost
. 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
Users
of aGroup
intoRoles
. - 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
http://rors.org/2008/10/26/dont-escape-in-strings
- Handle case where one side has_one and the other has_many