2012-03-13 24 views
9

Câu hỏi này là một phần mở rộng của một lớn lên ở đây:FactoryGirl trong Rails - Hội w/chế độc đáo

Using factory_girl in Rails with associations that have unique constraints. Getting duplicate errors

Câu trả lời được cung cấp đã làm việc một cách hoàn hảo đối với tôi. Dưới đây là hình thức:

# Creates a class variable for factories that should be only created once. 

module FactoryGirl 

    class Singleton 
    @@singletons = {} 

    def self.execute(factory_key) 
     begin 
     @@singletons[factory_key] = FactoryGirl.create(factory_key) 
     rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique 
     # already in DB so return nil 
     end 

     @@singletons[factory_key] 
    end 
    end 

end 

Vấn đề nảy sinh khi tôi cần xây dựng liên kết để hỗ trợ liên kết đa hình với ràng buộc duy nhất trong móc. Ví dụ:

class Matchup < ActiveRecord::Base 
    belongs_to :event 
    belongs_to :matchupable, :polymorphic => true 

    validates :event_id, :uniqueness => { :scope => [:matchupable_id, :matchupable_type] } 
end 

class BaseballMatchup < ActiveRecord::Base 
    has_one :matchup, :as => :matchupable 
end 

FactoryGirl.define do 
    factory :matchup do 
    event { FactoryGirl::Singleton.execute(:event) } 
    matchupable { FactoryGirl::Singleton.execute(:baseball_matchup) } 
    home_team_record '10-5' 
    away_team_record '9-6' 
    end 

    factory :baseball_matchup do 
    home_pitcher 'Joe Bloe' 
    home_pitcher_record '21-0' 
    home_pitcher_era 1.92 
    home_pitcher_arm 'R' 
    away_pitcher 'Jack John' 
    away_pitcher_record '0-21' 
    away_pitcher_era 9.92 
    away_pitcher_arm 'R' 
    after_build do |bm| 
     bm.matchup = Factory.create(:matchup, :matchupable => bm) 
    end 
    end 
end 

thực hiện singleton hiện tại của tôi không hỗ trợ gọi FactoryGirl::Singleton.execute(:matchup, :matchupable => bm), chỉ FactoryGirl::Singleton.execute(:matchup).

Bạn khuyên bạn nên sửa đổi nhà máy singleton để hỗ trợ cuộc gọi như FactoryGirl::Singleton.execute(:matchup, :matchupable => bm) HOẶC FactoryGirl::Singleton.execute(:matchup) như thế nào?

Bởi vì ngay bây giờ, mã ở trên sẽ ném lỗi xác thực duy nhất ("Sự kiện đã được thực hiện") mỗi lần móc được chạy trên nhà máy: baseball_matchup. Cuối cùng, đây là những gì cần phải được cố định để không có nhiều hơn một matchup hoặc baseball_matchup trong DB.

phương pháp

Trả lời

1

Bạn cần phải làm hai việc để làm cho công việc này:

  1. Chấp nhận các thuộc tính như một đối số phương pháp execute của bạn.
  2. Phím tắt của cả tên nhà máy và các thuộc tính khi tạo các nhà máy singleton.

Lưu ý rằng bước 1 không đủ để giải quyết vấn đề của bạn.Ngay cả khi bạn cho phép execute chấp nhận các thuộc tính, lệnh gọi đầu tiên tới execute(:matchup, attributes) sẽ lưu lại kết quả đó và trả lại bất kỳ lúc nào bạn execute(:matchup), ngay cả khi bạn cố chuyển các thuộc tính khác nhau sang execute. Đó là lý do tại sao bạn cũng cần phải thay đổi những gì bạn đang sử dụng làm khóa băm cho mã băm @@singletons của bạn.

Dưới đây là một thực hiện Tôi đã thử nghiệm ra:

module FactoryGirl 
    class Singleton 
    @@singletons = {} 

    def self.execute(factory_key, attributes = {}) 

     # form a unique key for this factory and set of attributes 
     key = [factory_key.to_s, '?', attributes.to_query].join 

     begin 
     @@singletons[key] = FactoryGirl.create(factory_key, attributes) 
     rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique 
     # already in DB so return nil 
     end 

     @@singletons[key] 
    end 
    end 
end 

Điều quan trọng là một chuỗi gồm tên của nhà máy và một đại diện chuỗi truy vấn của các thuộc tính băm (cái gì đó như "matchup?event=6&matchupable=2"). Tôi đã có thể tạo nhiều kết hợp khác nhau với các thuộc tính khác nhau, nhưng nó tôn trọng tính độc đáo của sự kiện kết hợp sự kiện/kết hợp.

> e = FactoryGirl.create(:event) 
> bm = FactoryGirl.create(:baseball_matchup) 
> m = FactoryGirl::Singleton.execute(:matchup, :event => e, :matchupable => bm) 
> m.id 
2 
> m = FactoryGirl::Singleton.execute(:matchup, :event => e, :matchupable => bm) 
> m.id 
2 
> f = FactoryGirl.create(:event) 
> m = FactoryGirl::Singleton.execute(:matchup, :event => f, :matchupable => bm) 
> m.id 
3 

Hãy cho tôi biết nếu điều đó không hiệu quả với bạn.

1

Ruby có thể có giá trị mặc định cho các đối số, vì vậy xác định phương pháp singleton của bạn với một trống tùy chọn mặc định băm:

def self.execute(factory_key, options={}) 

Bây giờ bạn có thể gọi nó là cả hai cách:

FactoryGirl::Singleton.execute(:matchup) 
    FactoryGirl::Singleton.execute(:matchup, :matchupable => bm) 

trong phương pháp , kiểm tra hàm băm đối số tùy chọn để xem có bất kỳ thứ gì được chuyển qua không:

if options.empty? 
    # no options specified 
else 
    # options were specified 
end 
3

A s zetetic đã đề cập, bạn có thể định nghĩa tham số thứ hai trên hàm execute của bạn để gửi các thuộc tính được sử dụng trong suốt cuộc gọi đến FactoryGirl.create, với giá trị mặc định của một hash rỗng để nó không ghi đè bất kỳ tham số nào trong trường hợp bạn không sử dụng nó (bạn không cần phải kiểm tra trong trường hợp cụ thể này nếu hash thuộc tính trống). Cũng cần lưu ý rằng bạn không cần phải xác định khối bắt đầu..thêm trong trường hợp này, vì không có gì phải được thực hiện sau khi bạn giải cứu, vì vậy bạn có thể đơn giản hóa phương pháp của mình bằng cách xác định phần cứu hộ như một phần của định nghĩa phương thức. Việc gán cho trường hợp khởi tạo là tốt cũng sẽ trả về giá trị đã gán, do đó không cần phải truy cập lại hash một cách rõ ràng để trả về nó. Với tất cả những thay đổi này, các mã sẽ kết thúc như:

# Creates a class variable for factories that should be only created once. 

module FactoryGirl 

    class Singleton 
    @@singletons = {} 

    def self.execute(factory_key, attrs = {}) 
     @@singletons[factory_key] = FactoryGirl.create(factory_key, attrs) 
    rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique 
     # already in DB so return nil 
    end 
    end 

end 
+0

tôi nghĩ rằng vấn đề ở đây là khi bạn gọi FactoryGirl.create và có một bản ghi hiện có trong DB (do đó ngoại lệ được nêu ra), singleton sẽ không được trả lại vì chúng tôi không gọi @@ singletons [factory_key ] sau khi giải cứu. – keruilin