postgres_ext 2.4.0.beta.1 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 45761b40db17573204e88b48c6eb91c135968fde
4
- data.tar.gz: d774a33f235ac5f450df26f6f517575ba1360bed
3
+ metadata.gz: c3246d3eb02a37797d39a1e4c9eb7ef17e010b2b
4
+ data.tar.gz: 569c3f94c31b6b7979eb95da5d5ab2dcb5f376cb
5
5
  SHA512:
6
- metadata.gz: c2b883ca3787352f2d6394d45116485565b4c0631b3f84e17405949cbd63049201c087860e470d88b3cad19e849f841b32d3537d5dfef29162d3e153bfb40538
7
- data.tar.gz: 7f0224e503f2d0883aae7974b94de5366d612cba43ad4e243e1bf7d16bbffc5d3e2562eae66ad60e55af76915c5c7b1d45907d3652e607471f9e803d77994128
6
+ metadata.gz: 5ab51fcf131d599a871238d2fd1738b43fc316df96f2810a9045bf513d515e29bfa13008077cf7cccf4349bd7cfbf569812156b9cd62c764d0a830328810dd9d
7
+ data.tar.gz: ba34c9e19290e4691dc28c337dc7aaa13b121b0fff51499a4720e16f816609e140115508f02c0a959971e74597ad46202822a7fabbb360d3b2d9c402ac533709
data/.travis.yml CHANGED
@@ -1,6 +1,10 @@
1
+ sudo: false
2
+ language: ruby
3
+ cache: bundler
1
4
  rvm:
2
5
  - 2.0.0
3
6
  - 2.1.2
7
+ - 2.2.0
4
8
 
5
9
  gemfile:
6
10
  - gemfiles/Gemfile.activerecord-4.0.x
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 2.4.0
2
+
3
+ * Fixes missing CTEProxy delegate - eidge
4
+ * Fixes where chain on joins - edpaget
5
+ * ActiveRecord 4.2 support added - edpaget
6
+
1
7
  ## 2.3.0
2
8
 
3
9
  * Fixes an issue with `where(table: { column: [] })` was not properly
@@ -2,7 +2,8 @@ source "https://rubygems.org"
2
2
 
3
3
  gemspec :path => '..'
4
4
 
5
- gem "activerecord", "~> 4.2.0.beta2"
5
+ gem "activerecord", "~> 4.2.0"
6
+ gem "pg", "~> 0.15"
6
7
 
7
8
  unless ENV['CI'] || RUBY_PLATFORM =~ /java/
8
9
  gem 'byebug'
@@ -1,3 +1,4 @@
1
+ require 'active_record'
1
2
  require 'postgres_ext/active_record/relation'
2
3
  require 'postgres_ext/active_record/cte_proxy'
3
4
  require 'postgres_ext/active_record/querying'
@@ -23,8 +23,8 @@ class CTEProxy
23
23
  end
24
24
 
25
25
  delegate :column_names, :columns_hash, :model_name, :primary_key, :attribute_alias?,
26
- :aggregate_reflections, :instantiate, :type_for_attribute, to: :@model
27
-
26
+ :aggregate_reflections, :instantiate, :type_for_attribute, :relation_delegate_class, to: :@model
27
+
28
28
  private
29
29
 
30
30
  def reflections
@@ -1,5 +1,3 @@
1
- require 'active_record/querying'
2
-
3
1
  module ActiveRecord::Querying
4
2
  delegate :with, :ranked, to: :all
5
3
 
@@ -1,2 +1,12 @@
1
+ ## TODO: Change to ~> 4.2.0 on gem release
2
+
3
+ gdep = Gem::Dependency.new('activerecord', '~> 4.2.0.beta4')
4
+ ar_version_cutoff = gdep.matching_specs.sort_by(&:version).last
5
+
1
6
  require 'postgres_ext/active_record/relation/query_methods'
2
- require 'postgres_ext/active_record/relation/predicate_builder'
7
+ if ar_version_cutoff
8
+ require 'postgres_ext/active_record/relation/predicate_builder/array_handler'
9
+ else
10
+ require 'postgres_ext/active_record/relation/predicate_builder'
11
+ end
12
+
@@ -1,5 +1,3 @@
1
- require 'active_record/relation/predicate_builder'
2
-
3
1
  module ActiveRecord
4
2
  class PredicateBuilder # :nodoc:
5
3
  private
@@ -0,0 +1,32 @@
1
+ require 'active_record/relation/predicate_builder'
2
+ require 'active_record/relation/predicate_builder/array_handler'
3
+
4
+ require 'active_support/concern'
5
+
6
+ module ActiveRecord
7
+ class PredicateBuilder
8
+ module ArrayHandlerPatch
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ def call_with_feature(attribute, value)
13
+ engine = attribute.relation.engine
14
+ column = engine.connection.schema_cache.columns(attribute.relation.name).detect{ |col| col.name.to_s == attribute.name.to_s }
15
+ if column.array
16
+ attribute.eq(value)
17
+ else
18
+ call_without_feature(attribute, value)
19
+ end
20
+ end
21
+
22
+ alias_method_chain(:call, :feature)
23
+ end
24
+
25
+ module ClassMethods
26
+
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ ActiveRecord::PredicateBuilder::ArrayHandler.send(:include, ActiveRecord::PredicateBuilder::ArrayHandlerPatch)
@@ -1,72 +1,117 @@
1
- require 'active_record/relation/query_methods'
2
-
3
1
  module ActiveRecord
4
2
  module QueryMethods
5
3
  class WhereChain
6
- def overlap(opts)
7
- opts.each do |key, value|
8
- @scope = @scope.where(arel_table[key].overlap(value))
9
- end
10
- @scope
4
+ def overlap(opts, *rest)
5
+ substitute_comparisons(opts, rest, Arel::Nodes::Overlap, 'overlap')
11
6
  end
12
7
 
13
- def contained_within(opts)
14
- opts.each do |key, value|
15
- @scope = @scope.where(arel_table[key].contained_within(value))
16
- end
8
+ def contained_within(opts, *rest)
9
+ substitute_comparisons(opts, rest, Arel::Nodes::ContainedWithin, 'contained_within')
10
+ end
17
11
 
18
- @scope
12
+ def contained_within_or_equals(opts, *rest)
13
+ substitute_comparisons(opts, rest, Arel::Nodes::ContainedWithinEquals, 'contained_within_or_equals')
19
14
  end
20
15
 
21
- def contained_within_or_equals(opts)
22
- opts.each do |key, value|
23
- @scope = @scope.where(arel_table[key].contained_within_or_equals(value))
16
+ def contains(opts, *rest)
17
+ build_where_chain(opts, rest) do |rel|
18
+ case rel
19
+ when Arel::Nodes::In, Arel::Nodes::Equality
20
+ column = left_column(rel) || column_from_association(rel)
21
+ equality_for_hstore(rel) if column.type == :hstore
22
+
23
+ if column.type == :hstore
24
+ Arel::Nodes::ContainsHStore.new(rel.left, rel.right)
25
+ elsif column.respond_to?(:array) && column.array
26
+ Arel::Nodes::ContainsArray.new(rel.left, rel.right)
27
+ else
28
+ Arel::Nodes::ContainsINet.new(rel.left, rel.right)
29
+ end
30
+ else
31
+ raise ArgumentError, "Invalid argument for .where.overlap(), got #{rel.class}"
32
+ end
24
33
  end
34
+ end
25
35
 
26
- @scope
36
+ def contains_or_equals(opts, *rest)
37
+ substitute_comparisons(opts, rest, Arel::Nodes::ContainsEquals, 'contains_or_equals')
27
38
  end
28
39
 
29
- def contains(opts)
30
- opts.each do |key, value|
31
- @scope = @scope.where(arel_table[key].contains(value))
32
- end
40
+ def any(opts, *rest)
41
+ equality_to_function('ANY', opts, rest)
42
+ end
33
43
 
34
- @scope
44
+ def all(opts, *rest)
45
+ equality_to_function('ALL', opts, rest)
35
46
  end
36
47
 
37
- def contains_or_equals(opts)
38
- opts.each do |key, value|
39
- @scope = @scope.where(arel_table[key].contains_or_equals(value))
40
- end
48
+ private
41
49
 
42
- @scope
50
+ def find_column(col, rel)
51
+ col.name == rel.left.name.to_s || col.name == rel.left.relation.name.to_s
43
52
  end
44
53
 
45
- def any(opts)
46
- equality_to_function('ANY', opts)
54
+ def left_column(rel)
55
+ rel.left.relation.engine.columns.find { |col| find_column(col, rel) }
47
56
  end
48
57
 
49
- def all(opts)
50
- equality_to_function('ALL', opts)
58
+ def column_from_association(rel)
59
+ if assoc = assoc_from_related_table(rel)
60
+ column = assoc.klass.columns.find { |col| find_column(col, rel) }
61
+ end
51
62
  end
52
63
 
53
- private
64
+ def equality_for_hstore(rel)
65
+ new_right_name = rel.left.name.to_s
66
+ if rel.right.respond_to?(:val)
67
+ return if rel.right.val.is_a?(Hash)
68
+ rel.right = Arel::Nodes.build_quoted({new_right_name => rel.right.val},
69
+ rel.left)
70
+ else
71
+ return if rel.right.is_a?(Hash)
72
+ rel.right = {new_right_name => rel.right }
73
+ end
74
+
75
+ rel.left.name = rel.left.relation.name.to_sym
76
+ rel.left.relation.name = rel.left.relation.engine.table_name
77
+ end
54
78
 
55
- def arel_table
56
- @arel_table ||= @scope.engine.arel_table
79
+ def assoc_from_related_table(rel)
80
+ engine = rel.left.relation.engine
81
+ engine.reflect_on_association(rel.left.relation.name.to_sym) ||
82
+ engine.reflect_on_association(rel.left.relation.name.singularize.to_sym)
57
83
  end
58
84
 
59
- def equality_to_function(function_name, opts)
60
- opts.each do |key, value|
61
- any_function = Arel::Nodes::NamedFunction.new(function_name, [arel_table[key]])
62
- predicate = Arel::Nodes::Equality.new(value, any_function)
63
- @scope = @scope.where(predicate)
85
+ def build_where_chain(opts, rest, &block)
86
+ where_value = @scope.send(:build_where, opts, rest).map(&block)
87
+ @scope.references!(PredicateBuilder.references(opts)) if Hash === opts
88
+ @scope.where_values += where_value
89
+ @scope
90
+ end
91
+
92
+ def substitute_comparisons(opts, rest, arel_node_class, method)
93
+ build_where_chain(opts, rest) do |rel|
94
+ case rel
95
+ when Arel::Nodes::In, Arel::Nodes::Equality
96
+ arel_node_class.new(rel.left, rel.right)
97
+ else
98
+ raise ArgumentError, "Invalid argument for .where.#{method}(), got #{rel.class}"
99
+ end
64
100
  end
101
+ end
65
102
 
66
- @scope
103
+ def equality_to_function(function_name, opts, rest)
104
+ build_where_chain(opts, rest) do |rel|
105
+ case rel
106
+ when Arel::Nodes::Equality
107
+ Arel::Nodes::Equality.new(rel.right, Arel::Nodes::NamedFunction.new(function_name, [rel.left]))
108
+ else
109
+ raise ArgumentError, "Invalid argument for .where.#{funciton_name.downcase}(), got #{rel.class}"
110
+ end
111
+ end
67
112
  end
68
113
  end
69
-
114
+
70
115
  # WithChain objects act as placeholder for queries in which #with does not have any parameter.
71
116
  # In this case, #with must be chained with #recursive to return a new relation.
72
117
  class WithChain
@@ -175,15 +220,15 @@ module ActiveRecord
175
220
  def build_rank(arel, rank_window_options)
176
221
  unless arel.projections.count == 1 && Arel::Nodes::Count === arel.projections.first
177
222
  rank_window = case rank_window_options
178
- when :order
179
- arel.orders
180
- when Symbol
181
- table[rank_window_options].asc
182
- when Hash
183
- rank_window_options.map { |field, dir| table[field].send(dir) }
184
- else
185
- Arel::Nodes::SqlLiteral.new "(#{rank_window_options})"
186
- end
223
+ when :order
224
+ arel.orders
225
+ when Symbol
226
+ table[rank_window_options].asc
227
+ when Hash
228
+ rank_window_options.map { |field, dir| table[field].send(dir) }
229
+ else
230
+ Arel::Nodes::SqlLiteral.new "(#{rank_window_options})"
231
+ end
187
232
 
188
233
  unless rank_window.blank?
189
234
  rank_node = Arel::Nodes::SqlLiteral.new 'rank()'
@@ -1,6 +1,5 @@
1
- ## TODO: Change to ~> 4.2.0 on gem release
2
1
 
3
- gdep = Gem::Dependency.new('activerecord', '~> 4.2.0.beta2')
2
+ gdep = Gem::Dependency.new('activerecord', '~> 4.2.0')
4
3
  ar_version_cutoff = gdep.matching_specs.sort_by(&:version).last
5
4
 
6
5
  require 'postgres_ext/arel/nodes'
@@ -1 +1,2 @@
1
+ require 'postgres_ext/arel/4.1/visitors/depth_first'
1
2
  require 'postgres_ext/arel/4.1/visitors/postgresql'
@@ -0,0 +1,9 @@
1
+ require 'arel/visitors/depth_first'
2
+
3
+ module Arel
4
+ module Visitors
5
+ class DepthFirst
6
+ alias :visit_IPAddr :terminal
7
+ end
8
+ end
9
+ end
@@ -4,7 +4,7 @@ module Arel
4
4
  module Visitors
5
5
  class PostgreSQL
6
6
  private
7
-
7
+
8
8
  def visit_Array o, a
9
9
  column = a.relation.engine.connection.schema_cache.columns(a.relation.name).find { |col| col.name == a.name.to_s } if a
10
10
  if column && column.respond_to?(:array) && column.array
@@ -13,6 +13,16 @@ module Arel
13
13
  o.empty? ? 'NULL' : o.map { |x| visit x }.join(', ')
14
14
  end
15
15
  end
16
+
17
+ def visit_Arel_Nodes_Contains o, a = nil
18
+ left_column = o.left.relation.engine.columns.find { |col| col.name == o.left.name.to_s }
19
+
20
+ if left_column && (left_column.type == :hstore || (left_column.respond_to?(:array) && left_column.array))
21
+ "#{visit o.left, a} @> #{visit o.right, o.left}"
22
+ else
23
+ "#{visit o.left, a} >> #{visit o.right, o.left}"
24
+ end
25
+ end
16
26
 
17
27
  def visit_Arel_Nodes_ContainedWithin o, a = nil
18
28
  "#{visit o.left, a} << #{visit o.right, o.left}"
@@ -22,14 +32,16 @@ module Arel
22
32
  "#{visit o.left, a} <<= #{visit o.right, o.left}"
23
33
  end
24
34
 
25
- def visit_Arel_Nodes_Contains o, a = nil
26
- left_column = o.left.relation.engine.columns.find { |col| col.name == o.left.name.to_s }
35
+ def visit_Arel_Nodes_ContainsArray o, a = nil
36
+ "#{visit o.left, a} @> #{visit o.right, o.left}"
37
+ end
27
38
 
28
- if left_column && (left_column.type == :hstore || (left_column.respond_to?(:array) && left_column.array))
29
- "#{visit o.left, a} @> #{visit o.right, o.left}"
30
- else
31
- "#{visit o.left, a} >> #{visit o.right, o.left}"
32
- end
39
+ def visit_Arel_Nodes_ContainsHStore o, a = nil
40
+ "#{visit o.left, a} @> #{visit o.right, o.left}"
41
+ end
42
+
43
+ def visit_Arel_Nodes_ContainsINet o, a = nil
44
+ "#{visit o.left, a} >> #{visit o.right, o.left}"
33
45
  end
34
46
 
35
47
  def visit_Arel_Nodes_ContainsEquals o, a = nil
@@ -4,7 +4,7 @@ module Arel
4
4
  module Visitors
5
5
  class PostgreSQL
6
6
  private
7
-
7
+
8
8
  def visit_Arel_Nodes_ContainedWithin o, collector
9
9
  infix_value o, collector, " << "
10
10
  end
@@ -14,15 +14,29 @@ module Arel
14
14
  end
15
15
 
16
16
  def visit_Arel_Nodes_Contains o, collector
17
- left_column = o.left.relation.engine.columns.find { |col| col.name == o.left.name.to_s }
17
+ left_column = o.left.relation.engine.columns.find do |col|
18
+ col.name == o.left.name.to_s || col.name == o.left.relation.name.to_s
19
+ end
18
20
 
19
21
  if left_column && (left_column.type == :hstore || (left_column.respond_to?(:array) && left_column.array))
20
- infix_value o, collector, " @> "
22
+ infix_value o, collector, " @> "
21
23
  else
22
24
  infix_value o, collector, " >> "
23
25
  end
24
26
  end
25
27
 
28
+ def visit_Arel_Nodes_ContainsINet o, collector
29
+ infix_value o, collector, " >> "
30
+ end
31
+
32
+ def visit_Arel_Nodes_ContainsHStore o, collector
33
+ infix_value o, collector, " @> "
34
+ end
35
+
36
+ def visit_Arel_Nodes_ContainsArray o, collector
37
+ infix_value o, collector, " @> "
38
+ end
39
+
26
40
  def visit_Arel_Nodes_ContainsEquals o, collector
27
41
  infix_value o, collector, " >>= "
28
42
  end
@@ -6,15 +6,27 @@ module Arel
6
6
  end
7
7
 
8
8
  class ContainedWithinEquals < Arel::Nodes::Binary
9
- def operator; '<<='.to_sym end
9
+ def operator; :"<<=" end
10
10
  end
11
11
 
12
12
  class Contains < Arel::Nodes::Binary
13
13
  def operator; :>> end
14
14
  end
15
+
16
+ class ContainsINet < Arel::Nodes::Binary
17
+ def operator; :>> end
18
+ end
15
19
 
20
+ class ContainsHStore < Arel::Nodes::Binary
21
+ def operator; :"@>" end
22
+ end
23
+
24
+ class ContainsArray < Arel::Nodes::Binary
25
+ def operator; :"@>" end
26
+ end
27
+
16
28
  class ContainsEquals < Arel::Nodes::Binary
17
- def operator; '>>='.to_sym end
29
+ def operator; :">>=" end
18
30
  end
19
31
  end
20
32
  end
@@ -1,3 +1,3 @@
1
1
  module PostgresExt
2
- VERSION = '2.4.0.beta.1'
2
+ VERSION = '2.4.0'
3
3
  end
data/postgres_ext.gemspec CHANGED
@@ -29,6 +29,6 @@ Gem::Specification.new do |gem|
29
29
  if RUBY_PLATFORM =~ /java/
30
30
  gem.add_development_dependency 'activerecord-jdbcpostgresql-adapter', '1.3.0.beta2'
31
31
  else
32
- gem.add_development_dependency 'pg', '~> 0.13.2'
32
+ gem.add_development_dependency 'pg', '~> 0.13'
33
33
  end
34
34
  end
@@ -15,9 +15,8 @@ describe 'Array queries' do
15
15
 
16
16
  describe '.where(joins: { array_column: [] })' do
17
17
  it 'returns an array string instead of IN ()' do
18
- skip
19
18
  query = Person.joins(:hm_tags).where(tags: { categories: ['working'] }).to_sql
20
- query.must_match equality_regex
19
+ query.must_match %r{\"tags\"\.\"categories\" = '\{"?working"?\}'}
21
20
  end
22
21
  end
23
22
 
@@ -33,6 +32,11 @@ describe 'Array queries' do
33
32
  query.must_match overlap_regex
34
33
  query.must_match equality_regex
35
34
  end
35
+
36
+ it 'works on joins' do
37
+ query = Person.joins(:hm_tags).where.overlap(tags: { categories: ['working'] }).to_sql
38
+ query.must_match %r{\"tags\"\.\"categories\" && '\{"?working"?\}'}
39
+ end
36
40
  end
37
41
 
38
42
 
@@ -41,16 +41,27 @@ describe 'Contains queries' do
41
41
  query.to_sql.must_match contains_array_regex
42
42
  end
43
43
 
44
- it 'generates the appropriate where clause for array columns' do
44
+ it 'generates the appropriate where clause for hstore columns' do
45
45
  query = Person.where.contains(data: { nickname: 'Dan' })
46
46
  query.to_sql.must_match contains_hstore_regex
47
47
  end
48
48
 
49
+ it 'generates the appropriate where clause for hstore columns on joins' do
50
+ query = Tag.joins(:person).where.contains(people: { data: { nickname: 'Dan' } })
51
+ query.to_sql.must_match contains_hstore_regex
52
+ end
53
+
49
54
  it 'allows chaining' do
50
55
  query = Person.where.contains(:tag_ids => [1,2]).where(:tags => ['working']).to_sql
51
56
 
52
57
  query.must_match contains_array_regex
53
58
  query.must_match equality_regex
54
59
  end
60
+
61
+ it 'generates the appropriate where clause for array columns on joins' do
62
+ query = Tag.joins(:person).where.contains(people: { tag_ids: [1,2] }).to_sql
63
+
64
+ query.must_match contains_array_regex
65
+ end
55
66
  end
56
67
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: postgres_ext
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.0.beta.1
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan McClain
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-10-13 00:00:00.000000000 Z
11
+ date: 2015-01-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -142,14 +142,14 @@ dependencies:
142
142
  requirements:
143
143
  - - "~>"
144
144
  - !ruby/object:Gem::Version
145
- version: 0.13.2
145
+ version: '0.13'
146
146
  type: :development
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
150
  - - "~>"
151
151
  - !ruby/object:Gem::Version
152
- version: 0.13.2
152
+ version: '0.13'
153
153
  description: Adds missing native PostgreSQL data types to ActiveRecord and convenient
154
154
  querying extensions for ActiveRecord and Arel
155
155
  email:
@@ -175,10 +175,12 @@ files:
175
175
  - lib/postgres_ext/active_record/querying.rb
176
176
  - lib/postgres_ext/active_record/relation.rb
177
177
  - lib/postgres_ext/active_record/relation/predicate_builder.rb
178
+ - lib/postgres_ext/active_record/relation/predicate_builder/array_handler.rb
178
179
  - lib/postgres_ext/active_record/relation/query_methods.rb
179
180
  - lib/postgres_ext/arel.rb
180
181
  - lib/postgres_ext/arel/4.1/predications.rb
181
182
  - lib/postgres_ext/arel/4.1/visitors.rb
183
+ - lib/postgres_ext/arel/4.1/visitors/depth_first.rb
182
184
  - lib/postgres_ext/arel/4.1/visitors/postgresql.rb
183
185
  - lib/postgres_ext/arel/4.2/predications.rb
184
186
  - lib/postgres_ext/arel/4.2/visitors.rb
@@ -211,12 +213,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
211
213
  version: '0'
212
214
  required_rubygems_version: !ruby/object:Gem::Requirement
213
215
  requirements:
214
- - - ">"
216
+ - - ">="
215
217
  - !ruby/object:Gem::Version
216
- version: 1.3.1
218
+ version: '0'
217
219
  requirements: []
218
220
  rubyforge_project:
219
- rubygems_version: 2.2.0
221
+ rubygems_version: 2.4.5
220
222
  signing_key:
221
223
  specification_version: 4
222
224
  summary: Extends ActiveRecord to handle native PostgreSQL data types