Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create "two-side" many-to-many relationships in Rails?

Suppose we have a photography site. Any author can subscribe to receive updates from any other author. Obviously if author A is subscribed to author B that doesn't mean that B is subscribed to A. So we build models

class Author < ActiveRecord::Base
  has_many :subscriptions
  has_many :subscribed_by_author, :through => :subscriptions, :source => :subscribed_to
end

class Subscription < ActiveRecord::Base
  belongs_to :author
  belongs_to :subscribed_to, :class_name => "Author", :foreign_key => "subscribed_to"
end

This way we can use

  1. some_author.subscribed_by_author -- the list of the authors to whom some_author is subscribed.
  2. For any subscription we can know both ends (who is subscribed to whom)

But the question is how to get the list of people subscribed to some author using only rails (not using plain SQL) i.e get the answer to :"Who is subscribed to some_author?"

Question: is there any ability in Rails to get the relationship working both sides i.e. not only writing some_author.subscribed_BY_author but having some_author_subscribed_TO_author? If there is one, then what is it?

P.S. Obvious solution is to

  1. Change the database design, adding a column named "direction"
  2. Create 2 records each time a subscription is created
  3. Add to the author model

    has_many :subscribed_BY_author, :through => :subscriptions, :source => :subscribed_to, :conditions => "direction = 'by'"

    has_many :subscribed_TO_author, :through => :subscriptions, :source => :subscribed_to, :conditions => "direction = 'to'"

But i wonder if there is a solution without changing the database design.

like image 873
Sergii Vozniuk Avatar asked Nov 06 '22 16:11

Sergii Vozniuk


1 Answers

I'd use plain HABTM for something simple like this, but you're going to need a join table no matter what.

create_table :subscriptions do |t|
  t.column :author_id, :integer
  t.column :subscriber_id, :integer
end

Point Author to it:

class Author < ActiveRecord::Base
  has_and_belongs_to_many :subscribers
    :class_name => "Author",
    :join_table => "subscriptions",
    :association_foreign_key => "subscriber_id"

  def subscriptions # "subscribers" is already included above
    self.subscribers.find(:all, :subscriber_id=>author.id) # hopefully not too 
  end                                                      # much SQL
end

If you're really committed to your method names:

  def subscribed_to_author
    subscribers
  end

  def subscribed_by_author(author)
    self.subscribers.find(:all, :subscriber_id=>author.id)
  end

Create some connections (I'd make SubscriptionsController to be RESTy)

SubscriptionsController < ApplicationController
  def create
    @author = Author.find(params[:author_id] # author to be subscribed to
    @user = current_user # user clicking the "subscribe" button

    @author.subscribers << @user # assuming authors should only 
    @author.save                 # be able to subscribe themselves
  end
end

Display names, or whatever

@author.subscribers.each do |s|
  s.name
end
# ...or...and...also...
<%= render :partial => @author.subscribers -%>
<%= render :partial => @author.subscriptions -%>
like image 173
Eric Avatar answered Nov 12 '22 14:11

Eric