rasti-db 0.3.0 → 0.4.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/lib/rasti/db/collection.rb +11 -11
- data/lib/rasti/db/query.rb +1 -1
- data/lib/rasti/db/relations/base.rb +51 -0
- data/lib/rasti/db/relations/graph_builder.rb +31 -0
- data/lib/rasti/db/relations/many_to_many.rb +60 -0
- data/lib/rasti/db/relations/many_to_one.rb +33 -0
- data/lib/rasti/db/relations/one_to_many.rb +42 -0
- data/lib/rasti/db/relations/one_to_one.rb +15 -0
- data/lib/rasti/db/version.rb +1 -1
- data/lib/rasti/db.rb +6 -1
- data/spec/collection_spec.rb +30 -20
- data/spec/minitest_helper.rb +56 -35
- data/spec/model_spec.rb +1 -1
- data/spec/relations_spec.rb +54 -0
- metadata +8 -3
- data/lib/rasti/db/relations.rb +0 -191
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 935cee1239288342a27bdcde6660f205b9fa1685
|
4
|
+
data.tar.gz: 2bc7b5c0cf6559c7ae5716b4814fea412e0481f3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b564fb1e883999d26e06b73c81d5ef68a68c2cdebc870403376c7c3b49e4729c020b552a010ffa47c13ca24743dfe9327b12963000a8892c55b3a4d0477f9170
|
7
|
+
data.tar.gz: 1f68b62bc955b2d7a4955a217877d9013c2ab7546740a0b90c452952e5cc241fa2573a7c1766da724e22013dfa176894c4ec81aa2da20bf322f0b84598f2ff63
|
data/lib/rasti/db/collection.rb
CHANGED
@@ -12,13 +12,17 @@ module Rasti
|
|
12
12
|
include Sequel::Inflections
|
13
13
|
|
14
14
|
def collection_name
|
15
|
-
@collection_name ||=
|
15
|
+
@collection_name ||= underscore(demodulize(name)).to_sym
|
16
16
|
end
|
17
17
|
|
18
18
|
def primary_key
|
19
19
|
@primary_key ||= :id
|
20
20
|
end
|
21
21
|
|
22
|
+
def foreign_key
|
23
|
+
@foreign_key ||= "#{singularize(collection_name)}_id".to_sym
|
24
|
+
end
|
25
|
+
|
22
26
|
def model
|
23
27
|
if @model.is_a? Class
|
24
28
|
@model
|
@@ -37,14 +41,6 @@ module Rasti
|
|
37
41
|
@queries ||= {}
|
38
42
|
end
|
39
43
|
|
40
|
-
def implicit_collection_name
|
41
|
-
underscore(demodulize(name)).to_sym
|
42
|
-
end
|
43
|
-
|
44
|
-
def implicit_foreign_key_name
|
45
|
-
"#{singularize(collection_name)}_id".to_sym
|
46
|
-
end
|
47
|
-
|
48
44
|
private
|
49
45
|
|
50
46
|
def set_collection_name(collection_name)
|
@@ -55,11 +51,15 @@ module Rasti
|
|
55
51
|
@primary_key = primary_key
|
56
52
|
end
|
57
53
|
|
54
|
+
def set_foreign_key(foreign_key)
|
55
|
+
@foreign_key = foreign_key
|
56
|
+
end
|
57
|
+
|
58
58
|
def set_model(model)
|
59
59
|
@model = model
|
60
60
|
end
|
61
61
|
|
62
|
-
[Relations::OneToMany, Relations::ManyToOne, Relations::ManyToMany].each do |relation_class|
|
62
|
+
[Relations::OneToMany, Relations::ManyToOne, Relations::ManyToMany, Relations::OneToOne].each do |relation_class|
|
63
63
|
define_method underscore(demodulize(relation_class.name)) do |name, options={}|
|
64
64
|
relations[name] = relation_class.new name, self, options
|
65
65
|
|
@@ -228,7 +228,7 @@ module Rasti
|
|
228
228
|
delete_relation_table relation, primary_keys
|
229
229
|
end
|
230
230
|
|
231
|
-
relations.select
|
231
|
+
relations.select { |r| r.one_to_many? || r.one_to_one? }.each do |relation|
|
232
232
|
relation_collection_name = with_schema(relation.target_collection_class.collection_name)
|
233
233
|
relations_ids = db[relation_collection_name].where(relation.foreign_key => primary_keys)
|
234
234
|
.select(relation.target_collection_class.primary_key)
|
data/lib/rasti/db/query.rb
CHANGED
@@ -99,7 +99,7 @@ module Rasti
|
|
99
99
|
|
100
100
|
def with_graph(data)
|
101
101
|
rows = data.is_a?(Array) ? data : [data]
|
102
|
-
Relations.graph_to rows, relations, collection_class, dataset.db, schema
|
102
|
+
Relations::GraphBuilder.graph_to rows, relations, collection_class, dataset.db, schema
|
103
103
|
data
|
104
104
|
end
|
105
105
|
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Rasti
|
2
|
+
module DB
|
3
|
+
module Relations
|
4
|
+
class Base
|
5
|
+
|
6
|
+
include Sequel::Inflections
|
7
|
+
|
8
|
+
attr_reader :name, :source_collection_class
|
9
|
+
|
10
|
+
def initialize(name, source_collection_class, options={})
|
11
|
+
@name = name
|
12
|
+
@source_collection_class = source_collection_class
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
def target_collection_class
|
17
|
+
@target_collection_class ||= options[:collection].is_a?(Class) ? options[:collection] : Consty.get(options[:collection] || camelize(pluralize(name)), source_collection_class)
|
18
|
+
end
|
19
|
+
|
20
|
+
def one_to_many?
|
21
|
+
self.class == OneToMany
|
22
|
+
end
|
23
|
+
|
24
|
+
def many_to_one?
|
25
|
+
self.class == ManyToOne
|
26
|
+
end
|
27
|
+
|
28
|
+
def many_to_many?
|
29
|
+
self.class == ManyToMany
|
30
|
+
end
|
31
|
+
|
32
|
+
def one_to_one?
|
33
|
+
self.class == OneToOne
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
attr_reader :options
|
39
|
+
|
40
|
+
def qualified_source_collection_name(schema=nil)
|
41
|
+
schema.nil? ? source_collection_class.collection_name : Sequel.qualify(schema, source_collection_class.collection_name)
|
42
|
+
end
|
43
|
+
|
44
|
+
def qualified_target_collection_name(schema=nil)
|
45
|
+
schema.nil? ? target_collection_class.collection_name : Sequel.qualify(schema, target_collection_class.collection_name)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Rasti
|
2
|
+
module DB
|
3
|
+
module Relations
|
4
|
+
class GraphBuilder
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def graph_to(rows, relations, collection_class, db, schema=nil)
|
8
|
+
return if rows.empty?
|
9
|
+
|
10
|
+
parse(relations).each do |relation, nested_relations|
|
11
|
+
raise "Undefined relation #{relation} for #{collection_class}" unless collection_class.relations.key? relation
|
12
|
+
collection_class.relations[relation].graph_to rows, db, schema, nested_relations
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def parse(relations)
|
19
|
+
relations.each_with_object({}) do |relation, hash|
|
20
|
+
tail = relation.to_s.split '.'
|
21
|
+
head = tail.shift.to_sym
|
22
|
+
hash[head] ||= []
|
23
|
+
hash[head] << tail.join('.') unless tail.empty?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Rasti
|
2
|
+
module DB
|
3
|
+
module Relations
|
4
|
+
class ManyToMany < Base
|
5
|
+
|
6
|
+
def source_foreign_key
|
7
|
+
@source_foreign_key ||= options[:source_foreign_key] || source_collection_class.foreign_key
|
8
|
+
end
|
9
|
+
|
10
|
+
def target_foreign_key
|
11
|
+
@target_foreign_key ||= options[:target_foreign_key] || target_collection_class.foreign_key
|
12
|
+
end
|
13
|
+
|
14
|
+
def relation_collection_name
|
15
|
+
@relation_collection_name ||= options[:relation_collection_name] || [source_collection_class.collection_name, target_collection_class.collection_name].sort.join('_').to_sym
|
16
|
+
end
|
17
|
+
|
18
|
+
def qualified_relation_collection_name(schema=nil)
|
19
|
+
schema.nil? ? relation_collection_name : Sequel.qualify(schema, relation_collection_name)
|
20
|
+
end
|
21
|
+
|
22
|
+
def graph_to(rows, db, schema=nil, relations=[])
|
23
|
+
pks = rows.map { |row| row[source_collection_class.primary_key] }
|
24
|
+
|
25
|
+
target_collection = target_collection_class.new db, schema
|
26
|
+
|
27
|
+
relation_name = qualified_relation_collection_name schema
|
28
|
+
|
29
|
+
join_rows = target_collection.dataset
|
30
|
+
.join(relation_name, target_foreign_key => target_collection_class.primary_key)
|
31
|
+
.where(Sequel.qualify(relation_name, source_foreign_key) => pks)
|
32
|
+
.select_all(qualified_target_collection_name(schema))
|
33
|
+
.select_append(Sequel.qualify(relation_name, source_foreign_key).as(:source_foreign_key))
|
34
|
+
.all
|
35
|
+
|
36
|
+
GraphBuilder.graph_to join_rows, relations, target_collection_class, db, schema
|
37
|
+
|
38
|
+
relation_rows = join_rows.each_with_object(Hash.new { |h,k| h[k] = [] }) do |row, hash|
|
39
|
+
attributes = row.select { |attr,_| target_collection_class.model.attributes.include? attr }
|
40
|
+
hash[row[:source_foreign_key]] << target_collection_class.model.new(attributes)
|
41
|
+
end
|
42
|
+
|
43
|
+
rows.each do |row|
|
44
|
+
row[name] = relation_rows.fetch row[target_collection_class.primary_key], []
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def apply_filter(dataset, schema=nil, primary_keys=[])
|
49
|
+
relation_name = qualified_relation_collection_name schema
|
50
|
+
|
51
|
+
dataset.join(relation_name, source_foreign_key => target_collection_class.primary_key)
|
52
|
+
.where(Sequel.qualify(relation_name, target_foreign_key) => primary_keys)
|
53
|
+
.select_all(qualified_source_collection_name(schema))
|
54
|
+
.distinct
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Rasti
|
2
|
+
module DB
|
3
|
+
module Relations
|
4
|
+
class ManyToOne < Base
|
5
|
+
|
6
|
+
def foreign_key
|
7
|
+
@foreign_key ||= options[:foreign_key] || target_collection_class.foreign_key
|
8
|
+
end
|
9
|
+
|
10
|
+
def graph_to(rows, db, schema=nil, relations=[])
|
11
|
+
fks = rows.map { |row| row[foreign_key] }.uniq
|
12
|
+
|
13
|
+
target_collection = target_collection_class.new db, schema
|
14
|
+
|
15
|
+
relation_rows = target_collection.where(source_collection_class.primary_key => fks)
|
16
|
+
.graph(*relations)
|
17
|
+
.each_with_object({}) do |row, hash|
|
18
|
+
hash[row.public_send(source_collection_class.primary_key)] = row
|
19
|
+
end
|
20
|
+
|
21
|
+
rows.each do |row|
|
22
|
+
row[name] = relation_rows[row[foreign_key]]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def apply_filter(dataset, schema=nil, primary_keys=[])
|
27
|
+
dataset.where(foreign_key => primary_keys)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Rasti
|
2
|
+
module DB
|
3
|
+
module Relations
|
4
|
+
class OneToMany < Base
|
5
|
+
|
6
|
+
def foreign_key
|
7
|
+
@foreign_key ||= options[:foreign_key] || source_collection_class.foreign_key
|
8
|
+
end
|
9
|
+
|
10
|
+
def graph_to(rows, db, schema=nil, relations=[])
|
11
|
+
pks = rows.map { |row| row[source_collection_class.primary_key] }.uniq
|
12
|
+
|
13
|
+
target_collection = target_collection_class.new db, schema
|
14
|
+
|
15
|
+
relation_rows = target_collection.where(foreign_key => pks)
|
16
|
+
.graph(*relations)
|
17
|
+
.group_by { |r| r.public_send(foreign_key) }
|
18
|
+
|
19
|
+
rows.each do |row|
|
20
|
+
row[name] = build_graph_result relation_rows.fetch(row[source_collection_class.primary_key], [])
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def apply_filter(dataset, schema=nil, primary_keys=[])
|
25
|
+
target_name = qualified_target_collection_name schema
|
26
|
+
|
27
|
+
dataset.join(target_name, foreign_key => source_collection_class.primary_key)
|
28
|
+
.where(Sequel.qualify(target_name, target_collection_class.primary_key) => primary_keys)
|
29
|
+
.select_all(qualified_source_collection_name(schema))
|
30
|
+
.distinct
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def build_graph_result(rows)
|
36
|
+
rows
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/rasti/db/version.rb
CHANGED
data/lib/rasti/db.rb
CHANGED
@@ -4,7 +4,12 @@ require 'consty'
|
|
4
4
|
require_relative 'db/version'
|
5
5
|
require_relative 'db/helpers'
|
6
6
|
require_relative 'db/query'
|
7
|
-
require_relative 'db/relations'
|
7
|
+
require_relative 'db/relations/graph_builder'
|
8
|
+
require_relative 'db/relations/base'
|
9
|
+
require_relative 'db/relations/one_to_many'
|
10
|
+
require_relative 'db/relations/one_to_one'
|
11
|
+
require_relative 'db/relations/many_to_one'
|
12
|
+
require_relative 'db/relations/many_to_many'
|
8
13
|
require_relative 'db/collection'
|
9
14
|
require_relative 'db/model'
|
10
15
|
require_relative 'db/type_converter'
|
data/spec/collection_spec.rb
CHANGED
@@ -8,21 +8,14 @@ describe 'Collection' do
|
|
8
8
|
Users.collection_name.must_equal :users
|
9
9
|
Users.model.must_equal User
|
10
10
|
Users.primary_key.must_equal :id
|
11
|
-
Users.
|
11
|
+
Users.foreign_key.must_equal :user_id
|
12
12
|
end
|
13
13
|
|
14
14
|
it 'Explicit' do
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
set_primary_key :code
|
20
|
-
set_model model_class
|
21
|
-
end
|
22
|
-
|
23
|
-
collection_class.collection_name.must_equal :countries
|
24
|
-
collection_class.model.must_equal model_class
|
25
|
-
collection_class.primary_key.must_equal :code
|
15
|
+
People.collection_name.must_equal :people
|
16
|
+
People.model.must_equal Person
|
17
|
+
People.primary_key.must_equal :document_number
|
18
|
+
People.foreign_key.must_equal :document_number
|
26
19
|
end
|
27
20
|
|
28
21
|
it 'Lazy model name' do
|
@@ -165,6 +158,13 @@ describe 'Collection' do
|
|
165
158
|
before :each do
|
166
159
|
1.upto(3) do |i|
|
167
160
|
user_id = db[:users].insert name: "User #{i}"
|
161
|
+
|
162
|
+
db[:people].insert document_number: "document_#{i}",
|
163
|
+
first_name: "John #{i}",
|
164
|
+
last_name: "Doe #{i}",
|
165
|
+
birth_date: Time.now - i,
|
166
|
+
user_id: user_id
|
167
|
+
|
168
168
|
category_id = db[:categories].insert name: "Category #{i}"
|
169
169
|
|
170
170
|
1.upto(3) do |n|
|
@@ -178,12 +178,6 @@ describe 'Collection' do
|
|
178
178
|
db[:comments].insert post_id: post_id, user_id: user_id, text: 'Comment'
|
179
179
|
end
|
180
180
|
end
|
181
|
-
|
182
|
-
db[:users].count.must_equal 3
|
183
|
-
db[:categories].count.must_equal 3
|
184
|
-
db[:posts].count.must_equal 9
|
185
|
-
db[:categories_posts].count.must_equal 9
|
186
|
-
db[:comments].count.must_equal 9
|
187
181
|
end
|
188
182
|
|
189
183
|
it 'Self relations' do
|
@@ -206,6 +200,7 @@ describe 'Collection' do
|
|
206
200
|
|
207
201
|
it 'Deep relations' do
|
208
202
|
db[:users].where(id: 1).count.must_equal 1
|
203
|
+
db[:people].where(user_id: 1).count.must_equal 1
|
209
204
|
db[:comments].where(user_id: 1).count.must_equal 3
|
210
205
|
db[:posts].where(user_id: 1).count.must_equal 3
|
211
206
|
db[:comments].join(:posts, id: :post_id).where(Sequel[:posts][:user_id] => 1).count.must_equal 3
|
@@ -214,12 +209,14 @@ describe 'Collection' do
|
|
214
209
|
users.delete_cascade 1
|
215
210
|
|
216
211
|
db[:users].where(id: 1).count.must_equal 0
|
212
|
+
db[:people].where(user_id: 1).count.must_equal 0
|
217
213
|
db[:comments].where(user_id: 1).count.must_equal 0
|
218
214
|
db[:posts].where(user_id: 1).count.must_equal 0
|
219
215
|
db[:comments].join(:posts, id: :post_id).where(Sequel[:posts][:user_id] => 1).count.must_equal 0
|
220
216
|
db[:categories_posts].join(:posts, id: :post_id).where(Sequel[:posts][:user_id] => 1).count.must_equal 0
|
221
217
|
|
222
218
|
db[:users].count.must_equal 2
|
219
|
+
db[:people].count.must_equal 2
|
223
220
|
db[:categories].count.must_equal 3
|
224
221
|
db[:posts].count.must_equal 6
|
225
222
|
db[:categories_posts].count.must_equal 6
|
@@ -312,12 +309,21 @@ describe 'Collection' do
|
|
312
309
|
describe 'Named queries' do
|
313
310
|
|
314
311
|
before do
|
315
|
-
1.upto(2)
|
316
|
-
|
312
|
+
1.upto(2) do |i|
|
313
|
+
db[:categories].insert name: "Category #{i}"
|
314
|
+
db[:users].insert name: "User #{i}"
|
315
|
+
db[:people].insert document_number: "document_#{i}",
|
316
|
+
first_name: "John #{i}",
|
317
|
+
last_name: "Doe #{i}",
|
318
|
+
birth_date: Time.now - i,
|
319
|
+
user_id: i
|
320
|
+
end
|
321
|
+
|
317
322
|
1.upto(3) do |i|
|
318
323
|
db[:posts].insert user_id: 1, title: "Post #{i}", body: '...'
|
319
324
|
db[:categories_posts].insert category_id: 1, post_id: i
|
320
325
|
end
|
326
|
+
|
321
327
|
4.upto(5) do |i|
|
322
328
|
db[:posts].insert user_id: 2, title: "Post #{i}", body: '...'
|
323
329
|
db[:categories_posts].insert category_id: 2, post_id: i
|
@@ -338,6 +344,10 @@ describe 'Collection' do
|
|
338
344
|
posts.with_users(2).primary_keys.must_equal [4,5]
|
339
345
|
end
|
340
346
|
|
347
|
+
it 'One to One' do
|
348
|
+
users.with_people('document_1').primary_keys.must_equal [1]
|
349
|
+
end
|
350
|
+
|
341
351
|
end
|
342
352
|
|
343
353
|
it 'Global' do
|
data/spec/minitest_helper.rb
CHANGED
@@ -8,15 +8,17 @@ require 'sequel/extensions/pg_hstore'
|
|
8
8
|
require 'sequel/extensions/pg_array'
|
9
9
|
require 'sequel/extensions/pg_json'
|
10
10
|
|
11
|
-
User = Rasti::DB::Model[:id, :name, :posts, :comments]
|
11
|
+
User = Rasti::DB::Model[:id, :name, :posts, :comments, :person]
|
12
12
|
Post = Rasti::DB::Model[:id, :title, :body, :user_id, :user, :comments, :categories]
|
13
13
|
Comment = Rasti::DB::Model[:id, :text, :user_id, :user, :post_id, :post]
|
14
14
|
Category = Rasti::DB::Model[:id, :name, :posts]
|
15
|
+
Person = Rasti::DB::Model[:document_number, :first_name, :last_name, :birth_date, :user_id, :user]
|
15
16
|
|
16
17
|
|
17
18
|
class Users < Rasti::DB::Collection
|
18
19
|
one_to_many :posts
|
19
20
|
one_to_many :comments
|
21
|
+
one_to_one :person
|
20
22
|
end
|
21
23
|
|
22
24
|
class Posts < Rasti::DB::Collection
|
@@ -56,16 +58,23 @@ class Categories < Rasti::DB::Collection
|
|
56
58
|
many_to_many :posts
|
57
59
|
end
|
58
60
|
|
61
|
+
class People < Rasti::DB::Collection
|
62
|
+
set_collection_name :people
|
63
|
+
set_primary_key :document_number
|
64
|
+
set_foreign_key :document_number
|
65
|
+
set_model Person
|
66
|
+
|
67
|
+
many_to_one :user
|
68
|
+
end
|
69
|
+
|
59
70
|
|
60
71
|
Rasti::DB::TypeConverter::CONVERTIONS[:sqlite] = {
|
61
|
-
|
72
|
+
Regexp.new('integer') => ->(value, match) { value.to_i }
|
62
73
|
}
|
63
74
|
|
64
75
|
|
65
76
|
class Minitest::Spec
|
66
77
|
|
67
|
-
DB_DRIVER = (RUBY_ENGINE == 'jruby') ? 'jdbc:sqlite::memory:' : {adapter: :sqlite}
|
68
|
-
|
69
78
|
let(:users) { Users.new db }
|
70
79
|
|
71
80
|
let(:posts) { Posts.new db }
|
@@ -74,40 +83,52 @@ class Minitest::Spec
|
|
74
83
|
|
75
84
|
let(:categories) { Categories.new db }
|
76
85
|
|
77
|
-
let
|
78
|
-
db = Sequel.connect DB_DRIVER
|
79
|
-
|
80
|
-
db.create_table :users do
|
81
|
-
primary_key :id
|
82
|
-
String :name, null: false, unique: true
|
83
|
-
end
|
86
|
+
let(:people) { People.new db }
|
84
87
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
88
|
+
let :db do
|
89
|
+
driver = (RUBY_ENGINE == 'jruby') ? 'jdbc:sqlite::memory:' : {adapter: :sqlite}
|
90
|
+
|
91
|
+
Sequel.connect(driver).tap do |db|
|
92
|
+
|
93
|
+
db.create_table :users do
|
94
|
+
primary_key :id
|
95
|
+
String :name, null: false, unique: true
|
96
|
+
end
|
97
|
+
|
98
|
+
db.create_table :posts do
|
99
|
+
primary_key :id
|
100
|
+
String :title, null: false, unique: true
|
101
|
+
String :body, null: false
|
102
|
+
foreign_key :user_id, :users, null: false, index: true
|
103
|
+
end
|
104
|
+
|
105
|
+
db.create_table :comments do
|
106
|
+
primary_key :id
|
107
|
+
String :text, null: false
|
108
|
+
foreign_key :user_id, :users, null: false, index: true
|
109
|
+
foreign_key :post_id, :posts, null: false, index: true
|
110
|
+
end
|
111
|
+
|
112
|
+
db.create_table :categories do
|
113
|
+
primary_key :id
|
114
|
+
String :name, null: false, unique: true
|
115
|
+
end
|
116
|
+
|
117
|
+
db.create_table :categories_posts do
|
118
|
+
foreign_key :category_id, :categories, null: false, index: true
|
119
|
+
foreign_key :post_id, :posts, null: false, index: true
|
120
|
+
primary_key [:category_id, :post_id]
|
121
|
+
end
|
122
|
+
|
123
|
+
db.create_table :people do
|
124
|
+
String :document_number, null: false, primary_key: true
|
125
|
+
String :first_name, null: false
|
126
|
+
String :last_name, null: false
|
127
|
+
Date :birth_date, null: false
|
128
|
+
foreign_key :user_id, :users, null: false, unique: true
|
129
|
+
end
|
103
130
|
|
104
|
-
db.create_table :categories_posts do
|
105
|
-
foreign_key :category_id, :categories, null: false, index: true
|
106
|
-
foreign_key :post_id, :posts, null: false, index: true
|
107
|
-
primary_key [:category_id, :post_id]
|
108
131
|
end
|
109
|
-
|
110
|
-
db
|
111
132
|
end
|
112
133
|
|
113
134
|
end
|
data/spec/model_spec.rb
CHANGED
data/spec/relations_spec.rb
CHANGED
@@ -29,6 +29,7 @@ describe 'Relations' do
|
|
29
29
|
relation.one_to_many?.must_equal true
|
30
30
|
relation.many_to_one?.must_equal false
|
31
31
|
relation.many_to_many?.must_equal false
|
32
|
+
relation.one_to_one?.must_equal false
|
32
33
|
end
|
33
34
|
|
34
35
|
it 'Graph' do
|
@@ -70,6 +71,7 @@ describe 'Relations' do
|
|
70
71
|
relation.one_to_many?.must_equal false
|
71
72
|
relation.many_to_one?.must_equal true
|
72
73
|
relation.many_to_many?.must_equal false
|
74
|
+
relation.one_to_one?.must_equal false
|
73
75
|
end
|
74
76
|
|
75
77
|
it 'Graph' do
|
@@ -117,6 +119,7 @@ describe 'Relations' do
|
|
117
119
|
relation.one_to_many?.must_equal false
|
118
120
|
relation.many_to_one?.must_equal false
|
119
121
|
relation.many_to_many?.must_equal true
|
122
|
+
relation.one_to_one?.must_equal false
|
120
123
|
end
|
121
124
|
|
122
125
|
it 'Graph' do
|
@@ -141,4 +144,55 @@ describe 'Relations' do
|
|
141
144
|
|
142
145
|
end
|
143
146
|
|
147
|
+
describe 'One To One' do
|
148
|
+
|
149
|
+
describe 'Specification' do
|
150
|
+
|
151
|
+
it 'Implicit' do
|
152
|
+
relation = Rasti::DB::Relations::OneToOne.new :person, Users
|
153
|
+
|
154
|
+
relation.target_collection_class.must_equal People
|
155
|
+
relation.foreign_key.must_equal :user_id
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'Explicit' do
|
159
|
+
relation = Rasti::DB::Relations::OneToOne.new :person, Users, collection: 'Users',
|
160
|
+
foreign_key: :id_user
|
161
|
+
|
162
|
+
relation.target_collection_class.must_equal Users
|
163
|
+
relation.foreign_key.must_equal :id_user
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
it 'Type' do
|
169
|
+
relation = Rasti::DB::Relations::OneToOne.new :person, User
|
170
|
+
|
171
|
+
relation.one_to_many?.must_equal false
|
172
|
+
relation.many_to_one?.must_equal false
|
173
|
+
relation.many_to_many?.must_equal false
|
174
|
+
relation.one_to_one?.must_equal true
|
175
|
+
end
|
176
|
+
|
177
|
+
it 'Graph' do
|
178
|
+
2.times do |i|
|
179
|
+
user_id = db[:users].insert name: "User #{i}"
|
180
|
+
db[:people].insert document_number: "document_#{i}",
|
181
|
+
first_name: "John #{i}",
|
182
|
+
last_name: "Doe #{i}",
|
183
|
+
birth_date: Time.now - i,
|
184
|
+
user_id: user_id
|
185
|
+
end
|
186
|
+
|
187
|
+
rows = db[:users].all
|
188
|
+
|
189
|
+
Users.relations[:person].graph_to rows, db
|
190
|
+
|
191
|
+
2.times do |i|
|
192
|
+
rows[i][:person].must_equal people.find("document_#{i}")
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
end
|
197
|
+
|
144
198
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rasti-db
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gabriel Naiman
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-12-
|
11
|
+
date: 2017-12-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sequel
|
@@ -192,7 +192,12 @@ files:
|
|
192
192
|
- lib/rasti/db/helpers.rb
|
193
193
|
- lib/rasti/db/model.rb
|
194
194
|
- lib/rasti/db/query.rb
|
195
|
-
- lib/rasti/db/relations.rb
|
195
|
+
- lib/rasti/db/relations/base.rb
|
196
|
+
- lib/rasti/db/relations/graph_builder.rb
|
197
|
+
- lib/rasti/db/relations/many_to_many.rb
|
198
|
+
- lib/rasti/db/relations/many_to_one.rb
|
199
|
+
- lib/rasti/db/relations/one_to_many.rb
|
200
|
+
- lib/rasti/db/relations/one_to_one.rb
|
196
201
|
- lib/rasti/db/type_converter.rb
|
197
202
|
- lib/rasti/db/version.rb
|
198
203
|
- rasti-db.gemspec
|
data/lib/rasti/db/relations.rb
DELETED
@@ -1,191 +0,0 @@
|
|
1
|
-
module Rasti
|
2
|
-
module DB
|
3
|
-
module Relations
|
4
|
-
|
5
|
-
class << self
|
6
|
-
|
7
|
-
def graph_to(rows, relations, collection_class, db, schema=nil)
|
8
|
-
return if rows.empty?
|
9
|
-
|
10
|
-
parse(relations).each do |relation, nested_relations|
|
11
|
-
raise "Undefined relation #{relation} for #{collection_class}" unless collection_class.relations.key? relation
|
12
|
-
collection_class.relations[relation].graph_to rows, db, schema, nested_relations
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
private
|
17
|
-
|
18
|
-
def parse(relations)
|
19
|
-
relations.each_with_object({}) do |relation, hash|
|
20
|
-
tail = relation.to_s.split '.'
|
21
|
-
head = tail.shift.to_sym
|
22
|
-
hash[head] ||= []
|
23
|
-
hash[head] << tail.join('.') unless tail.empty?
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
end
|
28
|
-
|
29
|
-
|
30
|
-
class Base
|
31
|
-
|
32
|
-
include Sequel::Inflections
|
33
|
-
|
34
|
-
attr_reader :name, :source_collection_class
|
35
|
-
|
36
|
-
def initialize(name, source_collection_class, options={})
|
37
|
-
@name = name
|
38
|
-
@source_collection_class = source_collection_class
|
39
|
-
@options = options
|
40
|
-
end
|
41
|
-
|
42
|
-
def target_collection_class
|
43
|
-
@target_collection_class ||= @options[:collection].is_a?(Class) ? @options[:collection] : Consty.get(@options[:collection] || camelize(pluralize(name)), source_collection_class)
|
44
|
-
end
|
45
|
-
|
46
|
-
def qualified_source_collection_name(schema=nil)
|
47
|
-
schema.nil? ? source_collection_class.collection_name : Sequel.qualify(schema, source_collection_class.collection_name)
|
48
|
-
end
|
49
|
-
|
50
|
-
def qualified_target_collection_name(schema=nil)
|
51
|
-
schema.nil? ? target_collection_class.collection_name : Sequel.qualify(schema, target_collection_class.collection_name)
|
52
|
-
end
|
53
|
-
|
54
|
-
def one_to_many?
|
55
|
-
is_a? OneToMany
|
56
|
-
end
|
57
|
-
|
58
|
-
def many_to_one?
|
59
|
-
is_a? ManyToOne
|
60
|
-
end
|
61
|
-
|
62
|
-
def many_to_many?
|
63
|
-
is_a? ManyToMany
|
64
|
-
end
|
65
|
-
|
66
|
-
private
|
67
|
-
|
68
|
-
attr_reader :options
|
69
|
-
|
70
|
-
end
|
71
|
-
|
72
|
-
|
73
|
-
class OneToMany < Base
|
74
|
-
|
75
|
-
def foreign_key
|
76
|
-
@foreign_key ||= @options[:foreign_key] || source_collection_class.implicit_foreign_key_name
|
77
|
-
end
|
78
|
-
|
79
|
-
def graph_to(rows, db, schema=nil, relations=[])
|
80
|
-
pks = rows.map { |row| row[source_collection_class.primary_key] }.uniq
|
81
|
-
|
82
|
-
target_collection = target_collection_class.new db, schema
|
83
|
-
|
84
|
-
relation_rows = target_collection.where(foreign_key => pks)
|
85
|
-
.graph(*relations)
|
86
|
-
.group_by { |r| r.public_send(foreign_key) }
|
87
|
-
|
88
|
-
rows.each do |row|
|
89
|
-
row[name] = relation_rows.fetch row[source_collection_class.primary_key], []
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
def apply_filter(dataset, schema=nil, primary_keys=[])
|
94
|
-
target_name = qualified_target_collection_name schema
|
95
|
-
|
96
|
-
dataset.join(target_name, foreign_key => source_collection_class.primary_key)
|
97
|
-
.where(Sequel.qualify(target_name, target_collection_class.primary_key) => primary_keys)
|
98
|
-
.select_all(qualified_source_collection_name(schema))
|
99
|
-
.distinct
|
100
|
-
end
|
101
|
-
|
102
|
-
end
|
103
|
-
|
104
|
-
|
105
|
-
class ManyToOne < Base
|
106
|
-
|
107
|
-
def foreign_key
|
108
|
-
@foreign_key ||= @options[:foreign_key] || target_collection_class.implicit_foreign_key_name
|
109
|
-
end
|
110
|
-
|
111
|
-
def graph_to(rows, db, schema=nil, relations=[])
|
112
|
-
fks = rows.map { |row| row[foreign_key] }.uniq
|
113
|
-
|
114
|
-
target_collection = target_collection_class.new db, schema
|
115
|
-
|
116
|
-
relation_rows = target_collection.where(source_collection_class.primary_key => fks)
|
117
|
-
.graph(*relations)
|
118
|
-
.each_with_object({}) do |row, hash|
|
119
|
-
hash[row.public_send(source_collection_class.primary_key)] = row
|
120
|
-
end
|
121
|
-
|
122
|
-
rows.each do |row|
|
123
|
-
row[name] = relation_rows[row[foreign_key]]
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
def apply_filter(dataset, schema=nil, primary_keys=[])
|
128
|
-
dataset.where(foreign_key => primary_keys)
|
129
|
-
end
|
130
|
-
|
131
|
-
end
|
132
|
-
|
133
|
-
|
134
|
-
class ManyToMany < Base
|
135
|
-
|
136
|
-
def source_foreign_key
|
137
|
-
@source_foreign_key ||= @options[:source_foreign_key] || source_collection_class.implicit_foreign_key_name
|
138
|
-
end
|
139
|
-
|
140
|
-
def target_foreign_key
|
141
|
-
@target_foreign_key ||= @options[:target_foreign_key] || target_collection_class.implicit_foreign_key_name
|
142
|
-
end
|
143
|
-
|
144
|
-
def relation_collection_name
|
145
|
-
@relation_collection_name ||= @options[:relation_collection_name] || [source_collection_class.collection_name, target_collection_class.collection_name].sort.join('_').to_sym
|
146
|
-
end
|
147
|
-
|
148
|
-
def qualified_relation_collection_name(schema=nil)
|
149
|
-
schema.nil? ? relation_collection_name : Sequel.qualify(schema, relation_collection_name)
|
150
|
-
end
|
151
|
-
|
152
|
-
def graph_to(rows, db, schema=nil, relations=[])
|
153
|
-
pks = rows.map { |row| row[source_collection_class.primary_key] }
|
154
|
-
|
155
|
-
target_collection = target_collection_class.new db, schema
|
156
|
-
|
157
|
-
relation_name = qualified_relation_collection_name schema
|
158
|
-
|
159
|
-
join_rows = target_collection.dataset
|
160
|
-
.join(relation_name, target_foreign_key => target_collection_class.primary_key)
|
161
|
-
.where(Sequel.qualify(relation_name, source_foreign_key) => pks)
|
162
|
-
.select_all(qualified_target_collection_name(schema))
|
163
|
-
.select_append(Sequel.qualify(relation_name, source_foreign_key).as(:source_foreign_key))
|
164
|
-
.all
|
165
|
-
|
166
|
-
Relations.graph_to join_rows, relations, target_collection_class, db, schema
|
167
|
-
|
168
|
-
relation_rows = join_rows.each_with_object(Hash.new { |h,k| h[k] = [] }) do |row, hash|
|
169
|
-
attributes = row.select { |attr,_| target_collection_class.model.attributes.include? attr }
|
170
|
-
hash[row[:source_foreign_key]] << target_collection_class.model.new(attributes)
|
171
|
-
end
|
172
|
-
|
173
|
-
rows.each do |row|
|
174
|
-
row[name] = relation_rows.fetch row[target_collection_class.primary_key], []
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
178
|
-
def apply_filter(dataset, schema=nil, primary_keys=[])
|
179
|
-
relation_name = qualified_relation_collection_name schema
|
180
|
-
|
181
|
-
dataset.join(relation_name, source_foreign_key => target_collection_class.primary_key)
|
182
|
-
.where(Sequel.qualify(relation_name, target_foreign_key) => primary_keys)
|
183
|
-
.select_all(qualified_source_collection_name(schema))
|
184
|
-
.distinct
|
185
|
-
end
|
186
|
-
|
187
|
-
end
|
188
|
-
|
189
|
-
end
|
190
|
-
end
|
191
|
-
end
|