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?