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 aletforuser, which means the first time you mentionuserin 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
getrequest and then, before thepostrequest, theusergets created. When your code tries to create auserafter submitting the form, it's already been created!I commented your test spec to make this more clear:
To fix this, just change the
letstatement to use FactoryGirl'sattributes_formethod instead and usewith: user[:username]instead ofwith: user.username.Also, you're passing the symbol version of
ownerin the nested form, which means Rails is going to build a new instance ofownerand then assign it to the@account. You already, however, built an instance ofownerfor@accountinside thenewaction. So that code is redundant. Either remove thebuild_ownerline from the controller action, or pass@account.ownerto thefields_forcall in your view.