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.