在我的Rails 4应用中,我有update
动作:
class UsersController < ApplicationController
...
def update
current_email = @user.email
new_email = user_params[:email].downcase
if @user.update_attributes(user_params)
if current_email != new_email
@user.email = current_email
@user.new_email = new_email.downcase
@user.send_email_confirmation_email
flash[:success] = "Please click the link we've just sent you to confirm your new email address."
else
flash[:success] = "User updated."
end
redirect_to edit_user_path(@user)
else
render :edit
end
end
...
end
它基本上确保user
不能简单地保存任何新的电子邮件地址。他必须首先通过点击我们发送给他的电子邮件中的链接来确认。
这很好用,但由于某些原因我还没有找到测试它的方法。
无论我做什么,以下RSpec测试都会失败:
it "changes the user's new_email attribute" do
@user = FactoryGirl.create(:user, :email => "john@doe.com")
patch :update, :id => @user, :user => FactoryGirl.attributes_for(:user, :email => "new@email.com")
expect(@user.reload.new_email).to eq("new@email.com")
end
@user.new_email
始终为nil
,测试始终失败。我在这里缺少什么?
重新分解我的update
行动根本不会成为问题。也许有更好的方法?谢谢你的帮助。
答案 0 :(得分:1)
我会像这样编写规范:
let(:user) { FactoryGirl.create(:user, email: "john@doe.com") }
it "changes the user's new_email attribute" do
expect do
patch :update, id: @user, user: FactoryGirl.attributes_for(:user, email: "new@email.com")
user.reload
end.to change(user, :new_email).from("john@doe.com").to("new@email.com")
end
当涉及到控制器动作本身时,问题是new_email属性永远不会保存到数据库中,除了那种混乱之外。您可以使用跟踪模型中属性更改的ActiveRecord::Dirty来清理它:
class User < ApplicationRecord
# updates user with attrs but moves a new email to the `new_email`
# column instead
def update_with_email(attrs, &block)
update(attrs) do |record|
if record.email_changed?
record.new_email = record.email.downcase
record.restore_attribute!(:email)
end
# keeps the method signature the same as the normal update
yield record if block_given?
end
end
end
将此业务逻辑放在模型中也可让您分别进行测试:
RSpec.describe User, type: :model do
describe "#update_with_email" do
let(:user) { FactoryGirl.create(:user) }
it "does not change the email attribute" do
expect do
user.update_with_email(email: ”xxx@example.com”)
user.reload
end.to_not change(user, :email)
end
it "updates the new_email" do
expect do
user.update_with_email(email: ”xxx@example.com”)
user.reload
end.to change(user, :new_email).to('xxx@example.com')
end
end
end
这可以让你保持控制器好看和瘦:
def update
if @user.update_with_email(user_params)
if @user.new_email_changed?
@user.send_email_confirmation_email
flash[:success] = "Please click the link we've just sent you to confirm your new email address."
else
flash[:success] = "User updated."
end
# You probably want to redirect the user away from the form instead.
redirect_to edit_user_path(@user)
else
render :edit
end
end