ActiveRecord and methods on your model associations

I left PHP to program in Ruby a little over a year ago now, though technically I think I focused more on learning Rails during my first month. Like most who made the journey, I found Ruby a joy to work with, and Rails to have everything I needed to make great web apps. But to this day, the part of Rails I struggle the most with is ActiveRecord. There’s just so much magic in there …

I want to share a syntax I learned this evening for declaring methods on an association in ActiveRecord.

In my system, Users can post content either as themselves, or as approved Affiliate identity. But since other Users might also want to post as this Affiliate, I use a has_many :through relationship to manage this. Imagine an Affiliate is a local business or organization, and Users might belong to multiple instances of them. This may explain things better.

class Affiliate < ActiveRecord::Base
  has_many :affiliations
  has_many :users, :through => :affiliations
  attr_accessor :status
end

class Affiliation < ActiveRecord::Base
  belongs_to :affiliate
  belongs_to :user
  attr_accessor :status
end

class User < ActiveRecord::Base
  has_many :affiliations
  has_many :affiliates, :through => :affiliations
end

I needed to create a select menu for Users creating content in my system so they could select an optional alternate Affiliate identity to post as. The thing is … these relationships aren’t cut and dry. Both the Affiliate and the Affiliation need to have a status of ‘approved’ or that User cant post as that Affiliate identity.

That said, I needed to populate this select menu for the User. At first I started thinking like this:

class User < ActiveRecord::Base
  has_many :affiliations
  has_many :affiliates, :through => :affiliations

  def approved_affiliates
    affiliates.with_status(:approved).find(
      :all,
      :include => [:affiliations],
      :conditions => ["affiliations.status = 'approved'"]
    )
  end
end

@affiliates = current_user.approved_affiliates.collect {|p| [ p.name, p.id.to_s ] }

But looking in my User model, this didn’t seem very elegant and I wanted to find a way to keep the method definition attached to the code where I declare the association. I was already using a namedscope in my Affiliates model “withstatus(:approved)” (not shown here) and so I thought I might be able to combine it with a similar named scope on the join.

So, really what I needed was an association method to scope the Affiliate AND the Affiliation to have statuses of ‘approved’. Try as I might I couldn’t figure out the syntax for this. If you know how it can be done, please do share.

What I ended up with, and am quite happy with is …

class User < ActiveRecord::Base
  has_many :affiliations
  has_many :affiliates, :through => :affiliations do
    def with_combined_statuses(status)
      find(:all, :include => [:affiliations], :conditions => ["affiliations.status = ? AND affiliates.status = ?", status, status])
    end
  end
end

@affiliates = current_user.affiliates.with_combined_statuses('approved').collect {|p| [ p.name, p.id.to_s ] }

So, I learned you can pass a block to the association and declare methods there, as well, those methods can accept parameters.

Yay me

comments powered by Disqus