Devise: Devise user creation with accepts_nested_attributes_for : belongs_to relation requires "optional: true" for validations to pass

Created on 19 Nov 2016  路  5Comments  路  Source: heartcombo/devise

Scenario:

  • User can have many Roles
  • 3 Models: User -< Userrole >- Role
  • User+Userrole are created using the "nested models" approach (accepts_nested_attributes_for)
  • Validation fails with: "Userroles user must exist"

Models:

class User < ApplicationRecord
  has_many :userroles
  accepts_nested_attributes_for :userroles
end

class Userrole < ApplicationRecord
  belongs_to :user, optional: true  # <--- This seems to be required and disables the foreign_key check
  belongs_to :role
end

class Role < ApplicationRecord
  has_many :userroles
end

The failed validation seems to be the result of how Devise interacts with Rails 5's enforced foreign_key presence validation. (I wrote a test script that represents this situation, but as a "pure" version, not based on/including the Devise account creation mechanism - the script passed without errors (change the file extension from .txt to .rb): record_creation_test_script.txt)

The mode Userrole thus currently requires "optional: true" and therefor disables the foreign_key check.

Needs more info

Most helpful comment

John is right...the optional: true on a has_one model had the same problem. It's super irritating and I wasted half a day trying to figure it out.

Let me be clear about this:

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

  has_one :user
  accepts_nested_attributes_for :user
end

class User < ApplicationRecord

  belongs_to :login, optional: true #<- Again as mentioned before, this is mandatory for the model to be created without throwing an error.

end

All 5 comments

optional: true is a feature of rails 5 to check associated objects are present. May i know what is the problem that you are facing with devise on this.

The problem is that if I use Devise's user creation mechanism to create a User in the scenario explained above (meaning: with the nested attributes of Userroles), then the User is not created - its validation fails with "Userroles user must exist". The validation does however pass if I add the "optional: true" as explained above (but having to add that is not the idea; and is also not necessary if I create a User in the above scenario but outside of the Devise mechanism).

John is right...the optional: true on a has_one model had the same problem. It's super irritating and I wasted half a day trying to figure it out.

Let me be clear about this:

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

  has_one :user
  accepts_nested_attributes_for :user
end

class User < ApplicationRecord

  belongs_to :login, optional: true #<- Again as mentioned before, this is mandatory for the model to be created without throwing an error.

end

Hi @john-999, thank you for the report.

I've changed your script to include Devise and still seems to work. Please let me know if I missing something. This is the code I've used:
```ruby
begin
require "bundler/inline"
rescue LoadError => e
$stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
raise e
end

gemfile(true) do
source "https://rubygems.org"
gem "rails", "~> 5.0"
gem "sqlite3"
gem "devise"
end

require "active_record"
require "minitest/autorun"
require "logger"
require 'rack/test'
require 'action_controller/railtie'
require 'devise/rails/routes'
require 'devise/rails/warden_compat'

This connection will do for database-independent bug reports.

ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = Logger.new(STDOUT)

Devise.setup do |config|
require 'devise/orm/active_record'
config.secret_key = 'secret_key_base'
end

class TestApp < Rails::Application
config.root = File.dirname(__FILE__)
config.session_store :cookie_store, key: 'cookie_store_key'
secrets.secret_token = 'secret_token'
secrets.secret_key_base = 'secret_key_base'
config.eager_load = false

config.middleware.use Warden::Manager do |config|
Devise.warden_config = config
end

config.logger = Logger.new($stdout)
Rails.logger = config.logger

end

class User < ActiveRecord::Base
devise :database_authenticatable
has_many :userroles
accepts_nested_attributes_for :userroles
end

class Userrole < ActiveRecord::Base
belongs_to :user #, optional: true # <--- This seems to be required with Devise, and it disables the foreign_key check
belongs_to :role
end

class Role < ActiveRecord::Base
has_many :userroles
end

Rails.application.initialize!

ActiveRecord::Schema.define do
create_table :users, force: true do |t|
t.string :name
t.string :email, null: false
t.string :encrypted_password, null: true
end

create_table :userroles, force: true do |t|
t.integer :user_id
t.integer :role_id
end

create_table :roles, force: true do |t|
t.string :name
end
end

Rails.application.routes.draw do
devise_for :users

get '/' => 'test#index'
end

class ApplicationController < ActionController::Base
end

class TestController < ApplicationController
include Rails.application.routes.url_helpers

before_action :authenticate_user!

def index
render plain: 'Home'
end
end

class BugTest < Minitest::Test
def test_parent_child_record_creation_with_nested_attributes
role = Role.create!(name: 'Administrator')
user = User.create!(name: 'Dan', email: '[email protected]', password: '123', password_confirmation: '123', userroles_attributes: [role_id: role.id])

assert_equal 1, Role.count
assert_equal 1, User.count
assert_equal role.id, user.userroles.first.role_id

end
end

I'm closing this issue because it has not had recent activity.
If you're still facing this on the latest version, please open a new one with all the information requested in the template.

Thank you!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

spaquet picture spaquet  路  3Comments

edipox picture edipox  路  4Comments

ragesoss picture ragesoss  路  3Comments

Pedroknoll picture Pedroknoll  路  3Comments

mvz picture mvz  路  3Comments