nullnullnull
nullnullnull

Reputation: 8189

Rails: Manipulating a Join Table in a Nested Form

I have two models connected by a join table:

class Publication < ActiveRecord::Base
  attr_accessible :title, :author_attributes, :translator_attributes

  has_many :publication_contributors
  has_many :authors, :through => :publication_contributors, :source => :contributor,
    :conditions => {:publication_contributors => {:contributor_type => "Author"}}
  has_many :translators, :through => :publication_contributors, :source => :contributor,
    :conditions => {:publication_contributors => {:contributor_type => "Translator"}}

  accepts_nested_attributes_for :authors, :translators
end

class Contributor < ActiveRecord::Base
  attr_accessible :name

  has_many :publications, :through => :publication_contributors
  has_many :publication_contributors
end

class PublicationContributor < ActiveRecord::Base
  attr_accessible :contributor_type

  belongs_to :publication
  belongs_to :contributor
end

Note that the join table has a third attribute (beside publication_id and contributor_id), which is called contributor_type. This attribute might contain a role such as "Author", "Translator", "Editor", or something else. In my Publication model, I created a pair of associations for two of the more common contributor_types, "Author" and "Translator". These associations work well at retrieving the relevant data, such as with @publication.authors. It sputters when creating these associations through a nested form, however.

My form looks something like this:

<%= form_for @publication, :remote => true do |f| %>

  <%= f.label :title %>:
  <%= f.text_field :title %>

  <%= f.fields_for :authors do |builder| %>
    <%= builder.label :name, "Author" %>:
    <%= builder.text_field :name %>
  <% end %>

  <%= f.fields_for :translators do |builder| %>
    <%= builder.label :name, "Translator" %>:
    <%= builder.text_field :name %>
  <% end %>

  <%= f.submit %>
<% end %>

In my controller:

def create
  publication = Publication.create(params[:publication])
end

The form renders as expected, but it sputters during the create action. I had hoped that Rails would know to magically assign the proper contributor_type based on the conditions in the Publication associations, such as:

has_many :authors, :through => :publication_contributors, :source => :contributor,
:conditions => {:publication_contributors => {:contributor_type => "Author"}}

Unfortunately, it does not. I get this error during creation:

Mysql2::Error: Column 'contributor_type' cannot be null

This makes me think that my only recourse is to manually assign the contributor_type in a before_create callback, but if that's the case, how do I determine which type to assign? The parameters have the proper type in their name, for instance:

publication[authors_attributes][0][name]

Is there some way to access that information in the model layer?

UPDATE:

My new action:

def new
  @publication = Publication.new
  publication_contributor = @publication.publication_contributors.build
  contributor = publication_contributor.contributor.build
end

It throws this error on the final line (the one setting contributor):

undefined method `build' for nil:NilClass

Upvotes: 4

Views: 3970

Answers (1)

jvnill
jvnill

Reputation: 29599

Can you try the following?

class Publication < ActiveRecord::Base
  attr_accessible :title, :publication_contributors_attributes

  has_many :publication_contributors
  has_many :authors, through: :publication_contributors, source: :contributor, conditions: { publication_contributors: { contributor_type: 'Author' } }
  has_many :translators, through: :publication_contributors, source: :contributor, conditions: { publication_contributors: { contributor_type: 'Translator' } }

  accepts_nested_attributes_for :publication_contributors
end

class Contributor < ActiveRecord::Base
  attr_accessible :name

  has_many :publications, through: :publication_contributors
  has_many :publication_contributors
end

class PublicationContributor < ActiveRecord::Base
  attr_accessible :contributor_type

  belongs_to :publication
  belongs_to :contributor

  accepts_nested_attributes_for :contributor
end

<%= form_for @publication, :remote => true do |f| %>

  <%= f.label :title %>:
  <%= f.text_field :title %>

  <%= f.fields_for :publication_contributors do |pc_form| %>
    <%= pc_form.hidden_field :contributor_type %>
    <%= pc_form.fields_for :contributor do |c_form| %>
      <%= c_form.label :name, "Author" %>:
      <%= c_form.text_field :name %>
    <% end %>
  <% end %>

  <%= f.submit %>
<% end %>

UPDATE: controller code

@publication.publication_contributors.build
@publication.publication_contributors.each do |pc|
  pc.build_contributor
end

Upvotes: 7

Related Questions