Home > OS >  How to create form for rails join table?
How to create form for rails join table?

Time:09-10

I'm making a gym managing app and I have a problem. I have three models member, pass and membership set up like this:

class Member < ApplicationRecord
  has_many :memberships
  has_many :passes, through: :memberships
  validates :name, presence: true

class Pass < ApplicationRecord
  has_many :memberships
  has_many :members, through: :memberships

class Membership < ApplicationRecord
  belongs_to :member
  belongs_to :pass 

and I'm trying to make a form where I can associate member with pass and add more attributes that membership hold (that's why I'm using has_many through). My concept is to create new membership object in separate form, using nested resources so my member_id will be passed and select pass_id in the form.
http://localhost:3000/members/3/memberships/new
This is my current code:

<%= fields_for([@membership, @member]) do |form| %>
  <div>
    <%= form.label :name %><br>
    <%= form.text_field :name %>
  </div>
  <div>
    <%= form.select :pass_id, @member.members_without_membership %><br>
    <%= form.text_field :name %>
  </div>
  <div>
    <%= form.submit %>
  </div>
<% end %>

and it gives me an error: "NoMethodError in Memberships#new undefined method `model_name' for [#<Membership id: nil, name: nil, member_id: nil, pass_id: nil, created_at: nil, updated_at: nil>, nil]:Array"

Edit membership_controller.rb

  def new
    @membership = Membership.new
  end

  def create
    @membership = Membership.new(membership_params)

    @membership.member_id = params[:member_id]
    if @membership.save
      redirect_to @membership
    else
      render :new, status: :unprocessable_entity
    end
  end

CodePudding user response:

I would actually rethink this and setup separate routes and controllers for the different contexts as the logic here is almost certain to diverge enough here for it to become really messy.

Signing up a new member and creating the membership could either just be done via GET /members/new with nested attributes:

class Member < ApplicationRecord
  has_many :memberships
  accepts_nested_attributes_for :memberships
end
class MembersController < ApplicationController
  # GET /members/new
  def new
    @member = Member.new
    @membership = @member.memberships.new
  end

  def create
    @member = Member.new(member_params)
    if @member.save
      redirect_to @member 
    else
      render :new, status: :unprocessable_entity
    end 
  end

  private

  def member_params
    params.require(:member)
          .permit(:foo, :bar, membership_attributes: [ :pass_id, :baz ])
  end
end 
<%= form_with(model: @member) do |form| %>
  ...
  <%= form.fields_for(:memberships) do |membership| %>
    <div >
      <%= membership.label :pass_id, "Select the pass" %>
      <%= membership.collection_select :pass_id, Pass.all, :id, :name %>
    </div>
  <% end %>
<% end %>

Or if you need to seperate this from the normal member signup with non-nested GET /memberships/new and POST /memberships routes.

Then just create separate controller for adding new memberships to existing members:

resources :members do
  resources :memberships, 
    only: [:new, :create],
    module: :members # optional - helps organize the code
end
module Members # optional - helps organize the code
  # Handles creating new memberships for existing members
  class MembershipsController
    before_action :set_member # important! ensures that the parent resource is present!

    # GET /members/1/memberships/new
    def new
      @membership = @member.memberships.new
    end

    # POST /members/1/memberships 
    def create
      @membership = @member.memberships.new(membership_params)
      if @membership.save
        redirect_to @membership
      else
        render :new, status: :unprocessable_entity
      end
    end

    private

    def set_member
      @member = Member.find(params[:member_id])
    end

    def membership_params
      params.require(:membership)
            .permit(:foo, :bar, :pass_id)
    end
  end 
end
<%= form_with(model: [@member, @membership]) do |form| %>
  <div >
    <%= membership.label :pass_id, "Select the pass" %>
    <%= membership.collection_select :pass_id, Pass.all, :id, :name %>
  </div>
  <div>
    <%= form.submit %>
  </div>
<% end %>

Reusing code between the different representations can later be done through concerns and partials.

And by the way - fields_for is never used with an array like form_for/form_with. It doesn't create a element - it just yields a FormBuilder instance to the block. This is used to change the object wrapped by the form builder and to nest the input parameters into a hash/array of hashes.

Bear in mind that HTML doesn't actually allow nested <form> elements - so the submit button will still submit the form thats wrapping the inputs.

In generally you almost never call it with anything but a symbol that corresponds to the name of the assocation on the model.

  • Related