使用has_many关系保存对象

时间:2015-04-08 07:11:46

标签: ruby-on-rails ruby-on-rails-4

我有一个通过category_tags拥有多个类别的地方模型。 我对标签进行了验证,以确保它有一个地方和一个类别,以及避免在一个地方两次使用相同的类别。

型号:

class PlaceCategory < ActiveRecord::Base

  has_many :place_category_tags, inverse_of: :place_category
  has_many :places, through: :place_category_tags

end

class Place < ActiveRecord::Base

  has_many :place_category_tags, inverse_of: :place
  has_many :place_categories, through: :place_category_tags

end

class PlaceCategoryTag < ActiveRecord::Base

  belongs_to :place
  belongs_to :place_category

  validates :place_id, presence: true
  validates :place_category_id, presence: true, uniqueness: {scope: :place_id}

end

我的place_category_tags表上还有一个索引,以确保数据库级别的唯一性:

add_index "place_category_tags", ["place_id", "place_category_id"], name: "index_place_category_tags_on_place_id_and_place_category_id", unique: true, using: :btree

我的问题是当我用标签保存新地方时。在保存地点之前对标签执行验证,因此它失败,因为place_id为空。

2.1.5 :001 > place = Place.new()
 => #<Place id: nil, name: "erg", created_at: nil, updated_at: nil> 
2.1.5 :002 > place.place_category_tags.build(place_category_id: 3)
 => #<PlaceCategoryTag id: nil, place_id: nil, place_category_id: 3, created_at: nil, updated_at: nil> 
2.1.5 :003 > place.save
   (0.3ms)  BEGIN
   (0.3ms)  BEGIN
  PlaceCategoryTag Exists (0.5ms)  SELECT  1 AS one FROM "place_category_tags"  WHERE ("place_category_tags"."place_category_id" = 3 AND "place_category_tags"."place_id" IS NULL) LIMIT 1
  PlaceCategoryTag Exists (0.5ms)  SELECT  1 AS one FROM "place_category_tags"  WHERE ("place_category_tags"."place_category_id" = 3 AND "place_category_tags"."place_id" IS NULL) LIMIT 1
   (0.2ms)  ROLLBACK
   (0.2ms)  ROLLBACK
 => false 
2.1.5 :004 > place.errors
 => #<ActiveModel::Errors:0x0000000384e8a8 @base=#<Place id: nil, created_at: nil, updated_at: nil>, @messages={:"place_category_tags.place_id"=>["can't be blank"]}>

我搜索了类似的问题,但我发现我必须在我所在的地方添加inverse_of选项has_many place_category_tags,我做了,但它没有解决我的问题。

我还尝试在place和place_category上执行验证,而不是place_id和place_category_id:

validates :place, presence: true
validates :place_category, presence: true, uniqueness: {scope: :place}

这似乎有效,但问题是当我尝试创建两个相同的place_category_tags时,不再执行唯一性验证,并且在数据库级别出现错误:

ActiveRecord::RecordNotUnique (PG::UniqueViolation: ERROR:  duplicate key value violates unique constraint "index_place_category_tags_on_place_id_and_place_category_id"
DETAIL:  Key (place_id, place_category_id)=(49, 3) already exists.
: INSERT INTO "place_category_tags" ("created_at", "place_category_id", "place_id", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"):
  app/controllers/backend/places_controller.rb:12:in `create'

有人知道我的代码有什么问题吗?

另外,我发现在第二个例子中完全跳过验证的事实非常奇怪。如果有人知道发生了什么,那就太棒了:)

编辑:我使用Rails 4.1.8和ruby 2.1.5p273

3 个答案:

答案 0 :(得分:0)

如果你在控制台中写道会发生什么:

place = Place.new()
place.place_category_tags.build(place_category_id: 3)
place.place_category_tags.first.place

如果inverse_of正在运行,它应该返回你的新地方。

在第一个示例中,place_id似乎是nil。 在第二个例子中,它似乎是重复的有效数字。

如果删除索引迁移中的unique: true标记,也许更容易尝试使用它。

我认为您应该对对象使用验证而不是ID。

答案 1 :(得分:0)

您可以使用validates_associated辅助方法进行验证。

ActiveRecord在保存到数据库之前验证对象,因此您不必在数据库端再次验证它。

在您的情况下,它将检查地点是否有效,然后验证其标签。但是,在构建标记之前,您需要创建地点(拥有ID),因此建议使用Place.create代替Place.new作为Sharvy Ahmed

答案 2 :(得分:0)

使用create代替new可以解决您的问题。这将确保在构建关联对象之前,场所具有id

place = Place.create
place.place_category_tags.build(place_category_id: 3)
place.save