postgres_ext 2.4.0.beta.1 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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