我是Rails编程的新手,在尝试保存嵌套记录时我的RSpec测试失败了。测试中的日志表明ActiveRecord正在尝试将父级保存两次,这会导致静默数据库保存失败。
sign_up_spec.rb (抱歉遗漏!)
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
帐户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 %>
以下测试结果日志中的以下摘录表明,ActiveRecord正在尝试两次插入用户记录,一次是在Account#new函数中,另一次是在Account#create函数中。我通过对过程中发生的事情的理解添加了评论:
测试日志
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
任何人都可以帮助我理解我所缺少的东西吗?感谢
答案 0 :(得分:1)
问题出在sign_up_spec.rb
。您的测试let
为user
,这意味着您第一次在测试中提及user
时会创建user
。但是,您的应用程序代码应该创建用户自己。
正如我在评论中所说,这就是为什么在您的测试日志中,您看到它成功完成了get
请求,然后在 post
请求之前, user
已创建。当您的代码在提交表单后尝试创建user
时,它已经创建了!
我评论了您的测试规范,以使其更加清晰:
let(:user) { FactoryGirl.create(:user) }
scenario "creating an account" do
visit root_path
click_link "Sign Up" # this is the successful get request
fill_in "Account name", :with => "Test Firm"
fill_in "Username", :with => user.username # this triggers the let and creates a user
fill_in "Password", :with => user.password
fill_in "Password confirmation", :with => user.password_confirmation
click_button "Create Account" # this starts the post request
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
要解决此问题,只需更改let
语句即可使用FactoryGirl的attributes_for
方法,而使用with: user[:username]
代替with: user.username
。
此外,您正在以嵌套形式传递owner
的符号版本,这意味着Rails将构建owner
的新实例,然后将其分配给@account
。但是,您已在owner
操作中为@account
构建了new
的实例。所以代码是多余的。从控制器操作中删除build_owner
行,或将@account.owner
传递给您视图中的fields_for
来电。