views:

193

answers:

2

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.

A: 
# Author model
has_many :subscriptions_to, :class_name => "Subscription", :foreign_key => "subscribed_to"
has_many :subscribed_to_author, :through => :subscriptions_to, :source => :author

As far as I know - it works! :)

klew
Unfortunately it didn't. But it gave me the right direction and the following code worked#Author modelhas_many :subscriptions_to, :class_name => "Subscription", :foreign_key => "subscribed_to" has_many :subscribed_to_author, :through => :subscriptions_to, :source => :authorSo I accept this answer, but you have to edit it to be correct first :)Thanks!
Sergii Vozniuk
It's always my big pleasure to fix my aswers :)
klew
+2  A: 

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 -%>
Eric Hill
Thank you! That was very helpful. Now I've found two methods for solving the problem.
Sergii Vozniuk
@Sergii, That's what I was talking about in my answer, which I deleted as Eric's is way superior.
Pavel Shved