Has Many Associations8:53 with Jay McGavren
Let's add a Comment model now, and then set up an association so we can add Comments to a Post.
- Let's add a Comment model now. We'll give it a
contentattribute with a type of
textto hold all the nice things our commenters have to say, and a
nameattribute with a type of
stringso we know who left it.
bin/rails g model Comment content:text name:string
- Don't forget to run
bin/rails db:migrate. That will add a
commentstable with our
contentcolumn to the database.
- Now, we need a
Postto add comments to.
post = Post.last
NoMethodError: undefined method 'comments' for #<Post...>
- This takes the symbol you specify,
:comments, and add a method with that same name to all
- Unlike with the Rails server, though, changes to your code aren't immediately available in the console. We need to
exitthe console first, then restart it with
- This takes the symbol you specify,
- Now we should be able to call our new
post = Post.last; post.comments
- Active Record attempts query
SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ?
no such column: comments.post_id
- Active Record attempts query
Active Record is looking for a column in the database that will allow it to associate comments with a
Post. We need to add the appropriate column. But what table should we add it on? The first thing you might attempt is to add several columns to the
posts table, each holding the ID of a record from the
comments table. But you remember the time we considered adding the comment text to the
posts table directly? We rejected that idea because no matter what number of columns we added, it would be too few in some cases, and too many in others. And this setup would give us the same problem.
So instead, we'll add a column on the
comments table, where each record will hold the ID of a record from the
posts table. All the comments with a
1 will refer back to the record with an
1 in the
posts table. Comments with a
3 will refer back to the post with an
3. Comments with a
7 will refer to the post with an
7. And so on.
This is a common practice in database design, and is known as adding a foreign key column to a table. The values that a column holds have to match the value of a primary key column, that is, the column that uniquely identifies a record, in a foreign table. In this case, the
post_id field in the
comments table is a foreign key because it refers to values in the
id column of the
posts table, which is that table's primary key. You can learn more about foreign keys in this video.
bin/rails generate migration AddPostToComments post_id:integer:index
indexsuffix will add an
add_indexmethod call to the migration.
add_indexcauses a database table to be indexed by a particular field, in this case the
post_idfield. Now, when we need to look up records with a particular
commentstable, our database won't have to look through the records one by one. Instead, it will just consult the index, which will point directly to all the records with the
post_idwe're looking for. Whenever you add a foreign key column to a table, you'll usually want to add an index on that column too, for speed's sake.
class AddPostToComments < ActiveRecord::Migration[5.0] def change add_column :comments, :post_id, :integer add_index :comments, :post_id end end
post = Post.first(You could also use
Post.find, or any other method that gets you a single
post.commentsreturns an empty list right now.
- To create a comment that's associated with a post:
comment = post.comments.build
comment.content = "Comment on first post"
comment.name = "Jay"
post.commentsnow returns a list that includes our new comment.
- To create a comment and save it immediately:
post.comments.create(content: "Cool!", name: "Alena")
- Added to list returned from
- To create and view comments on different post:
post = Post.last
post.comments.create(content: "Comment on last post", name: "Pasan")
Let's add a comment model to our blog app now. 0:00 We'll do that with bin/rails generate model. 0:03 We'll name the model Comment, with a capital C. 0:08 And we'll give it a content attribute with a type text to hold all the nice 0:12 things our commenters have to say. 0:17 And a name attribute with a type of string, so we know who left it. 0:19 That'll generate our model files as well as migration, and 0:26 don't forget to run bin/rails db:migrate. 0:29 That'll lay out a comments table with our content column to the database. 0:35 Now, let's run our rails console. 0:38 And we're going to need a post to add comments to. 0:43 So we'll say, post = Post, and we'll just grab the last one. 0:45 Now, let's try to take a look at the comments that are attached to our post. 0:50 We'll say, post.comments. 0:53 But this fails with no method error, undefined method comments. 0:58 For this to work, we're going to need to go into our post model, 1:02 and make a call to the class method has_many with a symbol of comments. 1:07 This will take the symbol that you specify, comments, and 1:13 add a method with that same name to all post model objects. 1:17 So let's save that and go back to our console. 1:21 Unlike with the real server, 1:24 changes to your code aren't immediately available in the console. 1:25 We need to exit the console first, and 1:29 then restart it with bin/rails console again. 1:32 Now, we should be able to call our new comments method. 1:35 So we'll call post = Post.last again to save the post object 1:37 in the post variable, and then we'll call post.comments. 1:42 This time, ActiveRecord attempts a query. 1:48 It tries to select all comment fields from the comments table, but 1:50 it only wants to select comments where the post_id equals 1:54 the specific post that we've requested here in the rails console. 1:57 But it's getting an error, no such column: comments.post_id. 2:01 ActiveRecord is looking for 2:07 a column in the database that will allow it to associate comments with a post. 2:09 We need to add the appropriate column, but what table should we add it on? 2:13 The first thing you might attempt is to add several columns to the post table, 2:18 each holding the id of a record from the comments table. 2:22 You remember the time we considered adding the comment text to the post table 2:25 directly? 2:29 We rejected that idea, because no matter what number of columns we added, 2:30 it would be too few in some cases, and too many in others. 2:34 And this setup would give us the same problem. 2:37 So instead, we'll add a column on the comments table, 2:40 where each record will hold the id of a record from the posts table. 2:43 All the comments with a post_id of 1, 2:47 we'll refer back to the record with an id of 1 on the post table. 2:50 Comments with a post_id of 3, we'll refer back to the post with an id of 3. 2:53 And comments with a post _id of 7, we'll refer to the post with an id of 7, 2:59 and so on. 3:03 This is a common practice in database design, and 3:04 is known as adding a foreign key column to a table. 3:07 The values that a column holds have to match the value of a primary key column, 3:11 that is, the column that uniquely identifies a record in a foreign table. 3:15 In this case, the post_id field in the comments table is a foreign key, 3:19 because it refers to values in the id column of the posts table, 3:23 which is that table's primary key. 3:27 If you'd like more info on foreign keys, check the teacher's notes. 3:29 So now we need to actually add the foreign key column to the comments table. 3:33 Let's exit out of our rails console. 3:37 And we're gonna run bin/rails generate 3:39 migration AddPostToComments. 3:45 This name's a little bit magical, and 3:51 we'll talk about its components in just a moment. 3:53 The colon we're going to want to add is post_id. 3:55 It's going to be an integer, because the id of the post table is also an integer. 3:59 And we're going to want to make sure that this colon gets 4:06 indexed as well to speed up lookups. 4:09 So we're gonna type :index, after that, colon. 4:11 And that's gonna generate a migration file called add_post_to_comments. 4:17 Let's take a look at that right now. 4:20 We use this special format for the migration name. 4:23 We started with add, because we're adding a column. 4:26 Here's the name of the column we're adding. 4:29 And we ended it with To and 4:31 the name of the table that we want to add the column to. 4:33 Because we ended it with two comments, the migration generator knows that our 4:36 operations are going to be taking place against the comments table. 4:41 If you didn't use that specific format for the migration, then you might need to 4:45 add this attribute to the migration contents manually, which is okay. 4:50 We specified that we're adding a post_id column, which is of type integer, so 4:54 that's here. 4:58 And here's the results of the index suffix that we added on the end. 4:59 It added an add_index method call to the migration. 5:03 Add_index causes a database table to be indexed by a particular field, 5:06 in this case, the post_id field. 5:11 Now, when we need to look up records with a particular post_id in the comments 5:13 table, our database won't have to look through the records one by one. 5:17 Instead, it'll just consult the index, which will point 5:21 directly to all the records with the post_id we're looking for. 5:24 Whenever you add a foreign key column to a table, 5:27 you'll usually want to add an index on that column too, for speed's sake. 5:30 Okay, now that we've generated the migration, we're gonna need to run it, 5:34 which we do with bin/rails db:migrate. 5:38 And that'll add the post_id column to the comments table and then index it. 5:45 Now, let's go back into bin/rails console, c for short. 5:49 And let's bring up a post again. 5:54 We'll say post = Post.first. 5:56 If we were to call the comments method, it would work but 6:00 it returns an empty list right now. 6:05 So let's create a comment that's associated with the post. 6:07 We'll say comment = post.comments. 6:10 And then we need to call a method on the comments collections, so we'll say, build. 6:17 That'll create a new comment that's associated with our post. 6:22 You can see the post_id 1 here at the end. 6:25 But not on the other attributes are populated, 6:30 then it's not safe to the database yet. 6:32 So let's set the common.content, 6:34 we'll set that = "Comment on first post". 6:37 Give a commenter name. 6:45 comment.name = "Jay", And let's save it. 6:46 comment.save. 6:54 And now, if we call post.comments, it'll return a list that includes our comment. 6:57 If we wanna create a comment and save it immediately, 7:03 we could use the create method. 7:05 So we say post.comments as before. 7:07 But this time, we're going to call the create method on that collection. 7:11 We'll set its content attribute to "Cool". 7:15 And we'll say the commenter's name as "Alena". 7:21 That'll be immediately saved to the database with those attributes. 7:26 And if we call post.comments again now, we'll see two comments in the results. 7:29 By the way, there's a useful method you can call in the rails console called pp, 7:36 it stands for pretty print. 7:41 So if we call pp with post.comments, 7:43 we can get a pretty formatted list of all the comments. 7:46 So now, we've got a couple comments on our first post. 7:50 Let's and create and view some comments on a different post. 7:52 We'll say, post = Post.last, instead of Post.first. 7:55 post.comments, and we'll create a new comment. 8:01 We'll set its content to, "Comments on last post". 8:06 And we'll say that this comment is from "Pasan". 8:14 If we were to say Post.last.comments, And 8:21 we'll pretty print that, we'll get the comment for the last post. 8:26 If we say Post.first.comments, we'll get the comments from the first post. 8:29 And if we say Comment.all, and we'll pretty print that, 8:36 we'll get a list for all the comments on all the posts. 8:40 Nice work, you've successfully set up your first rails association. 8:44 We set up a many association between the post model and the comments model. 8:48
You need to sign up for Treehouse in order to download course files.Sign up