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 +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
|
[![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/
|
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
|
|
@@ -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
|