ruby-on-railshashtextfieldstrong-parametersform-helpers

Rails params return as nil when updating a model's hash attribute


I have a model within my application called Admin.

This Admin can have multiple emails and these emails are stored within a hash called emails. For example,

{"sales"=>{"general"=>"sales@shop.com"},"support"=>{"general"=>"support@shop.com"}}

When creating an form to access these specific emails, I am able to get each individual email to appear within an input, but when I try to update the modal, nothing changes as my admin_params[:emails] is nil.

The following is my form within my edit.html.erb file:

<%= form_for @admin do |f| %>
    <dt class="col-sm-10">Admin Emails</dt>
    <%  @admin.emails.each do |type, subtype|%>
          <dt class="col-sm-10"> <%= f.label type %> </dt>
          <% if @admin.emails.include?(type) %>
            <% @admin.emails[type].each do |subtype_label, subtype_email| %>
              <%= f.fields :emails do |field| %>
                <dd class="col-sm-5"><%= field.label subtype_label %></dd>
                <dd class="col-sm-5"><%= field.text_field subtype_label, :value => subtype_email %></dd>
              <% end %>
            <% end %>
          <% end %>
    <% end %>

Here is the set_admin method that is called before any other method except index:

def admin_params
  params.require(:admin).permit(:name, :emails)
end

Here is my update method:

def update
  binding.pry
  @admin.update(
    name: admin_params[:name],
    emails: admin_params[:emails]
  )

  redirect_to admin_path(@admin)
end

And finally, here is the HTML that gets render on a specific input:

<input value="emails" type="text" name="admin[emails][general]" id="admin_emails_general">

Any clue what my problem is? Been scratching my head on this one all day.


Solution

  • I would consider just doing it the rails way for simplicity:

    class Admin < ApplicationRecord
      has_many :emails, dependent: :destroy
      accepts_nested_attributes_for :emails, 
        reject_if: proc { |attributes| attributes['email'].blank? }
    end
    
    class Email < ApplicationRecord
      enum type: [:work, :home]
      belongs_to :admin
    end
    

    Thats just a plain old one-to-many association and accepts_nested_attributes_for.

    <%= form_for(@admin) do |f| %>
      <%= f.fields_for :emails do |ef| %>
         # ...
         <%= ef.select :type, Email.types.keys.map {|k| [k.humanize, k] } %>
         <%= ef.text_field :email %>
      <% end %>
      # ...
    <% end %>
    

    fields_for creates an array of hashes in the parameters by naming the inputs admin[emails_attributes][][email] and admin[emails_attributes][][type].

    Your solution is overwriting the same single parameter. While you can hack around this by manually setting the name attribute I would consider if its worth the effort.

    To whitelist nested parameters you pass an array with the keys you want to whitelist:

    def admin_params
      params.require(:admin).permit(:name, emails_attributes: [:type, :email])
    end