I am trying to make an app in Rails 4.
I have a profile model, a qualification model, and a vision model.
The associations are:
profile.rb
has_many :qualifications
accepts_nested_attributes_for :qualification
has_one :vision
accepts_nested_attributes_for :vision
qualification.rb
belongs_to :profile
vision.rb
belongs_to :profile
I have several other models in a similar construct as qualification and vision (ie. they belong to profile).
All relevant attributes have been included in the strong params for the model and where they are nested, also in the profile strong params.
def profile_params
params[:profile].permit(:user_id, :title, :hero, :overview, :research_interest, :occupation, :external_profile,
:working_languages, :tag_list,
personality_attributes: [:average_day, :fantasy_project, :preferred_style],
vision_attributes: [:long_term, :immediate_challenge],
qualifications_attributes: [:id, :level, :title, :year_earned, :institution, :_destroy] )
end
The forms are set up as:
<%= simple_form_for(@profile) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<div class="intpol2">
Your professional qualifications
</div>
<%= render 'qualifications/form', f: f %>
</div>
<div class="intpol2">
Your research vision
</div>
<%= render 'visions/form', f: f %>
</div>
<div class="form-actions">
<%= f.button :submit, "Submit", :class => 'formsubmit' %>
</div>
<% end %>
Then in the qualifications form, I have:
<%= simple_fields_for :qualification do |f| %>
<div class="form-inputs">
<div class="row">
<div class="col-md-6">
<%= f.input :title, :label => "Your award" %>
</div>
<div class="col-md-6">
</div>
</div>
<div class="row">
<div class="col-md-6">
<%= f.input :level, collection: [ "Bachelor's degree", "Master's degree", "Ph.D", "Post Doctoral award"] %>
</div>
<div class="col-md-6">
<%= f.input :year_earned, :label => "When did you graduate?", collection: (Date.today.year - 50)..(Date.today.year) %>
</div>
</div>
</div>
<% end %>
And, similarly, in the vision form, I have:
<%= simple_fields_for :vision do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.input :long_term, as: :text, :label => "What is your long term research vision?", :input_html => {:rows => 10} %>
<%= f.input :immediate_challenge, as: :text, :label => "What do you see as the immediate challenge to be addressed in pursuit of your long-term vision?", :input_html => {:rows => 10} %>
</div>
<% end %>
The problem is, these attributes are not saving when I press submit on the profile form. The console shows that there are no inputs in either of these tables.
Is there another step to this process?
I think the problem runs deeper. This is because I have another model called address.rb. It's polymorphic.
The associations are:
address.rb
belongs_to :addressable, :polymorphic => true
profile.rb
has_many :addresses, as: :addressable
In my profiles show, I have:
<%= render 'profiles/geography' %>
In my geography partial, I have:
<% if @profile.addresses.any? %>
<%= @profile.addresses.first.country_name.titlecase %>
<% else %>
<span class="profileeditlink">
<%= link_to "Add your location", new_address_path %>
</span>
<% end %>
The console shows I have 4 saved addresses. I only have one user.
The profile show page displays the else condition (i.e. add an address), even when there is an address saved.
The only attributes that successfully display on my profile show page, are those that are in the profiles table. All associated attributes just appear blank. Those in qualifications/vision, aren't saving to the db.
I can't figure out why this is all messed up.
Another angle to this problem is that my qualifications form has now saved an entry. It shows in the rails console. However, my qualifications partial which is supposed to display that entry, only shows the title.
<div class="profilequalificationstitle">
Qualifications
</div>
</div>
</div>
<% Qualification.all.sort_by(&:year_earned).each do |qual| %>
<div class="row">
<div class="col-md-12">
<div class="profilequalifications">
<%= @profile.try(:qualification).try(:title) %>
</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="intpolmin">
<%= @profile.try(:qualification).try(:year_earned) %>:
</div>
</div>
<div class="col-md-8">
<div class="intpolmin">
<%= @profile.try(:qualification).try(:institution) %>
</div>
</div>
</div>
<% end %>
</div>
</div>
TAKING RICH PECK'S SUGGESTION BELOW:
I change the format of my strong params definition in the profiles controller so that it doesnt use the format provided by rails scaffolding and instead appears as:
def profile_params
params.require(:profile).permit(:user_id, :title, :hero, :overview, :research_interest, :occupation, :external_profile,
:working_languages, :tag_list,
user_attributes: [:avatar],
personality_attributes: [:average_day, :fantasy_project, :preferred_style],
vision_attributes: [:long_term, :immediate_challenge],
qualifications_attributes: [:id, :level, :title, :year_earned, :institution, :_destroy] )
end
All of the associated models still use the rails scaffolding format - i.e:
def vision_params
params[:vision].permit(:profile_id, :long_term, :immediate_challenge)
end
The new method in the profiles controller has:
def new
@profile = Profile.new
@profile.qualifications.build
@profile.vision.build
@profile.personality.build
authorize @profile
end
As for the manner in which the associations are expressed, its a personal preference to keep the nested attributes with the association. I can see that this suggestion uses less lines to achieve the same thing, but my preference is to have the association with the nested attribute declaration.
My profile model associations are:
belongs_to :user
has_many :addresses, as: :addressable
has_one :personality
accepts_nested_attributes_for :personality
has_many :qualifications
accepts_nested_attributes_for :qualifications, reject_if: :all_blank, allow_destroy: true
has_one :vision
accepts_nested_attributes_for :vision
My profile show page then tries to use attributes from other models in a number of ways. It works successfully when I try to use attributes from the user model (profile belongs to user).
It does not work when I try to use attributes from models that belong to profile.
I have the following ways of using those attributes - none of which are working:
- render a partial: <%= render 'profiles/qualifications' %>
Qualifications
<div class="row">
<div class="col-md-12">
<div class="profilequalifications">
<!-- @qualification.award.profile -->
</div>
</div>
</div>
- directly refer to the attribute: <%= @profile.try(:vision).try(:long_term) %>
Then - my nested forms for vision and personality are in this format:
<%= f.simple_fields_for :profile do |f| %>
<%= f.simple_fields_for :vision do |ff| %>
<div class="form-inputs">
<%= ff.input :long_term, as: :text, :label => "What is your long term research vision?", :input_html => {:rows => 10} %>
<%= ff.input :immediate_challenge, as: :text, :label => "What do you see as the immediate challenge to be addressed in pursuit of your long-term vision?", :input_html => {:rows => 10} %>
</div>
<% end %>
<% end %>
My qualifications form is slightly different because it uses cocoon gem to add functionality to repeat that form for several qualifications.
FURTHER INVESTIGATION
I've found a clue.
In my terminal, I can see after the patch for creating the attribute in the form, there is an error which says:
Unpermitted parameter: profile
Looking into that, I've found each of these posts, suggesting the following solutions:
Solution 1: change the strong parameters in the profiles controller to include the id of the parent (in my case: profile_id):
Unpermitted Parameters: profile (NestedAttributes) - RAILS 4
I try changing:
vision_attributes: [:id, :long_term, :immediate_challenge],
to
vision_attributes: [:id, :profile_id, :long_term, :immediate_challenge],
Outcome: no difference - the same error is generated.
Solution 2: change the form
Nested attributes - Unpermitted parameters Rails 4
Taking this suggestion, I tried changing the vision form to:
<%= f.simple_fields_for :profile_attributes do |f| %>
<%= f.simple_fields_for :vision, @profile.vision do |ff| %>
<div class="form-inputs">
<%= ff.input :long_term, as: :text, :label => "What is your long term research vision?", :input_html => {:rows => 10} %>
<%= ff.input :immediate_challenge, as: :text, :label => "What do you see as the immediate challenge to be addressed in pursuit of your long-term vision?", :input_html => {:rows => 10} %>
</div>
<% end %>
<% end %>
Outcome: no difference - the same error results.
Solution 3: a further variation on the way strong parameters are expressed in the profiles controller:
Rails 4 Nested Attributes Unpermitted Parameters
change the way the nested strong params are expressed in the profiles controller from:
vision_attributes: [:id, :profile_id, :long_term, :immediate_challenge],
to
:vision_attributes[:id, :profile_id, :long_term, :immediate_challenge],
Outcome:
A new error:
syntax error, unexpected ',', expecting => ...ng_term, :immediate_challenge], ... ^ /Users/mem/cl/cf3/app/controllers/profiles_controller.rb:95: syntax error, unexpected ',', expecting keyword_end /Users/mem/cl/cf3/app/controllers/profiles_controller.rb:96: syntax error, unexpected ',', expecting keyword_end /Users/mem/cl/cf3/app/controllers/profiles_controller.rb:97: syntax error, unexpected ')', expecting keyword_end
Im stuck, lost and frustrated. Have just dropped more $$ than I can afford on codementor.io for the outcome to be that the mentor doesnt know how to help. Would love some ideas for what to try next.
ANOTHER SOLUTION
I found this article: http://www.sitepoint.com/complex-rails-forms-with-nested-attributes/#adding-an-address
Taking the approach shown in that tutorial, I tried creating a form helper which has:
module FormHelper
def setup_profile(profile)
profile.vision ||= Vision.new
profile
end
end
(I don't know what the word profile on the second line of the definition body means) but it is consistent with the approach taken in the tutorial for user/address.
I then change my profile form to:
<%= f.simple_fields_for (setup_profile(profile)) do |f| %>
<%= render 'visions/form', f: f %>
<% end %>
And change my visions form partial to:
<div class="form-inputs">
<%= ff.input :long_term, as: :text, :label => "What is your long term research vision?", :input_html => {:rows => 10} %>
<%= ff.input :immediate_challenge, as: :text, :label => "What do you see as the immediate challenge to be addressed in pursuit of your long-term vision?", :input_html => {:rows => 10} %>
</div>
When I save these changes and try again, I get this error:
undefined local variable or method `profile' for #<#<Class:0x007fe113fcd680>:0x007fe11f2769d0>
The error message points to this line of the profiles form:
<%= f.simple_fields_for (setup_profile(profile)) do |f| %>
I don't know what this error message means, but I think it's because profile should either be '@profile' or ':profile' - I don't know why, it just often appears that way.
If i change this line to:
<%= f.simple_fields_for (setup_profile(@profile)) do |f| %>
I get this error:
undefined method `long_term' for #<Profile:0x007fe11c241c38>
I think this error means that the form is looking for an attribute called "long_term" in the profile table, instead of the vision table.
If i change this line to:
<%= f.simple_fields_for (setup_profile(@profile.vision)) do |f| %>
I get this error in my form helper:
undefined method `vision' for #<Vision:0x007fe117e95278>
Before I keep going with randomly trying things, can anyone see if there is a problem with the way I have tried to implement the solution shown in the site point article?