Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails Activerecord multiple table includes

I have four tables:

argument with fields

  • id

comments with

  • id
  • comment_id
  • argument_id
  • user_id

users

  • id

nicknames with

  • id
  • proposal_id
  • user_id
  • name

each argument has many comments,

each comment belongs to a user,

each user has a specific nickname in the argument.

When I fetch the argument comments from DB, I would like to include also the nicknames of each author.

The answer is about the ActiveRecord query I don't know how to write.

I tried with

@argument.comments.includes(:user => :nicknames)

but it doesn't seems to work and when I get the nickname through nickname = @argument.nicknames.find_by_user_id(comment.user.id) it executes the query...

[1m[36mNickname Load (0.6ms)[0m  [1mSELECT "nicknames".* FROM "nicknames" WHERE "nicknames"."argument_id" = 59 AND "nicknames"."user_id" = 9 LIMIT 1[0m

any suggestion?

like image 527
coorasse Avatar asked Nov 03 '22 17:11

coorasse


1 Answers

You can tell if an association is loaded with loaded?.

What is happening here, if I understand your problem, is that you are trying to run a finder on an ActiveRecord::Relation. Quickly browsing through the code, it does not appear that it will try to see if a collection is loaded before it issues the query. It does, however, take a block that will avoid multiple queries. For example (the model names have been changed because I am using a sample project I created for another question):

c = Canteen.first
Canteen Load (0.2ms)  SELECT "canteens".* FROM "canteens" LIMIT 1
=> #<Canteen id: 1, name: "Really good place", created_at: "2012-12-13 00:04:11", updated_at: "2012-12-13 00:04:11">

c.meals.loaded?
=> false

c.meals.find {|m| m.id == 3}
  Meal Load (0.2ms)  SELECT "meals".* FROM "meals" WHERE "meals"."canteen_id" = 1
=> #<Meal id: 3, canteen_id: 1, name: "Banana Pie", price: #<BigDecimal:7fcb6784fa78,'0.499E1',18(45)>, created_at: "2012-12-13 00:37:41", updated_at: "2012-12-13 00:37:41">

You see in the last example that ActiveRecord issues the query to load the associated records. This is because ActiveRecord is calling to_a on the association, forcing the entire set to be loaded, and then filtering based on the block conditions. Obviously, this is not ideal.

Let's try again, eager loading the association.

c = Canteen.includes(:meals).first
  Canteen Load (0.2ms)  SELECT "canteens".* FROM "canteens" LIMIT 1
  Meal Load (0.2ms)  SELECT "meals".* FROM "meals" WHERE "meals"."canteen_id" IN (1)
=> #<Canteen id: 1, name: "Really good place", created_at: "2012-12-13 00:04:11", updated_at: "2012-12-13 00:04:11">

c.meals.loaded?
=> true

c.meals.find {|m| m.id == 3}
=> #<Meal id: 3, canteen_id: 1, name: "Banana Pie", price: #<BigDecimal:7fcb68b596f0,'0.499E1',18(45)>, created_at: "2012-12-13 00:37:41", updated_at: "2012-12-13 00:37:41">

In the last example here, you see that the collection is not loaded again. Instead, the block is used to filter the already loaded records.

As you can see below, even if the records are loaded, ActiveRecord will issue a query to grab the associated record:

c.meals.loaded?
=> true

c.meals.find(1)
  Meal Load (0.1ms)  SELECT "meals".* FROM "meals" WHERE "meals"."canteen_id" = 1 AND "meals"."id" = ? LIMIT 1  [["id", 1]]
=> #<Meal id: 1, canteen_id: 1, name: "Enchiladas", price: #<BigDecimal:7fcb6584ce88,'0.699E1',18(45)>, created_at: "2012-12-13 00:04:40", updated_at: "2012-12-13 00:04:40">

SELECT "meals".* FROM "meals" WHERE "meals"."canteen_id" = 1 AND "meals"."id" = 3
=> [#<Meal id: 3, canteen_id: 1, name: "Banana Pie", price: #<BigDecimal:7fcb68b808e0,'0.499E1',18(45)>, created_at: "2012-12-13 00:37:41", updated_at: "2012-12-13 00:37:41">]
like image 68
Sean Hill Avatar answered Nov 15 '22 05:11

Sean Hill