Подтвердить что ты не робот

Как вы можете проверить наличие принадлежащего ассоциации с 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

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

4b9b3361

Ответ 1

Я также изучал эту тему, и вот мое резюме:

Основная причина, по которой это не работает OOTB (по крайней мере при использовании validates_presence_of :article, а не validates_presence_of :article_id), заключается в том, что рельсы не используют внутреннюю карту идентификации и, следовательно, сами по себе не знают, что article.comments[x].article == article

Я нашел три обходных решения, чтобы заставить его работать с небольшими усилиями:

  • Сохраните статью перед созданием комментариев (рельсы автоматически передадут идентификатор статьи, который был сгенерирован во время сохранения для каждого вновь созданного комментария, см. ответ Николаса Хаббарда)
  • Явным образом задайте статью о комментарии после ее создания (см. ответ W. Andrew Loe III)
  • Использовать inverse_of:
    class Article < ActiveRecord::Base
      has_many :comments, :inverse_of => :article
    end

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

Ответ 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 поставляется через сборку (это нуль), так что это не сработает. Вам сначала нужно сохранить статью, как указал Николя, или удалить проверку.