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?

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

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

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.

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

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

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

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.

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

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

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.

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
[]

Can you post the complete console output please.

Colin

It works if I close and restart console.

Closed and restarted after doing what?

Colin

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">]

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

Thanks for your help.

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

About this point I'm still confused.
I want delivery one or more bags to ruser and I want to set the date
of delivery.
I'm thinking:
user=Ruser.first;
bags=Bag.all
user << bags,
this create deliveries but set delivered_at == nil.
Then I can do user.deliveries.where(:delivered_at =>
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