rasti-db 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|