我想要做的是定义一个有效值列表,然后在相关表上添加新角色时验证该值列表。
让我举一个具体的例子:
说我有一个'就业'表,其中包含以下字段:
user_id (tied to a user table)
employer_id (tied to an employer table)
position_id (tied to a position table)
details
efbegdt
efenddt
当用户向此表添加新行时,我想确保employees_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
这个问题是否有“最佳实践”解决方案?
更新
添加另一个示例,详细说明所有设置。在此示例中,用户可以将多个电子邮件地址存储在电子邮件表中,但每种类型(个人,工作,学校等)限制一个地址。另一个表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+\-.]+@[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: "personal_email@test.com",
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 :(得分:1)
使用validate_associated
class Employment < ActiveRecord::Base
belongs_to :employee
validates_associated :employee
end
阅读文档here。
答案 1 :(得分:0)
怎么样,
validates :employer_id, presence: true, inclusion: { in: Employer.all.map(&:id), message: 'must be a valid employer.' }
答案 2 :(得分: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
这样的宝石,以缩小用户可以选择的选择范围。
用于授权和其他权限目的
例如,您不希望人们选择“禁用”/“预览”条目。如果你使用了cancan,你可以使用这样的方法。
<% f.select("user", "employer_id", Employer.accessible_by(current_ability) %>
如果他们的能力得到了正确定义,那么尝试欺骗系统最终会被拒绝访问。