Devise Invitable is modifying existing users - ruby-on-rails

CONTEXT
I'm using devise_invitable to allow a user (with an admin role) to register another user in my app.
User objects have an email, password, token (random string), role (also string) and an associated HealthRecord object which has name, last name, dni (personal id) plus some extra info
PROBLEM
For some reason, when I input an existing email, I get an error (which is intended validation) but it also destroys the HealthRecord associated with the user who has that existing email.
CODE
This is what my console shows upon trying to create the user with existing email
Started POST "/users/invitation" for ::1 at 2021-11-26 10:04:15 -0300
Processing by Users::InvitationsController#create as HTML
Parameters: {"authenticity_token"=>"[FILTERED]", "user"=>{"email"=>"paciente1#example.com", "role"=>"Paciente", "health_record_attributes"=>{"residencia"=>"Cementerio", "nombre"=>"overriding", "apellido"=>"test", "dni"=>"123456789", "risk"=>"0", "birth"=>"1999-02-12"}}, "commit"=>"Registrar"}
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT ? [["id", 5], ["LIMIT", 1]]
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."email" = ? ORDER BY "users"."id" ASC LIMIT ? [["email", "paciente1#example.com"], ["LIMIT", 1]]
HealthRecord Load (0.1ms) SELECT "health_records".* FROM "health_records" WHERE "health_records"."user_id" = ? LIMIT ? [["user_id", 1], ["LIMIT", 1]]
TRANSACTION (0.1ms) begin transaction
HealthRecord Destroy (0.5ms) DELETE FROM "health_records" WHERE "health_records"."id" = ? [["id", 1]]
TRANSACTION (207.2ms) commit transaction
User Exists? (0.3ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = ? AND "users"."id" != ? LIMIT ? [["email", "paciente1#example.com"], ["id", 1], ["LIMIT", 1]]
HealthRecord Exists? (0.4ms) SELECT 1 AS one FROM "health_records" WHERE "health_records"."dni" = ? LIMIT ? [["dni", "123456789"], ["LIMIT", 1]]
Rendering layout layouts/application.html.erb
Rendering users/invitations/new.html.erb within layouts/application
HealthRecord Load (0.1ms) SELECT "health_records".* FROM "health_records" WHERE "health_records"."user_id" = ? LIMIT ? [["user_id", 5], ["LIMIT", 1]]
↳ app/views/users/invitations/new.html.erb:18
The view to generate the new user
<h2>Registro excepcional</h2>
<%= form_for(setup_user(resource), as: resource_name, url: invitation_path(resource_name), html: { method: :post }) do |f| %>
<% resource.class.invite_key_fields.each do |field| -%>
<div class="field">
<%= f.label field %><br />
<%= f.text_field field, class: 'form-control'%>
</div>
<% end %>
<div class="field">
<%= f.hidden_field :role, :value=>"Paciente"%>
</div>
<%= f.fields_for :health_record do |ff| %>
<div class="field">
<%= ff.hidden_field :residencia, :value=>current_user.health_record.residencia%>
</div>
<div class="field">
<%= ff.label "Nombre" %><br/>
<%= ff.text_field :nombre, class: 'form-control',:required => true%>
</div>
<div class="field">
<%= ff.label "Apellido" %><br/>
<%= ff.text_field :apellido, class: 'form-control',:required => true%>
</div>
<div class="field">
<%= ff.label "DNI" %><br/>
<%= ff.text_field :dni, class: 'form-control',:required => true%>
</div>
<div class="field">
<%= ff.label "Es de riesgo:",:required => true %>
<%= ff.check_box :risk %>
</div>
<div class="field">
<%= ff.label "Fecha de nacimiento:"%><br/>
<%= ff.date_field :birth, class: 'form-control',:required => true%>
</div>
<% end %>
<br/>
<div class="actions">
<%= f.submit "Registrar" %>
</div>
<% end %>
The user model which has validate_on_invite
class User < ApplicationRecord
devise :invitable, :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :authentication_keys => [:token,:email], :validate_on_invite => true
has_many :comprobantes, :dependent => :destroy
has_one :health_record, :dependent => :destroy
has_many :TurnoAsignado, :dependent => :destroy
has_many :TurnoNoAsignado, :dependent => :destroy
validates :email, uniqueness: true
before_save :init
accepts_nested_attributes_for :health_record
def init()
if self.token.nil?
self.token = (rand()*1000000).to_i
end
end
end
The HealthRecord model
class HealthRecord < ApplicationRecord
belongs_to :user
validates :dni, presence: true
validates :dni, uniqueness: true
validates :nombre, presence: true
validates :apellido, presence: true
validates :birth, presence: true
before_save :upcase_content
def upcase_content
self.nombre=self.nombre.downcase
self.apellido=self.apellido.downcase
self.nombre=self.nombre.split(/ |\_/).map(&:capitalize).join(" ")
self.apellido=self.apellido.split(/ |\_/).map(&:capitalize).join(" ")
end
end
The invitation controller (it's pretty much default I just added parameters and an after_path)
class Users::InvitationsController < Devise::InvitationsController
before_action :configure_permitted_parameters
#Permit the new params here.
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:invite, keys: [
:token,
:role,
health_record_attributes: [
:apellido,
:nombre,
:dni,
:risk,
:birth,
:residencia
]
])
end
def after_invite_path_for(resource)
new_asignado_path(self.resource.id)
end
end

I think the problem may have been this line
form_for(setup_user(resource),...
I am using a helper to set the HealthRecord of a user to an empty one (fields_for needed the user to have a HealthRecord to work)
module FormHelper
def setup_user(user)
user.health_record ||= HealthRecord.new # ||= means “assign this value unless it already has a value”
user
end
end
Maybe what was happening is that the empty HealthRecord was assigned to the existing user, somehow?
I solved it by intercepting the flow of the create method in the invitations controller, by asking if the user email or dni exists
def create
#correo = User.find_by(email:params[:user][:email])
#dni = HealthRecord.find_by(dni:params[:user][:health_record_attributes][:dni])
if (#correo.nil? && #dni.nil?) #si no existe mail ni dni
super
else
mensaje="Los siguientes campos ya estan registrados:"
if !(#correo.nil?)
mensaje = mensaje + " email"
end
if !(#dni.nil?)
mensaje = mensaje + " dni"
end
flash[:notice] = mensaje
redirect_to new_user_invitation_path
end
end
Although this works, I'm not sure about the reason of the problem, any insight is welcome

Related

Unpermitted parameter on has_one and belongs_to

I'm trying to update a #user.profile (with differents solutions) but get unpermitted parameters or Profile User must exist.
But in my erb console :
[3] pry(main)> User.first.id
User Load (3.3ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1 [["LIMIT", 1]]
=> 1
[4] pry(main)> User.first.profile.id
User Load (2.8ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1 [["LIMIT", 1]]
Profile Load (2.2ms) SELECT "profiles".* FROM "profiles" WHERE "profiles"."user_id" = $1 LIMIT $2 [["user_id", 1], ["LIMIT", 1]]
=> 4352
What's wrong ??
{"_method"=>"patch", ....
"user"=>{"id"=>"1", "profile_attributes"=>{"user_id"=>"1", "last_name"=>"test", "id"=>"4352"}}, "commit"=>"Update", "locale"=>"fr", "controller"=>"users", "action"=>"update", "id"=>"ben"}
users_controller.rb
class UsersController < ApplicationController
def edit
#user = User.find(params[:id]
end
def update
#user = User.find(params[:id]
if #user.update_without_password(user_params)
bypass_sign_in(#user)
redirect_to edit_user_path(#user.slug), notice: t('users.user_updated')
else
flash.alert = "#{#user.errors.full_messages.join(', ')}"
render :edit
end
end
private
def user_params
params.require(:user).permit (
:id,
:email,
:pseudo,
:password,
:password_confirmation,
:current_password,
profile_attributes: %i[
id
user_id]
)
end
end
user.rb
class User < ApplicationRecord
has_one :profile, inverse_of: :user, dependent: :destroy
accepts_nested_attributes_for :profile
end
profile.rb
class Profile < ApplicationRecord
belongs_to :user, inverse_of: :profile
accepts_nested_attributes_for :user
attr_accessor :user
end
form
<%= simple_form_for #user do |f| %>
<%= f.error_notification %>
<%= f.input :id, input_html: { value: #user.id } %>
<%= f.simple_fields_for :profile do |profile| %>
<%= profile.input :user_id, input_html: { value: #user.id } %>
<%= profile.input :id, input_html: { value: #user.profile.id } %>
<% end %>
<%= f.button :submit %>
<% end %>

Double nested form not sending attributes to controller

All help/hints/debugging tips/thoughts are welcome, as I'm pretty much stuck for some time on this issue.
I have a 2-level deep nested form. The parameters of the 1st level are saving correctly (e.g. options_attributes), but unfortunately the parameters of the deepest form are not being sent to the controller (.e.g. option_prices_attributes is not shown at all in my parameters. I use the cocoon gem to create a dynamic nester form.
Interestingly,
(1) in my console I am able to create a 2-level deep object where also the option_price parameters are saving.
(2) when using :option_prices_attributes in the simple_field forms, they are being sent as parameters:
{"utf8"=>"✓", "_method"=>"patch", "authenticity_token"=>"P6oCZkZF8O+, "accommodation_category"=>{"options_attributes"=>{"1568305809712"=>{"name"=>"Option name", "_destroy"=>"false", "option_prices_attributes"=>{"name"=>"Option price", "_destroy"=>"0"}}}}, "commit"=>"Save", "park_id"=>"8", "id"=>"96"}
=> and consequently resulting in an error message in the terminal saying
no implicit conversion of Symbol into Integer
My models
class AccommodationCategory < ApplicationRecord
belongs_to :park
has_many :options, dependent: :destroy
accepts_nested_attributes_for :options, allow_destroy: true
end
class Option < ApplicationRecord
belongs_to :accommodation_category
has_many :option_prices, dependent: :destroy
accepts_nested_attributes_for :option_prices, allow_destroy: true
end
class OptionPrice < ApplicationRecord
belongs_to :option
end
Accommodation_categories_controller.rb
class AccommodationCategoriesController < ApplicationController
# skip_before_action :authenticate_user!
[...]
def update
#park = Park.find(params[:park_id])
#accommodation_category = #park.accommodation_categories.find(params[:id])
authorize #accommodation_category
#accommodation_category = #accommodation_category.update_attributes(accommodation_category_params)
end
def new_options
#accommodation_category = AccommodationCategory.find(params[:id])
#park = #accommodation_category.park
authorize #accommodation_category
#2nd level nesting
# #accommodation_category.options.build
#accommodation_category.options.each do |option|
option.option_prices.build
end
end
private
def accommodation_category_params
params.require(:accommodation_category).permit(:name, :description, :status, :persons_max, :persons_min, :persons_included, :accommodation_count, :enabled_accommodation_count, :thumb, :included_services, :photo,
options_attributes: [:name, :description, :_destroy,
option_prices_attributes: [:name, :price_type, :start_date, :end_date, :price, :duration, :duration_min, :duration_max, :backend_only, :weekend_extra, :_destroy]])
end
end
views/accommodation_categories/new_options.html.erb
<%= render 'options_new_form', park: #park%>
views/accommodation_categories/options_new_form.html.erb (1st level)
<%= simple_form_for [#park, #accommodation_category] do |f|%>
<h1> <%= #accommodation_category.name %> </h1>
<% #accommodation_category.options.each do |option| %>
<%= option %>
<% end %>
<%= f.simple_fields_for :options do |option| %>
<%= render 'option_fields', f: option %>
<% end %>
<div>
<%= link_to_add_association 'add option', f, :options %>
</div>
<%= f.submit "Save", class: "btn btn-primary" %>
<% end %>
views/accommodation_categories/option_fields.html.erb (2nd level)
<%= f.input :name %>
<%= f.check_box :_destroy %>
<%= link_to_remove_association "remove option", f %>
<%= f.simple_fields_for :option_prices do |option_price| %>
<%= render 'option_price_fields', f: option_price %>
<% end %>
<%= link_to_add_association 'add option price', f, :option_prices %>
views/accommodation_categories/option_price_fields.html.erb
<%= f.input :name %>
<%= f.check_box :_destroy %>
<%= link_to_remove_association "remove option price", f %>
The message in my terminal when sending the parameter to the controller is the following:
Processing by AccommodationCategoriesController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"QlROXu9ImP6GSvPJQgd7eVZtaWsiVT6myWzZEFIEtEulSrQmt75XVMEI/avKUzhjZaZG9Kj0Pmih6J/4UYO8IQ==", "accommodation_category"=>{"options_attributes"=>{"1568290804865"=>{"name"=>"option name", "_destroy"=>"false"}}}, "commit"=>"Save", "park_id"=>"8", "id"=>"93"}
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ /Users/xx/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activerecord-5.2.3/lib/active_record/log_subscriber.rb:98
Park Load (0.4ms) SELECT "parks".* FROM "parks" WHERE "parks"."id" = $1 LIMIT $2 [["id", 8], ["LIMIT", 1]]
↳ app/controllers/accommodation_categories_controller.rb:29
AccommodationCategory Load (0.2ms) SELECT "accommodation_categories".* FROM "accommodation_categories" WHERE "accommodation_categories"."park_id" = $1 AND "accommodation_categories"."id" = $2 LIMIT $3 [["park_id", 8], ["id", 93], ["LIMIT", 1]]
↳ app/controllers/accommodation_categories_controller.rb:30
(0.2ms) BEGIN
↳ app/controllers/accommodation_categories_controller.rb:32
Option Create (0.3ms) INSERT INTO "options" ("accommodation_category_id", "name", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["accommodation_category_id", 93], ["name", "option name"], ["created_at", "2019-09-12 12:20:14.265335"], ["updated_at", "2019-09-12 12:20:14.265335"]]
↳ app/controllers/accommodation_categories_controller.rb:32
(0.7ms) COMMIT
↳ app/controllers/accommodation_categories_controller.rb:32
AccommodationCategory Load (0.2ms) SELECT "accommodation_categories".* FROM "accommodation_categories" WHERE "accommodation_categories"."park_id" = $1 AND "accommodation_categories"."id" = $2 LIMIT $3 [["park_id", 8], ["id", 93], ["LIMIT", 1]]
↳ app/controllers/accommodation_categories_controller.rb:45
Redirected to http://localhost:3000/accommodation_categories/93/new_discounts
Completed 302 Found in 14ms (ActiveRecord: 2.3ms)
None of your nested-fields partials seem to have a wrapper-class? Cocoon explicitly relies on a specific mark-up, this could cause e.g. the nested fields to appear to be inserted correctly, but inserted outside of the form and then of course never posted to the server/controller.

Why does my Registrations#Update devise action not update all attributes in my `User` model?

This is my views/devise/registrations/edit.html.erb:
<%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put, class: "edit-user-form m-t" }) do |f| %>
<%= f.error_notification %>
<div class="form-group">
<%= f.input_field :email, required: true, autofocus: true %>
</div>
<div class="form-group">
<%= f.input_field :current_password, hint: "we need your current password to confirm your changes", placeholder: "current password", required: true %>
</div>
<div class="form-group">
<%= f.input_field :password, autocomplete: "off", hint: "leave it blank if you don't want to change it", placeholder: "new password", required: false %>
</div>
<div class="form-group">
<%= f.input_field :password_confirmation, placeholder: "confirm new password", required: false %>
</div>
<div class="form-group">
<%= f.association :school, collection: School.where(school_type: [:college, :university]), prompt: "Choose a school", class: 'col-lg-4 form-control', label: false %>
</div>
</div>
<div class="form-group">
<div class="col-lg-12 text-center">
<%= f.button :submit, "Update", class: "btn btn-lg edit-account-update-button" %>
</div>
</div>
<% end %>
And this is my ApplicationController:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_action :configure_permitted_parameters, if: :devise_controller?
check_authorization :unless => :devise_controller?
rescue_from CanCan::AccessDenied do |exception|
respond_to do |format|
format.json { head :forbidden }
format.html { redirect_back(fallback_location: root_path, flash: { danger: exception.message }) }
end
end
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:account_update, keys: [:avatar, :avatar_cache, :remove_avatar, :school_id])
end
end
The issue though is that it isn't updating the record, here is the log from that operation:
Started PUT "/users" for ::1 at 2016-11-02 19:22:59 -0500
Processing by DeviseInvitable::RegistrationsController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"Xo445XVpElgDmfcjywZKEbsXIqZR/2Wgw==", "user"=>{"remove_avatar"=>"0", "avatar_cache"=>"", "email"=>"coach#test.com", "current_password"=>"[FILTERED]", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]", "school_id"=>"3"}, "commit"=>"Update"}
User Load (4.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 7], ["LIMIT", 1]]
User Load (9.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 7], ["LIMIT", 1]]
(0.7ms) BEGIN
SQL (4.2ms) UPDATE "users" SET "updated_at" = $1, "avatar" = $2 WHERE "users"."id" = $3 [["updated_at", 2016-11-03 00:22:59 UTC], ["avatar", "a1.jpg"], ["id", 7]]
(0.8ms) COMMIT
Redirected to http://localhost:3000/
Completed 302 Found in 139ms (ActiveRecord: 19.1ms)
Note that the school_id is set in the params, so we know it properly accepts the input from the form. But the SQL UPDATE statement, doesn't include school_id, so it doesn't actually save that info to the DB.
Also note that there are no unpermitted params messages in the log.
What could be causing this?
Edit 1
This is the School.rb model:
class School < ApplicationRecord
has_many :users
enum school_type: { high_school: 0, college: 1, university: 2 }
end
This is the User.rb model:
class User < ActiveRecord::Base
belongs_to :school
end
The problem was that your devise model User was inheriting ActiveRecord::Base instead of ApplicationRecord which is an abstract class in Rails 5. With the inheritance of latter, most of the gems which deal with models can and do modify the code of ActiveRecord::Base in their own context without affecting other models, which isn't possible if you inherit the former.
Source: Why Rails 5 uses ApplicationRecord instead of ActiveRecord::Base?

nested attributes and form not saving Rails 4

I am trying to nest a user to an account and allow the user to be created when the account is created. I am having an issue when creating both the account and user from the same form, and my server output is not very helpful in trying to isolate the problem.
here is the output when I try to save the Account and User.
Started POST "/accounts" for ::1 at 2016-10-10 20:55:23 -0600
Processing by AccountsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"UK7h4edNzRomi7JGomoZD1ayYmlNxI/h2ZH+NEaJxWQzcFsYAJujr5EDDS2HeprAX41IuS5/crRxmXYz80YpYw==", "account"=>{"subdomain"=>"mydomain", "owner_attributes"=>{"email"=>"swilson#ta#####td.com", "f_name"=>"S####", "l_name"=>"W####", "date_of_birth"=>"19##-##-##", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}}, "commit"=>"Create Account"}
(0.3ms) BEGIN
User Exists (0.3ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = $1 LIMIT $2 [["email", "swilson#ta#####td.com"], ["LIMIT", 1]]
Account Exists (0.3ms) SELECT 1 AS one FROM "accounts" WHERE "accounts"."subdomain" IS NULL LIMIT $1 [["LIMIT", 1]]
(0.2ms) ROLLBACK
Rendering accounts/new.html.erb within layouts/application
Rendered accounts/_form.html.erb (3.8ms)
Rendered accounts/new.html.erb within layouts/application (5.1ms)
Rendered shared/_signed_out_nav.html.erb (1.3ms)
Completed 200 OK in 183ms (Views: 44.2ms | ActiveRecord: 1.1ms)
I believe the issue may have something to do with the account generation and the subdomain. but I Can not pin it down for the life of me!
also I commented out my personal info with the ####...
here is my account controller
class AccountsController < ApplicationController
def new
#account = Account.new
#account.build_owner
end
def create
#account = Account.new(account_params)
respond_to do |format|
if #account.save
format.html {redirect_to root_path, notice: 'Account successfully created.'}
else
format.html {render :new, alert: 'There was a problem, please try again.'}
end
end
end
private
def account_params
params.require(:account).permit(:subdomain, owner_attributes: [:email, :password, :password_confirmation, :f_name, :l_name, :date_of_birth])
end
def set_account
#account = Account.find(params[:id])
end
end
here is my account form:
<%= bootstrap_form_for(#account) do |f| %>
<div class="row">
<div class="col-xs-12">
<%= f.text_field :subdomain, hide_label: true, placeholder: 'Company Name', append: ".patrolvault.net" %>
</div>
</div>
<%= f.fields_for :owner do |o| %>
<div class="row">
<div class="col-xs-12">
<%= o.email_field :email, label: 'Email Address' %>
</div>
<div class="col-xs-12">
<%= o.text_field :f_name, label: 'First Name' %>
</div>
<div class="col-xs-12">
<%= o.text_field :l_name, label: 'Last Name' %>
</div>
<div class="col-xs-12">
<%= o.date_field :date_of_birth, label: 'Date Of Birth' %>
</div>
<div class="col-xs-6">
<%= o.password_field :password, label: 'Password' %>
</div>
<div class="col-xs-6">
<%= o.password_field :password_confirmation, label: 'Confirm Password' %>
</div>
</div>
<% end %>
<%= f.submit :class => 'btn btn-primary' %>
<% end %>
and here is my account model:
class Account < ApplicationRecord
# Before Actions
before_validation :downcase_subdomain
# Relationships
belongs_to :owner, class_name: 'User', optional: true
accepts_nested_attributes_for :owner
# Validations
validates :owner, presence: true
RESTRICTED_SUBDOMAINS = %w(www, patrolvault, test)
validates :subdomain, presence: true,
uniqueness: { case_sensitive: false },
format: { with: /\A[\w\-]+\Z/i, message: 'Contains invalid characters.' },
exclusion: { in: RESTRICTED_SUBDOMAINS, message: 'Restricted domain name'}
# Custom Methods
private
def downcase_subdomain
self.subdomain = subdomain.try(:subdomain)
end
end
Please let me know if you require further info or if I have missed something! Thanks!
so the problem was in my before_validation :downcase_subdomain. once I removed that and the matching method everything began to work fine. I will rework this.
Thanks.

Rails app creating join records (has_many through) with a form fields_for section and collection_select

I'm tearing my hair out with this. I have three models: Game, Genre and GameGenre. Games have many Genres through GameGenres, Genres have many Games through GameGenres.
I have a games/new form which creates new game records. To this form, I've added a fields_for :game_genres section, which is supposed to allow the user to add game_genre relationship records to the new game records they create.
I think I'm pretty close to getting this working. At the moment, on submission it returns the error: "Game genres game can't be blank." I take this to mean the form is connecting to the game_genres table. I'm not sure why the controller isn't submitting the :game_id value.
Here's my code.
new.html.erb
<%= form_for(#game) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="form-group">
<%= f.label :title_en, "Game title (English)" %>
<%= f.text_field :title_en, class: 'form-control',
placeholder: "E.g. Marble Madness" %>
</div>
<div class="form-group">
<%= f.label :year, "Year" %>
<%= f.select :year, options_for_select((1950..2017).reverse_each),
{},
{:class => 'form-control'} %>
</div>
<div class="form-group">
<%= f.label :description_en, "Summary" %>
<%= f.text_area :description_en, class: 'form-control' %>
</div>
<hr />
<div class="form-group">
<%= f.fields_for :game_genres do |gf| %>
<%= gf.label :genre_id, "Select a genre" %>
<%= gf.collection_select(:genre_id, Genre.all, :id, :name,
{},
{:class => "form-control"}) %>
<% end %>
</div>
<%= f.submit "Save", class: "btn btn-primary center-block" %>
<% end %>
games_controller.rb (excerpt)
def new
#game = current_user.games.build
#game.game_genres.build
end
def create
#game = current_user.games.build(game_params)
if #game.save
flash[:success] = "Game saved: #{#game.title_en} (#{#game.year})"
redirect_to game_path(#game.id)
else
render 'new'
end
end
private
def game_params
params.require(:game).permit(:title_en, :year, :description_en,
game_genres_attributes: [:genre_id])
game.rb
class Game < ActiveRecord::Base
belongs_to :user
has_many :game_genres, foreign_key: :game_id,
dependent: :destroy
has_many :genres, through: :game_genres
accepts_nested_attributes_for :game_genres,
allow_destroy: true
genre.rb
class Genre < ActiveRecord::Base
belongs_to :user
has_many :game_genres, foreign_key: :genre_id,
dependent: :destroy
has_many :games, through: :game_genres
game_genre.rb
class GameGenre < ActiveRecord::Base
belongs_to :game, inverse_of: :game_genres
belongs_to :genre, inverse_of: :game_genres
As I understand it I do not need to add resources for game_genres to routes.rb.
This is the output from the rails development server when I try to submit a new game record:
Started POST "/games" for ::1 at 2015-06-30 18:59:24 +0100
Processing by GamesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"cUbIu3i9niI0R8b9E3KZeYEbNpx8NDj9Jx1MeGvylYDKQj54d3BwSdLsMiPsRBoUTzoTEuMGTk5/KTKAXH7DMA==", "game"=>{"title_en"=>"fdsdcdc", "year"=>"2017", "description_en"=>"dsdc", "game_genres_attributes"=>{"0"=>{"genre_id"=>"8"}}}, "commit"=>"Save"}
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT 1 [["id", 1]]
(0.1ms) BEGIN
Genre Load (0.3ms) SELECT "genres".* FROM "genres" WHERE "genres"."id" = $1 ORDER BY "genres"."name" ASC LIMIT 1 [["id", 8]]
(0.1ms) ROLLBACK
Rendered shared/_error_messages.html.erb (0.6ms)
Genre Load (0.3ms) SELECT "genres".* FROM "genres" ORDER BY "genres"."name" ASC
As I understand it, the game_genres_attributes are properly nested and permitted. Why isn't game_id being recognised and saved?
Thanks so much to anyone who can help.
EDIT:
I got the form working by removing this validation from the GameGenre model:
validates :game_id, presence: true
I want this line in though, so I'm now wondering how to work around it?
EDIT 2:
I guess this is the answer (from http://makandracards.com/makandra/1346-popular-mistakes-when-using-nested-forms):
Nested records are validating the presence of their nesting parent
record’s foreign key. If you do this, you cannot create a new parent
record together with a new child record and will need to save the
parent before you can save the child. You can opt to only show the
nested child form when the parent has been saved before, or simply let
go of the validation.

Resources