Использование attr_accessible в модели соединения с отношением has_many :through

У меня есть ПОЛЬЗОВАТЕЛЬ, который создает КОМПАНИЮ и становится СОТРУДНИКОМ в процессе. В таблице сотрудников есть :user_id и :company_id.

class User
has_many :employees
has_many :companies, :through => :employees

class Employee
belongs_to :user
belongs_to :company
attr_accessible :active

class Company
has_many :employees
has_many :users, :through => employees

Довольно простой. Но вот в чем дело, у ресурса EMPLOYEE помимо внешних ключей есть и другие атрибуты, например, логическое значение :active. Я хотел бы использовать attr_accessible, но это вызывает некоторые проблемы. Атрибут :user_id установлен правильно, но :company_id равен нулю.

@user.companies << Company.new(...)
Employee id:1 user_id:1 company_id:nil

Итак, мой вопрос: если :user_id установлен правильно, несмотря на то, что это не attr_accessible, почему :company_id все равно не установлен правильно? Это не должно быть attr_accessible.

Я использую Rails 3.0.8, а также тестировал 3.0.7.


person Paulo Oliveira    schedule 22.06.2011    source источник


Ответы (2)


Здесь много битов, работающих вместе.

Вы определенно хотите использовать attr_accessible на всех моделях. (Погуглите «взломать рельсы массового назначения» и прочитайте Руководство Rails по массовому назначению. )

После того как вы добавите attr_accessible в модель, все назначения из хэшей (массовые назначения) будут отключены, кроме тех, которые вы явно разрешили. Однако вы по-прежнему можете назначать значения напрямую, по одному.

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

Методы .create и .build не используют массовое присваивание, поэтому они могут устанавливать значение одной ассоциации внешнего ключа. Если есть несколько ассоциаций, насколько я могу судить, вам придется установить все, кроме первой, отдельно.

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

Из вашего примера мне непонятно, как создается экземпляр Employee. Но поскольку Сотрудник принадлежит как Пользователю, так и Компании, я думаю, что что-то вроде этого может сработать, если предположить, что @user уже существует:

company  = @user.companies.create(..) # fills in company.user_id and saves to DB
employee = @user.employees.build(..)  # fills in employee.user_id but does NOT save yet
employee.company = company            # fills in employee.company_id
employee.save                         # now save to DB
person Mark Berry    schedule 28.03.2012
comment
как проверить что-то подобное в RSpec? рассмотрим User с has_many :roles и has_many :accounts, through: :roles. Допустим, вы отключили attr_accessible на roles.user_id, roles.account_id и roles.role. Я не могу представить, как вы будете тестировать роль в Rspec. Как бы вы создали роль и привязали ее к учетной записи и пользователю? - person Mohamad; 01.05.2012
comment
Мохамад, это зависит от того, что вы хотите проверить. Если вы хотите подтвердить, что роль не может быть назначена массово, вы можете использовать модельный тест, подобный тесту Майкла Хартла пример (требуется Rails 3.2) и/или интеграционный тест, подобный тому, который я предлагаю здесь. Для создания роли и привязки ее к учетной записи и пользователю вы будете использовать стандартные команды ActiveRecord в тесте, или, что более вероятно, FactoryGirl. - person Mark Berry; 03.05.2012

Company_id равен нулю просто потому, что Company еще не была сохранена в базе данных — Company.new просто создает объект в памяти, еще не сохраняя его.

Если вы сделаете:

@user.companies << Company.create(..)

or

@user.companies << Company.first

Они оба должны работать. Есть даже более короткий метод, который, я думаю, тоже должен работать:

@user.companies.create(..)

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

@user.companies.build(..) 

(что похоже на код в вашем примере).

С точки зрения вашего логического атрибута active в модели Employee, если это столбец в базе данных, вам не нужно явно объявлять для него attr_accessible — он будет доступен по умолчанию.

person Frankie Roberto    schedule 22.06.2011
comment
Фрэнки, спасибо, что нашли время помочь мне. Я использовал: @user.companies.create(), и ошибка была такой же. Экземпляр компании сохраняется в базе данных, но когда дело доходит до сотрудника, ActiveRecord жалуется, что компания не должна быть пустой. Я вообще не пробовал другое ваше предложение: @user.companies ‹‹ Company.create() В модели Employee я могу использовать проверки для company_id, такие как присутствие. Но мне было совершенно ясно, что это должен быть атрибут attr_accessible. Когда я использую его для company_id, работают как create, так и ‹‹ (с Company.new). Оба терпят неудачу, когда я этого не делаю. - person Paulo Oliveira; 22.06.2011
comment
Я также пробовал @user.companies ‹‹ Company.create(), в то время как :company_id не является attr_accessible. Неудача точно такая же. Компания не может быть пустой, поэтому сохраняется компания экземпляра, но не сотрудник. Когда :company_id настроен как доступный, работают три способа: create, ‹‹ Company.new и ‹‹ Company.create. - person Paulo Oliveira; 22.06.2011
comment
Привет. Во-первых, вам не нужно объявлять какие-либо атрибуты как attr_accessible — все они доступны по умолчанию. - person Frankie Roberto; 24.06.2011
comment
Во-вторых, если вы вызовете @user.companies.create(..), это создаст новую компанию (т.е. сохраните ее в БД), а также инициализирует (но не сохранит) новый объект Employee, который объединяет пользователя и компанию вместе и вернуть этот объект. Принимая во внимание, что @user.companies ‹‹ Company.create(..) создаст и сохранит записи как о компании, так и о сотрудниках. Если вы прочитали api.rubyonrails.org/classes/ ActiveRecord/Associations/ вы увидите все возможные варианты. - person Frankie Roberto; 24.06.2011
comment
Если я использую attr_accessible для двух атрибутов внешнего ключа, а затем использую f.association :user в форме @company (или наоборот), тогда ActiveRecord назначает только один атрибут внешнего ключа, как описано выше. Если я опускаю объявление attr_accessible (оно избыточно, как упоминалось выше), то все работает нормально. Я не понимаю, почему объявление или не объявление attr_accessible для внешних ключей должно иметь значение - я ожидал, что ActiveRecord сохранит компанию, получит ее идентификатор и установит его для сотрудника вместе с существующим идентификатором пользователя... - person danebez; 12.06.2014