Rails 4 / Factory Girl - has_many / has_many工厂并定义不同的对象

时间:2016-05-07 09:05:13

标签: ruby-on-rails ruby ruby-on-rails-4 factory-bot

我想为has_many / has_many关系定义一个工厂(我认为我做对了)但我不知道如何为这些创建工厂的每个对象定义属性。

这是主页,它是交易卡的列表

我们假设下面的主页视图是针对SIGNED-IN用户的(注意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 %>

基本上,如果用户有特定交易(在表user_deals上,请参见下面的模型)a user_deal.number_of_clicks = 4然后,在卡上会出现一个按钮,其中显示“您达到了最大点击次数”的消息。如果点击次数<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

表user_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.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.

修改

发布错误请求后:

如果我离开@user_deal_with_max_of_clicks“:user =&gt; @user,并且:deal =&gt; @ 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

1 个答案:

答案 0 :(得分:2)

首先,我建议你使用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是有选择地调整工厂的好方法。因此,如果您希望您的用户成为superadmin,现在只需使用

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