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.

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.

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

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.

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.

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

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.

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

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

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?

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

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

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:

http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html

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

> 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

Done.

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

Done.

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.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:

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

Done.

class Management < …

belongs_to :manager, :inverse_of => :managements

Done.

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.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:

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

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

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?

That because I'm using inherited_resources.

This seems to work:

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 = Manager.new(params[:manager])
@manager.managements.build(:role=>params[:role],:company => @company)
create!
end

Am I correct now?