Rails: как указать допустимые значения для полей в моей модели через другую модель - PullRequest
2 голосов
/ 05 марта 2012

Что я хочу сделать, это определить список допустимых значений, а затем проверить его по этому списку значений при добавлении новой роли в связанную таблицу.

Позвольте мне привести конкретный пример:

Допустим, у меня есть таблица "Занятость" со следующими полями:

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+\-.]+@[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, неверный код) завершается неудачей.

Ответы [ 3 ]

1 голос
/ 05 марта 2012

Использование validate_associated

class Employment < ActiveRecord::Base
  belongs_to :employee

  validates_associated :employee
end

Прочтите документацию здесь .

0 голосов
/ 05 марта 2012

Я предполагаю user belongs_to employer в ваших настройках отношений.

В этом случае, возможно, вы могли бы упростить код, используя методы ассоциации.

Если вы используете конструктор форм по умолчанию,

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

. Это поле выбора со всеми возможными работодателями, которые уже определены.Тем не менее, ваш пользователь все еще может подделать создание и вставить новый идентификатор работодателя, который не существует.(sidenote: я не знаю, почему они могут это сделать).

Насколько это возможно, я рекомендую использовать связанные методы вместо написания пользовательских функций проверки и т. Д., Поскольку это облегчает понимание вашего кода другими людьми.

Если вам просто нужна проверка, другой ответ, вероятно, лучше =)

В вашем действии создания вы можете сделать еще одну проверку.

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) %> 

Если их способность правильно определена, попытка подделки системы приведет к отказу в доступе.

0 голосов
/ 05 марта 2012

Как насчет

validates :employer_id, presence: true, inclusion: { in: Employer.all.map(&:id), message: 'must be a valid employer.' }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...