Рельсов много не сэкономить в многоуровневой форме за счет ИД = нуль


Используя Rails 4.1.13 и Ruby 2.0.0 (хотя у меня была та же проблема с Ralis 4.0 и Ruby 1.9.3. Я прочитал множество статей об этом конкретном вопросе и не могу понять, почему мое решение (которое выглядит точно так же, как это) не работает, поэтому, пожалуйста, помогите мне.

У меня есть две модели BlogPost и Tag. Блог-пост может иметь много тегов, а один тег может иметь много блог-постов. Я связываю их через третью модель Блогпостреляции. Таким образом, это мой основной настройка:

# blog_post.rb
has_many :blog_post_tag_relations, dependent: :destroy
has_many :tags, :through => :blog_post_tag_relations
accepts_nested_attributes_for :blog_post_tag_relations, :tags

# tag.rb
has_many :blog_post_tag_relations, dependent: :destroy
has_many :blog_posts, :through => :blog_post_tag_relations

# blog_post_tag_relation.rb
belongs_to :tag
belongs_to :blog_post  
validates_uniqueness_of :tag_id, :scope => [:blog_post_id]
validates :blog_post_id, :presence => true
validates :tag_id, :presence => true    
accepts_nested_attributes_for :tag, :blog_post

У меня есть форма для блогпоста, используя Formtastic, где я создаю флажки для блогпоста, используя:

<%= f.input :blog_title %>
<%= f.input :tags, as: :check_boxes, :collection => tags.order(:name) %>

Проблема, с которой я столкнулся, заключается в том, что BlogPost не сохраняется до добавления тегов, что приводит к ошибке проверки отсутствия blog_post_id (чего нет):

  Tag Load (1.6ms)  SELECT "tags".* FROM "tags"  WHERE "tags"."id" IN (678, 56)
   (0.9ms)  BEGIN
  BlogPost Exists (1.6ms)  SELECT  1 AS one FROM "blog_posts"  WHERE ("blog_posts"."id" IS NOT NULL) AND "blog_posts"."slug" = 'de22323' LIMIT 1
  BlogPostTagRelation Exists (1.2ms)  SELECT  1 AS one FROM "blog_post_tag_relations"  WHERE ("blog_post_tag_relations"."tag_id" = 678 AND "blog_post_tag_relations"."blog_post_id" IS NULL) LIMIT 1
  CACHE (0.0ms)  SELECT  1 AS one FROM "blog_posts"  WHERE ("blog_posts"."id" IS NOT NULL) AND "blog_posts"."slug" = 'de22323' LIMIT 1
  BlogPostTagRelation Exists (1.1ms)  SELECT  1 AS one FROM "blog_post_tag_relations"  WHERE ("blog_post_tag_relations"."tag_id" = 56 AND "blog_post_tag_relations"."blog_post_id" IS NULL) LIMIT 1
  CACHE (0.0ms)  SELECT  1 AS one FROM "blog_posts"  WHERE ("blog_posts"."id" IS NOT NULL) AND "blog_posts"."slug" = 'de22323' LIMIT 1
   (0.8ms)  ROLLBACK

Похоже, что решение должно заключаться в использовании inverse_of, что я, честно говоря, не понимаю на 100%. Следует также отметить, что я не уверен на 100% в том, как использовать accepts_nested_attributes_for либо для этот тип проблемы. Я пробовал все разные настройки, но, насколько я понимаю, единственное место, где они должны быть, - это модель соединения, BlogPostRelation, например:

# blog_post_tag_relation.rb
belongs_to :tag, :inverse_of => :blog_post_tag_relations
belongs_to :blog_post, :inverse_of => :blog_post_tag_relations

validates_uniqueness_of :tag_id, :scope => [:blog_post_id]
validates :blog_post_id, :presence => true
validates :tag_id, :presence => true    

accepts_nested_attributes_for :tag, :blog_post
Это тоже не работает, и я совершенно потерялся в том, что делать.
  • самое важное: что я должен делать?
  • является ли inverse_of решением этой проблемы? Если да, то как мне его использовать?
  • правильно ли я использую accepts_nested_attributes_for?
  • имеет ли это отношение к именованию из BlogPostTagRelation (должен ли он был называться BlogPostTag вместо этого?
4 4

4 ответа:

Часть проблемы здесь заключается в том, что вы проверяете идентификаторы. Rails не может проверить, что blog_post_id присутствует, если идентификатор неизвестен, но он может проверить, что blog_post присутствует.

Таким образом, часть ответа, по крайней мере, заключается в проверке наличия связанного экземпляра, а не идентификатора.

Измените проверки на:

validates :blog_post, :presence => true
validates :tag      , :presence => true    

Я бы всегда указывал inverse_of, но я не уверен, что это часть этой проблемы.

  1. ваша структура модели в порядке.
  2. Есть один отличный способ, которым вы можете добавить теги к вашим сообщениям после того, как сообщение создано. Для этого вам просто нужно использовать метод модели.Вам не нужно inverse_of . Вот как:

    В представлении добавьте пользовательский атрибут (all_tags).

    <%= f.text_field :all_tags, placeholder: "Tags separated with comma" %>
    

    Необходимо разрешить параметр в контроллере. В вашу модель Post добавьте следующие три метода:

    def all_tags=(names)
      self.tags = names.split(",").map do |name|
      Tag.where(name: name.strip).first_or_create!
     end
    end
       #used when the post is being created. Splits the tags and creates   entries of them if they do not exist. `self.tags` ensures that they tags will be related to the post.
    
    def all_tags
     self.tags.map(&:name).join(", ")
    end
    
     #Returns all the post tags separated by comma
    
    def self.tagged_with(name)
      Tag.find_by_name!(name).posts
    end
     #Returns all the posts who also contain the tag from the current post.
    

    Вот полная реализация

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

  4. Вы можете назвать это тегированием, хотя нет никакого соглашения для именования. Если вы и другие можете понять это, это прекрасно.

На самом деле вы хотите использовать ассоциацию has_and_belongs_to_many (HABTM): http://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association

Это, однако, предполагает, что вы не хотите ничего делать с моделью отношений (blog_post_tag_relations в вашем случае)

Вам понадобятся только следующие модели и ассоциации:

class BlogPost < ActiveRecord::Base
  has_and_belongs_to_many :tags
end

class Tag < ActiveRecord::Base
  has_and_belongs_to_many :blog_posts
end

Затем вам придется переименовать вашу таблицу соединений blog_post_tag_relations в blog_posts_tags, алфавитное сочетание множественного числа двух моделей. Rails автоматически ищет / использует эту таблицу плавно в фоновом режиме. Он будет иметь только внешние ключи отношения:

create_table :blog_posts_tags, id: false do |t|
  t.belongs_to :blog_post, index: true
  t.belongs_to :tag, index: true
end

Тогда ваша форма просто работает:

<%= f.input :blog_title %>
<%= f.input :tags, as: :check_boxes, :collection => tags.order(:name) %>

Попробуйте

validates :blog_post, :presence => true
validates :blog_post_id, :presence => true, allow_nil: true #can ignore this