Permalink
Browse files

Fixes arel visit_Array, adds .where.overlap

Need to update documentation
Need to add new where clauses for other array and cidr functions
Closes #50
  • Loading branch information...
1 parent d2ee8a4 commit 5e3522f4d4f90d14526bc1287ae7f8ba6980af7d @danmcclain danmcclain committed Feb 2, 2013
View
1 lib/postgres_ext/active_record.rb
@@ -1,3 +1,4 @@
require 'postgres_ext/active_record/sanitization'
require 'postgres_ext/active_record/connection_adapters'
require 'postgres_ext/active_record/schema_dumper'
+require 'postgres_ext/active_record/relation'
View
2 lib/postgres_ext/active_record/relation.rb
@@ -0,0 +1,2 @@
+require 'postgres_ext/active_record/relation/query_methods'
+require 'postgres_ext/active_record/relation/predicate_builder'
View
71 lib/postgres_ext/active_record/relation/predicate_builder.rb
@@ -0,0 +1,71 @@
+require 'active_record/relation/predicate_builder'
+
+module ActiveRecord
+ class PredicateBuilder # :nodoc:
+ def self.build_from_hash(engine, attributes, default_table, allow_table_name = true)
+ predicates = attributes.map do |column, value|
+ table = default_table
+
+ if allow_table_name && value.is_a?(Hash)
+ table = Arel::Table.new(column, engine)
+
+ if value.empty?
+ '1 = 2'
+ else
+ build_from_hash(engine, value, table, false)
+ end
+ else
+ column = column.to_s
+
+ if allow_table_name && column.include?('.')
+ table_name, column = column.split('.', 2)
+ table = Arel::Table.new(table_name, engine)
+ end
+
+ attribute = table[column.to_sym]
+
+ case value
+ when ActiveRecord::Relation
+ value = value.select(value.klass.arel_table[value.klass.primary_key]) if value.select_values.empty?
+ attribute.in(value.arel.ast)
+ when Array, ActiveRecord::Associations::CollectionProxy
+ column_definition = engine.columns.find { |col| col.name == column }
+
+ if column_definition.respond_to?(:array) && column_definition.array
+ attribute.eq(value)
+ else
+ values = value.to_a.map {|x| x.is_a?(ActiveRecord::Base) ? x.id : x}
+ ranges, values = values.partition {|v| v.is_a?(Range) || v.is_a?(Arel::Relation)}
+
+ array_predicates = ranges.map {|range| attribute.in(range)}
+
+ if values.include?(nil)
+ values = values.compact
+ if values.empty?
+ array_predicates << attribute.eq(nil)
+ else
+ array_predicates << attribute.in(values.compact).or(attribute.eq(nil))
+ end
+ else
+ array_predicates << attribute.in(values)
+ end
+
+ array_predicates.inject {|composite, predicate| composite.or(predicate)}
+ end
+ when Range, Arel::Relation
+ attribute.in(value)
+ when ActiveRecord::Base
+ attribute.eq(value.id)
+ when Class
+ # FIXME: I think we need to deprecate this behavior
+ attribute.eq(value.name)
+ else
+ attribute.eq(value)
+ end
+ end
+ end
+
+ predicates.flatten
+ end
+ end
+end
View
29 lib/postgres_ext/active_record/relation/query_methods.rb
@@ -0,0 +1,29 @@
+require 'active_record/relation/query_methods'
+
+module ActiveRecord
+ module QueryMethods
+ class WhereChain
+ def initialize(scope)
+ @scope = scope
+ end
+
+ def overlap(opts)
+ opts.each do |key, value|
+ arel_table = @scope.engine.arel_table
+ @scope = @scope.where(arel_table[key].array_overlap(value))
+ end
+ @scope
+ end
+ end
+
+ def where_with_chaining(opts = :chaining, *rest)
+ if opts == :chaining
+ WhereChain.new(self)
+ else
+ where_without_chaining(opts, *rest)
+ end
+ end
+
+ alias_method_chain :where, :chaining
+ end
+end
View
1 lib/postgres_ext/arel/visitors.rb
@@ -1 +1,2 @@
require 'postgres_ext/arel/visitors/visitor'
+require 'postgres_ext/arel/visitors/to_sql'
View
11 lib/postgres_ext/arel/visitors/to_sql.rb
@@ -0,0 +1,11 @@
+require 'arel/visitors/to_sql'
+
+module Arel
+ module Visitors
+ class ToSql
+ def visit_Array o
+ quote(o, last_column)
+ end
+ end
+ end
+end
View
5 lib/postgres_ext/arel/visitors/visitor.rb
@@ -21,9 +21,8 @@ def visit_Arel_Nodes_ContainsEquals o
end
def visit_Arel_Nodes_ArrayOverlap o
- if Array === o.right
- right = "{#{o.right.map{|v| change_string(visit(v))}.join(',')}}"
- "#{visit o.left} && '#{right}'"
+ if Array === o.right
+ "#{visit o.left} && #{visit o.right}"
else
"#{visit o.left} && #{visit o.right}"
end
View
30 spec/migrations/array_spec.rb
@@ -34,16 +34,32 @@
before { connection.create_table :data_types }
after { connection.drop_table :data_types }
describe 'Add Column' do
- it 'creates an array column' do
- lambda do
- connection.add_column :data_types, :array_5, :cidr, :array => true
- end.should_not raise_exception
+ context 'no default' do
+ it 'creates an array column' do
+ lambda do
+ connection.add_column :data_types, :array_5, :cidr, :array => true
+ end.should_not raise_exception
- columns = connection.columns(:data_types)
+ columns = connection.columns(:data_types)
- array_5 = columns.detect { |c| c.name == 'array_5'}
- array_5.sql_type.should eq 'cidr[]'
+ array_5 = columns.detect { |c| c.name == 'array_5'}
+ array_5.sql_type.should eq 'cidr[]'
+ end
end
+
+ context 'with default' do
+ it 'creates an array column' do
+ lambda do
+ connection.add_column :data_types, :array_6, :integer, :array => true, :default => []
+ end.should_not raise_exception
+
+ columns = connection.columns(:data_types)
+
+ array_6 = columns.detect { |c| c.name == 'array_6'}
+ array_6.sql_type.should eq 'integer[]'
+ end
+ end
+
end
describe 'Change table methods' do
View
36 spec/queries/overlap_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+describe 'where array clauses' do
+ let!(:adapter) { ActiveRecord::Base.connection }
+
+ before do
+ adapter.create_table :overlap_arel_arrays, :force => true do |t|
+ t.string :tags, :array => true
+ t.integer :tag_ids, :array => true
+ end
+
+ class OverlapArelArray < ActiveRecord::Base
+ attr_accessible :tags, :tags_ids
+ end
+ end
+ describe '.where(:array_column => [])' do
+ it 'returns an array string instead of IN ()' do
+ query = OverlapArelArray.where(:tags => ['working']).to_sql
+ query.should match /\"overlap_arel_arrays\"\.\"tags\" = '\{\"working\"\}'/
+ end
+ end
+
+ describe '.where.overlap(:column => value)' do
+ it 'generates the appropriate where clause' do
+ query = OverlapArelArray.where.overlap(:tag_ids => [1,2])
+ query.to_sql.should match /\"overlap_arel_arrays\"\.\"tag_ids\" && '\{1,2\}'/
+ end
+
+ it 'allows chaining' do
+ query = OverlapArelArray.where.overlap(:tag_ids => [1,2]).where(:tags => ['working']).to_sql
+
+ query.should match /\"overlap_arel_arrays\"\.\"tag_ids\" && '\{1,2\}'/
+ query.should match /\"overlap_arel_arrays\"\.\"tags\" = '\{\"working\"\}'/
+ end
+ end
+end

0 comments on commit 5e3522f

Please sign in to comment.