views:

187

answers:

2

So Rails doesn't have support for :through associations through a habtm relationship. There are plugins out there that will add this in for Rails 2.x, but I'm using Rails 3 / Edge, and only need the association for one particular model. So I thought I'd stump it out myself with the beauty that is Arel.

First, the models:

class CardSet < ActiveRecord::Base
  has_and_belongs_to_many :cards, :uniq => true
end

class Card < ActiveRecord::Base
  has_and_belongs_to_many :card_sets, :uniq => true
  has_many :learnings, :dependent => :destroy
end

class Learning < ActiveRecord::Base
  belongs_to :card
end

I want to get all the learnings that belong to a particular card set, :through => cards.

This is what I have so far in my CardSet model:

def learnings
  c = Table(Card.table_name)
  l = Table(Learning.table_name)
  j = Table(self.class.send(:join_table_name, Card.table_name, CardSet.table_name))
  learning_sql = l.where(l[:card_id].eq(c[:id])).where(c[:id].eq(j[:card_id])).join(j).on(j[:card_set_id].eq(self.id)).to_sql
  Learning.find_by_sql(learning_sql)
end

which gives me (damn, Arel is beautiful!):

SELECT     `learnings`.`id`, `learnings`.`card_id`, `learnings`.`user_id`, `learnings`.`ef`, `learnings`.`times_seen`, `learnings`.`next_to_be_seen`, `learnings`.`interval`, `learnings`.`reps`, `learnings`.`created_at`, `learnings`.`updated_at`, `card_sets_cards`.`card_set_id`, `card_sets_cards`.`card_id` FROM       `learnings` INNER JOIN `card_sets_cards` ON `card_sets_cards`.`card_set_id` = 1 WHERE     `learnings`.`card_id` = `cards`.`id` AND `cards`.`id` = `card_sets_cards`.`card_id`

which is oh so close to what I'm aiming for - just needs to add in the cards table in the FROM part of the statement.

And there's my question: I've looked through the Arel source, and for the life of me I can't figure out how to add in another table.

As a sidenote, is there a better way to run the result from Arel (which usually returns an Arel::Relation, which I don't want) through ActiveRecord, other than rendering to sql and running the query with find_by_sql as I'm doing?

A: 

I'm not familiar with Arel, but won't it handle:

l.includes(:card)
AKWF
That will pull all learnings, rather than just the ones associated with cards that are associated with the card_set (a.k.a., nested habtm).
brady8
A: 

This is easier than what I wrote above, after I learned how Rails encapsulates Arel so you don't need to build the query directly.

If you're trying to stub nested habtm in the CardSet model, a.k.a.:

has_many :learnings, :through => :cards

then you can use this to simulate the behaviour:

class CardSet < ActiveRecord::Base

  has_and_belongs_to_many :cards, :uniq => true

  def learnings
    Learning.joins(:card).where(:cards => { :id => self.card_ids })
  end
end

then a query like this will work:

CardSet.first.learnings
brady8