2012-03-05 41 views
2

我想要做的是定義一個有效值列表,然後在相關表上添加新角色時,根據該值列表進行驗證。Rails:如何通過另一個模型爲我的模型中的字段指定有效值

讓我提供一個具體的例子:

說我有一個「就業」表,具有以下字段:

user_id (tied to a user table) 
employer_id (tied to an employer table) 
position_id (tied to a position table) 
details 
efbegdt 
efenddt 

當用戶添加一個新行到這個表,我想確保其他表上的employer_id和position_id已經存在,並且如果兩種情況都不是這種情況,則不允許保存。

的解決方案,我已經看到了迄今爲​​止採取這種形式:

class Employment < ActiveRecord::Base 
    EMPLOYERS = ['Google', 'Yahoo', 'Microsoft'] 
    POSITIONS = ['Web Developer', 'Database Admin', 'QA'] 
    validates_inclusion_of :employer_id, :in => EMPLOYERS 
    validates_inclusion_of :position_id, :in => POSITIONS 
end 

但這種方法不夠靈活,以適應潛在的僱主數千名和立場,也沒有提供一種簡單的方法,讓用戶添加新的有效條目,如果他們的僱主目前不存在。

我也看到了這種做法:

class Employment < ActiveRecord::Base 
    validate :employer_exists 

    protected 

    def employer_exists 
    ids = Employer.all.map(&:id) 
    if !employer_id.blank? && !ids.member?(employer_id) 
     errors.add(:employer_id, "invalid employer") 
    end 
    end 
end 

這更接近我想要的東西,但是當我測試使用RSpec,檢查是否對僱主的表裏是有效的失敗:

Failure/Error: it { should be_valid } 
    expected valid? to return true, got false 

是否存在針對此問題的「最佳實踐」解決方案?

UPDATE

只是增加另一個例子,所有的設置詳細說明。在此示例中,用戶可以在電子郵件表中存儲多個電子郵件地址,但每種類型(個人,工作,學校等)的地址限制爲一個。另一張表,email_dfn,定義了所有有效的類型:

遷移文件

class CreateEmailDfns < ActiveRecord::Migration 
    def change 
    create_table :email_dfns do |t| 
     t.string :short_description 
     t.string :long_description 

     t.timestamps 
    end 
    end 
end 

class CreateEmails < ActiveRecord::Migration 
    def change 
    create_table :emails do |t| 
     t.integer :user_id 
     t.integer :email_dfn_id 
     t.string :value 
     t.text :notes 

     t.timestamps 
    end 
    add_index :emails, [:user_id, :email_dfn_id] 
    end 
end 

型號

class Email < ActiveRecord::Base 
    attr_accessible :value, :notes, :email_dfn_id 
    belongs_to :user 
    belongs_to :email_dfn 

    validates_associated :email_dfn 

    valid_email_regex = /\A[\w+\-.][email protected][a-z\d\-.]+\.[a-z]+\z/i 
    validates :value, presence: true, 
        length: { maximum: 256 }, 
        format: { with: valid_email_regex }, 
        uniqueness: { case_sensitive: false } 

    validates :user_id, presence: true 
    validates :email_dfn_id, presence: true 
end 

class EmailDfn < ActiveRecord::Base 

    attr_accessible :short_description, 
        :long_description, 
    validates_uniqueness_of :short_description, 
          :long_description 

    has_many :emails 
end 

測試

require 'spec_helper' 

describe Email do 

    let(:user) { FactoryGirl.create(:user) } 
    before { @email = user.emails.build(email_dfn_id: 1, 
             value: "[email protected]", 
             notes: "My personal email address") } 

    subject { @email } 

    it { should respond_to(:value) } 
    it { should respond_to(:notes) } 
    it { should respond_to(:email_dfn_id) } 
    it { should respond_to(:user_id) } 
    it { should respond_to(:user) } 
    its(:user) { should == user } 

    it { should be_valid } 

    describe "when user id is not present" do 
    before { @email.user_id = nil } 
    it { should_not be_valid } 
    end 

    describe "when email id is invalid" do 
    before { @email.email_dfn_id = 999 } 
    it { should_not be_valid } 
    end 
end 

在該當前的設置,最後一次試驗(設定email_dfn_id = 999,無效代碼)失敗。

回答

0

怎麼樣,

validates :employer_id, presence: true, inclusion: { in: Employer.all.map(&:id), message: 'must be a valid employer.' } 
+0

您的解決方案可能是昂貴的,當'employees'表有行的1000,你會加載所有ID的內存和每個驗證執行數組查找。除此之外,在生產模式下,該解決方案不包括啓動服務器後添加的員工。 –

+0

我不認爲這會很貴。我也不確定你的第二點是否正確。我試圖在生產模式下啓動我的控制檯,並且ModelName.all在創建新記錄時進行了更新。 – Max

+0

這似乎工作要求ID是有效的,但我關心縮放。我用這種方法碰到的另一個問題是,檢查新行是否有效會失敗。 (見更新我的帖子上面...它應該{} be_valid是一個會在這種情況下失敗) – user1248862

0

我在你們的關係設置承擔user belongs_to employer

在這種情況下,也許你可以使用關聯方法簡化代碼。

如果您使用默認的表單生成器,

<% f.select("user", "employer_id", Employer.all) %> 

這給你所有誰已經定義了可能的僱主選擇框。但是,您的用戶仍然可以欺騙創建並插入一個不存在的新僱主ID。 (旁註:我不知道他們爲什麼會那樣做)。

儘可能,我會建議使用相關的方法,而不是編寫自定義驗證功能等,因爲它使你的代碼更容易理解別人。

如果您只是需要驗證,對方的回答可能是更好=)

在你創建的動作,你可以做一個更多的檢查。

def create 
    @user = User.new(params[:id]) 
    @user.employer = Employer.find(params[:user][:employer_id]) 

    ..... # Standard save code or your own. 

end 

如果它更多的是與授權和安全性,可以考慮實施類似cancan寶石縮小的選擇,用戶可以選擇宇宙。

授權和其他權限目的

例如,你不希望人們選擇「禁用」 /「預覽」條目。如果你使用康康,你可以有這樣的方法。

<% f.select("user", "employer_id", Employer.accessible_by(current_ability) %> 

如果他們有能力正確定義,試圖欺騙系統將最終被拒絕訪問。

+0

這裏的好主意 - 但正如你所說的,我真的想通過關聯方法來實現這一點,如果可能的話。我在主帖中發佈了另一個(非常具體的)示例 - 關於爲什麼當前設置不強制要求email_dfn模型中定義的電子郵件ID是「有效」的任何想法? – user1248862

1

使用validate_associated

class Employment < ActiveRecord::Base 
    belongs_to :employee 

    validates_associated :employee 
end 

閱讀文檔here

+0

我想過這樣做,以及...我打這個方法的問題是以下RSpec的測試將失敗: 「描述‘當employer_id無效’之前{@ employment.employer_id = 999} 做 它{ should_not be_valid} 結束」 其中employer_id = 999目前沒有對僱主表 – user1248862

+0

有效ID在測試之前,應預先創建的'Employer'。 –

+0

是的 - 我也是這麼做的,儘管我在最初的一些工作中沒有意識到,我已經將自動遞增的ID加到了比我想象的更高的數字上。重置測試數據庫後,我可以驗證正確的ID爲1(請參閱主帖子中的新示例),但這種「validates_associated」設置似乎還不足以要求驗證ID是有效的(即,測試ID = 999仍然會返回「有效」,但不應該) – user1248862

相關問題