使用pg数组类型的RoR model.where()

时间:2018-04-14 03:41:47

标签: sql arrays ruby postgresql

我偶然发现了在我的模型like so中使用数组限制我的表和关联的想法。

我正在开发一个任务分配应用程序,用户将在本质上构造一个句子来执行操作。我使用关键字来帮助构建句子的边界。

示例包括(括号中的关键字):

  • " [我会]画围栏" < - 创建一个新任务,分配给current_user
  • " [问]哈克[画]围栏" < - find_or_create任务,分配给find_or_create用户
  • " [存档]画围栏" < - soft-delete task

所以这是我的迁移:

class CreateKeywords < ActiveRecord::Migration[5.1]
  def change
    create_table :keywords do |t|
      t.string :name, null: false
      t.text :pre, array: true, default: []
      t.text :post, array: true, default: []
      t.string :method, null: false, default: "read" # a CRUD indicator
    end
  end
end

keyword.post表示关键字

可以使用哪些模型

keyword.pre表示关键字

之前的模型

我的种子数据如下所示:

Keyword.create([
  { name: "i will", post: ["task"] },
  { name: "ask", post: ["user"] },
  { name: "assign", post: ["user", "task"] },
  { name: "find a", post: ["user", "task"] },
  { name: "make a new", post: ["user", "task"], method: "create" },
  { name: "finalize", post: ["task"] },
  { name: "archive", post: ["user", "task"], method: "delete" },
  { name: "update", post: ["user", "task"], method: "update" },
  { name: "for", post: ["user", "task"], pre: ["user", "task"] },
  { name: "to", post: ["user", "task"], pre: ["user", "task"] },
  { name: "and repeat", pre: ["task"] },
  { name: "before", pre: ["task"] },
  { name: "after", pre: ["task"] },
  { name: "on", pre: ["task"] }
])

现在我想做的事情如下:

key = Keyword.third

Keyword.where(pre: key.post)

但这会返回完全匹配,我想做类似的事情:

&#34;返回key.post&#34;

中可以找到Keyword.pre的任何值的所有关键字

我在这些方面没有运气: Keyword.where(pre.include? key.post)

我可以遍历所有关键字并使用AND:

results = []
Keyword.all.each do |k|
  comb = k.pre & key.post
  if comb.present?
    results << k
  end
end
results.map { |k| k.name }

但这感觉不好。

我对SQL所需的SQL有点深入了解。

任何指针?

2 个答案:

答案 0 :(得分:1)

您想了解两件事:

  1. PostgreSQL的"array constructor" syntax看起来像array['a', 'b', 'c']而不是更常见的'{a,b,c}'语法。
  2. PostgreSQL&#39; array operators
  3. 数组构造函数语法很方便,因为当ActiveRecord将数组视为占位符的值时,它会将数组扩展为逗号分隔列表,该列表与x in (?)x && array[?]同样适用。

    要运营商使用,您需要:

      

    可以在key.post

    中找到Keyword.pre的任何值的所有关键字

    这是另一种表示key.postKeyword.pre重叠的方式,其运算符为&&。如果您需要稍微不同的逻辑,还有子集(<@)和超集(@>)运算符。

    把它们放在一起:

    Keyword.where('pre && array[?]', key.post)
    

    或:

    Keyword.where('pre && array[:post]', post: key.post)
    

    lacostenycoder,在评论中,关注空数组是正确的。填充占位符时,ActiveRecord会将空数组扩展为单个null,这样您最终可能会像以下那样执行SQL:

    pre && array[null]
    

    并且PostgreSQL无法确定array[null]的类型。你可以加一个演员:

    Keyword.where('pre && array[:post]::text[]', post: key.post)
    # --------------------------------^^^^^^^^ tell PostgreSQL that this is an array of text
    

    但是,由于pre && array[null]::text[]永远不会成真,如果key.post.empty?,你可以跳过整个事情。

    空数组不会与任何其他数组(甚至不是另一个空数组)重叠,因此您不必担心ActiveRecord将使用它们的空数组。同样地,对于pre is nullnull && any_array将永远不会成立(它实际上将评估为null),因此不会与此类事物重叠。

答案 1 :(得分:0)

这实际上比你想象的容易得多:

Keyword.where("pre && post")

# testing vs your seed data plus a few I added returns:

#Keyword Load (1.0ms)  SELECT "keywords".* FROM "keywords" WHERE (pre && post)
#[<Keyword:0x007fc03c0438b0 id: 9, name: "for", pre: ["user", "task"], post: ["user", "task"], method: "read">,
 #<Keyword:0x007fc03c0435b8 id: 10, name: "to", pre: ["user", "task"], post: ["user", "task"], method: "read">,
 #<Keyword:0x007fc03c043248 id: 15, name: "foo", pre: ["bar", "baz"], post: ["baz", "boo"], method: "read">,
 #<Keyword:0x007fc03c042f28 id: 16, name: "rock", pre: ["anthem"], post: ["ballad", "anthem"], method: "read">,
 #<Keyword:0x007fc03c042cf8 id: 17, name: "disco", pre: ["mood"], post: ["ballad", "anthem", "mood"], method: "read">]