Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Array contains operator ( @> ) support

1. Add support for the Postgres array contains operator. This operator
is like overlap, but expects all of the provided elements to be in the
array.

    MyModel.where.array_contains(tags: [1, 2])

2. Moved the array nodes into their own file
  • Loading branch information...
commit 2d025826ab99482e3484301809ca858981ba5fad 1 parent cf1053e
@jagregory jagregory authored
View
7 lib/postgres_ext/active_record/relation/query_methods.rb
@@ -14,6 +14,13 @@ def overlap(opts)
@scope
end
+ def array_contains(opts)
+ opts.each do |key, value|
+ @scope = @scope.where(arel_table[key].array_contains(value))
+ end
+ @scope
+ end
+
def contained_within(opts)
opts.each do |key, value|
@scope = @scope.where(arel_table[key].contained_within(value))
View
1  lib/postgres_ext/arel/nodes.rb
@@ -1 +1,2 @@
+require 'postgres_ext/arel/nodes/array_nodes'
require 'postgres_ext/arel/nodes/contained_within'
View
13 lib/postgres_ext/arel/nodes/array_nodes.rb
@@ -0,0 +1,13 @@
+require 'arel/nodes/binary'
+
+module Arel
+ module Nodes
+ class ArrayOverlap < Arel::Nodes::Binary
+ def operator; '&&' end
+ end
+
+ class ArrayContains < Arel::Nodes::Binary
+ def operator; '@>' end
+ end
+ end
+end
View
4 lib/postgres_ext/arel/nodes/contained_within.rb
@@ -16,9 +16,5 @@ def operator; :>> end
class ContainsEquals < Arel::Nodes::Binary
def operator; '>>='.symbolize end
end
-
- class ArrayOverlap < Arel::Nodes::Binary
- def operator; '&&' end
- end
end
end
View
4 lib/postgres_ext/arel/predications.rb
@@ -21,5 +21,9 @@ def contains_or_equals(other)
def array_overlap(other)
Nodes::ArrayOverlap.new self, other
end
+
+ def array_contains(other)
+ Nodes::ArrayContains.new self, other
+ end
end
end
View
4 lib/postgres_ext/arel/visitors/visitor.rb
@@ -28,6 +28,10 @@ def visit_Arel_Nodes_ArrayOverlap o
end
end
+ def visit_Arel_Nodes_ArrayContains o
+ "#{visit o.left} @> #{visit o.right}"
+ end
+
def visit_IPAddr value
"'#{value.to_s}/#{value.instance_variable_get(:@mask_addr).to_s(2).count('1')}'"
end
View
38 spec/arel/array_spec.rb
@@ -54,4 +54,42 @@ class ArelArray < ActiveRecord::Base
ArelArray.find_by_sql(query.to_sql).should include(one)
end
end
+
+ describe 'Array Contains' do
+ it 'converts Arel array_contains statement and escapes strings' do
+ arel_table = ArelArray.arel_table
+
+ arel_table.where(arel_table[:tags].array_contains(['tag','tag 2'])).to_sql.should match /@> '\{"tag","tag 2"\}'/
+ end
+
+ it 'converts Arel array_contains statement with numbers' do
+ arel_table = ArelArray.arel_table
+
+ arel_table.where(arel_table[:tag_ids].array_contains([1,2])).to_sql.should match /@> '\{1,2\}'/
+ end
+
+ it 'works with count (and other predicates)' do
+ arel_table = ArelArray.arel_table
+
+ ArelArray.where(arel_table[:tag_ids].array_contains([1,2])).count.should eq 0
+ end
+
+ it 'returns matched records' do
+ one = ArelArray.create!(:tags => ['one', 'two', 'three'])
+ two = ArelArray.create!(:tags => ['one', 'three'])
+ arel_table = ArelArray.arel_table
+
+ query = arel_table.where(arel_table[:tags].array_contains(['one', 'two'])).project(Arel.sql('*'))
+ ArelArray.find_by_sql(query.to_sql).should include one
+ ArelArray.find_by_sql(query.to_sql).should_not include two
+
+ query = arel_table.where(arel_table[:tags].array_contains(['one', 'three'])).project(Arel.sql('*'))
+ ArelArray.find_by_sql(query.to_sql).should include one
+ ArelArray.find_by_sql(query.to_sql).should include two
+
+ query = arel_table.where(arel_table[:tags].array_contains(['two'])).project(Arel.sql('*'))
+ ArelArray.find_by_sql(query.to_sql).should include one
+ ArelArray.find_by_sql(query.to_sql).should_not include two
+ end
+ end
end
View
15 spec/queries/array_queries_spec.rb
@@ -3,6 +3,7 @@
describe 'Array queries' do
let(:equality_regex) { %r{\"people\"\.\"tags\" = '\{\"working\"\}'} }
let(:overlap_regex) { %r{\"people\"\.\"tag_ids\" && '\{1,2\}'} }
+ let(:contains_regex) { %r{\"people\"\.\"tag_ids\" @> '\{1,2\}'} }
let(:any_regex) { %r{2 = ANY\(\"people\"\.\"tag_ids\"\)} }
let(:all_regex) { %r{2 = ALL\(\"people\"\.\"tag_ids\"\)} }
@@ -27,6 +28,20 @@
end
end
+ describe '.where.array_contains(:column => value)' do
+ it 'generates the appropriate where clause' do
+ query = Person.where.array_contains(:tag_ids => [1,2])
+ query.to_sql.should match contains_regex
+ end
+
+ it 'allows chaining' do
+ query = Person.where.array_contains(:tag_ids => [1,2]).where(:tags => ['working']).to_sql
+
+ query.should match contains_regex
+ query.should match equality_regex
+ end
+ end
+
describe '.where.any(:column => value)' do
it 'generates the appropriate where clause' do
query = Person.where.any(:tag_ids => 2)
Please sign in to comment.
Something went wrong with that request. Please try again.