Search code examples
ruby-on-railsactiverecordruby-on-rails-3.1

ActiveRecords returning model object from a join table


I have the following DB for a simple flash cards example i'm building:

create_table "card_associations", :force => true do |t|
  t.integer  "card_id"
  t.integer  "deck_id"
end

create_table "cards", :force => true do |t|
  t.string   "question"
  t.string   "answer"
end

create_table "decks", :force => true do |t|
  t.string   "name"
  t.string   "description"
end

I've setup the has_many through relationships in all my models. Now I want to be able to return a list of all cards from the join table, given the deck id. If I run the query:

CardAssociation.find_by_deck_id(3).card

It retruns the first card with the deck_id of 3. But when I try.

CardAssociation.find_all_by_deck_id(3).card

I get the error

NoMethodError: undefined method `card' for #

Can someone help me with this? I feel like i'm making a very simple mistake.

Thanks for the help


Solution

  • The find_all_* methods always return an Array (which could be empty)!

    CardAssociation.find_all_by_deck_id(3) # => Array of results
    CardAssociation.find_all_by_deck_id(3).first # => first result of the Array or nil if no result
    

    I advise you to first read the Ruby on Rails Style Guide, and then use the Rails3 way of finding object with ActiveRecord:

    CardAssociation.where(:deck_id => 3) # => Array of results
    CardAssociation.where(:deck_id => 3).first # => first result of the Array if exists
    

    In your case, a scope can be set up on the Card model:

    You said: "Now I want to be able to return a list of all cards from the join table, given the deck id"

    class Card < ActiveRecord::Base
      scope :for_deck, lambda { |deck| joins(:card_associations).where('card_associations.deck_id = ?', deck.try(:id) || deck) }
    end
    

    This scope can be used like following:

    Card.for_deck(deck) # returns an Array of Card objects matching the deck.id
    

    As defined in the scope, the parameter of Card.for_deck(deck) can be a deck object or a deck_id (type Integer)

    Hope this helped!