Рельсов много не сэкономить в многоуровневой форме за счет ИД = нуль
Используя 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 ответа:
Часть проблемы здесь заключается в том, что вы проверяете идентификаторы. Rails не может проверить, что blog_post_id присутствует, если идентификатор неизвестен, но он может проверить, что blog_post присутствует.
Таким образом, часть ответа, по крайней мере, заключается в проверке наличия связанного экземпляра, а не идентификатора.Измените проверки на:
validates :blog_post, :presence => true validates :tag , :presence => true
Я бы всегда указывал inverse_of, но я не уверен, что это часть этой проблемы.
- ваша структура модели в порядке.
Есть один отличный способ, которым вы можете добавить теги к вашим сообщениям после того, как сообщение создано. Для этого вам просто нужно использовать метод модели.Вам не нужно
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.
Вы используете
nested_attributes_for
правильно, но в этом случае у вас есть модели, у которых есть только имя и столбец belongs_to, поэтому использование этого является излишним.Вы можете назвать это тегированием, хотя нет никакого соглашения для именования. Если вы и другие можете понять это, это прекрасно.
На самом деле вы хотите использовать ассоциацию 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) %>