I'm using a form in Rails to create an instance of one Model and create a join table to another model on create. In my Accounts
controller, after verifying the new instance of Account
has been created, I create a new join table based on checkboxes chosen in the form from another model.
Here is my code:
Models:
class Account < ApplicationRecord
has_many :account_authorizations
has_many :authorizations, through: :account_authorizations
accepts_nested_attributes_for :authorizations
end
class Authorization < ApplicationRecord
has_many :account_authorizations
has_many :accounts, through: :account_authorizations
end
class AccountAuthorization < ApplicationRecord
belongs_to :account
belongs_to :authorization
end
My Accounts
controller:
class AccountsController < ApplicationController
before_action :find_account, only: [:show, :update, :destroy]
def index
@accounts = Account.all
end
def show
end
def new
@authorizations = Authorization.all
end
def create
byebug
@account = Account.new(account_params)
if @account.save
authorization_ids = params.permit![:authorization][:ids]
authorization_ids.each do |auth_id|
AccountAuthorization.create(authorization_id: auth_id, account_id: params[:id]) if !auth_id.empty?
end
flash[:notice] = "Account created sucsessfully"
redirect_to account_path(@account)
else
flash[:error] = @item.errors.full_messages.to_sentence
render :new
end
end
def edit
end
def update
@account = Account.update(account_params)
if @account.valid?
flash[:notice] = "Account created sucsessfully"
redirect_to accounts_path(@account)
else
flash[:error] = @item.errors.full_messages.to_sentence
render :edit
end
end
def destroy
@account.delete
redirect_to accounts_path
end
private
def find_account
@account = Account.find(params[:id])
end
def account_params
params.permit(
:account_name,
:account_address_line1,
:account_address_line2,
:account_city,
:account_state,
:account_zipcode,
:account_ppu,
:account_notes,
:contact_first_name,
:contact_last_name,
:contact_phone,
:contact_email
)
end
end
I've tried using .permit! on params but this does not allow the the ids from Authorization
to pass into a new join instance of AccountAuthorization
.
The attributes that don't pass are:
Unpermitted parameters: :authenticity_token, :authorization, :commit
I've also tried putting those in the permit hash in the strong_params method.
** UPDATE **
My Views page with form
:
<% states = [ 'AL', 'AK', 'AS', 'AZ', 'AR', 'CA', 'CO', 'CT', 'DC', 'DE', 'DC', 'FL', 'GA', 'HI', 'ID', 'IL', 'IN', 'IA', 'KS', 'KY', 'LA', 'ME', 'MH', 'MD', 'MA', 'MI', 'MN', 'MS', 'MO', 'MT', 'NE', 'NV', 'NH', 'NJ', 'NM', 'NY', 'NC', 'ND', 'MP', 'OH', 'OK', 'OR', 'PA' 'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VT' 'VA', 'WA', 'WV', 'WI', 'WY'] %>
<% ppu = ['Employment', 'Insurance', 'Law Enforement'] %>
<div class="row justify-content-center">
<div class="col-lg-16">
<h3>Create New Account</h3>
</div>
</div>
<div class="row justify-content-center">
<div class="col-lg-16">
<%= form_with(url: accounts_path, model: @account, local: true) do |f| %>
<%= f.text_field :account_name, class: "form-control", placeholder: "Account Name" %>
<%= f.text_field :account_address_line1, class: "form-control", placeholder: "address line 1" %>
<%= f.text_field :account_address_line2, class: "form-control", placeholder: "address line 2" %>
<%= f.text_field :account_city, class: "form-control", placeholder: "account city" %>
<%= f.select :account_state, options_for_select(states), { :prompt => true }, class: "form-control", include_blank: true, placeholder: "state" %>
<%= f.text_field :account_zipcode, class: "form-control", placeholder: "account zipcode" %>
<%= f.text_field :contact_first_name, class: "form-control", placeholder: "contact first name" %>
<%= f.text_field :contact_last_name, class: "form-control", placeholder: "contact last name" %>
<%= f.text_field :contact_phone, class: "form-control", placeholder: "conact phone" %>
<%= f.text_field :contact_email, class: "form-control", placeholder: "contact email" %>
<%= f.select :account_ppu, options_for_select(ppu), { :prompt => true }, class: "form-control", include_blank: true, placeholder: "ppu" %>
<%= f.text_area :account_notes, class: "form-control", placeholder: "Notes..." %>
<div class="d-flex justify-content-between flex-wrap">
<%= f.fields_for :authorization do |auth| %>
<div class="order-3 p-2 bd-highlight">
<%= auth.collection_check_boxes :ids, Authorization.all, :id, :auth_name %>
</div>
<% end %>
</div>
<%= f.submit "Create", class: 'btn btn-success' %>
<% end %>
</div>
</div>
You don't need to manually create join table records.
For every has_many
and has_many_and_belongs_to_many
association Rails creates a _ids
setter that takes an array of ids. Rails will automatically create/delete join table rows from the input.
This mates up perfectly with the collection helpers.
app/views/accounts/_form.html.erb
<%= form_with(model: account, local: true) do |f| %>
<div class="field">
<%= f.label :account_name %>
<%= f.text_field :account_name %>
</div>
# ... more inputs
<div class="field">
<%= f.label :authorization_ids, 'Authorizations' %>
<%= f.collection_checkboxes :authorization_ids,
Authorization.all, # the collection
:id, # value method
:name # label method
%>
</div>
<div class="actions">
<%= f.submit %>
<div>
<% end %>
app/views/accounts/new.html.erb
<%= render partial: 'form', account: @account %>
accounts/edit.html.erb
<%= render partial: 'form', account: @account %>
app/controllers/accounts_controller.rb
class AccountsController < ApplicationController
before_action :find_account, only: [:show, :edit, :update, :destroy]
# ...
def create
@account = Account.new(account_params)
if @account.save
flash[:notice] = "Account created sucsessfully"
redirect_to @account
else
# render :new does not redirect so you need to use flash.now
# to display the flash message in this request
flash.now[:error] = @item.errors.full_messages.to_sentence
render :new
end
end
def edit
end
# This is how you update a resource.
def update
# don't check .valid? - it just tells you if the validations passed
# not if the actual DB update query was a successes
if @account.update(account_params)
flash[:notice] = "Account updated successfully"
redirect_to @account
else
flash.now[:error] = @item.errors.full_messages.to_sentence
render :edit
end
end
# ...
private
def account_params
params.require(:account).permit(
:account_name,
:account_address_line1,
:account_address_line2,
:account_city,
:account_state,
:account_zipcode,
:account_ppu,
:account_notes,
:contact_first_name,
:contact_last_name,
:contact_phone,
:contact_email,
account_ids: []
)
end
end
Since this slices out the key :account
from the params hash you won't get the unpermitted parameters error. authenticity_token
is the Rails CSRF protection token and commit
is the value of the submit button which was clicked to submit the form. These should not be whitelisted or sent to your model
You whitelist the an array of authorization ids by passing a keyword argument (account_ids: []
) to .permit
with an empty array as the value. Since this is a keyword argument it must come after the list of positional arguments.
You also don't need accepts_nested_attributes_for
unless you're doing something more advanced then just selecting existing records. But you're not quite ready for accepts_nested_attributes_for
until you have got the basics of doing CRUD down.