simple-sql 0.4.11 → 0.4.12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/lib/simple/sql/connection_adapter.rb +1 -1
- data/lib/simple/sql/helpers/row_converter.rb +53 -12
- data/lib/simple/sql/result.rb +45 -3
- data/lib/simple/sql/result/association_loader.rb +10 -11
- data/lib/simple/sql/result/records.rb +2 -3
- data/lib/simple/sql/version.rb +1 -1
- data/spec/simple/sql_associations_spec.rb +39 -4
- metadata +3 -4
- data/lib/simple/sql/result/rows.rb +0 -30
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 219be87ab80103dce585c2c71a8be92dc8274448
|
4
|
+
data.tar.gz: aacb51c639d27d3348695c075a9e1e12c27fe915
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1f8250eac83d27aa869fdc01814f1c565e71470d8805b876eac12c875466f780341259ee9e6f10f84e27a0f3dada384a2044c109267c5c8bcf34d4eb2d14ffcd
|
7
|
+
data.tar.gz: 4260387271f5c4ab8e25d6bdc4d457a94412453598f0e4ce876800a89db0862871950efe9849ed1f5823c1bce8727f47d81df81f4c28ed4c0851c8cefe6c8405
|
@@ -41,7 +41,7 @@ module Simple::SQL::ConnectionAdapter
|
|
41
41
|
#
|
42
42
|
# Even if into is set to something different than a Hash, we'll convert
|
43
43
|
# each row into a Hash initially, and only later convert it to the final
|
44
|
-
# target type (via RowConverter.
|
44
|
+
# target type (via RowConverter.convert_ary). This is to allow to fill in
|
45
45
|
# more entries later on.
|
46
46
|
records = enumerate(pg_result, into: into)
|
47
47
|
|
@@ -1,14 +1,46 @@
|
|
1
|
-
module Simple::SQL::Helpers::RowConverter
|
1
|
+
module Simple::SQL::Helpers::RowConverter # :private:
|
2
|
+
SELF = self
|
3
|
+
|
2
4
|
# returns an array of converted records
|
3
|
-
def self.
|
5
|
+
def self.convert_ary(records, into:)
|
4
6
|
hsh = records.first
|
5
7
|
return records unless hsh
|
6
8
|
|
7
|
-
if into == :struct
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
converter = if into == :struct
|
10
|
+
StructConverter.for(attributes: hsh.keys)
|
11
|
+
else
|
12
|
+
TypeConverter.for(type: into)
|
13
|
+
end
|
14
|
+
|
15
|
+
records.map { |record| converter.convert_ary(record) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.convert(record, into:) # :nodoc:
|
19
|
+
ary = convert_ary([record], into: into)
|
20
|
+
ary.first
|
21
|
+
end
|
22
|
+
|
23
|
+
class TypeConverter #:nodoc:
|
24
|
+
def self.for(type:)
|
25
|
+
new(type: type)
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(type:)
|
29
|
+
@type = type
|
30
|
+
end
|
31
|
+
|
32
|
+
def convert_ary(hsh)
|
33
|
+
updates = {}
|
34
|
+
hsh.each do |key, value|
|
35
|
+
case value
|
36
|
+
when Hash then updates[key] = SELF.convert(value, into: @type)
|
37
|
+
when Array then updates[key] = SELF.convert_ary(value, into: @type)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
hsh = hsh.merge(updates)
|
42
|
+
|
43
|
+
@type.new hsh
|
12
44
|
end
|
13
45
|
end
|
14
46
|
|
@@ -18,16 +50,25 @@ module Simple::SQL::Helpers::RowConverter
|
|
18
50
|
@cache[attributes] ||= new(attributes)
|
19
51
|
end
|
20
52
|
|
21
|
-
private
|
22
|
-
|
23
53
|
def initialize(attributes)
|
24
54
|
@klass = Struct.new(*attributes)
|
25
55
|
end
|
26
56
|
|
27
|
-
|
28
|
-
|
29
|
-
def convert(hsh)
|
57
|
+
def convert_ary(hsh)
|
30
58
|
values = hsh.values_at(*@klass.members)
|
59
|
+
updates = {}
|
60
|
+
|
61
|
+
values.each_with_index do |value, idx|
|
62
|
+
case value
|
63
|
+
when Hash then updates[idx] = SELF.convert(value, into: :struct)
|
64
|
+
when Array then updates[idx] = SELF.convert_ary(value, into: :struct)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
updates.each do |idx, updated_value|
|
69
|
+
values[idx] = updated_value
|
70
|
+
end
|
71
|
+
|
31
72
|
@klass.new(*values)
|
32
73
|
end
|
33
74
|
end
|
data/lib/simple/sql/result.rb
CHANGED
@@ -1,27 +1,69 @@
|
|
1
|
+
# rubocop:disable Metrics/AbcSize
|
2
|
+
# rubocop:disable Naming/AccessorMethodName
|
3
|
+
|
1
4
|
require_relative "helpers"
|
2
5
|
|
3
6
|
class ::Simple::SQL::Result < Array
|
4
7
|
end
|
5
8
|
|
6
|
-
require_relative "result/rows"
|
7
9
|
require_relative "result/records"
|
8
10
|
|
9
11
|
# The result of SQL.all
|
10
12
|
#
|
11
|
-
# This class implements the interface of a Result.
|
13
|
+
# This class implements the basic interface of a Result set. Record result sets
|
14
|
+
# support the conversion of a record into a custom type of the callers choice,
|
15
|
+
# via the :into option for <tt>SQL.all</tt> and <tt>SQL.ask</tt>.
|
16
|
+
#
|
17
|
+
#
|
12
18
|
class ::Simple::SQL::Result < Array
|
13
19
|
# A Result object is requested via ::Simple::SQL::Result.build, which then
|
14
20
|
# chooses the correct implementation, based on the <tt>target_type:</tt>
|
15
21
|
# parameter.
|
16
22
|
def self.build(records, target_type:, pg_source_oid:) # :nodoc:
|
17
23
|
if target_type.nil?
|
18
|
-
|
24
|
+
new(records)
|
19
25
|
else
|
20
26
|
Records.new(records, target_type: target_type, pg_source_oid: pg_source_oid)
|
21
27
|
end
|
22
28
|
end
|
23
29
|
|
30
|
+
def initialize(records) # :nodoc:
|
31
|
+
replace(records)
|
32
|
+
end
|
33
|
+
|
34
|
+
# returns the total_count of search hits
|
35
|
+
#
|
36
|
+
# This is filled in when resolving a paginated scope.
|
24
37
|
attr_reader :total_count
|
38
|
+
|
39
|
+
# returns the total number of pages of search hits
|
40
|
+
#
|
41
|
+
# This is filled in when resolving a paginated scope. It takes
|
42
|
+
# into account the scope's "per" option.
|
25
43
|
attr_reader :total_pages
|
44
|
+
|
45
|
+
# returns the current page number in a paginated search
|
46
|
+
#
|
47
|
+
# This is filled in when resolving a paginated scope.
|
26
48
|
attr_reader :current_page
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def set_pagination_info(scope)
|
53
|
+
raise ArgumentError, "per must be > 0" unless scope.per > 0
|
54
|
+
|
55
|
+
if scope.page <= 1 && empty?
|
56
|
+
# This branch is an optimization: the call to the database to count is
|
57
|
+
# not necessary if we know that there are not even any results on the
|
58
|
+
# first page.
|
59
|
+
@total_count = 0
|
60
|
+
@current_page = 1
|
61
|
+
else
|
62
|
+
sql = "SELECT COUNT(*) FROM (#{scope.order_by(nil).to_sql(pagination: false)}) simple_sql_count"
|
63
|
+
@total_count = ::Simple::SQL.ask(sql, *scope.args)
|
64
|
+
@current_page = scope.page
|
65
|
+
end
|
66
|
+
|
67
|
+
@total_pages = (@total_count * 1.0 / scope.per).ceil
|
68
|
+
end
|
27
69
|
end
|
@@ -103,17 +103,16 @@ module ::Simple::SQL::Result::AssociationLoader # :nodoc:
|
|
103
103
|
|
104
104
|
# preloads a has_one or has_many association.
|
105
105
|
def preload_has_one_or_many(records, relation, as:, order_by:, limit:)
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
end
|
106
|
+
# To really make sense limit must be implemented using window
|
107
|
+
# functions, because one (or, at lieast, I) would expect this code
|
108
|
+
#
|
109
|
+
# organizations = SQL.all "SELECT * FROM organizations", into: Hash
|
110
|
+
# organizations.preload :users, limit: 2, order_by: "id DESC"
|
111
|
+
#
|
112
|
+
# to return up to two users **per organization**.
|
113
|
+
#
|
114
|
+
raise "Support for limit: is not implemented yet!" if limit
|
115
|
+
raise "Support for order_by: is not implemented yet!" if order_by && as.to_s.singularize == as.to_s
|
117
116
|
|
118
117
|
belonging_column = relation.belonging_column.to_sym
|
119
118
|
having_column = relation.having_column.to_sym
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require_relative "association_loader"
|
4
4
|
|
5
|
-
class ::Simple::SQL::Result::Records < ::Simple::SQL::Result
|
5
|
+
class ::Simple::SQL::Result::Records < ::Simple::SQL::Result
|
6
6
|
def initialize(records, target_type:, pg_source_oid:) # :nodoc:
|
7
7
|
expect! records.first => Hash unless records.empty?
|
8
8
|
|
@@ -64,8 +64,7 @@ class ::Simple::SQL::Result::Records < ::Simple::SQL::Result::Rows
|
|
64
64
|
|
65
65
|
def materialize
|
66
66
|
records = @hash_records
|
67
|
-
records = RowConverter.
|
68
|
-
|
67
|
+
records = RowConverter.convert_ary(records, into: @target_type) if @target_type != Hash
|
69
68
|
replace(records)
|
70
69
|
end
|
71
70
|
end
|
data/lib/simple/sql/version.rb
CHANGED
@@ -66,7 +66,7 @@ describe "Simple::SQL::Result#preload" do
|
|
66
66
|
it "detects a has_one association" do
|
67
67
|
organizations = SQL.all "SELECT * FROM organizations", into: Hash
|
68
68
|
organizations.preload :user
|
69
|
-
|
69
|
+
|
70
70
|
organization = organizations.first
|
71
71
|
users_of_organization = SQL.all "SELECT * FROM users WHERE organization_id=$1", organization[:id], into: Hash
|
72
72
|
expect(users_of_organization).to include(organization[:user])
|
@@ -94,13 +94,48 @@ describe "Simple::SQL::Result#preload" do
|
|
94
94
|
organizations = SQL.all "SELECT * FROM organizations", into: Hash
|
95
95
|
organizations.preload :user, as: :usr
|
96
96
|
expect(organizations.first.keys).not_to include(:user)
|
97
|
-
|
97
|
+
|
98
98
|
organization = organizations.first
|
99
99
|
users_of_organization = SQL.all "SELECT * FROM users WHERE organization_id=$1", organization[:id], into: Hash
|
100
100
|
expect(users_of_organization).to include(organization[:usr])
|
101
101
|
end
|
102
102
|
end
|
103
103
|
|
104
|
+
|
105
|
+
describe ":into option" do
|
106
|
+
it "creates a :struct for singular association" do
|
107
|
+
users = SQL.all "SELECT * FROM users WHERE organization_id=$1", org1.id, into: :struct
|
108
|
+
users.preload :organization
|
109
|
+
|
110
|
+
expect(users.first.class.superclass).to eq(Struct)
|
111
|
+
expect(users.first.organization.class.superclass).to eq(Struct)
|
112
|
+
end
|
113
|
+
|
114
|
+
it "creates a :struct for array associations" do
|
115
|
+
organizations = SQL.all "SELECT * FROM organizations", into: :struct
|
116
|
+
organizations.preload :users
|
117
|
+
expect(organizations.first.class.superclass).to eq(Struct)
|
118
|
+
|
119
|
+
expect(organizations.first.users.first.class.superclass).to eq(Struct)
|
120
|
+
end
|
121
|
+
|
122
|
+
it "creates a OpenStruct for singular association" do
|
123
|
+
users = SQL.all "SELECT * FROM users WHERE organization_id=$1", org1.id, into: OpenStruct
|
124
|
+
users.preload :organization
|
125
|
+
|
126
|
+
expect(users.first).to be_a(OpenStruct)
|
127
|
+
expect(users.first.organization).to be_a(OpenStruct)
|
128
|
+
end
|
129
|
+
|
130
|
+
it "creates a OpenStruct for array associations" do
|
131
|
+
organizations = SQL.all "SELECT * FROM organizations", into: OpenStruct
|
132
|
+
organizations.preload :users
|
133
|
+
expect(organizations.first).to be_a(OpenStruct)
|
134
|
+
|
135
|
+
expect(organizations.first.users.first).to be_a(OpenStruct)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
104
139
|
describe ":order_by" do
|
105
140
|
it "supports order_by" do
|
106
141
|
organizations = SQL.all "SELECT * FROM organizations", into: Hash
|
@@ -110,11 +145,11 @@ describe "Simple::SQL::Result#preload" do
|
|
110
145
|
ordered_user_ids = SQL.all("SELECT id FROM users WHERE organization_id=$1 ORDER BY id", organizations.first[:id])
|
111
146
|
expect(users.pluck(:id)).to eq(ordered_user_ids)
|
112
147
|
end
|
113
|
-
|
148
|
+
|
114
149
|
it "supports order_by DESC" do
|
115
150
|
organizations = SQL.all "SELECT * FROM organizations", into: Hash
|
116
151
|
organizations.preload :users, order_by: "id DESC"
|
117
|
-
users = organizations.first[:users]
|
152
|
+
users = organizations.first[:users]
|
118
153
|
|
119
154
|
ordered_user_ids = SQL.all("SELECT id FROM users WHERE organization_id=$1 ORDER BY id", organizations.first[:id])
|
120
155
|
expect(users.pluck(:id)).to eq(ordered_user_ids.reverse)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: simple-sql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.12
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- radiospiel
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2018-08-
|
12
|
+
date: 2018-08-13 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: pg_array_parser
|
@@ -196,7 +196,6 @@ files:
|
|
196
196
|
- lib/simple/sql/result.rb
|
197
197
|
- lib/simple/sql/result/association_loader.rb
|
198
198
|
- lib/simple/sql/result/records.rb
|
199
|
-
- lib/simple/sql/result/rows.rb
|
200
199
|
- lib/simple/sql/scope.rb
|
201
200
|
- lib/simple/sql/scope/filters.rb
|
202
201
|
- lib/simple/sql/scope/order.rb
|
@@ -244,7 +243,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
244
243
|
version: '0'
|
245
244
|
requirements: []
|
246
245
|
rubyforge_project:
|
247
|
-
rubygems_version: 2.
|
246
|
+
rubygems_version: 2.5.1
|
248
247
|
signing_key:
|
249
248
|
specification_version: 4
|
250
249
|
summary: SQL with a simple interface
|
@@ -1,30 +0,0 @@
|
|
1
|
-
# rubocop:disable Metrics/AbcSize
|
2
|
-
# rubocop:disable Naming/AccessorMethodName
|
3
|
-
|
4
|
-
class ::Simple::SQL::Result::Rows < ::Simple::SQL::Result
|
5
|
-
def initialize(records)
|
6
|
-
replace(records)
|
7
|
-
end
|
8
|
-
|
9
|
-
# -- pagination info ------------------------------------------------------
|
10
|
-
|
11
|
-
private
|
12
|
-
|
13
|
-
def set_pagination_info(scope)
|
14
|
-
raise ArgumentError, "per must be > 0" unless scope.per > 0
|
15
|
-
|
16
|
-
if scope.page <= 1 && empty?
|
17
|
-
# This branch is an optimization: the call to the database to count is
|
18
|
-
# not necessary if we know that there are not even any results on the
|
19
|
-
# first page.
|
20
|
-
@total_count = 0
|
21
|
-
@current_page = 1
|
22
|
-
else
|
23
|
-
sql = "SELECT COUNT(*) FROM (#{scope.order_by(nil).to_sql(pagination: false)}) simple_sql_count"
|
24
|
-
@total_count = ::Simple::SQL.ask(sql, *scope.args)
|
25
|
-
@current_page = scope.page
|
26
|
-
end
|
27
|
-
|
28
|
-
@total_pages = (@total_count * 1.0 / scope.per).ceil
|
29
|
-
end
|
30
|
-
end
|