在 Rails 中更高效地查找或创建多个记录

发布于 2024-10-19 14:09:29 字数 782 浏览 3 评论 0原文

我有一个应用程序需要发送用户活动邀请。当用户邀请朋友(用户)参加活动时,如果尚不存在一条将用户连接到该活动的新记录,则会创建一条新记录。我的模型由用户、事件和 events_user 组成。

class Event
    def invite(user_id, *args)
        user_id.each do |u|
            e = EventsUser.find_or_create_by_event_id_and_user_id(self.id, u)
            e.save!
        end
    end
end

用法

Event.first.invite([1,2,3])

我认为上述方法不是完成我的任务的最有效方法。我设想了一种类似的方法

 Model.find_or_create_all_by_event_id_and_user_id

,但不存在。

未经验证的模型

class User 
  has_many :events_users 
  has_many :events 
end 
class EventsUser 
  belongs_to :events 
  belongs_to :users 
end 
class Event 
  has_many :events_users 
  has_many :users, :through => :events_users 
end

I have an app that needs to send out user invitations for events. When a user invites a friend (user) to an event, a new record connecting the user to the event is created if one doesn't already exist. My models consists of user, event, and events_user.

class Event
    def invite(user_id, *args)
        user_id.each do |u|
            e = EventsUser.find_or_create_by_event_id_and_user_id(self.id, u)
            e.save!
        end
    end
end

Usage

Event.first.invite([1,2,3])

I don't think the above is the most efficient way to accomplish my task. I envisioned a method like

 Model.find_or_create_all_by_event_id_and_user_id

but one does not exist.

Models without validations

class User 
  has_many :events_users 
  has_many :events 
end 
class EventsUser 
  belongs_to :events 
  belongs_to :users 
end 
class Event 
  has_many :events_users 
  has_many :users, :through => :events_users 
end

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

江南烟雨〆相思醉 2024-10-26 14:09:29

首先获取所有现有记录,然后创建所有缺失的记录可能会更快:

class Event
  def invite(user_ids, *args)
    existing_user_ids = event_users.where(user_id: user_ids).pluck(:user_id)
    (user_ids - existing_user_ids).each do |u|
      event_users.create(user_id: u)
     end
  end
end

这样,如果所有 event_users 均已存在,您只需进行 1 次查询。但是,如果不存在 event_users,则与每个 EventUser 创建所需的查询数量相比,此方法会执行额外的查询。

It might be quicker to first fetch all existing records and then create all missing records:

class Event
  def invite(user_ids, *args)
    existing_user_ids = event_users.where(user_id: user_ids).pluck(:user_id)
    (user_ids - existing_user_ids).each do |u|
      event_users.create(user_id: u)
     end
  end
end

This way you make only 1 query if all event_users already exist. However, if no event_users exist, this method does an extra query - compared to the number of queries required for each single EventUser creation.

罪#恶を代价 2024-10-26 14:09:29

你所说的最有效率是什么意思?我假设高效意味着高性能,而不是优雅、干燥、可维护的代码等。

从数据库的角度来看,如果您想将 100 条记录插入数据库,这将转换为 100“INSERT INTO events_models VALUES (x, x)" sql 查询(如果您还有唯一性验证,则可能有 100 个“SELECT COUNT(*) ..”查询)。因此,即使您想要的方法在 AR 中实现,它仍然会在属性数组上有一个循环,并保存每个 event_id、user_id 对。

从 Ruby/Rails 的角度来看,如果你想在模型上进行验证/回调等,那么你必须在循环中一一创建一个 ActiveRecord 实例。现在,如果您想超级优化您的方法(删除 ActiveRecord 类的实例化),您可以手动编写 sql 查询(从而节省一些时间和内存)。然而,与风险相比,收益微乎其微。

顺便提一句,
e.save! 不是必需的,因为:

同样的动态取景器风格可以
用于创建对象,如果它
尚不存在。这个动态
查找器被调用
find_or_create_by_ 并将返回
对象(如果它已经存在)并且
否则创建它,然后返回它。

What do you mean by most efficient? I will assume that by efficient you mean performant, and not elegant, DRY, maintainable code, etc.

From a DB viewpoint, if you want to insert 100 records into the DB, this will translate to 100 "INSERT INTO events_models VALUES (x, x)" sql queries (and maybe 100 "SELECT COUNT(*) .." queries if you also have a uniqueness validation). So, even if the method you want would be implemented in AR, it would still have a loop on the attribute arrays with a save on each event_id, user_id pair).

From Ruby/Rails point of view, if you want to have validations/callbacks/etc on your model, then you must create an ActiveRecord instance one by one in a loop. Now, if you want to super optimize your method (to drop the instantiation of an ActiveRecord class), you can manually write the sql queries (therefore saving some time and memory). The gains are however minimal in comparison with the risks.

Btw,
e.save! is not necessary as:

The same dynamic finder style can be
used to create the object if it
doesn’t already exist. This dynamic
finder is called with
find_or_create_by_ and will return the
object if it already exists and
otherwise creates it, then returns it.

一抹苦笑 2024-10-26 14:09:29

如果连接模型中没有任何验证或回调,最快的方法是原始 sql:


self.connection.execute(%Q{insert into events_users (event_id,user_id) 
                             select distinct events.id,users.id from events,users 
                             where events.id = #{self.id} 
                             and users.id in ( #{user_ids.join(",")} )
                           }
                        )

在进行上述调用之前,您可以编写一个查询来消除 user_ids 中的现有记录。我有一个关于此的上一个问题< /a>,特别是关于没有模型的连接表。

由于您确实有一个连接模型,因此您可以使用 ar-import gem:

books = []
10.times do |i| 
  books << Book.new(:name => "book #{i}")
end
Book.import books

If you don't have any validations or callbacks in the join model, the fastest method is raw sql:


self.connection.execute(%Q{insert into events_users (event_id,user_id) 
                             select distinct events.id,users.id from events,users 
                             where events.id = #{self.id} 
                             and users.id in ( #{user_ids.join(",")} )
                           }
                        )

You could write a query to eliminate existing records from user_ids before making the above call. I had a previous question about this, specifically about join tables that don't have a model.

Since you do have a join model, you could use the ar-import gem:

books = []
10.times do |i| 
  books << Book.new(:name => "book #{i}")
end
Book.import books
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文