SimplyRichAssociation
This plugin is based on Chad Fowler's Rails Recipe #18 :
Self-referential Many-to-Many Relationships in Rails Recipe book.
This plugin simplifies self referential many to many relationship. It manages the
bidirectional link creation and deletion automatically.
For example:
class Person < ActiveRecord::Base
has_and_belongs_to_many :friends,
:class_name => "Person",
:join_table => "friends_people",
:association_foreign_key => "friend_id",
:foreign_key => "person_id",
:after_add => :be_friendly_to_friend,
:after_remove => :no_more_mr_nice_guy
def be_friendly_to_friend(friend)
friend.friends << self unless friend.friends.include?(self)
end
def no_more_mr_nice_guy(friend)
friend.friends.delete(self) rescue nil
end
end
becomes:
class Person < ActiveRecord::Base
has_self_referential_many_to_many :friends
end
In the script/console you can see:
~/work/plugins/sra > script/console
Loading development environment.
>> p1 = Person.create :name => "Chad"
=> #<Person:0x31dd5c8 @errors=#<ActiveRecord::Errors:0x31d4644
@errors={}, @base=#<Person:0x31dd5c8 ...>, new_recordfalse,
attributes{"name"=>"Chad", "id"=>48}, new_record_before_savetrue
>> p2 = Person.create :name => "Tintin"
=> #<Person:0x31cb92c @errors=#<ActiveRecord::Errors:0x31cafa4
@errors={}, @base=#<Person:0x31cb92c ...>, new_recordfalse,
attributes{"name"=>"Tintin", "id"=>49}, new_record_before_savetrue
>> p1.friends << p2
=> [#<Person:0x31cb92c
@errors=#<ActiveRecord::Errors:0x31cafa4 @errors={},
@base=#<Person:0x31cb92c ...>, new_recordfalse,
friends[#<Person:0x31dd5c8
@errors=#<ActiveRecord::Errors:0x31d4644 @errors={},
@base=#<Person:0x31dd5c8 ...>, new_recordfalse, friends[....],
attributes{"name"=>"Chad", "id"=>48},
new_record_before_savetrue], attributes{"name"=>"Tintin",
"id"=>49}, new_record_before_savetrue]
>> p1.friends.size
=> 1
>> p2.friends.size
=> 1
It also provides syntactic sugar for ActiveRecord has_many :through macro:
For instance if you have a migration as:
class Subscription < ActiveRecord::Migration
def self.up
create_table :subscriptions do |t|
t.column :reader_id, :integer
t.column :magazine_id, :integer
t.column :last_renewal_on, :date
t.column :length_in_issues, :integer
end
create_table :magazines do |t|
t.column :title, :string
end
create_table :readers do |t|
t.column :name, :string
end
end
def self.down
end
end
and the models as:
class Subscription < ActiveRecord::Base
belongs_to :reader
belongs_to :magazine
end
class Reader < ActiveRecord::Base
has_many :subscriptions
has_many :magazines, :through => :subscriptions
end
class Magazine < ActiveRecord::Base
has_many :subscriptions
has_many :readers, :through => :subscriptions
end
using this plugin, your models become:
class Subscription < ActiveRecord::Base
belongs_to :reader
belongs_to :magazine
end
class Reader < ActiveRecord::Base
has_many_through :magazines, :subscriptions
end
class Magazine < ActiveRecord::Base
has_many_through :readers, :subscriptions
end
This example is based on Rails Recipe #22, Many-to-Many Relationships with
Extra Data of Chad Fowler's Rails Recipes book.
~/work/plugins/test/hmt > script/console
Loading development environment.
>> m = Magazine.create :title => "Ruby Illustrated"
=> #<Magazine:0x31d9e8c
@errors=#<ActiveRecord::Errors:0x31d0f08 @errors={},
@base=#<Magazine:0x31d9e8c ...>, new_recordfalse,
attributes{"title"=>"Ruby Illustrated", "id"=>2},
new_record_before_savetrue
>> r = Reader.create :name => "TinTin"
=> #<Reader:0x31ba3e8 @errors=#<ActiveRecord::Errors:0x31b89bc
@errors={}, @base=#<Reader:0x31ba3e8 ...>, new_recordfalse,
attributes{"name"=>"TinTin", "id"=>2}, new_record_before_savetrue
>> s = Subscription.create(:last_renewal_on => Date.today, :length_in_issues => 6)
=> #<Subscription:0x357846c
@errors=#<ActiveRecord::Errors:0x3571860 @errors={},
@base=#<Subscription:0x357846c ...>>, @new_record=false,
@attributes={"last_renewal_on"=>#<Date: 4908351/2,0,2299161>,
"id"=>2, "length_in_issues"=>6, "reader_id"=>nil,
"magazine_id"=>nil}>
>> m.subscriptions << s
=> [#<Subscription:0x357846c
@errors=#<ActiveRecord::Errors:0x3571860 @errors={},
@base=#<Subscription:0x357846c ...>>, @new_record=false,
@attributes={"last_renewal_on"=>#<Date: 4908351/2,0,2299161>,
"id"=>2, "length_in_issues"=>6, "reader_id"=>nil,
"magazine_id"=>2}>]
>> r.subscriptions << s
=> [#<Subscription:0x357846c
@errors=#<ActiveRecord::Errors:0x3571860 @errors={},
@base=#<Subscription:0x357846c ...>>, @new_record=false,
@attributes={"last_renewal_on"=>#<Date: 4908351/2,0,2299161>,
"id"=>2, "length_in_issues"=>6, "reader_id"=>2,
"magazine_id"=>2}>]
>> s.save
=> true
>> m.readers
=> [#<Reader:0x353c1c4 @attributes={"name"=>"TinTin", "id"=>"2"}]
>> r.magazines
=> [#<Magazine:0x35378a4 @attributes={"title"=>"Ruby Illustrated", "id"=>"2"}]
>>
CREDITS: Thanks to Chad Fowler for his help during the development of this plugin.