Has_many :through best practices

I don’t find useful examples on using has_many through associations.
First of all I need to know if it is necessary to have a model.
I have this type of association:
models: User, Bag.
User has_many :deliveries, has_many :bags, :through => :deliveries,
Bag has_many :deliveries, has_many :ruser, :through => :deliveries.
I need to create Delivery model?

On 22 January 2011 15:13, Mauro [email protected] wrote:

I don’t find useful examples on using has_many through associations.
First of all I need to know if it is necessary to have a model.
I have this type of association:
models: User, Bag.
User has_many :deliveries, has_many :bags, :through => :deliveries,
Bag has_many :deliveries, has_many :ruser, :through => :deliveries.
I need to create Delivery model?

Yes, Delivery belongs_to user and belongs_to bag. But if there is no
other data in Delivery then you could use
User has_and_belongs_to_many :bags
Bag has_and_belongs_to_many :users
then you don’t need deliveries, but you still need to provide a table
bags_users containing the ids.
I tend to favour having a model though, and using :through as you have
suggested, especially if the join table has some meaning in the real
world (such as deliveries).

Have you seen the Rails Guide on ActiveRecord Associations, I think it
should have examples of both of these strategies.

Colin

On 22 January 2011 16:22, Colin L. [email protected] wrote:

other data in Delivery then you could use
User has_and_belongs_to_many :bags
Bag has_and_belongs_to_many :users
then you don’t need deliveries, but you still need to provide a table
bags_users containing the ids.

In Delivery I have the attribute delivered_at of type date.
This is why I used :through => deliveries, I think this is correct.
Isn’t it?

On 22 January 2011 16:42, Colin L. [email protected] wrote:

Yes, that is fine. In that case you obviously need a Delivery model
and a deliveries table, otherwise you will have no way of setting the
date.

Ok, this now it’s clear.
But how can I assign bags with delivered_at to users?
I’ve created users and bags, then I’ve tried:
user=User.first, bag=Bag.first
Delivery.create(:user_id=>user.id, :bag_id=>bag.id:
delivered_at=>‘2011-01-01’)
but when I do user.deliveries I have no data and also for user.bags.

On 22 January 2011 15:47, Mauro [email protected] wrote:

Delivery.create(:user_id=>user.id, :bag_id=>bag.id: delivered_at=>‘2011-01-01’)
but when I do user.deliveries I have no data and also for user.bags.

There are much better ways of doing that, but that should work. Can
you post the class definitions for your three classes, and also
db/schema.rb?

Colin

On 22 January 2011 15:38, Mauro [email protected] wrote:

Yes, Delivery belongs_to user and belongs_to bag. But if there is no
other data in Delivery then you could use
User has_and_belongs_to_many :bags
Bag has_and_belongs_to_many :users
then you don’t need deliveries, but you still need to provide a table
bags_users containing the ids.

In Delivery I have the attribute delivered_at of type date.
This is why I used :through => deliveries, I think this is correct.
Isn’t it?

Yes, that is fine. In that case you obviously need a Delivery model
and a deliveries table, otherwise you will have no way of setting the
date.

Colin

On 22 January 2011 17:09, Colin L. [email protected] wrote:

user=User.first, bag=Bag.first
Delivery.create(:user_id=>user.id, :bag_id=>bag.id: delivered_at=>‘2011-01-01’)
but when I do user.deliveries I have no data and also for user.bags.

There are much better ways of doing that, but that should work. Can
you post the class definitions for your three classes, and also
db/schema.rb?

Yes:

class Ruser < ActiveRecord::Base
has_many :deliveries
has_many :bags, :through => :deliveries
has_one :document
default_scope :order => ‘fullname ASC’

def self.search(fullname)
where(‘fullname ILIKE ?’, “#{fullname}%”) if fullname
end
end

class Bag < ActiveRecord::Base
has_many :deliveries
has_many :rusers, :through => :deliveries
end

class Delivery < ActiveRecord::Base
belongs_to :ruser
belongs_to :bag
end

ActiveRecord::Schema.define(:version => 20110114084202) do

create_table “bags”, :force => true do |t|
t.string “bag_type”
t.datetime “created_at”
t.datetime “updated_at”
end

create_table “delegates”, :force => true do |t|
t.string “fullname”
t.datetime “created_at”
t.datetime “updated_at”
end

create_table “deliveries”, :force => true do |t|
t.integer “ruser_id”, :limit => 10
t.integer “bag_id”, :limit => 10
t.date “delivery_at”
t.datetime “created_at”
t.datetime “updated_at”
end

create_table “documents”, :force => true do |t|
t.string “doctype”
t.string “docnumber”
t.integer “ruser_id”, :limit => 10
t.integer “delegate_id”, :limit => 10
t.datetime “created_at”
t.datetime “updated_at”

end

create_table “rusers”, :force => true do |t|
t.integer “year”, :limit => 10
t.integer “code”, :limit => 10
t.string “fullname”
t.date “birthdate”
t.string “homeaddress”
t.string “homepostalcode”, :limit => 6
t.string “homelocality”
t.string “fiscalcode”, :limit => 16
t.string “contractnumber”
t.integer “squaremeters”, :limit => 10
t.integer “category”, :limit => 10
t.string “taxedaddress”
t.datetime “created_at”
t.datetime “updated_at”
end

end

On 22 January 2011 16:13, Mauro [email protected] wrote:

I’ve created users and bags, then I’ve tried:
user=User.first, bag=Bag.first
Delivery.create(:user_id=>user.id, :bag_id=>bag.id:
delivered_at=>‘2011-01-01’)
but when I do user.deliveries I have no data and also for user.bags.

This won’t work with the class definitions you post below as the class
is Ruser not User.

What happens if you open a ruby console (rails console if using rails
3, script/console otherwise) and enter the lines above (but using the
correct class names)?

Colin

On 22 January 2011 17:09, Colin L. [email protected] wrote:

There are much better ways of doing that, but that should work.

I’ve not found any exhaustive examples of has_many through.
Which are the better ways?

On 22 January 2011 16:16, Mauro [email protected] wrote:

On 22 January 2011 17:09, Colin L. [email protected] wrote:

There are much better ways of doing that, but that should work.

I’ve not found any exhaustive examples of has_many through.
Which are the better ways?

Did you work through the Rails Guide on Associations as I suggested?
Note particularly << and the .build methods. I would get what you
have basically working first in the console as in my previous post.

Colin

On 22 January 2011 16:33, Mauro [email protected] wrote:

Ok, this now it’s clear.
user=User.first but it is user=Ruser.first.
user.bags
[]

Can you post the complete console output please.

Colin

On 22 January 2011 17:39, Colin L. [email protected] wrote:

Can you post the complete console output please.

It works if I close and restart console.

On 22 January 2011 17:25, Colin L. [email protected] wrote:

But how can I assign bags with delivered_at to users?
I’ve created users and bags, then I’ve tried:
user=User.first, bag=Bag.first
Delivery.create(:user_id=>user.id, :bag_id=>bag.id:
delivered_at=>‘2011-01-01’)
but when I do user.deliveries I have no data and also for user.bags.

This won’t work with the class definitions you post below as the class
is Ruser not User.

Sorry for my mistake but what I’ve posted is correct, I’ve write
user=User.first but it is user=Ruser.first.

What happens if you open a ruby console (rails console if using rails
3, script/console otherwise) and enter the lines above (but using the
correct class names)?

user=Ruser.first
bag=Bag.first
Delivery.create(:ruser_id=>user.id,
:bag_id=>bag.id,:delivery_at=>‘2011-01-01’)
user.deliveries
[]
user.bags
[]

On 22 January 2011 17:51, Colin L. [email protected] wrote:

On 22 January 2011 16:42, Mauro [email protected] wrote:

On 22 January 2011 17:39, Colin L. [email protected] wrote:

Can you post the complete console output please.

It works if I close and restart console.

Closed and restarted after doing what?

I do:

irb(main):001:0> user=Ruser.first
=> #<Ruser id: 82469, year: 2010, code: 100037787, fullname:
“zzzzzz.”, birthdate: nil, homeaddress: “xxxxxxxxx”, homepostalcode:
“11111”, homelocality: “zzzzzzzz”, fiscalcode: “0111111111”,
contractnumber: “30236”, squaremeters: 56, category: 101,
taxedaddress: “zzzzzzzzzzz\r\n”, created_at: “2011-01-15 17:26:44”,
updated_at: “2011-01-15 17:26:44”>

irb(main):004:0> bag=Bag.first
=> #<Bag id: 1, bag_type: “secco”, created_at: “2011-01-15 23:32:01”,
updated_at: “2011-01-15 23:32:01”>

irb(main):010:0> Delivery.create(:ruser_id=>user.id,
:bag_id=>bag.id,:delivery_at=>‘2011-01-01’)
=> #<Delivery id: 23, ruser_id: 82469, bag_id: 1, delivery_at:
“2011-01-01”, created_at: “2011-01-22 16:32:10”, updated_at:
“2011-01-22 16:32:10”>

irb(main):012:0> user.deliveries
=> []
irb(main):013:0> user.bags
=> []

irb(main):014:0> exit

jruby -S rails console

irb(main):002:0> user.deliveries
=> [#<Delivery id: 23, ruser_id: 82469, bag_id: 1, delivery_at:
“2011-01-01”, created_at: “2011-01-22 16:32:10”, updated_at:
“2011-01-22 16:32:10”>]
irb(main):003:0> user.bags
=> [#<Bag id: 1, bag_type: “secco”, created_at: “2011-01-15 23:32:01”,
updated_at: “2011-01-15 23:32:01”>]

On 22 January 2011 16:42, Mauro [email protected] wrote:

On 22 January 2011 17:39, Colin L. [email protected] wrote:

Can you post the complete console output please.

It works if I close and restart console.

Closed and restarted after doing what?

Colin

On 22 January 2011 16:58, Mauro [email protected] wrote:

I do:
irb(main):004:0> bag=Bag.first
=> []
“2011-01-22 16:32:10”>]
irb(main):003:0> user.bags
=> [#<Bag id: 1, bag_type: “secco”, created_at: “2011-01-15 23:32:01”,
updated_at: “2011-01-15 23:32:01”>]

That is interesting, I guess it is because you have already loaded
user and have not told it that its deliveries have changed under its
feet so to speak. I suspect that if you did user = Ruser.first again
it would be ok. If you use the << or build methods then you should
not have that problem. You said previously that you had problems
finding tutorials with the through construct, if you work through some
just using has_many and belongs_to you will get the basic principles
and the through bit should not be a problem.

Colin

On 22 January 2011 18:15, Colin L. [email protected] wrote:

That is interesting, I guess it is because you have already loaded
user and have not told it that its deliveries have changed under its
feet so to speak. I suspect that if you did user = Ruser.first again
it would be ok. If you use the << or build methods then you should
not have that problem. You said previously that you had problems
finding tutorials with the through construct, if you work through some
just using has_many and belongs_to you will get the basic principles
and the through bit should not be a problem.

Thanks for your help.

On Jan 22, 10:58am, Mauro [email protected] wrote:

irb(main):002:0> user.deliveries
=> [#<Delivery id: 23, ruser_id: 82469, bag_id: 1, delivery_at:
“2011-01-01”, created_at: “2011-01-22 16:32:10”, updated_at:
“2011-01-22 16:32:10”>]
irb(main):003:0> user.bags
=> [#<Bag id: 1, bag_type: “secco”, created_at: “2011-01-15 23:32:01”,
updated_at: “2011-01-15 23:32:01”>]

In this scenario .deliveries and .bags are actually AssociationProxy
objects, which have a .reload method. So above, before you exit your
console you could have done

user.deliveries.reload
user.bags.reload

And you would have gotten the correct collection. As a debugging
tool, you can direct your rails logging to stdout (i.e.
ActiveRecord::Base.logger = Logger.new(STDOUT)) and then you can see
the order of SQL commands. In your example you would have seen the
optimistic association loads when you did Ruser.first, and then when
you did user.deliveries later you would have noticed no SQL query,
indicating that it was using the cached value, and thus indicating you
should do the .reload.

As Colin mentions though, you’re probably best using user.deliveries
<<, as that will both set your association IDs correctly, and have the
collection all happy.

HTH,
\Peter

On Jan 24, 1:16pm, Mauro [email protected] wrote:

nil).update_all(:delivered_at => Date.today).
Can it be a solution?

Sorry I missed the part about the delivered at. You want something
like below; note that I added two items to show how the caching
affects it:

user = Ruser.first
bag1 = Bag.find(…)
bag2 = Bag.find(…)
d1 = user.deliveries.create(:bag => bag1, :delivered_at => Time.now)
user.deliveries # => [d1]
user.bags # => [bag1] - Performs the query, finds the bag
through the delivery, caches it
d2 = user.deliveries.create(:bag => bag2, :delivered_at => 1.day.ago)
user.deliveries # => [d1, d2] - Using the association to create made
it know about the new one
user.bags # => [bag1] - Still cached from above
user.bags.reload # => [bag1, bag2] - Forces it to rerun the query

you forgot to write the answer? :slight_smile: