Has Many Through Associations7:26 with Jay McGavren
Here we have a new app with Subscriber and Magazine models. We want people to be able to subscribe to multiple magazines, and we want to be able to look up all of a magazine's subscribers. We could do this through a has_and_belongs_to_many association. But what if we want to track additional info regarding the subscription itself, like how many months a person is subscribed to a magazine for? has_and_belongs_to_many associations can't do that sort of thing. Instead, we can set up a has_many :through association.
We have a new app with Subscriber and Magazine models. We want people to be able to subscribe to multiple magazines, and we want to be able to look up all of a magazine's subscribers.
We could do this through a
has_and_belongs_to_many association. We'd have a simple join table with
magazine_id columns that could be used to link Subscribers to Magazines. But what if we want to track additional info regarding the subscription itself, like how many months a person is subscribed to a magazine for?
has_and_belongs_to_many associations can't do that sort of thing.
Instead, we could set up a
has_many :through association.
has_many :through basically says that one model is associated with many instances of another model through a third model. In this case, we would say that a Subscriber has many Magazines through Subscriptions.
It works a lot like a
has_and_belongs_to_many association. The table for the Subscriptions model has ID fields for the two models it's joining, Subscribers and Magazines.
But in addition to the Subscriber and Magazine ID fields, it also has an ID field of its own, allowing you to look up Subscriptions independently of Subscribers or Magazines.
And because a Subscription is a model on its own, it can also have additional attributes. In
has_many :through, two models are still joined by a database table, but that table also represents an entire third model.
You can set up the app we use in this video using the following shell commands:
rails new periodical cd periodical rails g model Subscriber name:string rails g model Magazine title:string bin/rails db:migrate bin/rake db:seed
And you can generate test data using the following commands in the Rails console:
Subscriber.create(name: 'Jay') Subscriber.create(name: 'Pasan') Magazine.create(title: 'Ruby Reader') Magazine.create(title: 'iOS Inspired')
- Generate Subscription model
bin/rails g model Subscription months:integer subscriber_id:integer magazine_id:integer bin/rails db:migrate
- Need to update model classes with associations
class Subscription < ApplicationRecord belongs_to :subscriber belongs_to :magazine end
class Subscriber < ApplicationRecord has_many :subscriptions has_many :magazines, through: :subscriptions end
class Magazine < ApplicationRecord has_many :subscriptions has_many :subscribers, through: :subscriptions end
- Create a subscription to a magazine, add to subscriber:
subscription = Subscription.create(months: 12, magazine: Magazine.find_by(title: "Ruby Reader")) Subscriber.find_by(name: "Jay").subscriptions << subscription
- Subscription now associated with subscriber. Show it with:
pp Subscriber.find_by(name: "Jay").subscriptions
- Magazine also associated with subscriber, through subscription. Show it with:
pp Subscriber.find_by(name: "Jay").magazines
- Can also auto-create subscription model record by adding magazine direct to subscriber
Subscriber.find_by(name: "Jay").magazines << Magazine.find_by(title: "iOS Inspired") pp Subscriber.find_by(name: "Jay").subscriptions
- Auto-created join model won't have its attributes set so you may need to update those
pp Subscriber.find_by(name: "Jay").subscriptions.last.update(months: 6) pp Subscriber.find_by(name: "Jay").subscriptions
There's a lot more you can do with
has_many :through associations. You can learn more from the official Rails Guides.
Now we're going to be learning about another type of association that you'll 0:00 probably be using more frequently than has and belongs to many. 0:03 It's called has many three. 0:07 Here we have a new app with subscriber and magazine models. 0:08 We want people to be able to subscribe to multiple magazines and 0:12 we wanna be able to look up all of the magazine subscribers. 0:15 We could do this through a has and belongs to many association. 0:19 We'd have a simply joined table with subscriber id and 0:22 magazine id call ups that could be used to link subscribers to magazines. 0:25 But what if we wanna track additional info regarding the subscription itself, 0:30 like how many months a person has subscribed to a magazine for? 0:33 Has and belongs to many associations can't do that sort of thing. 0:37 Instead, we could set up a has many through association. 0:41 Has many through basically says that one model is associated with many instances of 0:45 another model through a third model. 0:49 In the case, 0:52 we could say that a subscriber has many magazines through subscriptions. 0:53 A subscription model instance joins a subscriber to a magazine and 0:58 a magazine to a subscriber. 1:01 It works a lot like a has and belongs to many association. 1:04 The table for the subscriptions model has id fields for 1:07 the two models it's joined, subscribers and magazines. 1:10 But in addition to the subscriber and magazine id fields, 1:14 it also has an id field of its own. 1:17 Allowing you to look up subscriptions independently of subscribers or magazines. 1:20 [SOUND] And because a subscription is a model on its own, 1:24 it can also have additional attributes. 1:27 Here we show an attribute that tracks the number of months a subscription is for. 1:29 In a has many through association, two models are still joined by a database 1:34 table but that table also represents an entire third model. 1:38 The first thing we're going to need to do is generate the subscription model. 1:42 So we'll run a bin, rails, generate model subscription. 1:46 We'll set up the subscription zone attributes, so 1:53 the number of months, that's an integer. 1:56 And then we'll need to set up the foreign keys that will associate the subscriber 2:00 and the magazine together, so subscriber, _id is gonna be an integer. 2:05 And that'll reference the id column of the subscriber's table. 2:14 And then we'll need to do the same for 2:18 magazines, magazine_id, that's also gonna be an integer. 2:19 Then we'll need to go through and update all three model classes with associations. 2:27 So we'll go to the newly generated subscription model. 2:31 And we're going to say that it belongs to, A subscriber, 2:37 And it also belongs to, A magazine. 2:44 That will allow us to access the subscriber and 2:49 the magazine if we look at the subscription first. 2:52 Then we'll need to go into the subscriber model class, And 2:55 we're going to say that a subscriber has many, Subscriptions. 2:59 That'll allow us to look up the subscriptions that belong to a subscriber. 3:07 But we're also going to say that it has many, Magazines, 3:11 Through its subscriptions. 3:19 This will add a magazines method to any subscriber model instance that will look 3:25 up all subscriptions the subscriber has. 3:30 And then look up all magazines that belong to those descriptions. 3:32 We'll also need to do the same for the magazine model class. 3:36 We'll say that magazine has many, Subscriptions, 3:41 And it has many, Subscribers, 3:49 Through its subscriptions. 3:55 Okay, let's save all that and 4:01 go to our Rails console, And try our new associations out. 4:03 First, we'll create a new subscription object. 4:09 So we'll say, subscription = Subscription class.create, 4:11 we'll set its month attribute to 12, so this will be a 12 month subscription. 4:17 And we'll say the magazine that the subscription is for 4:24 is a result of magazine, Class find_by, 4:28 And we'll look a magazine up by title or look up magazine titled Ruby Reader. 4:37 I'm getting the error, could not find table subscriptions and 4:45 I know exactly what happened, I forgot to run my migrations. 4:48 So bin/rails db migrate. 4:51 There we go, now I'll run Rails console again. 4:57 And previously, my call to subscription.create was unsuccessful 5:01 because the table didn't exist. 5:05 This time it should work. 5:07 Yeah, there we go. 5:08 So we've created a 12 month subscription to the Ruby Reader magazine, 5:09 now let's find the subscriber. 5:14 Subscriber.find_by, Could have find one with a name of Jay, 5:17 And I'm going to add to that subscriber's subscriptions collection. 5:25 I'm gonna add the current subscription to it. 5:32 And that subscription should now be associated with that subscriber. 5:38 So let's pretty print Subscriber.find_by, Name Jay. 5:41 And let's print that subscribers collection of subscriptions. 5:50 So there's that 12 month subscription. 5:56 And the magazine itself should also be associated with that subscriber 5:59 through the subscription. 6:02 So I'll pretty print the subscriber Jay's magazines, 6:04 And there's the Ruby Reader magazine. 6:09 If we don't wanna create a subscription manually, 6:12 we can auto-create it by adding a magazine direct to the subscriber. 6:15 So let's say, Subscriber find by name, Jay. 6:19 Magazines, And we'll append to that collection, magazine find by, 6:23 Title, iOS Inspired. 6:31 And now if we look at that subscriber's collection of subscriptions, 6:40 we'll see a new automatically created subscription on there. 6:44 The auto-created join model won't have its attributes set by default though. 6:47 You'll notice that this subscription is for nill months. 6:51 So you'll need to update those attributes if you want those set. 6:56 We can say, Subscriber find_by name Jay subscriptions.last.update, 6:59 and we'll update its months attribute to 6. 7:05 Now if we print the list of subscriptions again, we'll see that the last ones, 7:11 months attribute has been set to 6. 7:15 There's a lot more you can do with has many through associations. 7:20 See the teacher's notes if you'd like more info. 7:24
You need to sign up for Treehouse in order to download course files.Sign up