Как вы можете проверить наличие принадлежащего ассоциации с Rails?
Скажем, у меня есть базовое приложение Rails с базовым отношением "один ко многим", в котором каждый комментарий относится к статье:
$ rails blog
$ cd blog
$ script/generate model article name:string
$ script/generate model comment article:belongs_to body:text
Теперь я добавляю в код для создания ассоциаций, но я также хочу быть уверенным, что когда я создаю комментарий, у него всегда есть статья:
class Article < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :article
validates_presence_of :article_id
end
Итак, теперь скажем, что я хотел бы сразу создать статью с комментарием:
$ rake db:migrate
$ script/console
Если вы это сделаете:
>> article = Article.new
=> #<Article id: nil, name: nil, created_at: nil, updated_at: nil>
>> article.comments.build
=> #<Comment id: nil, article_id: nil, body: nil, created_at: nil, updated_at: nil>
>> article.save!
Вы получите эту ошибку:
ActiveRecord::RecordInvalid: Validation failed: Comments is invalid
Это имеет смысл, потому что в комментарии еще нет страницы_id.
>> article.comments.first.errors.on(:article_id)
=> "can't be blank"
Итак, если я удалю validates_presence_of :article_id
из comment.rb
, тогда я могу сделать сохранение, но это также позволит вам создавать комментарии без идентификатора статьи. Каков типичный способ справиться с этим?
ОБНОВЛЕНИЕ. Основываясь на предположении Николаса, здесь реализована реализация save_with_comments, которая работает, но является уродливой:
def save_with_comments
save_with_comments!
rescue
false
end
def save_with_comments!
transaction do
comments = self.comments.dup
self.comments = []
save!
comments.each do |c|
c.article = self
c.save!
end
end
true
end
Не уверен, что я хочу добавить что-то подобное для каждой ассоциации "один ко многим". Энди, вероятно, прав в том, что лучше всего не пытаться выполнить каскадное сохранение и использовать решение вложенных атрибутов. Я оставлю это открытым какое-то время, чтобы узнать, есть ли у кого-нибудь другие предложения.
Ответы
Ответ 1
Я также изучал эту тему, и вот мое резюме:
Основная причина, по которой это не работает OOTB (по крайней мере при использовании validates_presence_of :article
, а не validates_presence_of :article_id
), заключается в том, что рельсы не используют внутреннюю карту идентификации и, следовательно, сами по себе не знают, что article.comments[x].article == article
Я нашел три обходных решения, чтобы заставить его работать с небольшими усилиями:
Последнее решение было ботом, но в этой статье было упомянуто, но, похоже, это решение для быстрого исправления рельсов из-за отсутствия карты идентификации. Это также выглядит наименее навязчивым для меня одним из трех.
Ответ 2
Вы правы. Статья нуждается в идентификаторе до того, как эта проверка будет работать. Один из способов - сохранить статью так:
>> article = Article.new
=> #<Article id: nil, name: nil, created_at: nil, updated_at: nil>
>> article.save!
=> true
>> article.comments.build
=> #<Comment id: nil, article_id: 2, body: nil, created_at: nil, updated_at: nil>
>> article.save!
=> true
Если вы создаете новую статью с комментарием в одном методе или действии, я бы рекомендовал создать статью и сохранить ее, а затем создать комментарий, но обернуть всю вещь внутри блока Article.transaction, чтобы вы не 't в конечном итоге с любыми дополнительными статьями.
Ответ 3
Вместо проверки наличия идентификатора статьи вы можете проверить наличие статьи.
validates_presence_of :article
Затем, когда вы создаете свой комментарий:
article.comments.build :article => article
Ответ 4
Я исправил эту проблему, добавив следующую строку в мой _comment.html.erb:
"NEW", если form.object.new_record? % > Теперь проверка выполняется в автономной форме и в нескольких форматах.
Ответ 5
Это не удается, поскольку в Rails версии 2.3 или 3.0 отсутствует идентификационная карта. Вы можете вручную исправить это, сшив их вместе.
a = Article.new
c = a.comments.build
c.article = a
a.save!
Это ужасно, и то, что поможет идентификационная карта в 3.1 (помимо повышения производительности). c.article
и a.comments.first.article
- разные объекты без идентификационного отображения.
Ответ 6
Если вы используете Rails 2.3, вы используете новую вложенную модель. Я заметил те же ошибки с validates_presence_of
, как вы указали, или если в миграции для этого поля был указан :null => false
.
Если вы намерены создавать вложенные модели, вы должны добавить accepts_nested_attributes_for :comments
в article.rb
. Это позволит вам:
a = Article.new
a.comments_attributes = [{:body => "test"}]
a.save! # creates a new Article and a new Comment with a body of "test"
У вас есть способ, которым он должен работать для меня, но я вижу, что это не с Rails 2.3.2. Я отлаживал before_create
в комментариях с помощью вашего кода, а article_id
поставляется через сборку (это нуль), так что это не сработает. Вам сначала нужно сохранить статью, как указал Николя, или удалить проверку.