postgres_ext 2.0.0 → 2.1.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: 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