Reputation: 1086
I have a Trial model, which creates treatment_selection and repetition on create.
With repetition I'm looking to save the trial_id and treatment_selection_id which i'm trying to do via a :through association.
When the Trial form is submitted I getting the following error:
undefined method `repetitions' for #<TreatmentSelection:0x007ff76b8c3830>
Trial Controller
def create
@trial = Trial.new(trial_params)
@trial.treatment_selections.build.repetitions.build
end
private
def trial_params
params.require(:trial).permit(:year, :cultivation_id, :region_id, :cooperator_id, :field_id, :property_id, treatment_selections_attributes: [TreatmentSelection.attribute_names.map(&:to_sym).push(:_destroy), repetitions_attributes: [:trial_id, :treatment_selection_id]])
end
Models
class Trial < ApplicationRecord
has_many :treatment_selections, dependent: :destroy
has_many :repetitions, through: :treatment_selections, source: :treatment
accepts_nested_attributes_for :repetitions, allow_destroy: true
accepts_nested_attributes_for :treatment_selections, allow_destroy: true
end
class TreatmentSelection < ApplicationRecord
belongs_to :trial
belongs_to :treatment
end
class Repetition < ApplicationRecord
belongs_to :treatment_selection
end
The treatment_selection model has a helper as follow, which the user can add dynamically. trials_helper
module TrialsHelper
def link_to_add_row(name, f, association, **args)
new_object = f.object.send(association).klass.new
id = new_object.object_id
fields = f.fields_for(association, new_object, child_index: id) do |builder|
render(association.to_s.singularize, f: builder)
end
link_to(name, '#', class: "add_fields " + args[:class], data: {id: id, fields: fields.gsub("\n", "")})
end
end
Schema
create_table "repetitions", force: :cascade do |t|
t.integer "trial_id"
t.integer "treatment_selection_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Log
Started POST "/trials" for 127.0.0.1 at 2018-10-22 14:45:57 +1000
Processing by TrialsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"I2QheHAPOuhnv61Ghs0UoVgXcbYvX73AA7+nI/JTvRU4lTRYJbOdCstuTt/i7/4H2dV6wv4Vdt6zcQ7oo1id6w==", "trial"=>{"year(2i)"=>"1", "year(3i)"=>"1", "year(1i)"=>"2018", "region_id"=>"3", "cooperator_id"=>"1", "property_id"=>"1", "field_id"=>"5", "cultivation_id"=>"1", "treatment_selections_attributes"=>{"0"=>{"treatment_id"=>"3", "ambassador"=>"0", "_destroy"=>"false"}, "1"=>{"treatment_id"=>"", "ambassador"=>"0", "_destroy"=>"false"}, "2"=>{"treatment_id"=>"", "ambassador"=>"0", "_destroy"=>"false"}}}, "commit"=>"Submit"}
Completed 500 Internal Server Error in 65ms (ActiveRecord: 11.6ms)
NoMethodError (undefined method `repetitions' for #<TreatmentSelection:0x007ff76b8c3830>):
app/controllers/trials_controller.rb:48:in `block in create'
app/controllers/trials_controller.rb:47:in `times'
app/controllers/trials_controller.rb:47:in `create'
Trial Form
<%= form_with(model: trial, local: true) do |f| %>
<%= f.date_select :year, {order: [:year] %>
... Other Trial fields...
<%= f.fields_for :treatment_selections do |builder| %>
<%= render 'treatment_selection', f: builder %>
<% end %>
<%= f.submit 'Submit' %>
<% end %>
Treatment selection partial
<%= f.collection_select :treatment_id, Treatment.order('name'), :id, :name %>
<%= f.check_box :ambassador %>
<%= f.hidden_field :_destroy, as: :hidden %>
<%= link_to 'Delete', '#', class: "remove_treatment" %>
Upvotes: 0
Views: 1268
Reputation: 101811
Your assocations are not quite right.
In order to join Trial to Repetition you need to setup an association in TreatmentSelection to go through:
class TreatmentSelection < ApplicationRecord
belongs_to :trial
belongs_to :treatment
has_many :repetitions, dependent: :destroy
end
This lets you properly go through this assocation:
class Trial < ApplicationRecord
has_many :treatment_selections, dependent: :destroy
has_many :repetitions, through: :treatment_selections
end
You are also using the source
option improperly. Its used when you have to tell ActiveRecord which assocation on the join model corresponds to the desired model (when the names don't match).
You should also remove the repetions.trial_id
column as it creates an undesirable duplication.
You also need to add the assocations to repetitions that go back up the chain:
class Repetition < ApplicationRecord
belongs_to :treatment_selection
has_one :treatment, through: :treatment_selection
has_one :trial, through: :treatment_selection
end
You also should setup the nested attributes as follows:
class Trial < ApplicationRecord
# ...
accepts_nested_attributes_for :treatment_selections, allow_destroy: true
end
class TreatmentSelection < ApplicationRecord
# ...
accepts_nested_attributes_for :repetitions, allow_destroy: true
end
This is crucial since the attributes should be nested two levels.
{
trial: {
treatment_selection_attributes: [
{
treatment_id: 1,
repetition_attributes: [
{ foo: 'bar' }
]
},
{
treatment_id: 1,
repetition_attributes: [
{ foo: 'bar' }
]
}
]
}
}
ActiveRecord does not understand that the nested attributes go through another assocation.
You also should nest the calls to fields_for.
<%= form_with(model: trial, local: true) do |f| %>
<%= f.fields_for :treatment_selections do |tsf| %>
<%= tsf.fields_for :repetitions do |rf| %>
<% end %>
<% end %>
<% end %>
Upvotes: 1
Reputation: 1
you are building repetitions object on TreatmentSelection model which does not specify relationship between TreatmentSelection and Repetition model , you need to add has_many :repetitions in TreatmentSelection model to make this working.
Upvotes: 0
Reputation: 186
Your code in create
action @trial.treatment_selections.build
will return a TreatmentSelection
object which doesn't have a definition for repetitions
as defined in its model.
Maybe you mean:
@trial.treatment_selections.build
@trial.repetitions.build
Upvotes: 2