在惯用的ActiveRecord中查找或创建唯一的关联对象?

时间:2014-08-31 19:02:48

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

如果不存在新主题,如何以惯用方式创建新主题,如果主题确实存在,如何创建与会议的关联?

我正在开展一个有会议和主题的项目。它们通过ConferenceTopic对象相互关联。话题'名字是独一无二的。

以下是我在名为#find_or_create_topic_with的会议上创建的自定义方法,该方法采用名称参数。我之所以这样做,是因为当我在会议主题上尝试#find_or_create_by名称时,我没有成功为第二个主题创建一个名称(下面的Rails控制台日志)

The commit for this kludge is on Github.

class Conference < ActiveRecord::Base
  …

  def find_or_create_topic_with(name)
    if topic = Topic.find_by(name: name)
      self.topics.include?(topic) ? topic : self.topics << topic
    else
      self.topics.create(name: name)
    end
  end
end

关于主题的find_or_create_by的Rails控制台日志

2.1.2 :001 > Conference.last.topics.find_or_create_by(name: "fun")
  Conference Load (1.0ms)  SELECT  "conferences".* FROM "conferences"   ORDER BY "conferences"."id" DESC LIMIT 1
  Topic Load (0.5ms)  SELECT  "topics".* FROM "topics" INNER JOIN "conference_topics" ON "topics"."id" = "conference_topics"."topic_id" WHERE "conference_topics"."conference_id" = $1 AND "topics"."name" = 'fun' LIMIT 1  [["conference_id", 3]]
   (0.1ms)  BEGIN
  Topic Exists (0.4ms)  SELECT  1 AS one FROM "topics"  WHERE "topics"."name" = 'fun' LIMIT 1
  SQL (0.3ms)  INSERT INTO "topics" ("created_at", "name", "updated_at") VALUES ($1, $2, $3) RETURNING "id"  [["created_at", "2014-08-31 18:57:56.274109"], ["name", "fun"], ["updated_at", "2014-08-31 18:57:56.274109"]]
  SQL (0.5ms)  INSERT INTO "conference_topics" ("conference_id", "created_at", "topic_id", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["conference_id", 3], ["created_at", "2014-08-31 18:57:56.287526"], ["topic_id", 8], ["updated_at", "2014-08-31 18:57:56.287526"]]
   (2.1ms)  COMMIT
 => #<Topic id: 8, name: "fun", created_at: "2014-08-31 18:57:56", updated_at: "2014-08-31 18:57:56">
2.1.2 :002 > Conference.first.topics.find_or_create_by(name: "fun")
  Conference Load (0.6ms)  SELECT  "conferences".* FROM "conferences"   ORDER BY "conferences"."id" ASC LIMIT 1
  Topic Load (0.4ms)  SELECT  "topics".* FROM "topics" INNER JOIN "conference_topics" ON "topics"."id" = "conference_topics"."topic_id" WHERE "conference_topics"."conference_id" = $1 AND "topics"."name" = 'fun' LIMIT 1  [["conference_id", 1]]
   (0.2ms)  BEGIN
  Topic Exists (0.3ms)  SELECT  1 AS one FROM "topics"  WHERE "topics"."name" = 'fun' LIMIT 1
   (0.2ms)  COMMIT
 => #<Topic id: nil, name: "fun", created_at: nil, updated_at: nil>
2.1.2 :003 > Conference.first.topics
  Conference Load (0.5ms)  SELECT  "conferences".* FROM "conferences"   ORDER BY "conferences"."id" ASC LIMIT 1
  Topic Load (0.3ms)  SELECT "topics".* FROM "topics" INNER JOIN "conference_topics" ON "topics"."id" = "conference_topics"."topic_id" WHERE "conference_topics"."conference_id" = $1  [["conference_id", 1]]
 => #<ActiveRecord::Associations::CollectionProxy [#<Topic id: 7, name: "ruby", created_at: "2014-08-31 18:40:40", updated_at: "2014-08-31 18:40:40">]>

以下是查找主题会议的日志&#34; fun&#34;

2.1.2 :015 > Topic.find_by(name: "fun").conferences
  Topic Load (0.5ms)  SELECT  "topics".* FROM "topics"  WHERE "topics"."name" = 'fun' LIMIT 1
  Conference Load (0.3ms)  SELECT "conferences".* FROM "conferences" INNER JOIN "conference_topics" ON "conferences"."id" = "conference_topics"."conference_id" WHERE "conference_topics"."topic_id" = $1  [["topic_id", 8]]
 => #<ActiveRecord::Associations::CollectionProxy [#<Conference id: 3, name: "TooLongDidn'tCareConf", location: "Boston", code_of_conduct: false, childcare: false, last_years_attendance: 0, created_at: "2014-08-31 17:47:35", updated_at: "2014-08-31 17:47:35">]>
2.1.2 :016 > Topic.find_by(name: "fun").conferences.count
  Topic Load (0.5ms)  SELECT  "topics".* FROM "topics"  WHERE "topics"."name" = 'fun' LIMIT 1
   (0.3ms)  SELECT COUNT(*) FROM "conferences" INNER JOIN "conference_topics" ON "conferences"."id" = "conference_topics"."conference_id" WHERE "conference_topics"."topic_id" = $1  [["topic_id", 8]]
 => 1

我希望收集代理包含第一次和最后一次会议,并且计数为2.只有#find_or_create_by我只能将它与首次创建的会议相关联,而不是后续会议找到id为nil的主题。

2 个答案:

答案 0 :(得分:2)

只是阅读了代码,看起来你正在尝试满足三个用例:

  • 该主题已存在且已加入会议。 =&GT;什么都不做。
  • 该主题存在且未加入会议。 =&GT;将主题添加到会议的主题中。
  • 该主题不存在。 =&GT;创建主题并将其添加到会议的主题中。

我认为您的代码很好。真。没有什么太重要的,我会立即想要重写它。

那就是说,我可能会这样写:

def find_or_create_topic_with(name)
  transaction do
    topic = Topic.where(name: name).first_or_create!
    self.topics.where(topic: topic).first_or_create!
  end
rescue
  # need to handle uniqueness violations, etc.
end

一些补充说明:

  • 测试在哪里,兄弟!?
  • 为了避免竞争条件创建错误的数据,您可能需要一个关于名称主题的唯一索引(在模型中永远不会有一个没有唯一索引的validates_unique来备份它!)而另一个关于topic_id和conference_id的会议主题。< / LI>

如果有任何不合理的话,请随意通过Twitter或其他任何方式来打我。

答案 1 :(得分:0)

看起来像会议has_many主题。鉴于此,您应该只能在.topics关系上使用find_or_create_by

topic = my_conference.topics.find_or_create_by(name: "Name")

# topic will now contain either a newly created Topic that is associated with `my_conference`,
# or will be the existing Topic with name "Name" from `my_conference.topics`.