Scenario:
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.
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'
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!
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: