has_many through sql - ruby-on-rails-3

Well my models are User, Exercise and Writing.
class User < ActiveRecord::Base
has_many :exercises, :through => :writings
has_many :writings
end
class Exercise < ActiveRecord::Base
has_many :users, :through => :writings
has_many :writings
end
class Writing < ActiveRecord::Base
belongs_to :user
belongs_to :exercise
attr_accessible :writing_date, :exercise_id
end
my db/schema.rb
ActiveRecord::Schema.define(:version => 20120517142448) do
create_table "exercises", :force => true do |t|
t.string "etitle"
t.text "ebody"
t.decimal "average", :precision => 3, :scale => 1
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "users", :force => true do |t|
t.string "email", :default => "", :null => false
t.datetime "created_at"
t.datetime "updated_at"
t.integer "user_kind", :default => 0
end
create_table "writings", :force => true do |t|
t.integer "user_id"
t.integer "exercise_id"
t.date "writing_date"
t.datetime "created_at"
t.datetime "updated_at"
end
the join table is Writing. I try to show on exercise view (app/view/exercises/index.html.erb) for each exercise_id her writing_date. Each user_id has many exercise_id through Writing table. Each record on Writing table has writing_id, user_id, exercise_id and writing_date. In console for specifically exercise_id I made it like this:
1.9.2p290 :001 > #exercise = Exercise.find_by_id(9)
=> #<Exercise id: 9, etitle: "PLS51-2012-E01", ebody: "q", average: #<BigDecimal:53eeba8,'0.1E2',9(18)>, created_at: "2012-05-20 14:31:07", updated_at: "2012-05-20 14:34:27", askisi_file_name: nil, askisi_content_type: nil, askisi_file_size: nil, askisi_updated_at: nil>
1.9.2p290 :002 > #exercise.writings
=> [#<Writing id: 6, user_id: 1, exercise_id: 9, writing_date: "2012-06-29", created_at: "2012-05-20 14:36:45", updated_at: "2012-05-20 14:36:45">, #<Writing id: 12, user_id: 7, exercise_id: 9, writing_date: "2012-06-20", created_at: "2012-05-20 14:40:12", updated_at: "2012-05-20 14:40:12">, #<Writing id: 13, user_id: 7, exercise_id: 9, writing_date: "2012-05-02", created_at: "2012-05-20 15:41:54", updated_at: "2012-05-20 15:41:54">]
1.9.2p290 :003 > #exercise.writings.order("writing_date ASC")
=> [#<Writing id: 13, user_id: 7, exercise_id: 9, writing_date: "2012-05-02", created_at: "2012-05-20 15:41:54", updated_at: "2012-05-20 15:41:54">, #<Writing id: 12, user_id: 7, exercise_id: 9, writing_date: "2012-06-20", created_at: "2012-05-20 14:40:12", updated_at: "2012-05-20 14:40:12">, #<Writing id: 6, user_id: 1, exercise_id: 9, writing_date: "2012-06-29", created_at: "2012-05-20 14:36:45", updated_at: "2012-05-20 14:36:45">]
1.9.2p290 :004 > #exercise.writings.order("writing_date ASC").last
=> #<Writing id: 6, user_id: 1, exercise_id: 9, writing_date: "2012-06-29", created_at: "2012-05-20 14:36:45", updated_at: "2012-05-20 14:36:45">
Do I need scope?
Thanks in advance!

Finaly the solution for my problem that can help someone with the same situation is simple. File app/view/exercises/index.html.erb
Code:
<%= exercise.writings.last.try(:writing_date) %>

Related

How to form a rails query to grab all the discussion posts a particular user has made?

I am using rails 5.0.0
I want to make a query that will let me display all the posts the current user has made. The two relevant tables I have are:
create_table "discussions", force: :cascade do |t|
t.string "title"
t.text "content"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
t.integer "channel_id"
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
....
t.string "unconfirmed_email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "username"
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
t.index ["username"], name: "index_users_on_username", unique: true
end
create_table "replies", force: :cascade do |t|
t.text "reply"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "discussion_id"
t.integer "user_id"
end
and the relationships are as follows:
class Discussion < ApplicationRecord
belongs_to :channel
belongs_to :user
has_many :replies, dependent: :destroy
has_many :users, through: :replies
class Reply < ApplicationRecord
belongs_to :discussion
belongs_to :user
class User < ApplicationRecord
rolify
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:confirmable
has_many :notifications, foreign_key: :recipient_id
has_many :discussions, dependent: :destroy
has_many :channels, through: :discussions
in my discussions_controller.rb file i have the following line
#discussions = Discussion.includes(:users).where('users.id' => current_user).order('discussions.created_at desc')
and in my view file I have
<% #discussions.each do |discussion| %>
...
<% end %>
I expect there to be a few entries since I have created them, however no entries are displayed at all. This is what is printed in the terminal window
Processing by DiscussionsController#index as HTML
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT ? [["id", 1], ["LIMIT", 1]]
Rendering discussions/index.html.erb within layouts/application
SQL (0.5ms) SELECT "discussions"."id" AS t0_r0, "discussions"."title" AS t0_r1, "discussions"."content" AS t0_r2, "discussions"."created_at" AS t0_r3, "discussions"."updated_at" AS t0_r4, "discussions"."user_id" AS t0_r5, "discussions"."channel_id" AS t0_r6, "users"."id" AS t1_r0, "users"."email" AS t1_r1, "users"."encrypted_password" AS t1_r2, "users"."reset_password_token" AS t1_r3, "users"."reset_password_sent_at" AS t1_r4, "users"."remember_created_at" AS t1_r5, "users"."sign_in_count" AS t1_r6, "users"."current_sign_in_at" AS t1_r7, "users"."last_sign_in_at" AS t1_r8, "users"."current_sign_in_ip" AS t1_r9, "users"."last_sign_in_ip" AS t1_r10, "users"."confirmation_token" AS t1_r11, "users"."confirmed_at" AS t1_r12, "users"."confirmation_sent_at" AS t1_r13, "users"."unconfirmed_email" AS t1_r14, "users"."created_at" AS t1_r15, "users"."updated_at" AS t1_r16, "users"."username" AS t1_r17 FROM "discussions" LEFT OUTER JOIN "replies" ON "replies"."discussion_id" = "discussions"."id" LEFT OUTER JOIN "users" ON "users"."id" = "replies"."user_id" WHERE "users"."id" = 1 ORDER BY discussions.created_at desc
Rendered shared/_discussions.html.erb (8.5ms)
Channel Load (0.3ms) SELECT "channels".* FROM "channels" ORDER BY created_at desc
Role Load (0.3ms) SELECT "roles".* FROM "roles" INNER JOIN "users_roles" ON "roles"."id" = "users_roles"."role_id" WHERE "users_roles"."user_id" = ? AND (((roles.name = 'admin') AND (roles.resource_type IS NULL) AND (roles.resource_id IS NULL))) [["user_id", 1]]
Rendered discussions/_sidebar.html.erb (34.1ms)
Rendered discussions/index.html.erb within layouts/application (46.1ms)
Completed 200 OK in 245ms (Views: 165.7ms | ActiveRecord: 6.7ms)
If I use
#discussions = Discussion.includes(:users).order('discussions.created_at desc')
Then all the discussion posts display as they normally would as if the .includes statement was not there.
So, how can I change my query to list out the discussions made by the current user?
Update As per Shiko's comment, here is the output given with his input in the rails console
2.3.0 :001 > User.find(1).discussions
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
Discussion Load (0.5ms) SELECT "discussions".* FROM "discussions" WHERE "discussions"."user_id" = ? [["user_id", 1]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Discussion id: 1, title: "Test", content: "alkjdflk slkfj ", created_at: "2018-04-08 22:40:06", updated_at: "2018-04-08 22:40:06", user_id: 1, channel_id: nil>, #<Discussion id: 2, title: "Fake Bakesale", content: "Come buy cookies", created_at: "2018-04-08 23:29:17", updated_at: "2018-04-08 23:29:17", user_id: 1, channel_id: 1>, #<Discussion id: 3, title: "Fake Bakesale", content: "Come buy cookies", created_at: "2018-04-08 23:30:18", updated_at: "2018-04-08 23:30:18", user_id: 1, channel_id: 1>, #<Discussion id: 4, title: "Meeting today", content: "Come to the meeting", created_at: "2018-04-08 23:35:59", updated_at: "2018-04-08 23:35:59", user_id: 1, channel_id: 1>, #<Discussion id: 5, title: "New post", content: "asdf ", created_at: "2018-04-15 21:50:20", updated_at: "2018-04-15 21:50:20", user_id: 1, channel_id: 2>]>
2.3.0 :002 >
First thing, if you run below command in rails console, you should get an below expected error :
#discussions = Discussion.includes(:users).where('users.id' => current_user).order('discussions.created_at desc')
Expected error:
ActiveRecord::ConfigurationError: Can't join 'Discussion' to
association named 'users'; perhaps you misspelled it?
To fix this issue, you have to use :user instead of :users as below, simply because each discussion belongs to one user and not many :
#discussions = Discussion.includes(:user).where('users.id' => current_user).order('discussions.created_at desc')
There is a more a clean one way to get the current user discussions, using below:
Controller:
#user = User.find(curren_user_id)
ERB file:
<% #user.discussions.each do |discussion| %>
.....
<% end %>
You can do this:
#discussions = Discussion.includes(:user).
where(users: { id: current_user.id }).
order("discussions.created_at desc")
If you don't need to reference the user attributes in your view, you can also do this, which avoids the join altogether:
#discussions = Discussion.
where(user_id: current_user.id).order(created_at: :desc)

self-referential has_many trouble

I getting troubles with a self referential table.
I got an orb model which can hold planets, stars and moons. I want to tell that one thing "orbit" another
i look at the rails guide, but i coulnd get it working
My model:
class Orb < ActiveRecord::Base
belongs_to :orb_type
has_and_belongs_to_many :books
belongs_to :orbit, :class_name => "Orb"
has_many :orbs, :class_name => "Orb", :foreign_key => "orb_id"
attr_accessible :descr, :nome, :orb_type_id, :book_ids, :orb_id
validates :nome, uniqueness: true, presence: true
end
I think i am using bad relations names (maybe in the wrong way around)
1.9.3-p448 :002 > earth = Orb.find(1)
Orb Load (0.2ms) SELECT "orbs".* FROM "orbs" WHERE "orbs"."id" = ? LIMIT 1 [["id", 1]]
=> #<Orb id: 1, nome: "Terra", descr: "123123", orb_type_id: 1, created_at: "2013-09-25 14:53:35", updated_at: "2013-09-25 14:57:40", orb_id: nil>
1.9.3-p448 :003 > moon = Orb.find(2)
Orb Load (0.2ms) SELECT "orbs".* FROM "orbs" WHERE "orbs"."id" = ? LIMIT 1 [["id", 2]]
=> #<Orb id: 2, nome: "Lua", descr: "asd", orb_type_id: 2, created_at: "2013-09-25 14:53:46", updated_at: "2013-09-25 14:55:31", orb_id: nil>
1.9.3-p448 :004 > sun = Orb.find(3)
Orb Load (0.2ms) SELECT "orbs".* FROM "orbs" WHERE "orbs"."id" = ? LIMIT 1 [["id", 3]]
=> #<Orb id: 3, nome: "Sol", descr: "asd", orb_type_id: 3, created_at: "2013-09-25 14:53:55", updated_at: "2013-09-25 14:53:55", orb_id: nil>
1.9.3-p448 :006 > moon.orbit=earth
=> #<Orb id: 1, nome: "Terra", descr: "123123", orb_type_id: 1, created_at: "2013-09-25 14:53:35", updated_at: "2013-09-25 14:57:40", orb_id: nil>
1.9.3-p448 :007 > earth.orbit=sun
=> #<Orb id: 3, nome: "Sol", descr: "asd", orb_type_id: 3, created_at: "2013-09-25 14:53:55", updated_at: "2013-09-25 14:53:55", orb_id: nil>
1.9.3-p448 :008 > earth
=> #<Orb id: 1, nome: "Terra", descr: "123123", orb_type_id: 1, created_at: "2013-09-25 14:53:35", updated_at: "2013-09-25 14:57:40", orb_id: nil>
1.9.3-p448 :009 > sun
=> #<Orb id: 3, nome: "Sol", descr: "asd", orb_type_id: 3, created_at: "2013-09-25 14:53:55", updated_at: "2013-09-25 14:53:55", orb_id: nil>
1.9.3-p448 :010 > moon
=> #<Orb id: 2, nome: "Lua", descr: "asd", orb_type_id: 2, created_at: "2013-09-25 14:53:46", updated_at: "2013-09-25 14:55:31", orb_id: nil>
in the end nothing get associated, the FK still nill.
The collun orb_id was added latter on the model. I setup a migration and added it in the model. I don't think it could be related to my problem...
EDIT:
Now everything is even odd. i change my model to:
class Orb < ActiveRecord::Base
belongs_to :orb_type
has_and_belongs_to_many :books
belongs_to :orbit, :class_name => "Orb"
has_many :orbits, :class_name => "Orb", :foreign_key => "orb_id"
attr_accessible :descr, :nome, :orb_type_id, :book_ids, :orb_id
validates :nome, uniqueness: true, presence: true
end
In rails console (rails c) i try:
1.9.3-p448 :008 > earth = Orb.find(1)
Orb Load (0.2ms) SELECT "orbs".* FROM "orbs" WHERE "orbs"."id" = ? LIMIT 1 [["id", 1]]
=> #<Orb id: 1, nome: "Terra", descr: "", orb_type_id: 1, created_at: "2013-09-25 17:51:26", updated_at: "2013-09-25 18:16:58", orb_id: 3>
1.9.3-p448 :009 > earth.orbit
=> nil
1.9.3-p448 :010 > earth.orbits
Orb Load (0.3ms) SELECT "orbs".* FROM "orbs" WHERE "orbs"."orb_id" = 1
=> [#<Orb id: 2, nome: "Lua", descr: "", orb_type_id: 2, created_at: "2013-09-25 17:51:40", updated_at: "2013-09-25 18:17:31", orb_id: 1>]
1.9.3-p448 :011 >
What the heck? orbits seen to return what i want, but it i try to use it:
1.9.3-p448 :004 > earth.orbits.nome
NoMethodError: Orb Load (0.4ms) SELECT "orbs".* FROM "orbs" WHERE "orbs"."orb_id" = 1
undefined method `nome' for #<ActiveRecord::Relation:0x00000003d593c0>
from /usr/local/rvm/gems/ruby-1.9.3-p448/gems/activerecord-3.2.12/lib/active_record/relation/delegation.rb:45:in `method_missing'
from /usr/local/rvm/gems/ruby-1.9.3-p448/gems/activerecord-3.2.12/lib/active_record/associations/collection_proxy.rb:100:in `method_missing'
from (irb):4
from /usr/local/rvm/gems/ruby-1.9.3-p448/gems/railties-3.2.12/lib/rails/commands/console.rb:47:in `start'
from /usr/local/rvm/gems/ruby-1.9.3-p448/gems/railties-3.2.12/lib/rails/commands/console.rb:8:in `start'
from /usr/local/rvm/gems/ruby-1.9.3-p448/gems/railties-3.2.12/lib/rails/commands.rb:41:in `<top (required)>'
from script/rails:6:in `require'
from script/rails:6:in `<main>'
Technically, it's not great that you have planets, stars, and moons all within the same model. They all behave differently and should each warrant their own model. E.g. a star will never orbit a planet, and a planet will never orbit a moon (by definition). By structuring your database the way you have, you're setting yourself up for corrupted data, which will always happen if you allow it to.
If you must have them all within the Orb model, I would recommend 2 models: 1 for the Orbs (stars, planets, moons), and 1 for the Orbits. The Orbit class would essentially be a table like so:
Orbit model:
| id | orbited_id | orbiter_id |
--------------------------------
| 0 | planet_id | moon_id |
| 1 | star_id | planet_id |
| 2 | planet_id | moon_id |
| ...| etc | etc |
And then you can set up the association in orb.rb:
has_and_belongs_to_many :orbits,
class_name: 'Orb',
join_table: :orbits,
foreign_key: :orbited_id,
association_foreign_key: :orbiter_id,
uniq: true
This will allow you to do things like
>> sun = Orb.find(1)
>> sun.orbits
=> [ <Orb mercury>, <Orb venus>, ..., <Orb pluto> ]
i figure it out. I should make the rom in the table withe the name of relation (orbit_id) instead of doing it the table name (orb_id)
Fixing that everything started to work nicely :)
has_many :orbters, class_name: "Orb", foreign_key: "orbit_id"
belongs_to :orbit, class_name: "Orb"

Adding attributes to a join model during assignment to parent model

I have the following setup:
Schema.rb
ActiveRecord::Schema.define(version: 20130923235150) do
create_table "addresses", force: true do |t|
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "user_addresses", force: true do |t|
t.integer "user_id"
t.integer "address_id"
t.string "purpose"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "users", force: true do |t|
t.datetime "created_at"
t.datetime "updated_at"
end
end
User.rb:
class User < ActiveRecord::Base
has_one :user_address
has_one :primary_shipping_address, through: :user_address, class_name: :UserAddress, source: :address
has_one :primary_billing_address, through: :user_address, class_name: :UserAddress, source: :address
end
Address.rb:
class Address < ActiveRecord::Base
has_one :user_address
has_one :primary_shipping_user, through: :user_address, class_name: :UserAddress, source: :user
has_one :primary_billing_user, through: :user_address, class_name: :UserAddress, source: :user
end
UserAddress.rb:
class UserAddress < ActiveRecord::Base
belongs_to :user
belongs_to :address
end
When someone does user.primary_billing_address = address, I want the join model instance to have "billing" set as its purpose. Similarly with shipping and "shipping". Ex.
irb(main):013:0> u = User.new
=> #<User id: nil, created_at: nil, updated_at: nil>
irb(main):014:0> a = Address.create
=> #<Address id: 3, created_at: "2013-09-24 00:13:07", updated_at: "2013-09-24 00:13:07">
irb(main):015:0> u.primary_billing_address = a
=> #<Address id: 3, created_at: "2013-09-24 00:13:07", updated_at: "2013-09-24 00:13:07">
irb(main):016:0> u.save!
=> true
irb(main):017:0> u.user_address
=> #<UserAddress id: 2, user_id: 3, address_id: 3, purpose: nil, created_at: "2013-09-24 00:13:18", updated_at: "2013-09-24 00:13:18">
(not what I want... purpose should be "billing")
How can I do this such that it works for new AND persisted records?. I've come up with solutions that are 90% there, but break on some random spec due to an edge case my approach didn't catch.
The trickiest part to work around is how association= behaves: on new records, it queues the association for assignment through the join model.
PS: I left out the conditionals on the has_one relationships that I'd use to get the address I want. I think this issue is independent of that.
First, the associations are a bit off, both primary_shipping_address and primary_billing_address will return same address. You can change it to
class User < ActiveRecord::Base
has_many :user_addresses # user can have multiple addresses one for shipping and one for billing
has_one :primary_shipping_address,
through: :user_address, class_name: :UserAddress,
source: :address, :conditions => ['user_addresses.purpose = ?','shipping']
has_one :primary_billing_address,
through: :user_address, class_name: :UserAddress,
source: :address, :conditions => ['user_addresses.purpose = ?','billing']
end
To save the purpose while saving the address, there are two options.
Option 1 : Override the default association= method
# alias is needed to refer to original method
alias_method :orig_primary_billing_address=, :primary_billing_address=
def primary_billing_address=(obj)
self.orig_primary_billing_address = obj
self.user_addresses.where("address_id = ?", obj.id).update_attribute(:purpose, 'billing')
end
# repeat for shipping
Option 2 : Create a custom method (I prefer this as it is cleaner and DRY)
def save_address_with_purpose(obj,purpose)
self.send("primary_#{purpose}_address=", obj)
self.user_addresses.where("address_id = ?", obj.id).update_attribute(:purpose, purpose)
end

creating with a belongs_to has_many association

I'm getting the error unknown attribute: user_id durring execution of #user.posts.create in my specs
User Class
class User < ActiveRecord::Base
# new columns need to be added here to be writable through mass assignment
attr_accessible :username, :email, :password, :password_confirmation
has_many :posts, :dependent => :destroy
end
Post Class
class Post < ActiveRecord::Base
attr_accessible :title, :body
belongs_to :user
end
DB Schema
ActiveRecord::Schema.define(:version => 20111214045425) do
create_table "posts", :force => true do |t|
t.string "title"
t.string "body"
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "users", :force => true do |t|
t.string "username"
t.string "email"
t.string "password_hash"
t.string "password_salt"
t.datetime "created_at"
t.datetime "updated_at"
end
end
Any help? I've followed every guide I can find for using ActiveRecord. All I want to do is create a Post with an associated User.
You can use
#user = User.find(1)
#post = #user.posts.build(posts_attributes_as_hash)
#post.save
Or even
post = Post.new(posts_attributes)
#user = User.find(1)
#user.posts << post
Edit
To use create directly:
#user = User.find(1)
#post = #user.posts.create(posts_attributes_as_hash)
For more information have a look at has_many-association-reference especially at section called 4.3.1 Methods Added by has_many
New Edit:
I created a new project with your code and in rails console I tried the following commands
User.create(:username => "UserNamedTest", :email => "usernamedtest#somewhere.com")
SQL (13.6ms) INSERT INTO "users" ("created_at", "email", "password_hash", "password_salt", "updated_at", "username") VALUES (?, ?, ?, ?, ?, ?) [["created_at", Wed, 14 Dec 2011 09:02:46 UTC +00:00], ["email", "usernamedtest#somewhere.com"], ["password_hash", nil], ["password_salt", nil], ["updated_at", Wed, 14 Dec 2011 01:03:26 UTC +00:00], ["username", "UserNamedTest"]]
=> #<User id: 2, username: "UserNamedTest", email: "usernamedtest#somewhere.com", password_hash: nil, password_salt: nil, created_at: "2011-12-14 09:02:46", updated_at: "2011-12-14 09:02:46">
user = User.find_by_username("UserNamedTest")
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."username" = 'UserNamedTest' LIMIT 1
=> #<User id: 2, username: "UserNamedTest", email: "usernamedtest#somewhere.com", password_hash: nil, password_salt: nil, created_at: "2011-12-14 09:02:46", updated_at: "2011-12-14 09:02:46">
new_post = user.posts.create(:title => "just a test", :body =>"body of article test")
SQL (0.5ms) INSERT INTO "posts" ("body", "created_at", "title", "updated_at", "user_id") VALUES (?, ?, ?, ?, ?) [["body", "body of article test"], ["created_at", Wed, 14 Dec 2011 09:03:59 UTC +00:00], ["title", "just a test"], ["updated_at", Wed, 14 Dec 2011 09:03:59 UTC +00:00], ["user_id", 2]]
=> #<Post id: 2, title: "just a test", body: "body of article test", user_id: 2, created_at: "2011-12-14 09:03:59", updated_at: "2011-12-14 09:03:59">
irb(main):022:0> new_post.inspect
=> "#<Post id: 2, title: \"just a test\", body: \"body of article test\", user_id: 2, created_at: \"2011-12-14 09:03:59\", updated_at: \"2011-12-14 09:03:59\">"
From what I see the code is ok, the post get created with no errors
Discovered the issue after a dump of the test.sqlite3 schema. user_id was not defined as a column in the db. Blowing out the database and running rake spec migrates the database and fixes everything.

Rails :include doesn't include

My models:
class Contact < ActiveRecord::Base
has_many :addresses
has_many :emails
has_many :websites
accepts_nested_attributes_for :addresses, :emails, :websites
attr_accessible :prefix, :first_name, :middle_name, :last_name, :suffix,
:nickname, :organization, :job_title, :department, :birthday,
:emails_attributes
end
class Email < ActiveRecord::Base
belongs_to :contact
validates_presence_of :account
validates_format_of :account, :with => /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create
attr_accessible :contact_id, :account, :label
end
If I run the following query, the emails are returned as expected:
c = Contact.find(3)
Contact Load (3.2ms) SELECT `contacts`.* FROM `contacts` LIMIT 1
=> #<Contact id: 3, prefix: nil, first_name: "Micah", middle_name: nil, last_name: "Alcorn", suffix: nil, nickname: nil, organization: nil, job_title: nil, department: nil, birthday: nil, created_at: "2011-07-04 23:50:04", updated_at: "2011-07-04 23:50:04">
c.emails
Email Load (4.4ms) SELECT `emails`.* FROM `emails` WHERE `emails`.`contact_id` = 3
=> [#<Email id: 3, contact_id: 3, account: "not#real.address", label: "work", created_at: "2011-07-04 23:50:04", updated_at: "2011-07-04 23:50:04">]
However, attempting to :include the relationship does not:
c = Contact.find(3, :include => :emails)
Contact Load (0.5ms) SELECT `contacts`.* FROM `contacts` WHERE `contacts`.`id` = 3 LIMIT 1
Email Load (0.8ms) SELECT `emails`.* FROM `emails` WHERE `emails`.`contact_id` IN (3)
=> #<Contact id: 3, prefix: nil, first_name: "Micah", middle_name: nil, last_name: "Alcorn", suffix: nil, nickname: nil, organization: nil, job_title: nil, department: nil, birthday: nil, created_at: "2011-07-04 23:50:04", updated_at: "2011-07-04 23:50:04">
As you can see, the SQL is being executed, but the emails are not being returned. I intend to return all contacts with each containing email(s), so :joins won't do any good. What am I missing?
The emails are there. Did you try c.emails? You will find that the emails will be there without Rails doing an additional DB query.
The thing that :include does is called eager loading, which basically means Rails will try a best effort method of prepopulating your objects with their relations, so that when you actually ask for the relation no additional DB queries are needed.
See the section "Eager loading of associations" here:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
You might also want to check out this RailsCast:
http://railscasts.com/episodes/181-include-vs-joins