Mel
Mel

Reputation: 2685

Rails 4 - Associations - nested attributes - unpermitted parameter

I am trying to make an app in Rails 4. I'm having an awful time of getting basic relationships set up.

I have models for user, profile, vision, personality & qualifications.

The associations are:

User.rb

Profile.rb

belongs_to :user


  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, reject_if: :all_blank, allow_destroy: true    

Vision.rb

belongs_to :profile

Personality.rb

  belongs_to :profile

Qualifications.rb

belongs_to :profile

The controllers are:

User

class UsersController < ApplicationController
before_action :set_user, only: [:index, :show, :edit, :update, :finish_signup, :destroy]

  def index
    # if params[:approved] == "false"
    #   @users = User.find_all_by_approved(false)
    # else
      @users = User.all
    # end

  end

  # GET /users/:id.:format
  def show
    # authorize! :read, @user
  end

  # GET /users/:id/edit
  def edit
    # authorize! :update, @user
  end

  # PATCH/PUT /users/:id.:format
  def update
    # authorize! :update, @user
    respond_to do |format|
      if @user.update(user_params)
        sign_in(@user == current_user ? @user : current_user, :bypass => true)
        format.html { redirect_to @user, notice: 'Your profile was successfully updated.' }
        format.json { head :no_content }
      else
        format.html { render action: 'edit' }
        format.json { render json: @user.errors, status: :unprocessable_entity }
      end
    end
  end

  # GET/PATCH /users/:id/finish_signup
  def finish_signup
    # authorize! :update, @user 
    if request.patch? && params[:user] #&& params[:user][:email]
      if @user.update(user_params)
        @user.skip_reconfirmation!
        sign_in(@user, :bypass => true)
        redirect_to root_path, notice: 'Your profile was successfully updated.'
        # redirect_to [@user, @user.profile || @user.build_profile]
        # sign_in_and_redirect(@user, :bypass => true)
      else
        @show_errors = true
      end
    end
  end

  # DELETE /users/:id.:format
  def destroy
    # authorize! :delete, @user
    @user.destroy
    respond_to do |format|
      format.html { redirect_to root_url }
      format.json { head :no_content }
    end
  end

  private
    def set_user
      @user = User.find(params[:id])
    end

    def user_params
      # params.require(:user).permit(policy(@user).permitted_attributes)
      accessible = [ :first_name, :last_name, :email, :avatar ] # extend with your own params
      accessible << [ :password, :password_confirmation ] unless params[:user][:password].blank?
      # accessible << [:approved] if user.admin
      params.require(:user).permit(accessible)
    end

end

Profile

class ProfilesController < ApplicationController
  before_action :set_profile, only: [:show, :edit, :update, :destroy]
  before_action :authenticate_user!
  after_action :verify_authorized

  # GET /profiles
  # GET /profiles.json
  def index
    @profiles = Profile.all
    authorize @profiles
  end

  # GET /profiles/1
  # GET /profiles/1.json
  def show
  end

  # GET /profiles/new
  def new
    @profile = Profile.new
    @profile.qualifications.build
    @profile.build.vision
    @profile.build.personality
    @profile.addresses.build

    authorize @profile

  end

  # GET /profiles/1/edit
  def edit
  end

  # POST /profiles
  # POST /profiles.json
  def create
    @profile = Profile.new(profile_params)
    authorize @profile

    respond_to do |format|
      if @profile.save
        format.html { redirect_to @profile }
        format.json { render :show, status: :created, location: @profile }
      else
        format.html { render :new }
        format.json { render json: @profile.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /profiles/1
  # PATCH/PUT /profiles/1.json
  def update

    # successful = @profile.update(profile_params)

    # Rails.logger.info "xxxxxxxxxxxxx"
    # Rails.logger.info successful.inspect
    # [email protected]
    # user.update.avatar
    respond_to do |format|
      if @profile.update(profile_params)
        format.html { redirect_to @profile }
        format.json { render :show, status: :ok, location: @profile }
      else
        format.html { render :edit }
        format.json { render json: @profile.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /profiles/1
  # DELETE /profiles/1.json
  def destroy
    @profile.destroy
    respond_to do |format|
      format.html { redirect_to profiles_url }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_profile
      @profile = Profile.find(params[:id])
      authorize @profile
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    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: [:id, :profile_id, :long_term, :immediate_challenge], 
          qualifications_attributes: [:id, :level, :title, :year_earned, :pending, :institution, :_destroy],
          addresses_attributes: [:id, :unit, :building, :street_number, :street, :city, :region, :zip, :country, :latitude, :longitude, :_destroy],
          industries_attributes: [:id, :sector, :icon] )
    end
end

Vision

class VisionsController < ApplicationController
  before_action :set_vision, only: [:show, :edit, :update, :destroy]
  before_action :authenticate_user!

  # GET /visions
  # GET /visions.json
  def index
    @visions = Vision.all
    authorize @visions
  end

  # GET /visions/1
  # GET /visions/1.json
  def show
  end

  # GET /visions/new
  def new
    @vision = Vision.new
    authorize @vision
  end

  # GET /visions/1/edit
  def edit
  end

  # POST /visions
  # POST /visions.json
  def create
    @vision = Vision.new(vision_params)
    authorize @vision

    respond_to do |format|
      if @vision.save
        format.html { redirect_to @vision }
        format.json { render :show, status: :created, location: @vision }
      else
        format.html { render :new }
        format.json { render json: @vision.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /visions/1
  # PATCH/PUT /visions/1.json
  def update
    respond_to do |format|
      if @vision.update(vision_params)
        format.html { redirect_to @vision }
        format.json { render :show, status: :ok, location: @vision }
      else
        format.html { render :edit }
        format.json { render json: @vision.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /visions/1
  # DELETE /visions/1.json
  def destroy
    @vision.destroy
    respond_to do |format|
      format.html { redirect_to visions_url }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_vision
      @vision = Vision.find(params[:id])
      authorize @vision
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def vision_params
      params[:vision].permit(:profile_id, :long_term, :immediate_challenge)
    end
end

Qualifications

class QualificationsController < ApplicationController
  before_action :set_qualification, only: [:show, :edit, :update, :destroy]
  before_action :authenticate_user!

  # GET /qualifications
  # GET /qualifications.json
  def index
    @qualifications = Qualification.all
    authorize @qualifications
  end

  # GET /qualifications/1
  # GET /qualifications/1.json
  def show
  end

  # GET /qualifications/new
  def new
    @qualification = Qualification.new
    authorize @qualification
  end

  # GET /qualifications/1/edit
  def edit
  end

  # POST /qualifications
  # POST /qualifications.json
  def create
    @qualification = Qualification.new(qualification_params)
    authorize @qualification

    respond_to do |format|
      if @qualification.save
        format.html { redirect_to @qualification }
        format.json { render :show, status: :created, location: @qualification }
      else
        format.html { render :new }
        format.json { render json: @qualification.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /qualifications/1
  # PATCH/PUT /qualifications/1.json
  def update
    respond_to do |format|
      if @qualification.update(qualification_params)
        format.html { redirect_to @qualification }
        format.json { render :show, status: :ok, location: @qualification }
      else
        format.html { render :edit }
        format.json { render json: @qualification.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /qualifications/1
  # DELETE /qualifications/1.json
  def destroy
    @qualification.destroy
    respond_to do |format|
      format.html { redirect_to qualifications_url }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_qualification
      @qualification = Qualification.find(params[:id])
      authorize @qualification
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def qualification_params
      params[:qualification].permit(:profile_id, :level, :title, :year_earned, :pending, :institution)
    end
end

Personality

class PersonalitiesController < ApplicationController
  before_action :set_personality, only: [:show, :edit, :update, :destroy]
  before_action :authenticate_user!

  # GET /personalities
  # GET /personalities.json
  def index
    @personalities = Personality.all
    authorize @personalities
  end

  # GET /personalities/1
  # GET /personalities/1.json
  def show
  end

  # GET /personalities/new
  def new
    @personality = Personality.new
    authorize @personality
  end

  # GET /personalities/1/edit
  def edit
  end

  # POST /personalities
  # POST /personalities.json
  def create
    @personality = Personality.new(personality_params)
    authorize @personality

    respond_to do |format|
      if @personality.save
        format.html { redirect_to @personality }
        format.json { render :show, status: :created, location: @personality }
      else
        format.html { render :new }
        format.json { render json: @personality.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /personalities/1
  # PATCH/PUT /personalities/1.json
  def update
    respond_to do |format|
      if @personality.update(personality_params)
        format.html { redirect_to @personality }
        format.json { render :show, status: :ok, location: @personality }
      else
        format.html { render :edit }
        format.json { render json: @personality.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /personalities/1
  # DELETE /personalities/1.json
  def destroy
    @personality.destroy
    respond_to do |format|
      format.html { redirect_to personalities_url }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_personality
      @personality = Personality.find(params[:id])
      authorize @personality
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def personality_params
      params[:personality].permit( :average_day, :fantasy_project, :preferred_style)
    end
end

The profile form has:

<div class="container-fluid"> 
  <div class="row">
    <div class="col-xs-10 col-xs-offset-1" >

    <%= simple_form_for(@profile, multipart: true) do |f| %>
            <%= f.error_notification %>

              <div class="form-inputs">

          <div class="intpol2">
            About you
          </div>

          <div class="row">
            <div class="col-md-4">
              <%= f.input :title,  autofocus: true %>
            </div>

            <div class="col-md-8">
              <%= f.input :occupation, :label => "Your occupation or job title" %>
            </div>

          </div>

          <div class="row">
            <div class="col-md-6">
               <%= render 'users/profileimgform', f: f %>   
            </div>

            <div class="col-md-6">
             <%= f.input :hero, as: :file, :label => "Add a background image to your profile page" %>
            </div>

          </div>

          <div class="row">
            <div class="col-md-6">
               <%= f.input :working_languages, :label => "Select your working languages" %>
            </div>

            <div class="col-md-6">
             <%= f.input :external_profile, :label => "Add a link to your external profile"  %>
            </div>

          </div>

          <div class="row">
            <div class="col-md-12">
              <%= f.input :overview, :label => "Tell us about yourself", :input_html => {:rows => 10} %>
            </div>
          </div>

          <div class="row">
            <div class="col-md-12">


              <%= render 'industries/industry_selector', f: f %>  
            </div>
          </div>

          <div class="row">

            <div class="intpol2">
              Your professional qualifications
            </div>
            <%= f.simple_fields_for :qualifications do |f| %>

              <%= render 'qualifications/qualification_fields', f: f %>  
            <% end %>
          </div>


          <div class="row">

            <div class="col-md-6">

               <%= link_to_add_association 'Add a qualification', f, :qualifications, partial: 'qualifications/qualification_fields' %>

            </div>

          </div>

          <div class="row">

            <div class="intpol2">
              Your career
            </div>

              <%= render 'personalities/form', f: f %>
          </div>

          <div class="row">

            <div class="intpol2">
              Your research vision
            </div>

          </div>

          <div class="row">

            <div class="intpol2">

              Your addresss
            </div>

              <%= f.simple_fields_for :addresses do |f| %>

              <%= render 'addresses/address_fields', f: f %>  
              <% end %>
          </div>

          <div class="row">
            <div class="col-md-6">

               <%= link_to_add_association 'Add an address', f, :addresses, partial: 'addresses/address_fields' %>

            </div>

                </div>

              <div class="form-actions">
                <%= f.button :submit, "Submit", :class => 'formsubmit' %>
              </div>

        <% end %>
         </div>
    </div>  
  </div>
</div>      

The nested forms are:

Vision

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

Qualifications

<div class="nested-fields">
<div class="container-fluid">



          <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-8">
                    <%= f.input :pending, :label => "Are you currently studying toward this qualification?" %>
                </div>
            </div>       

            <div class="row">
                <div class="col-md-4">
                    <%= f.input :level,   collection: [ "Bachelor's degree", "Master's degree", "Ph.D", "Post Doctoral award"] %>
                </div>




                <div class="col-md-4">
                <%= f.input :year_earned, :label => "When did you graduate?", collection: (Date.today.year - 50)..(Date.today.year) %>
                </div>

          </div>


          <div class="row">
                <div class="col-md-6">
                    <%= link_to_remove_association 'Remove this qualification', f %>
                </div>

          </div>


          </div>

</div>  
</div>      

Personality

<%= f.simple_fields_for :profile do |f| %>
      <%= f.simple_fields_for :personality do |ff| %>

  <div class="form-inputs">
      <%= ff.input :average_day, :label => "What does your day to day work life involve?", as: :text, :input_html => {:rows => 5}  %>
      <%= ff.input :fantasy_project, :label => "Describe your fantasy project", as: :text, :input_html => {:rows => 5}  %>
      <%= ff.input :preferred_style, :label => "How do you like to work (distributed teams, on site, easy going about it)?", as: :text, :input_html => {:rows => 5}  %>


  </div>

  <% end %>

<% end %>

The profile show page has:

<div class="intpol3">
                <%= @profile.personality.try(:average_day) %>
            </div>

            <div class="profilesubhead">
                My fantasy league research project
            </div>
            <div class="intpol3">
                <%= @profile.personality.try(:fantasy_project) %>
            </div>

            <div class="profilesubhead">
                Working style
            </div>
            <div class="intpol3">
                <%= @profile.personality.try(:preferred_style) %>
            </div>

            <div class="profilesubhead">
                My research vision
            </div>
            <div class="profilesubhead">
                <%= @profile.vision.try(:long_term) %>
            </div>

            <div class="profilesubhead">
                Immediate research challenge in my field
            </div>
            <div class="profilesubhead">
                <%= @profile.vision.try(:immediate_challenge) %>
            </div>

The routes are:

resources :profiles, only: [:show, :edit, :update, :destroy]

  resources :qualifications
  resources :personalities
  resources :visions
  resources :users do
     resources :profiles, only: [:new, :create]
  end

From the above, you can see that I have experimented with a variety of different solutions, including using cocoon gem (which I have used for the qualifications model).

Nothing works. The only attributes displaying on the profiles show page are those stored in the profiles table or the users table. None of the models that belong to profile are displaying in the profiles show. Models that have a polymorphic association with profile are displaying.

Currently, when I save and try this, the local host server shows an error called 'Unpermitted parameter: profile'. I can see for qualifications, that the entries in the form are showing in the server, but they do not appear on the profile page. For each of the rest of the nested attributes - none of them display on the profile show page. The rails console shows all of the attributes for personality (with profile id of the user whose profile page is showing are nil).

In my profiles form, I can't even display the vision form fields at all. I thought that might be because I added this:

reject_if: :all_blank, allow_destroy: true 

to the accepts nested attributes for :vision in my profile model - but commenting that section out does not improve the situation.

I previously asked this question but could not find any help.

Rails - Displaying associated attributes - unpermitted parameters

I have tried each of the solutions I listed in that post (I think the site point article I attach at the end of that post is the closest thing to relevant that I've been able to find).

I'm out of ideas for things to try to check or change. Can anyone see what I have done wrong?

The server shows the following output for when I try to update the profile by adding entries to the personalities section of the profile form:

Started PATCH "/profiles/8" for ::1 at 2016-01-09 12:38:04 +1100
Processing by ProfilesController#update as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"Xul2nPMbg/3MnMPoTMtEIFfVg==", "profile"=>{"title"=>"", "occupation"=>"tester", "working_languages"=>"tester", "external_profile"=>"tester", "overview"=>"tester tester tester tester tester tester tester tester tester tester tester tester tester tester tester tester tester tester tester tester tester tester tester tester tester tester tester tester tester tester tester tester tester tester tester tester tester tester tester tester tester tester tester tester ", "qualifications_attributes"=>{"0"=>{"title"=>"dfds", "pending"=>"0", "level"=>"Master's degree", "year_earned"=>"1967", "_destroy"=>"false", "id"=>"4"}}, "profile"=>{"personality"=>{"average_day"=>"sdf", "fantasy_project"=>"", "preferred_style"=>""}}}, "industry"=>{"sector"=>"5"}, "commit"=>"Submit", "id"=>"8"}
  Profile Load (0.3ms)  SELECT  "profiles".* FROM "profiles" WHERE "profiles"."id" = $1 LIMIT 1  [["id", 8]]
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1  ORDER BY "users"."id" ASC LIMIT 1  [["id", 9]]
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1  [["id", 9]]
Unpermitted parameter: profile
   (0.1ms)  BEGIN
  Qualification Load (0.3ms)  SELECT "qualifications".* FROM "qualifications" WHERE "qualifications"."profile_id" = $1 AND "qualifications"."id" = 4  [["profile_id", 8]]
   (0.1ms)  COMMIT
Redirected to http://localhost:3000/profiles/8
Completed 302 Found in 12ms (ActiveRecord: 1.3ms)

TAKING MIHAILS SUGGESTION

I replace the personality form fields with:

      <%= f.simple_fields_for :personality do |ff| %>

  <div class="form-inputs">
      <%= ff.input :average_day, :label => "What does your day to day work life involve?", as: :text, :input_html => {:rows => 5}  %>
      <%= ff.input :fantasy_project, :label => "Describe your fantasy project", as: :text, :input_html => {:rows => 5}  %>
      <%= ff.input :preferred_style, :label => "How do you like to work (distributed teams, on site, easy going about it)?", as: :text, :input_html => {:rows => 5}  %>


  </div>

  <% end %>

When I try this, the personality form fields do not render as part of the profiles form at all

TAKING MIHAILS NEXT SUGGESTION

I replace the build personality action in the profiles controller with:

@profile.personality = Personality.new

It was previously: @profile.build.personality

When I try this, I get the same error as previously - I can't see the form fields for personality at all.

TAKING TRH'S SUGGESTION:

If i make the new action:

@profile.build_personality 

I still can't see the form for personality fields. No change to the prior attempts

FURTHER UPDATE

It seems the explanation in Mihail's second suggestion is the same effect as TRH's suggestion. Currently, my profiles controller has this new action:

 def new
    @profile = Profile.new
    @profile.qualifications_build
    @profile.build_vision
    @profile.build_personality 
    @profile.addresses_build

    authorize @profile

  end

It's still not working.

TAKING MIHAIL'S NEXT SUGGESTION

def new
    @profile = Profile.new
    @profile.qualifications_build
    @profile.build_vision
    @profile.build_personality unless personality
    @profile.addresses_build

    authorize @profile

  end

I still can't see the profile form fields for personality.

SOLUTION:

def new
    @profile = Profile.new
    @profile.qualifications_build
    @profile.build_vision
    @profile.build_personality unless @profile.personality
    @profile.addresses_build

    authorize @profile

  end

  # GET /profiles/1/edit
  def edit
    @profile.build_personality unless @profile.personality
  end

Upvotes: 1

Views: 531

Answers (1)

Mihails Butorins
Mihails Butorins

Reputation: 918

The problem is in the personality fields partial. If you will look closer on the attributes submitted to server on update, you will see that they are wrong.

"profile"=>{"profile"=>{"personality"=>{"average_day"=>"sdf"}}

So, to fix this you need to check this partial and leave only nested fields block in it.

<%= f.simple_fields_for :personality do |ff| %>

  <div class="form-inputs">
    <%= ff.input :average_day, :label => "What does your day to day work life involve?", as: :text, :input_html => {:rows => 5}  %>
    <%= ff.input :fantasy_project, :label => "Describe your fantasy project", as: :text, :input_html => {:rows => 5}  %>
    <%= ff.input :preferred_style, :label => "How do you like to work (distributed teams, on site, easy going about it)?", as: :text, :input_html => {:rows => 5}  %>
  </div>

<% end %>

Upvotes: 3

Related Questions