postgres_ext 2.0.0 → 2.1.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: 16a9a614595cf23330e0ef345abaa250b7c22ce4
4
- data.tar.gz: a76e0358088afb3a32ee35acedf993178887a323
3
+ metadata.gz: 4fb1baea00022bce72d77b44835d7468bad8b5c3
4
+ data.tar.gz: beee8834d070b830cc100f1f76485888e9a7e655
5
5
  SHA512:
6
- metadata.gz: d72d5bb3c07f5ff7a41dabe3a2bc4ee2c825b4868ad8f55ca6e8b147dcd35b71543f0a2f3fadaf364a5a77a7799cbe2ed8a440b2a0e4d2758605a573c6349499
7
- data.tar.gz: bd734f9475b84bee09f43d8b8c6c6f076455ffc65f1a9bc66965391af9ea51a88156dba5c36a816cb395c8627adc65188d23357cbfc2b1641bd0181871d507b5
6
+ metadata.gz: 626ff8558284671aa255caa67e79e1570f01ab481c18ba7fd3a48d782ef6a6e9b805676bf26c2fb4f8b3d160bf382fcfbd006b565056e4d5395be7e34e49300f
7
+ data.tar.gz: 3bd135a3456d19ad3719f93d676551d24f367baa8711a91eec08bdb93e0f43db4e229da7cfacfcbc05c09bf3fb73d716337bb80f91cd68cabeb3c0724a1f318f
data/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  Adds missing native PostgreSQL data types to ActiveRecord and convenient querying extensions for ActiveRecord and Arel for Rails 4.x
4
4
 
5
5
  [![Build Status](https://secure.travis-ci.org/dockyard/postgres_ext.png?branch=master)](http://travis-ci.org/dockyard/postgres_ext)
6
- [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/dockyard/postgres_ext)
6
+ [![Code Climate](https://codeclimate.com/github/dockyard/postgres_ext.png)](https://codeclimate.com/github/dockyard/postgres_ext)
7
7
 
8
8
  ## Looking for help? ##
9
9
 
@@ -1 +1,3 @@
1
1
  require 'postgres_ext/active_record/relation'
2
+ require 'postgres_ext/active_record/cte_proxy'
3
+ require 'postgres_ext/active_record/querying'
@@ -0,0 +1,33 @@
1
+ class CTEProxy
2
+ include ActiveRecord::Querying
3
+ include ActiveRecord::Sanitization::ClassMethods
4
+ include ActiveRecord::Reflection::ClassMethods
5
+
6
+ attr_accessor :reflections
7
+ attr_reader :connection, :arel_table
8
+
9
+ def initialize(name, model)
10
+ @name = name
11
+ @arel_table = Arel::Table.new(name)
12
+ @model = model
13
+ @connection = model.connection
14
+ @reflections = {}
15
+ end
16
+
17
+ def name
18
+ @name
19
+ end
20
+
21
+ def table_name
22
+ name
23
+ end
24
+
25
+ def column_names
26
+ @model.column_names
27
+ end
28
+
29
+ def columns_hash
30
+ @model.columns_hash
31
+ end
32
+ end
33
+
@@ -0,0 +1,13 @@
1
+ require 'active_record/querying'
2
+
3
+ module ActiveRecord::Querying
4
+ delegate :with, :ranked, to: :all
5
+
6
+ def from_cte(name, expression)
7
+ table = Arel::Table.new(name)
8
+
9
+ cte_proxy = CTEProxy.new(name, self)
10
+ relation = ActiveRecord::Relation.new cte_proxy, cte_proxy.arel_table
11
+ relation.with name => expression
12
+ end
13
+ end
@@ -66,5 +66,92 @@ module ActiveRecord
66
66
  @scope
67
67
  end
68
68
  end
69
+
70
+ [:with, :rank].each do |name|
71
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
72
+ def #{name}_values # def select_values
73
+ @values[:#{name}] || [] # @values[:select] || []
74
+ end # end
75
+ #
76
+ def #{name}_values=(values) # def select_values=(values)
77
+ raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded
78
+ @values[:#{name}] = values # @values[:select] = values
79
+ end # end
80
+ CODE
81
+ end
82
+
83
+ def with(*args)
84
+ check_if_method_has_arguments!('with', args)
85
+ spawn.with!(*args.compact.flatten)
86
+ end
87
+
88
+ def with!(*args)
89
+ self.with_values += args
90
+ self
91
+ end
92
+
93
+ def ranked(options = :order)
94
+ spawn.ranked! options
95
+ end
96
+
97
+ def ranked!(*args)
98
+ self.rank_values += args
99
+ self
100
+ end
101
+
102
+ def build_arel_with_extensions
103
+ arel = build_arel_without_extensions
104
+
105
+ build_with(arel, with_values)
106
+
107
+ build_rank(arel, rank_values)
108
+
109
+ arel
110
+ end
111
+
112
+ def build_with(arel, withs)
113
+ with_statements = withs.flat_map do |with_value|
114
+ case with_value
115
+ when String
116
+ with_value
117
+ when Hash
118
+ with_value.map do |name, expression|
119
+ case expression
120
+ when String
121
+ select = Arel::SqlLiteral.new "(#{expression})"
122
+ when ActiveRecord::Relation
123
+ select = Arel::SqlLiteral.new "(#{expression.to_sql})"
124
+ end
125
+ as = Arel::Nodes::As.new Arel::SqlLiteral.new(name.to_s), select
126
+ end
127
+ end
128
+ end
129
+ arel.with with_statements unless with_statements.empty?
130
+ end
131
+
132
+ def build_rank(arel, ranks)
133
+ rank_orders = ranks.uniq.reject(&:blank?).flat_map do |value|
134
+ case value
135
+ when :order
136
+ arel.orders
137
+ when Symbol
138
+ table[value].asc
139
+ when Hash
140
+ value.map { |field, dir| table[field].send(dir) }
141
+ else
142
+ Arel::Nodes::SqlLiteral.new value
143
+ end
144
+ end
145
+
146
+ unless rank_orders.blank?
147
+ rank_node = Arel::Nodes::SqlLiteral.new 'rank()'
148
+ window = Arel::Nodes::Window.new.order(rank_orders)
149
+ over_node = Arel::Nodes::Over.new rank_node, window
150
+
151
+ arel.project(over_node)
152
+ end
153
+ end
154
+
155
+ alias_method_chain :build_arel, :extensions
69
156
  end
70
157
  end
@@ -1,3 +1,3 @@
1
1
  module PostgresExt
2
- VERSION = '2.0.0'
2
+ VERSION = '2.1.0'
3
3
  end
data/postgres_ext.gemspec CHANGED
@@ -23,6 +23,7 @@ Gem::Specification.new do |gem|
23
23
  gem.add_development_dependency 'rails', '~> 4.0.0'
24
24
  gem.add_development_dependency 'rspec-rails', '~> 2.12.0'
25
25
  gem.add_development_dependency 'bourne', '~> 1.3.0'
26
+ gem.add_development_dependency 'database_cleaner'
26
27
  if RUBY_PLATFORM =~ /java/
27
28
  gem.add_development_dependency 'activerecord-jdbcpostgresql-adapter', '1.3.0.beta2'
28
29
  else
File without changes
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Common Table Expression queries' do
4
+ describe '.with(common_table_expression_hash)' do
5
+ it 'generates an expression with the CTE' do
6
+ query = Person.with(lucky_number_seven: Person.where(lucky_number: 7)).joins('JOIN lucky_number_seven ON lucky_number_seven.id = people.id')
7
+ query.to_sql.should eq 'WITH lucky_number_seven AS (SELECT "people".* FROM "people" WHERE "people"."lucky_number" = 7) SELECT "people".* FROM "people" JOIN lucky_number_seven ON lucky_number_seven.id = people.id'
8
+ end
9
+
10
+ it 'generates an expression with multiple CTEs' do
11
+ query = Person.with(lucky_number_seven: Person.where(lucky_number: 7), lucky_number_three: Person.where(lucky_number: 3)).joins('JOIN lucky_number_seven ON lucky_number_seven.id = people.id').joins('JOIN lucky_number_three ON lucky_number_three.id = people.id')
12
+ query.to_sql.should eq 'WITH lucky_number_seven AS (SELECT "people".* FROM "people" WHERE "people"."lucky_number" = 7), lucky_number_three AS (SELECT "people".* FROM "people" WHERE "people"."lucky_number" = 3) SELECT "people".* FROM "people" JOIN lucky_number_seven ON lucky_number_seven.id = people.id JOIN lucky_number_three ON lucky_number_three.id = people.id'
13
+ end
14
+
15
+ it 'generates an expression with multiple with calls' do
16
+ query = Person.with(lucky_number_seven: Person.where(lucky_number: 7)).with(lucky_number_three: Person.where(lucky_number: 3)).joins('JOIN lucky_number_seven ON lucky_number_seven.id = people.id').joins('JOIN lucky_number_three ON lucky_number_three.id = people.id')
17
+ query.to_sql.should eq 'WITH lucky_number_seven AS (SELECT "people".* FROM "people" WHERE "people"."lucky_number" = 7), lucky_number_three AS (SELECT "people".* FROM "people" WHERE "people"."lucky_number" = 3) SELECT "people".* FROM "people" JOIN lucky_number_seven ON lucky_number_seven.id = people.id JOIN lucky_number_three ON lucky_number_three.id = people.id'
18
+ end
19
+ end
20
+
21
+ describe '.from_cte(common_table_expression_hash)' do
22
+ it 'generates an expression with the CTE as the main table' do
23
+ query = Person.from_cte('lucky_number_seven', Person.where(lucky_number: 7)).where(id: 5)
24
+ query.to_sql.should eq 'WITH lucky_number_seven AS (SELECT "people".* FROM "people" WHERE "people"."lucky_number" = 7) SELECT "lucky_number_seven".* FROM "lucky_number_seven" WHERE "lucky_number_seven"."id" = 5'
25
+ end
26
+
27
+ it 'returns instances of the model' do
28
+ 3.times { Person.create! lucky_number: 7 }
29
+ 3.times { Person.create! lucky_number: 3 }
30
+ people = Person.from_cte('lucky_number_seven', Person.where(lucky_number: 7))
31
+
32
+ people.count.should eq 3
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Window functions' do
4
+ describe 'ranked' do
5
+ it 'uses the order when no order is passed to ranked' do
6
+ query = Person.order(lucky_number: :desc).ranked
7
+ query.to_sql.should eq 'SELECT "people".*, rank() OVER (ORDER BY "people"."lucky_number" DESC) FROM "people" ORDER BY "people"."lucky_number" DESC'
8
+ end
9
+
10
+ it 'uses the order when no order is passed to ranked, swapped calls' do
11
+ query = Person.ranked.order(lucky_number: :desc)
12
+ query.to_sql.should eq 'SELECT "people".*, rank() OVER (ORDER BY "people"."lucky_number" DESC) FROM "people" ORDER BY "people"."lucky_number" DESC'
13
+ end
14
+
15
+ it 'uses the rank value when there is an order passed to it' do
16
+ query = Person.ranked(lucky_number: :desc)
17
+ query.to_sql.should eq 'SELECT "people".*, rank() OVER (ORDER BY "people"."lucky_number" DESC) FROM "people"'
18
+ end
19
+
20
+ it 'uses the rank value when a symbol passed to it' do
21
+ query = Person.ranked(:lucky_number)
22
+ query.to_sql.should eq 'SELECT "people".*, rank() OVER (ORDER BY "people"."lucky_number" ASC) FROM "people"'
23
+ end
24
+
25
+ it 'uses the rank value when a string passed to it' do
26
+ query = Person.ranked('lucky_number desc')
27
+ query.to_sql.should eq 'SELECT "people".*, rank() OVER (ORDER BY lucky_number desc) FROM "people"'
28
+ end
29
+
30
+ it 'combines the order and rank' do
31
+ query = Person.ranked(lucky_number: :desc).order(id: :asc)
32
+ query.to_sql.should eq 'SELECT "people".*, rank() OVER (ORDER BY "people"."lucky_number" DESC) FROM "people" ORDER BY "people"."id" ASC'
33
+ end
34
+
35
+ it 'executes the query with the rank' do
36
+ Person.create!
37
+ Person.create!
38
+
39
+ ranked_people = Person.ranked(:id)
40
+ ranked_people[0].rank.should eq 1
41
+ ranked_people[1].rank.should eq 2
42
+ end
43
+ end
44
+ end
data/spec/spec_helper.rb CHANGED
@@ -12,9 +12,17 @@ ENGINE_RAILS_ROOT=File.join(File.dirname(__FILE__), '../')
12
12
  # in spec/support/ and its subdirectories.
13
13
  Dir[File.join(ENGINE_RAILS_ROOT, 'spec/support/**/*.rb')].each { |f| require f }
14
14
  require 'postgres_ext'
15
+ require 'database_cleaner'
15
16
 
16
17
  RSpec.configure do |config|
17
- config.before(:suite) { ActiveRecord::Base.connection.enable_extension('pg_trgm')}
18
+ config.before(:suite) { ActiveRecord::Base.connection.enable_extension('pg_trgm') }
19
+ config.before(:suite) do
20
+ DatabaseCleaner.clean
21
+ DatabaseCleaner.strategy = :deletion
22
+ end
23
+ config.before(:each) do
24
+ DatabaseCleaner.clean
25
+ end
18
26
  config.use_transactional_fixtures = false
19
27
  config.treat_symbols_as_metadata_keys_with_true_values = true
20
28
  config.mock_with :mocha
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.0.0
4
+ version: 2.1.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: 2013-08-23 00:00:00.000000000 Z
11
+ date: 2013-09-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - ~>
95
95
  - !ruby/object:Gem::Version
96
96
  version: 1.3.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: database_cleaner
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: pg
99
113
  requirement: !ruby/object:Gem::Requirement
@@ -127,6 +141,8 @@ files:
127
141
  - docs/querying.md
128
142
  - lib/postgres_ext.rb
129
143
  - lib/postgres_ext/active_record.rb
144
+ - lib/postgres_ext/active_record/cte_proxy.rb
145
+ - lib/postgres_ext/active_record/querying.rb
130
146
  - lib/postgres_ext/active_record/relation.rb
131
147
  - lib/postgres_ext/active_record/relation/predicate_builder.rb
132
148
  - lib/postgres_ext/active_record/relation/query_methods.rb
@@ -140,9 +156,9 @@ files:
140
156
  - lib/postgres_ext/arel/visitors/visitor.rb
141
157
  - lib/postgres_ext/version.rb
142
158
  - postgres_ext.gemspec
143
- - spec/arel/arel_spec.rb
144
159
  - spec/arel/array_spec.rb
145
160
  - spec/arel/inet_spec.rb
161
+ - spec/arel/rank_spec.rb
146
162
  - spec/dummy/.gitignore
147
163
  - spec/dummy/README.rdoc
148
164
  - spec/dummy/Rakefile
@@ -195,8 +211,10 @@ files:
195
211
  - spec/dummy/vendor/assets/stylesheets/.gitkeep
196
212
  - spec/dummy/vendor/plugins/.gitkeep
197
213
  - spec/queries/array_queries_spec.rb
214
+ - spec/queries/common_table_expression_spec.rb
198
215
  - spec/queries/contains_querie_spec.rb
199
216
  - spec/queries/sanity_spec.rb
217
+ - spec/queries/window_functions_spec.rb
200
218
  - spec/spec_helper.rb
201
219
  homepage: ''
202
220
  licenses:
@@ -223,9 +241,9 @@ signing_key:
223
241
  specification_version: 4
224
242
  summary: Extends ActiveRecord to handle native PostgreSQL data types
225
243
  test_files:
226
- - spec/arel/arel_spec.rb
227
244
  - spec/arel/array_spec.rb
228
245
  - spec/arel/inet_spec.rb
246
+ - spec/arel/rank_spec.rb
229
247
  - spec/dummy/.gitignore
230
248
  - spec/dummy/README.rdoc
231
249
  - spec/dummy/Rakefile
@@ -278,6 +296,8 @@ test_files:
278
296
  - spec/dummy/vendor/assets/stylesheets/.gitkeep
279
297
  - spec/dummy/vendor/plugins/.gitkeep
280
298
  - spec/queries/array_queries_spec.rb
299
+ - spec/queries/common_table_expression_spec.rb
281
300
  - spec/queries/contains_querie_spec.rb
282
301
  - spec/queries/sanity_spec.rb
302
+ - spec/queries/window_functions_spec.rb
283
303
  - spec/spec_helper.rb
@@ -1,29 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe 'Don\'t stomp all over the default ActiveRecord queries' do
4
- let!(:adapter) { ActiveRecord::Base.connection }
5
-
6
- before do
7
- adapter.create_table :cars, :force => true do |t|
8
- t.string :make
9
- t.string :model
10
- t.timestamps
11
- end
12
-
13
- class Car < ActiveRecord::Base
14
- end
15
- end
16
-
17
- after do
18
- adapter.drop_table :cars
19
- Object.send(:remove_const, :Car)
20
- end
21
-
22
- describe 'Where Queries' do
23
- describe 'Set query' do
24
- it '' do
25
- Car.where('id in (?)', [1,2]).to_sql.should eq "SELECT \"cars\".* FROM \"cars\" WHERE (id in (1,2))"
26
- end
27
- end
28
- end
29
- end