Search code examples
ruby-on-railsnested-formsvirtual-attribute

Virtual attributes in nested forms are not recorded


You'll see in my code I've got a "has_many => belongs_to" models association and a nested form in the new actions's view.

Plus, I've used Using two separate fields for the same parameter in a Rails form handler?

The problem is the "prix" attribute (in the "Projet" model) is recorded in the database, but not the "nom" attribute (in the "Activite" model).

Maybe the problem is around the strong parameters, but I thinks it's all good in my code... Or maybe in the code I've found on the other Stackoverflow question I've linked.

There is french words in my code : activite is activity, projet is project, nom is name, prix is price, very easy :)

Thank's for your help !

app/model/projet.rb :

class Projet < ActiveRecord::Base

    has_many :activites

    accepts_nested_attributes_for :activites,
    reject_if: lambda {|attributes| attributes['nom'].blank?}

end

app/models/activite.rb :

class Activite < ActiveRecord::Base

    belongs_to :projet

    def acttext=(value)
      @acttext = value if value
      prepare_act
    end

  def actselect=(value)
    @actselect = value if value
    prepare_act
  end

  def acttext
    @acttext || self.nom
  end

  def actselect
    @actselect || self.nom
  end

private 
  def prepare_act
    self.nom = acttext if acttext
    self.nom = actselect if actselect
  end

end

app/controllers/projets_controller.rb :

class ProjetsController < ApplicationController

    def new
        @projet = Projet.new
        @activites_options = Activite.pluck(:nom).uniq
        2.times { @projet.activites.new}
    end

    def create
        @projet = Projet.new(projet_params)

        if @projet.save
          redirect_to @projet
        else
          render 'new'
        end
    end

  private

    def projet_params
      params.require(:projet).permit(:prix, activites_attributes: [:id, :nom, :heures, :minutes, :acttext, :actselect])
    end
end

app/views/projets/new.html.erb :

<div>
    <%= form_for @projet do |f| %>    
    <%= f.label :prix %><br> 
    <%= f.text_area :prix %><br>
            <ul>
              <%= f.fields_for :activites do |activites_form| %>
                <li>
                  <%= activites_form.label :choisissez_un_type_dactivité %>
                  <%= activites_form.select :actselect, @activites_options, {include_blank: true} %>

                  <%= activites_form.label :ou_créez_en_un_nouveau %>
                  <%= activites_form.text_field :acttext %><br>

                  <%= activites_form.label :heures %>
                  <%= activites_form.text_field :heures %>

                  <%= activites_form.label :minutes %>
                  <%= activites_form.text_field :minutes %>
                </li>
                <br>
              <% end %>
            </ul>
    <p><%= f.submit "Créer le projet" %></p>
    <% end %>
    </div>

Solution

  • In order for virtual attributes to work, you'll also need to define a setter and a getter. This can be done by either defining the getters and setters explicitly yourself or by using attr_accessor.

    Example using attr_accessor:

    class Activite < ActiveRecord::Base
      attr_accessor :nom
      ....
    
    end
    

    Example defining both setter and getter manually:

    class Activite < ActiveRecord::Base
      ....
    
      def nom=(value)
        super
      end
    
      def nom
        @nom
      end
    end
    

    For the second issue with your class naming due to them being French, you'd also want specify the class_name option on both has_many and belongs_to to specify your non english class names as follows:

    class Projet < ActiveRecord::Base
      has_many :activites, class_name: 'Activite'
    end
    

    Similarly, for Activite model:

    class Activite < ActiveRecord::Base
      belongs_to :projet, class_name: 'Projet'
    end