Rails 4/ Factory Girl - has_many/has_many 工厂和定义不同的对象
Rails 4/ Factory Girl - has_many/has_many factory and defining different objects
我想为 has_many/has_many 关系定义一个工厂(我想我做对了)但是我不知道如何为这些创建的工厂的每个对象定义属性。
这是首页,是交易牌列表
我们假设下面的主页视图是针对登录用户的(注意 current_user 来自 Devise)。
<% @deals.each do |deal| %>
@userdeal = UserDeal.where('user_id = ? AND deal_id = ?', current_user.id, deal.id).take
<div>
<div class="button">
<% if @userdeal.number_of_clicks = 4 %>you reached the maximum clicks
<% end %>
</div>
</div>
<% end %>
基本上如果用户有一个特定的交易(在 table user_deals,见下面的模型)一个 user_deal.number_of_clicks = 4 然后,在卡片上会出现一个按钮带有 "you reached the maximum clicks" 之类的消息。如果点击次数 <4,则不会出现任何按钮。
所以我想在功能测试中使用工厂,我将检查如果我用 fatcory girl 创建一个对象@userdeal1,用户点击了 4 次,他会在这张卡片上看到按钮及其文本,但对于其他交易,他什么也没看到。
这是我目前所拥有的
型号
class Deal < ActiveRecord::Base
has_many :user_deals, dependent: :destroy
has_many :users, through: :user_deals
end
class User < ActiveRecord::Base
has_many :user_deals
has_many :deals, through: :user_deals
end
class UserDeal < ActiveRecord::Base
belongs_to :user, :foreign_key => 'user_id'
belongs_to :deal, :foreign_key => 'deal_id'
end
和tableuser_deal
的结构
# Table name: user_deals
#
# id :integer not null, primary key
# user_id :integer
# deal_id :integer
# number_of_clicks :integer default(0)
# created_at :datetime
# updated_at :datetime
到目前为止我找到了如何创建
FactoryGirl.define do
factory :user_deal do
association :user
association :deal
end
根据 factorygirl 自述文件,我创建了这个:
FactoryGirl.define do
factory :user do
sequence(:email) { |n| "person#{n}@example.com"}
password "vddfdf"
password_confirmation "vddfdf"
confirmed_at Time.now
confirmation_token nil
factory :superadmin do
after(:create) {|user| user.add_role(:superadmin)}
end
after(:create) do |user|
user.deals << FactoryGirl.create(:deal)
end
factory :user_with_deals do
# used for defining user_deals
transient do
deals_count 5
end
after(:create) do |user, evaluator|
create_list(:deal, evaluator.deals_count, user: user)
end
end
end
end
但现在我不知道如何说‘好的,其中一个是工厂创建的 user_deals,我想要一个 number_of_clicks =4,另一个number_of_clicks=1)。所以我试着直接把它放在测试中,如下所示:
describe 'HP deal card features', :type => :feature do
context "As signed-in USER" do
let(:subject) { ApplicationController.new }
before do
@user = FactoryGirl.create(:user, :user_country_name => 'Germany')
@deal1 = FactoryGirl.build( :deal)
@deal2 = FactoryGirl.build( :deal)
@user_deal_with_max_of_clicks = FactoryGirl.build( :user_with_deals,
:user => @user,
:deal => @deal1,
:number_of_clicks => 4
).save(validate: false)
@user_deal_with_clicks_available = FactoryGirl.build( :user_with_deals,
:user => @user,
:deal => @deal2,
number_of_clicks => 1 # still has clicks
).save(validate: false)
it "the right behavior for the buttons telling him how many clicks he has left" do
sign_in @user
visit root_path
# I'll find a way to test here if there is the text "you reached the maximum clicks" for the first card and not for the second
end
after do
visit destroy_user_session_path
end
end
但是测试不起作用,根据我尝试的小改动,给我不同类型的错误。我很确定我没有设法真正创建 2 个对象 user_deals ,一个 number_of_clicks= 4 另一个 number_of_clicks= 1.
编辑
请求 post 后出现错误:
如果我离开@user_deal_with_max_of_clicks ":user=> @user, and :deal => @deal1", 我得到
NoMethodError:
undefined method `user=' for #<User:0x0000000b4754e0>
和
NoMethodError:
undefined method `deal=' for #<User:0x0000000b451568>
但我删除它们如下:
@user_deal_with_max_of_clicks = FactoryGirl.build( :user_with_deals,
:number_of_clicks => 4
).save(validate: false)
然后我得到这个错误:
NoMethodError:
undefined method `number_of_clicks=' for #<User:0x0000000b46ce80>
编辑 2
utilities.rb
include ApplicationHelper
def sign_in(user)
visit new_user_session_path
fill_in I18n.t("formtastic.labels.user.email"), with: user.email
fill_in I18n.t("formtastic.labels.user.password"), with: user.password
click_on I18n.t("devise.sessions.login_page.login")
end
首先,我建议您使用 let
。写 cleaner and better specs.
有一个很好的指南
其次,办好工厂很重要。他们将极大地简化测试过程:
所以开始时,您的用户工厂可以重组为
FactoryGirl.define do
factory :user do
sequence(:email) { |n| "person#{n}@example.com" }
password "vddfdf"
password_confirmation "vddfdf"
confirmed_at Time.now
confirmation_token nil
trait :superadmin do
role :superadmin
end
trait :with_deals do
after(:create) do |user|
create_list(:deal, 5, user: user)
end
end
end
end
Traits 是选择性调整工厂的好方法。因此,如果您希望您的用户成为超级管理员,现在只需使用
create(:user, :superadmin)
想要交易的超级管理员吗?简单。
create(:user, :superadmin, :with_deals)
您还没有发布交易工厂,但我相信您也可以将这些技巧应用到他们身上。
终于通向了user_deals
。在您当前的工厂中,您没有解决您的 number_of_clicks
专栏。
同样,您可以使用特征轻松设置它:
FactoryGirl.define do
factory :user_deal do
association :user
association :deal
trait :few_clicks do
number_of_clicks 1
end
trait :many_clicks do
number_of_clicks 4
end
end
end
现在回到规范本身,通过学到的技巧,设置您想要的关系是一项简单的任务:
describe 'HP deal card features', :type => :feature do
context "As signed-in USER" do
let(:subject) { ApplicationController.new }
let(:user) { create(:user, user_country_name: 'Germany') }
let(:deal1) { create(:deal) }
let(:deal2) { create(:deal) }
before do
create(:user_deal, :few_clicks, user: user, deal: deal1)
create(:user_deal, :many_clicks, user: user, deal: deal2)
end
it "tells the user how many clicks he has left" do
sign_in user
visit root_path
# I'll find a way to test here if there is the text "you reached the maximum clicks" for the first card and not for the second
end
after do
visit destroy_user_session_path
end
end
end
我想为 has_many/has_many 关系定义一个工厂(我想我做对了)但是我不知道如何为这些创建的工厂的每个对象定义属性。
这是首页,是交易牌列表
我们假设下面的主页视图是针对登录用户的(注意 current_user 来自 Devise)。
<% @deals.each do |deal| %>
@userdeal = UserDeal.where('user_id = ? AND deal_id = ?', current_user.id, deal.id).take
<div>
<div class="button">
<% if @userdeal.number_of_clicks = 4 %>you reached the maximum clicks
<% end %>
</div>
</div>
<% end %>
基本上如果用户有一个特定的交易(在 table user_deals,见下面的模型)一个 user_deal.number_of_clicks = 4 然后,在卡片上会出现一个按钮带有 "you reached the maximum clicks" 之类的消息。如果点击次数 <4,则不会出现任何按钮。
所以我想在功能测试中使用工厂,我将检查如果我用 fatcory girl 创建一个对象@userdeal1,用户点击了 4 次,他会在这张卡片上看到按钮及其文本,但对于其他交易,他什么也没看到。
这是我目前所拥有的
型号
class Deal < ActiveRecord::Base
has_many :user_deals, dependent: :destroy
has_many :users, through: :user_deals
end
class User < ActiveRecord::Base
has_many :user_deals
has_many :deals, through: :user_deals
end
class UserDeal < ActiveRecord::Base
belongs_to :user, :foreign_key => 'user_id'
belongs_to :deal, :foreign_key => 'deal_id'
end
和tableuser_deal
的结构# Table name: user_deals
#
# id :integer not null, primary key
# user_id :integer
# deal_id :integer
# number_of_clicks :integer default(0)
# created_at :datetime
# updated_at :datetime
到目前为止我找到了如何创建
FactoryGirl.define do
factory :user_deal do
association :user
association :deal
end
根据 factorygirl 自述文件,我创建了这个:
FactoryGirl.define do
factory :user do
sequence(:email) { |n| "person#{n}@example.com"}
password "vddfdf"
password_confirmation "vddfdf"
confirmed_at Time.now
confirmation_token nil
factory :superadmin do
after(:create) {|user| user.add_role(:superadmin)}
end
after(:create) do |user|
user.deals << FactoryGirl.create(:deal)
end
factory :user_with_deals do
# used for defining user_deals
transient do
deals_count 5
end
after(:create) do |user, evaluator|
create_list(:deal, evaluator.deals_count, user: user)
end
end
end
end
但现在我不知道如何说‘好的,其中一个是工厂创建的 user_deals,我想要一个 number_of_clicks =4,另一个number_of_clicks=1)。所以我试着直接把它放在测试中,如下所示:
describe 'HP deal card features', :type => :feature do
context "As signed-in USER" do
let(:subject) { ApplicationController.new }
before do
@user = FactoryGirl.create(:user, :user_country_name => 'Germany')
@deal1 = FactoryGirl.build( :deal)
@deal2 = FactoryGirl.build( :deal)
@user_deal_with_max_of_clicks = FactoryGirl.build( :user_with_deals,
:user => @user,
:deal => @deal1,
:number_of_clicks => 4
).save(validate: false)
@user_deal_with_clicks_available = FactoryGirl.build( :user_with_deals,
:user => @user,
:deal => @deal2,
number_of_clicks => 1 # still has clicks
).save(validate: false)
it "the right behavior for the buttons telling him how many clicks he has left" do
sign_in @user
visit root_path
# I'll find a way to test here if there is the text "you reached the maximum clicks" for the first card and not for the second
end
after do
visit destroy_user_session_path
end
end
但是测试不起作用,根据我尝试的小改动,给我不同类型的错误。我很确定我没有设法真正创建 2 个对象 user_deals ,一个 number_of_clicks= 4 另一个 number_of_clicks= 1.
编辑
请求 post 后出现错误:
如果我离开@user_deal_with_max_of_clicks ":user=> @user, and :deal => @deal1", 我得到
NoMethodError:
undefined method `user=' for #<User:0x0000000b4754e0>
和
NoMethodError:
undefined method `deal=' for #<User:0x0000000b451568>
但我删除它们如下:
@user_deal_with_max_of_clicks = FactoryGirl.build( :user_with_deals,
:number_of_clicks => 4
).save(validate: false)
然后我得到这个错误:
NoMethodError:
undefined method `number_of_clicks=' for #<User:0x0000000b46ce80>
编辑 2
utilities.rb
include ApplicationHelper
def sign_in(user)
visit new_user_session_path
fill_in I18n.t("formtastic.labels.user.email"), with: user.email
fill_in I18n.t("formtastic.labels.user.password"), with: user.password
click_on I18n.t("devise.sessions.login_page.login")
end
首先,我建议您使用 let
。写 cleaner and better specs.
其次,办好工厂很重要。他们将极大地简化测试过程:
所以开始时,您的用户工厂可以重组为
FactoryGirl.define do
factory :user do
sequence(:email) { |n| "person#{n}@example.com" }
password "vddfdf"
password_confirmation "vddfdf"
confirmed_at Time.now
confirmation_token nil
trait :superadmin do
role :superadmin
end
trait :with_deals do
after(:create) do |user|
create_list(:deal, 5, user: user)
end
end
end
end
Traits 是选择性调整工厂的好方法。因此,如果您希望您的用户成为超级管理员,现在只需使用
create(:user, :superadmin)
想要交易的超级管理员吗?简单。
create(:user, :superadmin, :with_deals)
您还没有发布交易工厂,但我相信您也可以将这些技巧应用到他们身上。
终于通向了user_deals
。在您当前的工厂中,您没有解决您的 number_of_clicks
专栏。
同样,您可以使用特征轻松设置它:
FactoryGirl.define do
factory :user_deal do
association :user
association :deal
trait :few_clicks do
number_of_clicks 1
end
trait :many_clicks do
number_of_clicks 4
end
end
end
现在回到规范本身,通过学到的技巧,设置您想要的关系是一项简单的任务:
describe 'HP deal card features', :type => :feature do
context "As signed-in USER" do
let(:subject) { ApplicationController.new }
let(:user) { create(:user, user_country_name: 'Germany') }
let(:deal1) { create(:deal) }
let(:deal2) { create(:deal) }
before do
create(:user_deal, :few_clicks, user: user, deal: deal1)
create(:user_deal, :many_clicks, user: user, deal: deal2)
end
it "tells the user how many clicks he has left" do
sign_in user
visit root_path
# I'll find a way to test here if there is the text "you reached the maximum clicks" for the first card and not for the second
end
after do
visit destroy_user_session_path
end
end
end