I'm new at Rails programming, and my RSpec test is failing when trying to save nested records. The logs from the test suggest that ActiveRecord is trying to save the parent twice, which results in a silent database save failure.
sign_up_spec.rb (sorry for the omission!)
require 'rails_helper'
RSpec.feature "Accounts", type: :feature do
let(:user) { FactoryGirl.create(:user) }
scenario "creating an account" do
visit root_path
click_link "Sign Up"
fill_in "Account name", :with => "Test Firm"
fill_in "Username", :with => user.username
fill_in "Password", :with => user.password
fill_in "Password confirmation", :with => user.password_confirmation
click_button "Create Account"
success_message = "Your account has been successfully created."
expect(page).to have_content(success_message)
expect(page).to have_content("Signed in as #{user.username}")
end
end
account.rb
class Account < ActiveRecord::Base
belongs_to :owner, :class_name => "User"
accepts_nested_attributes_for :owner
end
user.rb
class User < ActiveRecord::Base
has_secure_password
validates :username, presence: true, uniqueness: { case_sensitive: false }
validates :password, presence: true, length: { minimum: 8, maximum: 20 }
end
account_controller.rb
class AccountsController < ApplicationController
def new
@account = Account.new
@account.build_owner
end
def create
account = Account.create(account_params)
env["warden"].set_user(account.owner, :scope => :user)
env["warden"].set_user(account, :scope => :account)
flash[:success] = "Your account has been successfully created."
redirect_to root_url
end
private
def account_params
params.require(:account).permit(:account_name, {:owner_attributes => [
:username, :password, :password_confirmation
]})
end
end
account new.html.erb
<h2>Sign Up</h2>
<%= form_for(@account) do |account| %>
<p>
<%= account.label :account_name %><br>
<%= account.text_field :account_name %>
</p>
<%= account.fields_for :owner do |owner| %>
<p>
<%= owner.label :username %><br>
<%= owner.text_field :username %>
</p>
<p>
<%= owner.label :password %><br>
<%= owner.password_field :password %>
</p>
<p>
<%= owner.label :password_confirmation %><br>
<%= owner.password_field :password_confirmation %>
</p>
<% end %>
<%= account.submit %>
<% end %>
The following excerpt from the test result log below suggests that ActiveRecord is trying to insert the User record twice, once during the Account#new function, and again in the Account#create function. I've added comments with my understanding of what's going on through the process:
test log
Started GET "/sign_up" for 127.0.0.1 at 2015-06-17 15:50:43 +0000
Processing by AccountsController#new as HTML
Rendered accounts/new.html.erb within layouts/application (45.6ms)
Completed 200 OK in 110ms (Views: 51.6ms | ActiveRecord: 6.9ms)
# it appears that this action is happening from the Account#new method;
# why would @account.build_owner cause a database action?
(0.4ms) SAVEPOINT active_record_1
User Exists (30.8ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."username") = LOWER('user1') LIMIT 1
SQL (3.8ms) INSERT INTO "users" ("username", "password_digest", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["username", "user1"], ["password_digest", "blahblah"], ["created_at", "2015-06-17 13:15:40.667965"], ["updated_at", "2015-06-17 13:15:40.667965"]]
(1.4ms) RELEASE SAVEPOINT active_record_1
Started POST "/accounts" for 127.0.0.1 at 2015-06-17 13:15:40 +0000
Processing by AccountsController#create as HTML
Parameters: {"utf8"=>"✓", "account"=>{"account_name"=>"Test Firm", "owner_attributes"=>{"username"=>"user1", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}}, "commit"=>"Create Account"}
# the Account#create function wants to add the user a second time (or more
# accurately, the first time), but the user parent already exists from above
# so it fails the uniqueness test
(0.2ms) SAVEPOINT active_record_1
User Exists (0.9ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."username") = LOWER('user1') LIMIT 1
# which causes the query to bomb
(0.2ms) ROLLBACK TO SAVEPOINT active_record_1
#and neither the parent or child records are created
#<Account id: nil, account_name: nil, created_at: nil, updated_at: nil, owner_id: nil>
#<User id: nil, username: "user1", created_at: nil, updated_at: nil, password_digest: "blahblah">
Redirected to http://www.example.com/
Completed 302 Found in 17ms (ActiveRecord: 1.3ms)
Started GET "/" for 127.0.0.1 at 2015-06-17 13:15:40 +0000
Processing by StaticPagesController#home as HTML
Rendered static_pages/home.html.erb within layouts/application (0.5ms)
Completed 200 OK in 6ms (Views: 5.3ms | ActiveRecord: 0.0ms)
(4.3ms) ROLLBACK
Can anyone help me understand what I'm missing? Thanks
The issue is in
sign_up_spec.rb
. Your test has alet
foruser
, which means the first time you mentionuser
in your tests it will create auser
. However, your application code is supposed to create the user itself.As I said in the comment, this is why in your test log you see that it successfully completes the
get
request and then, before thepost
request, theuser
gets created. When your code tries to create auser
after submitting the form, it's already been created!I commented your test spec to make this more clear:
To fix this, just change the
let
statement to use FactoryGirl'sattributes_for
method instead and usewith: user[:username]
instead ofwith: user.username
.Also, you're passing the symbol version of
owner
in the nested form, which means Rails is going to build a new instance ofowner
and then assign it to the@account
. You already, however, built an instance ofowner
for@account
inside thenew
action. So that code is redundant. Either remove thebuild_owner
line from the controller action, or pass@account.owner
to thefields_for
call in your view.