RSpec测试中的问题 - 两次保存父记录

时间:2015-06-17 16:50:13

标签: ruby-on-rails ruby-on-rails-4 activerecord rspec nested-attributes

我是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

任何人都可以帮助我理解我所缺少的东西吗?感谢

1 个答案:

答案 0 :(得分:1)

问题出在sign_up_spec.rb。您的测试letuser,这意味着您第一次在测试中提及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来电。