v2lrf
v2lrf

Reputation: 331

Rails 5.1 - How nested 2 models inside form for - between a normal table and a polymorphic one?

My target is to create a project instance and also its nested attributes for credits (polymorphic table) inside one single form.

⚠️ "error param is missing or the value is empty: credit" ⚠️

I can't find the correct syntax to provide the instance to the polymorphic table. The problem is inside my controller I guess Project(action create & new).

👌 When I test my code with the console everything seams to be fine. (I add the copy of that test below.)

But when I'm starting to do that within my view I have some issues and

I can't figure out the right syntax.

🔥 => to create the credit instance through the project form

🔥 => And for sure I can't display and update those instance

🙏🏻

1 - Let's say that you have 3 models.

    Project has_many credits, as creditable
    Profile has_many credits, as creditable
    Credit belongs_to :creditable, polymorphic: true

    # in this post we will focus on a form which  include @credit 
    # inside a @project form
    # so I let the @profile for now but I will use it later 

routes

    resources :projects do
       resources :credits, only: [:create, :edit, :update, :destroy]
    end
For instance let's say that I would like to display a single form for @project which include field for some credit attributes (like content, link, category).

My objective it to create that project instance and also its nested attributes for credits.

=> the form is inside Project_new ProjectController action new /create...

    <%= simple_form_for @project do |f| %>
        <div class="content-form">
          <div class="form-inputs">
            <%= f.simple_fields_for :credit do |credit_form| %>
               <%= credit_form.hidden_field :creditable_type, :value => @creditable_type %>
               <%= credit_form.input :category %>
               <%= credit_form.input :content %>
               <%= credit_form.input :link %>
            <% end %>
            <%= f.input :name, label: false, placeholder: "nom du project" %>
            <%= f.input :year, label: false, placeholder: "Année de création" %>
            <%= f.input :description, label: false, placeholder: "Une description" %>
            <label class="super-label">
              <%= f.input :mainphoto, input_html: {preview_version: :thumb}, label: false, id: "over-btn"  %>
              <%= f.attachinary_file_field :photos, as: :attachinary, cloudinary: {use_filename: true, unique_filename: false},  label: false, id: "over-btn" %>
              <div class="form-actionss">
                <%= f.button :submit, class: "btn-main-romain" %>
              </div>
            </label>
            <p id="filename hidden"></p>
          </div>
        </div>
      <% end %>

2 - Exact copy from current models

    class Profile < ApplicationRecord
     belongs_to :user
     has_many :credits, :as => {:creditable => 'profile'}, :dependent => :destroy, autosave: true
     has_attachment :photo
    end

   class Project < ApplicationRecord
    #validates :name, presence:true
    #validates :year, presence:true
    #validates :description, presence:true
    has_many :credits, :as => {:creditable => 'project'}, :dependent => :destroy, autosave: true
    attr_accessor :link
    attr_accessor :category
    attr_accessor :content
    accepts_nested_attributes_for :credits

    has_attachments :photos, maximum: 10
    mount_uploader :mainphoto, PhotoUploader
   end

   class Credit < ApplicationRecord
    belongs_to :creditable, polymorphic: true
   end

3 - Exact copy from current schema

   create_table "credits", force: :cascade do |t|
    t.string "category"
    t.string "content"
    t.string "link"
    t.string "creditable_type"
    t.integer "creditable_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
   end

   create_table "projects", force: :cascade do |t|
    t.string "name"
    t.integer "year"
    t.text "description"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.string "place"
    t.string "mainphoto"
   end

   create_table "profiles", force: :cascade do |t|
    t.bigint "user_id"
    t.string "photo"
    t.string "cell_phone"
    t.string "city"
    t.string "email"
    t.text "description"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.string "first_name"
    t.string "last_name"
    t.integer "zip_code"
    t.string "street"
    t.string "coutry"
    t.index ["user_id"], name: "index_profiles_on_user_id"
   end

4 - Crash Test inside the console

=> Everything seams to be fine.

   #create an instance of project
   [1] Project.create(name:'project1')
    => #<Project:0x007fb3d7a64e78
   id: 15,
   name: "project1",
   year: nil,
   description: nil,
   created_at: Sun, 04 Feb 2018 12:09:29 UTC +00:00,
   updated_at: Sun, 04 Feb 2018 12:09:29 UTC +00:00,
   place: nil,
   mainphoto: nil>

   #put that instance of project inside a variable called "p"
   [2]  p =  Project.last
    ...

   #create an instance of credit with an instance of project ("p") as 
   attribute
   [3]  Credit.create(category:'webmaster', creditable:p)

   => #<Credit:0x007fb3d0f9e4e0
   id: 1,
   category: "webmaster",
   content: nil,
   link: nil,
   creditable_type: "Project",
   creditable_id: 15,


   # check if it's possible to pick up back the project instance
   [5] Credit.last.creditable

   => #<Project:0x007fb3d0e827c8
   id: 15,
   name: "project1",
   year: nil,
   description: nil,
   created_at: Sun, 04 Feb 2018 12:09:29 UTC +00:00,
   updated_at: Sun, 04 Feb 2018 12:09:29 UTC +00:00,
   place: nil,
   mainphoto: nil

5 - So what is the matter?

It's not working yet inside my app view, controller

6 - Investigate the project controller

  class ProjectsController < ApplicationController
    skip_before_action :authenticate_user!, only: [:index]

    before_action :set_project, only:[:show, :edit, :update, :destroy]

    layout "application", only: [:index, :destroy,:show,]
    layout "devise", only: [ :create, :new, :edit, :update ]

    def index
      @projects = Project.all
      @profile = User.find(1).profile
    end


    def show
    end

    def new
      @project = Project.new
      @credit = Credit.new
      @creditable_type = "project"
    end

    def create
      @project = Project.create(project_params)
      @credit = Credit.create(credit_params) #syntax pblm here

      creditable = params["project"]["credit"]["creditable_type"]

      #category = params["project"]["credit"]["category"]
      #link = params["project"]["credit"]["link"]
      #content = params["project"]["credit"]["content"]
      #allcreditables = {creditable,category,link,content}
      #@project.credits.create(allcreditables)
      
      

      #something like ...
      #@credit = @project.credits.create...
      # give me an error like ERROR "no implicit conversion of symbol 
      # into string"

      if @project.save
        redirect_to @project
      else
        render :new
      end
    end

    def edit
    end

    def update
      @project.update(project_params)
      if @project.save
        redirect_to @project
      else
        render :edit
      end
    end

    def destroy
      @project.destroy
      redirect_to projects_path
      flash[:notice] = "Le project - #{@project.name} -  à bien été supprimé"
    end


    private

    def set_project
      @project = Project.find(params[:id])
      @projects = Project.all
      @profile = User.find(1).profile
    end

    def credit_params
      params.require(:credit)
      .permit(:link,
        :category,
        :content,
        :creditable_type)
      .merge(project: @project)
    end

    def project_params
      params.require(:project).permit(
        :name,
        :year,
        :place,
        :description,
        :mainphoto,
        photos: [],
        :credits_attributes => [:link,:category,:content])
    end
  end

⚠️ 💀 the problem comes form the new and create method, I tried to do something with credit_params, but at the end @project is recorded but @Project.credits is not. I can't get the trick. Credits_attributes are inside a hash and hard for me to figure out back the way to pick them.

7 - Investigate the params that I have when I submit the form inside my view/projects/Project_new

    {
      "utf8"=>"✓", "authenticity_token"=>"WKAiVi3xbzi8GpcISADZb2e3GvEut230QtmWnfPdEpFqz9ko2kuzXxHFnjufqaMfrESr96Qg33TtvIOPuv/bQw==", "project"=>
        {
          "credit"=>{
            "category"=>"Photographer",
            "content"=>"These photos have been taken by Martin Polak, Main photographer at ECAL, DesignSchool, Lauzane",
            "link"=>"www.martinpolak.com"
            },
          "name"=>"Adidas By ECAL ",
          "year"=>"2015",
          "description"=>"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book",
          "photos"=>[""]
        },
      "commit"=>"Create Project",
      "controller"=>"projects",
      "action"=>"create"
    }

    >> params["project"]["credit"]["creditable_type"]
    => "project" 
    👌 so I have that attribute but it's not inside the DB.

8 - error message

    => "ERROR:  error param is missing or the value is empty: credit"
  

9 - Param is missing cause the syntax is bad, and creditable_attributes are inside a hash, can't get the correct syntax to record them.

Hope that my question will help other junior dev to figure out the correct syntax to explore these nested form.

Upvotes: 2

Views: 163

Answers (1)

v2lrf
v2lrf

Reputation: 331

Solution

Rails bug solved in June 2017

Rails bug : Rails 5 polymorphic table requires optional: true

A description here

An example here

About my personal problem

👉 ProjetsController

class ProjetsController < ApplicationController
   ...

   def new
    @projet = Projet.new
    @projet.credits.build
    # @creditable_type = "projet"
  end

  def create
    @projet = Projet.new(projet_params)
    if @projet.save
      redirect_to @projet
    else
      render :new
    end
  end

   ...
end

👉 fix a mistake inside the form :credit => :credits

<div class="form-inputs">
   <%= f.simple_fields_for :credits do |credit_form| %>
     <%= credit_form.input :category %>
     <%= credit_form.input :content %>
     <%= credit_form.input :link %>
   <% end %> 
   ...
</div>

👉 fix syntax inside model Projet as: :creditable syntax better like this 😅

class Projet < ApplicationRecord
  has_many :credits, as: :creditable, dependent: :destroy
end

Upvotes: 1

Related Questions