I really don't know hot to set an extra attribute of a join model

Scuse me but, as the subject says, I’m not be able to set an extra
attribute of a join model.
Situation is:

class Company < ActiveRecord::Base
has_many :managements
has_many :managers, :through => :managements

class Manager < ActiveRecord::Base
has_many :managements
has_many :companies, :through => :managements

class Management < ActiveRecord::Base
belongs_to :company
belongs_to :manager

ROLES = [“Rappresentante legale”, “Titolare”].freeze

Management has an extra attribute :role and the values are those in
the ROLES array .

I want
create a Company;
then add a Manager but, at the same time, set the :role attribute of
Management join model.
The form for creating a new Manager is:

  • args = @company.nil? ? [@manager] : [[@company, @manager], {:remote =>
    true}]
    = semantic_form_for *args do |f|

= f.inputs :name, :surname, :fiscal_code, :city, :zip_code,
:address, :street_number, :tel, :email
= f.semantic_fields_for :managements do |management|
= management.inputs do
= management.input :role, :as => :select, :collection =>
Management::ROLES

= f.buttons :commit

I don’t see the role field.

Manager controller is

def new
@company = Company.find(params[:company_id])
@manager = @company.managers.build

def create
@company = Company.find(params[:company_id])
@manager = Manager.new(params[:manager])
@manager.company_ids = @company.id

I can add a Manager to @company but I don’t know how to set the :role
attribute.

If in the controller I put: (I’m using inherited resources)
like you sugggest

def new
@company = Company.find(params[:company_id])
new!
end

def create
puts params.inspect
@company = Company.find(params[:company_id])
@manager = @company.managers.build(params[:manager])
create!
end

Then a new manager is created but it is not associated at any Company,
I think I have explicity do
@manager.company_ids = @company.id in the controller.

On Thu, Jan 12, 2012 at 12:22 PM, Mauro [email protected] wrote:

has_many :companies, :through => :managements
I want
:address, :street_number, :tel, :email
= f.semantic_fields_for :managements do |management|
= management.inputs do
= management.input :role, :as => :select, :collection =>
Management::ROLES

= f.buttons :commit

I don’t see the role field.

Could you explain where you do not see the “role” field,

Could you show us the params.inspect when submit is clicked.

Manager controller is

def new
@company = Company.find(params[:company_id])
@manager = @company.managers.build

def create

place here logger.debug params.inspect

or puts params.inspect

or raise params.inspect

@company = Company.find(params[:company_id])
@manager = Manager.new(params[:manager]) ## [A]
@manager.company_ids = @company.id ## [B]

Try not to manipulate _id yourself. Always better to use the
association functions.

Is that company_ids (with a ‘s’) a typo error ?

Here (in the ‘create’) it does make sense to say as in the ‘new’
method:

@manager = @company.managers.build(params[:manager]) # [C]

this replace lines [A] and [B].

Now for the attribute on the newly created management record …

After line [C] is executed, what do you when looking for

@manager.managements

If you are lucky, then there is exactly 1 record there and you could
try to write

@manager.managements.first.role = # the role that you read from the
params
hash

I don’ know if you could get multiple @manager.managements entries at
this
stage (because in the database, there could certainly be muplitple,
unless
you
had a unique composite key on
[managements.manager_id, managements.company_id] which could make sense.

HTH, but not entirely sure,

Peter


Peter V.
http://twitter.com/peter_v
http://rails.vandenabeele.com

Here are params:

{“utf8”=>“✓”,
“authenticity_token”=>“RpOtdQMIm6B45/e91h6ljXKpMCtRD//QJ+bP4knrcic=”,
“manager”=>{“name”=>“q”, “surname”=>"",
“fiscal_code”=>“1121111111111111”, “city”=>"", “zip_code”=>"",
“address”=>"", “street_number”=>"", “tel”=>"", “email”=>""},
“commit”=>“Crea Amministratore”, “action”=>“create”,
“controller”=>“managers”, “company_id”=>“6”}

Started POST “/companies/6/managers” for 0:0:0:0:0:0:0:1 at 2012-01-12
16:58:49 +0100
Processing by ManagersController#create as JS
Parameters: {“utf8”=>“✓”,
“authenticity_token”=>“RpOtdQMIm6B45/e91h6ljXKpMCtRD//QJ+bP4knrcic=”,
“manager”=>{“name”=>“q”, “surname”=>"",
“fiscal_code”=>“1121111111111111”, “city”=>"", “zip_code”=>"",
“address”=>"", “street_number”=>"", “tel”=>"", “email”=>""},
“commit”=>“Crea Amministratore”, “company_id”=>“6”}

As you see i have company_id => but the association is not created, is
created only a new Manager but with no Company association.

On Thu, Jan 12, 2012 at 5:02 PM, Mauro [email protected] wrote:

As you see i have company_id => but the association is not created, is
created only a new Manager but with no Company association.

You are correct (and I learned something). In this many_to_many
association
a build and a later save on the newly build object, seems to NOT create
the intermediate association object in the database … (this works as I
would
expect in a straight has_many, but seemingly not in a has_many :through
…).
Using a ‘create’ resolves that problem, but that means you cannot delay
the
saving to database to one consolidated save at the end, as I prefer to
do.

Some code:

002:0> Company.create
SQL (11.6ms) INSERT INTO “companies” (“created_at”, “updated_at”)
VALUES
(?, ?) [[“created_at”, Thu, 12 Jan 2012 23:28:57 UTC +00:00],
[“updated_at”, Thu, 12 Jan 2012 23:28:57 UTC +00:00]]

004:0> @company = Company.find(1)
Company Load (0.2ms) SELECT “companies”.* FROM “companies” WHERE
“companies”.“id” = ? LIMIT 1 [[“id”, 1]]
=> #<Company id: 1, created_at: “2012-01-12 23:28:57”, updated_at:
“2012-01-12 23:28:57”>

006:0> @manager = @company.managers.build()
=> #<Manager id: nil, created_at: nil, updated_at: nil>

007:0> @manager.save!
SQL (0.6ms) INSERT INTO “managers” (“created_at”, “updated_at”)
VALUES
(?, ?) [[“created_at”, Thu, 12 Jan 2012 23:30:25 UTC +00:00],
[“updated_at”, Thu, 12 Jan 2012 23:30:25 UTC +00:00]]
=> true

This does NOT save the intermediate management record :frowning:

008:0> @manager = @company.managers.create()
SQL (0.5ms) INSERT INTO “managers” (“created_at”, “updated_at”)
VALUES
(?, ?) [[“created_at”, Thu, 12 Jan 2012 23:30:42 UTC +00:00],
[“updated_at”, Thu, 12 Jan 2012 23:30:42 UTC +00:00]]
SQL (0.8ms) INSERT INTO “managements” (“company_id”, “created_at”,
“manager_id”, “role”, “updated_at”) VALUES (?, ?, ?, ?, ?)
[[“company_id”,
1], [“created_at”, Thu, 12 Jan 2012 23:30:42 UTC +00:00], [“manager_id”,
2], [“role”, nil], [“updated_at”, Thu, 12 Jan 2012 23:30:42 UTC +00:00]]
=> #<Manager id: 2, created_at: “2012-01-12 23:30:42”, updated_at:
“2012-01-12 23:30:42”>

but this does save the intermediate management record :slight_smile:

009:0> @manager.managements.first
Management Load (0.3ms) SELECT “managements”.* FROM “managements”
WHERE
“managements”.“manager_id” = 2 LIMIT 1
=> #<Management id: 1, company_id: 1, manager_id: 2, role: nil,
created_at:
“2012-01-12 23:30:42”, updated_at: “2012-01-12 23:30:42”>
010:0> @manager.managements.first.role = “test role”
Management Load (0.3ms) SELECT “managements”.* FROM “managements”
WHERE
“managements”.“manager_id” = 2 LIMIT 1
=> “test role”

Huh, Y U Reload ? (this is a different problem)

011:0> @manager.managements.first.save!
Management Load (0.3ms) SELECT “managements”.* FROM “managements”
WHERE
“managements”.“manager_id” = 2 LIMIT 1
=> true

Huh, Y U Reload again … and now save a pristine management record

without my role assigned ?

I could not reproduce this behavior … never seen this before.

013:0> management = @manager.managements.first
Management Load (0.3ms) SELECT “managements”.* FROM “managements”
WHERE
“managements”.“manager_id” = 2 LIMIT 1
=> #<Management id: 1, company_id: 1, manager_id: 2, role: nil,
created_at:
“2012-01-12 23:30:42”, updated_at: “2012-01-12 23:30:42”>
014:0> management.role = “test role”
=> “test role”
015:0> management.save!
(0.4ms) UPDATE “managements” SET “role” = ‘test role’, “updated_at”

‘2012-01-12 23:32:24.704538’ WHERE “managements”.“id” = 1
=> true

forcing management to be the same record

017:0> @manager.managements.first
Management Load (0.3ms) SELECT “managements”.* FROM “managements”
WHERE
“managements”.“manager_id” = 2 LIMIT 1
=> #<Management id: 1, company_id: 1, manager_id: 2, role: “test role”,
created_at: “2012-01-12 23:30:42”, updated_at: “2012-01-12 23:32:24”>
018:0> @manager.managements.first.role
Management Load (0.3ms) SELECT “managements”.* FROM “managements”
WHERE
“managements”.“manager_id” = 2 LIMIT 1
=> “test role”

And now, reading it back from the database yields the role.

Please not that the use of “first” here is dangerous, unless you make
sure
there is never
a double association record between the same combination of Company and
Manager.

Another problem, I do not see the “role” value anywhere in your params,
so
probably also
the form needs fixing somewhere ?

HTH,

Peter


Peter V.
http://twitter.com/peter_v
http://rails.vandenabeele.com

On Sun, Jan 15, 2012 at 5:36 PM, Mauro [email protected] wrote:

You have created and saved a Manager.
This create automatically a new Management and it is correct, but with
role => nil.

Indeed that is at line 008 of my code above (triggering 2 * SQL INSERT)

And only in a second set of commands (013, 014, 015 above) I was
able to set the role for the “management” association record (triggering
1 * SQL UPDATE)

I want to assign a role to Management at the same time I create a new
Manager.

I understand, but I was unable to find a solution that does it in 1
time…

HTH,

Peter


Peter V.
http://twitter.com/peter_v
http://rails.vandenabeele.com

On 15 January 2012 17:54, Peter V. [email protected]
wrote:

I want to assign a role to Management at the same time I create a new
Manager.

I understand, but I was unable to find a solution that does it in 1 time…

And tha’ts my problem, reading other posts in internet it seems
impossible.
Perhaps the role can be set with a before_save in Management model but
how to use in Management model the params of the manager form?

You have created and saved a Manager.
This create automatically a new Management and it is correct, but with
role => nil.
I want to assign a role to Management at the same time I create a new
Manager.

On Sun, Jan 15, 2012 at 7:25 PM, Mauro [email protected] wrote:

And tha’ts my problem, reading other posts in internet it seems impossible.
Perhaps the role can be set with a before_save in Management model but
how to use in Management model the params of the manager form?

I would not use before_filter for this.

Just do it in 2 separate saves.

  1. build the manager indepedently
  2. build the management with all associations and his role

Save them. You might be lucky to do @manager.save that
saves both of them if you had built the management with
the build on the association from @manager. Like this:

Pseudo code (untested)

@company = Company.find(params[:company_id]) # 1 FIND
@manager = Manager.new(params[:manager])
@manager.managements.build(:role => role, :company => @company)

If you’re lucky,

@manager.save # Check this in your rails console (for 1 or 2 INSERTs)

will also save the newly built management in 1 transaction and
you would not have to juggle _id variables manually.

HTH,

Peter

On Sun, Jan 15, 2012 at 7:46 PM, Mauro [email protected] wrote:

It creates two records while I want:

company_id => 1, manager_id => 1, role => “test”

Is it really hard to do this with rails?

It shouldn’t be …

What happens if you try my code above literally in rails
console ? Of course not with ‘params’ then but just with

@company = Company.find(1)
@manager = Manager.new()
@management = @manager.managements.build(:role => “Big chief”, :company
=>
@company)

The @management that is built here, does NOT have all _id’s set (yet!),
but do not be misguided. Ask for

puts @management.manager
puts @manager.company
puts @manager.role

They should be present, all 3.

Doing a save on @manager.save will “probably” execute 2 INSERTs
(if only 1 INSERT is executed, you need to do @management.save
separately).

After that, the _id 's in the managements record will also be set.

If you have time, try it out. I think that should work.

HTH,

Peter

On 15 January 2012 19:33, Peter V. [email protected]
wrote:

time…

@manager = Manager.new(params[:manager])
@manager.managements.build(:role => role, :company => @company)

I’ve tried something like this but the result in the managements table
is:

company_id => 1, manager_id => 1, role => nil
company_id => nil, manager_id =>1, role => “test”

It creates two records while I want:

company_id => 1, manager_id => 1, role => “test”

Is it really hard to do this with rails?

On 15 January 2012 19:58, Peter V. [email protected]
wrote:

puts @management.manager

puts @management.manager

=> nil

puts @manager.company

It is @manager.companies:

irb(main):019:0> puts @manager.companies
[]
=> nil

puts @manager.role

puts @management.role
Big chief

On 15 January 2012 21:01, Peter V. [email protected]
wrote:

has_many :managements, :inverse_of => :manager
Done.

class Management < …
belongs_to :manager, :inverse_of => :managements

Done.

puts @management.manager

puts @management.manager
#Manager:0xaeba3ff
=> nil

puts @management.company

puts @management.company
#Company:0x1dc9b12c
=> nil

puts @management.role

puts @management.role
Role
=> nil

@manager.save
@manager.reload

puts @management.manager

puts @management.manager
#Manager:0x73785e0f

puts @management.company

puts @management.company
#Company:0x1dc9b12c

puts @management.role

puts @management.role
Role

Wow!!!
Now with adding :inverse_of it seems to work :slight_smile:

On Sun, Jan 15, 2012 at 8:13 PM, Mauro [email protected] wrote:

On 15 January 2012 19:58, Peter V. [email protected]
wrote:

puts @management.manager

puts @management.manager

=> nil

OK, I get it.

That’s because we do not have the “inverse_of” relationship in place.

But after a save, also this belongs_to should be OK.

You could add the inverse_of to have the
management belongs_to :manager available immediately

class Manager < …
has_many :managements, :inverse_of => :manager

end

Also, another :inverse_of (in the Management class) may even
resolve the original problem that the :through association is not
automatically saved after a build:

If you are using a belongs_to on the join model, it is a good idea to
set
the :inverse_of option

on the belongs_to, which will mean that the following example works
correctly (where tags is

a has_many :through association):

@post = Post.first @tag = @post.tags.build :name => “ruby” @tag.save

that would be:

class Management < …
belongs_to :manager, :inverse_of => :managements

end

puts @manager.company

It is @manager.companies:

Sorry, I made a typo there …

puts @management.company

That should certainly be present (wit’s explicitely assigned in the
build).

irb(main):019:0> puts @manager.companies
[]
=> nil

Yes, that is logical at this point in the code (we have
only made the associaton object, but not done anything
on that association).

Maybe the inverse_of resolves this ??

puts @manager.role

puts @management.role
Big chief

That works … 1/3 is not a high score …

Could try the 2 modifications suggested above.

And do the same puts after @manager.save

@company = Company.find(1)
@manager = Manager.new()
@management = @manager.managements.build(:role => “Role”, :company =>
@company)

puts @management.manager
puts @management.company
puts @management.role

@manager.save
@manager.reload

puts @management.manager
puts @management.company
puts @management.role

Actually, I think that after the save, it will be OK, even
without adding the “inverse_of” options.

I think we are getting closer,

Peter


You received this message because you are subscribed to the Google G.
“Ruby on Rails: Talk” group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/rubyonrails-talk?hl=en.


Peter V.
http://twitter.com/peter_v
http://rails.vandenabeele.com
gsm: +32-478-27.40.69
e-mail: [email protected]

I didn’t knew :inverse_of, now I read something about it.
But I can I set now the controller actions new and create?
This code creates two managers and not only one:

def new
@company = Company.find(params[:company_id])
@manager = Manager.new
@manager.managements.build
new!
end

def create
@company = Company.find(params[:company_id])
@manager = @company.managers.build(params[:manager])
@manager.company_ids = @company.id
create!
end

On Sun, Jan 15, 2012 at 9:43 PM, Mauro [email protected] wrote:

def create
@company = Company.find(params[:company_id])
@manager = @company.managers.build(params[:manager])
@manager.company_ids = @company.id
create!
end

What are the new! and create! methods?

Are the 2 entries created both in the create action? You should
certainly not see an INSERT in the logs during the new action.

Peter

Yes you’re right, it works also without :inverse_of, sorry for my
mistake.
But I can’t do it work using the manager form.
Can you help me for another little piece?

On 15 January 2012 21:49, Peter V. [email protected]
wrote:

new!
What are the new! and create! methods?
That because I’m using inherited_resources.

On Sun, Jan 15, 2012 at 9:31 PM, Mauro [email protected] wrote:

On 15 January 2012 21:01, Peter V. [email protected]
wrote:

class Management < …
@company = Company.find(1)
puts @management.company
=> nil
puts @management.company
Wow!!!
Now with adding :inverse_of it seems to work :slight_smile:

OK, glad.

For clarity, I believe that after the

@manager.save

the results would also be OK without the :inverse_of.

The only thing the :inverse_of does, is the fill in the “inverse”
association in memory, before the save. Once a save or
reload has happened, both associations (belongs_to and
has_{one|many} are always filled in).

Sorry that it took so long to get this fairly simple case to work.
I feel I was not very efficient at certain parts of this story …

Peter

On Sun, Jan 15, 2012 at 10:03 PM, Mauro [email protected] wrote:

@company = Company.find(params[:company_id])
@manager = Manager.new(params[:manager])
@manager.managements.build(:role=>params[:role],:company => @company)
create!

end

Am I correct now?

Possibly, for company and manager.

But the params you showed some time ago:

Parameters: {“utf8”=>“✓”,
“authenticity_token”=>"
RpOtdQMIm6B45/e91h6ljXKpMCtRD//QJ+bP4knrcic=“,
“manager”=>{“name”=>“q”, “surname”=>”“,
“fiscal_code”=>“1121111111111111”, “city”=>”“, “zip_code”=>”“,
“address”=>”“, “street_number”=>”“, “tel”=>”“, “email”=>”"},
“commit”=>“Crea Amministratore”, “company_id”=>“6”}

I did not see a

“role” => “Something”

in there.

Have you changed your view to now also have a “role” input field that is
returned upon submit?

Peter