Rails has_many 由于 id = null 无法以嵌套形式保存
Rails has_many through cannot save in nested form due to id = null
使用 Rails 4.1.13 和 Ruby 2.0.0(尽管我在 Ralis 4.0 和 Ruby 1.9.3 上遇到了同样的问题。我已经阅读了很多关于这个的文章特殊问题,无法理解为什么我的解决方案(看起来与 this 完全一样)不起作用,所以请帮助我。
我有两个模型 BlogPost 和 Tag。一篇博文可以有很多标签,一个标签可以有很多博文。我通过第三个模型 BlogPostRelation 连接它们。因此,这是我的基本设置:
# 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
我有一个 BlogPost 表单,使用 Formtastic,我在其中为 BlogPost 创建复选框使用:
<%= 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 吗?
- 你的模型结构没问题。
创建 post 后,有一种巧妙的方法可以将标签添加到 post。你只需要使用模型方法来做 that.You 不需要 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
然后您必须将联接 table blog_post_tag_relations 重命名为 blog_posts_tags,这是两个模型的字母复数组合。 Rails 自动在后台无缝地 up/uses 看起来 table。它只会有关系的 foreign_keys:
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) %>
这里的部分问题是您正在验证 ID。如果 id 未知,Rails 无法验证 blog_post_id 是否存在, 但是 它可以验证 blog_post 是否存在。
所以部分答案至少是验证关联实例的存在,而不是 id。
将验证更改为:
validates :blog_post, :presence => true
validates :tag , :presence => true
我也总是指定 inverse_of,但我不确定这是这个问题的一部分。
尝试
validates :blog_post, :presence => true
validates :blog_post_id, :presence => true, allow_nil: true #can ignore this
使用 Rails 4.1.13 和 Ruby 2.0.0(尽管我在 Ralis 4.0 和 Ruby 1.9.3 上遇到了同样的问题。我已经阅读了很多关于这个的文章特殊问题,无法理解为什么我的解决方案(看起来与 this 完全一样)不起作用,所以请帮助我。
我有两个模型 BlogPost 和 Tag。一篇博文可以有很多标签,一个标签可以有很多博文。我通过第三个模型 BlogPostRelation 连接它们。因此,这是我的基本设置:
# 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
我有一个 BlogPost 表单,使用 Formtastic,我在其中为 BlogPost 创建复选框使用:
<%= 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 吗?
- 你的模型结构没问题。
创建 post 后,有一种巧妙的方法可以将标签添加到 post。你只需要使用模型方法来做 that.You 不需要
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
然后您必须将联接 table blog_post_tag_relations 重命名为 blog_posts_tags,这是两个模型的字母复数组合。 Rails 自动在后台无缝地 up/uses 看起来 table。它只会有关系的 foreign_keys:
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) %>
这里的部分问题是您正在验证 ID。如果 id 未知,Rails 无法验证 blog_post_id 是否存在, 但是 它可以验证 blog_post 是否存在。
所以部分答案至少是验证关联实例的存在,而不是 id。
将验证更改为:
validates :blog_post, :presence => true
validates :tag , :presence => true
我也总是指定 inverse_of,但我不确定这是这个问题的一部分。
尝试
validates :blog_post, :presence => true
validates :blog_post_id, :presence => true, allow_nil: true #can ignore this