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 +4 -4
- data/README.md +1 -1
- data/lib/postgres_ext/active_record.rb +2 -0
- data/lib/postgres_ext/active_record/cte_proxy.rb +33 -0
- data/lib/postgres_ext/active_record/querying.rb +13 -0
- data/lib/postgres_ext/active_record/relation/query_methods.rb +87 -0
- data/lib/postgres_ext/version.rb +1 -1
- data/postgres_ext.gemspec +1 -0
- data/spec/arel/rank_spec.rb +0 -0
- data/spec/queries/common_table_expression_spec.rb +35 -0
- data/spec/queries/window_functions_spec.rb +44 -0
- data/spec/spec_helper.rb +9 -1
- metadata +24 -4
- data/spec/arel/arel_spec.rb +0 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4fb1baea00022bce72d77b44835d7468bad8b5c3
|
4
|
+
data.tar.gz: beee8834d070b830cc100f1f76485888e9a7e655
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
[](http://travis-ci.org/dockyard/postgres_ext)
|
6
|
-
[](https://codeclimate.com/github/dockyard/postgres_ext)
|
7
7
|
|
8
8
|
## Looking for help? ##
|
9
9
|
|
@@ -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
|
data/lib/postgres_ext/version.rb
CHANGED
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.
|
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-
|
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
|
data/spec/arel/arel_spec.rb
DELETED
@@ -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
|