RSpec 测试中出现问题 - 保存父记录两次
Trouble in RSpec test - saving parent record twice
我是 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 (, , , ) 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
任何人都可以帮助我了解我所缺少的吗?谢谢
问题在 sign_up_spec.rb
。你的测试有一个 let
for 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
。但是,您已经在 new
操作中为 @account
构建了一个 owner
实例。所以这段代码是多余的。从控制器操作中删除 build_owner
行,或者将 @account.owner
传递给视图中的 fields_for
调用。
我是 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 (, , , ) 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
任何人都可以帮助我了解我所缺少的吗?谢谢
问题在 sign_up_spec.rb
。你的测试有一个 let
for 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
。但是,您已经在 new
操作中为 @account
构建了一个 owner
实例。所以这段代码是多余的。从控制器操作中删除 build_owner
行,或者将 @account.owner
传递给视图中的 fields_for
调用。