使用Postgres数组列实现Rails多对多关系

时间:2015-05-06 22:55:50

标签: ruby-on-rails ruby performance postgresql orm

我最近听说在Postgres中可以更快地查询显着的一些many-to-manyone-to-many关系(没有额外的连接数据,例如用户会员组)使用id的数组列而不是连接表see discussion for Node Postgres ORM

对于many-to-many关系,选择维护关系数组的哪个表(或两者)将取决于查询的方向(用户< - >组将需要两个表上的数组列,用户 - &gt ; tag只需要在users表的数组列中维护关系。

几个问题:

  • 是否有任何宝石(Rails或其他)使用新的Postgres数组列来维护关系?
  • 在双向和单向many-to-many关系中,是否有人有比较简单连接表与数组列的基准?
  • 要使用Ruby捕获性能改进,非常基本的功能将如下所示。除了自定义主键,方法和类名的配置之外,您是否看到下面的代码有任何明显的改进?

```

module ArrayRelationships

 def self.included(base)
   base.extend(ClassMethods)
 end

 module ClassMethods
   # association must be lower case, pluralized name of associated class
   def array_has_many(association)
    define_method(association) do 
      instance_name = association.singularize
      class_name = instance_name.titleize
      class_name.constantize.where(id: self.send("#{instance_name}_ids"))
    end
   end
 end
 end

 class User << ActiveRecord::Base
   include ArrayRelationships
   array_has_many :tags
 end

当然,users表必须在数据库中包含:tag_ids数组字段。如果我们想为Tag#用户添加反向关系,我们只需添加db字段,包括ArrayRelationships和array_has_many :users

1 个答案:

答案 0 :(得分:1)

我还没有尝试过,但似乎有人构建了一个gem来支持关联数组:https://github.com/marshall-lee/has_array_of。从README中复制:

它是如何工作的?

假设我们有一个包含许多视频的播放列表。一个视频可以包含在许多播放列表中。这是一个经典的多对多情况,但我们以不同的方式实现它。

# db/migrate/20141027125227_create_playlist.rb
class CreatePlaylist < ActiveRecord::Migration
  def change
    create_table :playlists do |t|
      t.integer :video_ids, array: true # adding array fields works only starting from Rails 4
      t.index :video_ids, using: :gin   # we add GIN index to speed up specific queries on array
    end
  end
end

# app/models/playlist.rb
class Playlist < ActiveRecord::Base
  has_array_of :videos  # by convention, it assumes that Post has a video_ids array field
end

# app/models/video.rb
class Video < ActiveRecord::Base
  belongs_to_array_in_many :playlists # optional
end

现在我们可以像常规数组一样使用videos。它会正确代理video_ids字段的所有更改。

playlist = Playlist.find(1)
playlist.videos = [video1,video2]  # playlist.video_ids = [1, 2]
playlist.videos[0] = video3        # playlist.video_ids[0] = 3
playlist.videos.insert(1, video4)  # playlist.video_ids = [3, 4, 2]
playlist.videos.delete_at(1)       # playlist.video_ids = [3, 2]
playlist.videos.pop                # playlist.video_ids = [3]
# ... and so on

video3.playlists
# => [playlist]