Conor
Conor

Reputation: 740

Rails - new fields not getting created on form submission

So I have the following models. User, Team & Membership with the following definitions

User

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
        :recoverable, :rememberable, :validatable

  belongs_to :current_team, class_name: "Team", optional: true
  has_many :memberships
  has_many :teams, through: :memberships
  has_many :owner_memberships, -> { where(role: :owner) }, class_name: "Membership"
  has_many :owned_teams, through: :owner_memberships, source: :team

  def membership_for(team)
    memberships.where(team: team).take
  end
end

Team

class Team < ApplicationRecord
    has_many :memberships
    has_many :users, through: :memberships
    has_one :owner_membership, -> { where(role: :owner, status: :active) }, class_name: "Membership"
    has_one :owner, through: :owner_membership, source: :user
end

Membership

class Membership < ApplicationRecord
  belongs_to :user
  belongs_to :team

end

All of the forms generated using scaffold work fine. The issue I am now facing is that I have added 2 extra fields to the Membership model, Role & Status meaning the membership class now looks like the below but I am struggling to pass this these fields to the database.

class Membership < ApplicationRecord
  belongs_to :user
  belongs_to :team

  enum status: [:pending, :active]

  enum role: [:owner, :admin, :user]
  
  def owner_or_admin?
    owner? || admin?
  end
end

When I look at the console for rails s I can see the following which would suggest that the new fields aren't getting sent as part of the membership definition but as separate parameters. How do I get them to send and create properly

Processing by MembershipsController#create as HTML
    Parameters: {
        "authenticity_token"=>"5gzC/7YLIzqb+uNqHi2izth07MPv4WXrF49444+2bK7ML7ceLwk+BR2tP9fHiqCVJFJrxJHjuaz7dXxcp0yq0A==", 
        "membership"=>{"user_id"=>"2", "team_id"=>"1"}, 
        "role"=>"0", 
        "status"=>"0", 
        "commit"=>"Create Membership"
    }


Membership Create (1.3ms)  INSERT INTO "memberships" ("user_id", "team_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["user_id", 2], ["team_id", 1], ["created_at", "2021-08-30 14:58:03.929323"]

Edit: added view for form

<%= form_with(model: membership, local: true) do |form| %>
  <% if membership.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(membership.errors.count, "error") %> prohibited this membership from being saved:</h2>

      <ul>
        <% membership.errors.full_messages.each do |message| %>
          <li><%= message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= form.label :user_id %>
    <%= form.collection_select :user_id, User.order(:email),:id,:email, include_blank: true %>

  </div>

  <div class="field">
    <%= form.label :team_id %>
    <%= form.collection_select :team_id, Team.where(id: current_team.id),:id,:name, :selected => current_team %>

  </div>

  <div class="field">
    <%= form.label :role %>    
    <%= select_tag :role, options_for_select(Membership.roles.map {|k, v| [k.humanize.capitalize, v]}) %>
  </div>

  <div class="field">
    <%= form.label :status %>    
    <%= select_tag :status, options_for_select(Membership.statuses.map {|k, v| [k.humanize.capitalize, v]}) %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

Upvotes: 0

Views: 84

Answers (1)

max
max

Reputation: 102036

First setup a method that gives you the enum states as an array of [label, value] pairs suitible for building selector, checkboxes or radio buttons:

module EnumHelper
  # creates an array of [label, value] pairs from an enum
  # the translations can be customized through for example:
  # -  membership.statuses.active
  # -  membership.roles.active
  def options_from_enum(klass, name)
    i18n_scope = "#{klass.model_name.i18n_key}.#{name}"
    klass.public_send(name).map do |key|
      label = I18n.t(
        key,
        default: key.to_s.humanize
        scope: i18n_scope 
      )
      [label, key]
    end
  end
end

Note that the value that you pass from a form should be the name of the enum state ("pending") and not the integer ("0") which is an internal implementation detail which should only be known to the model.

Then make sure you create the inputs off the form builder:

<%= form_with(model: @membership) do |form| %>
  <div class="field">
    <%= form.label :role %>
    <%= form.select :role, options_from_enum(Membership, :roles) %>
  </div>
  <div class="field">
    <%= form.label :status %>
    <%= form.select :status, options_from_enum(Membership, :statuses) %>
  </div>
 
  # ...
<% end %>

Upvotes: 1

Related Questions