Как вы можете проверить наличие принадлежности к ассоциации с Rails? - PullRequest
18 голосов
/ 22 июня 2009

Скажем, у меня есть базовое приложение 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

Что имеет смысл, потому что в комментарии еще нет page_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

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

Ответы [ 6 ]

22 голосов
/ 18 января 2012

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

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

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

  1. Сохраните статью перед созданием комментариев (rails будет автоматически передавать идентификатор статьи, сгенерированный во время сохранения, каждому новому созданному комментарию; см. Ответ Николаса Хаббарда)
  2. Явно установите статью в комментарии после ее создания (см. Ответ В. Эндрю Ло III)
  3. Использовать inverse_of:
    <code>class Article < ActiveRecord::Base
      has_many :comments, :inverse_of => :article
    end

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

1 голос
/ 03 июня 2011

Сбой, потому что в 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 - это разные объекты без карты идентификации.

1 голос
/ 20 июля 2009

Я исправил эту проблему, добавив следующую строку в мой _comment.html.erb:

«NEW», если form.object.new_record? %>

Теперь проверка работает в автономной форме, а также в мульти-форме.

1 голос
/ 22 июня 2009

Вместо проверки наличия идентификатора статьи вы можете проверить наличие статьи.

validates_presence_of :article

Затем, когда вы создаете свой комментарий:

article.comments.build :article => article
1 голос
/ 22 июня 2009

Вы правы. Статья нуждается в идентификаторе, прежде чем эта проверка будет работать. Одним из способов решения этой проблемы является сохранение статьи, например:

>> 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, чтобы вы не заканчиваются никакими дополнительными статьями.

0 голосов
/ 22 июня 2009

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...