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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 898bf83981559ff4a054d73ad018a7f07e65d1a3
4
- data.tar.gz: dda154478784631a4683281364c43d249db3356c
3
+ metadata.gz: 935cee1239288342a27bdcde6660f205b9fa1685
4
+ data.tar.gz: 2bc7b5c0cf6559c7ae5716b4814fea412e0481f3
5
5
  SHA512:
6
- metadata.gz: cf9dacf0e63b028ad3ac4a32fcaff83dbb3b355ee02f60782d85203c2d931015e5ec1aa7cbcbc35b54ab943a3fb78f62970ebde2680d603d5998547ba3c347a4
7
- data.tar.gz: ad67c48927b98ae619bd7c80fd044f518d26d906ff238972ab07871d987f2ccd616230b34c6816c0e5f49a928ba6bc77436c1834a71a21675cb8f546299d72a4
6
+ metadata.gz: b564fb1e883999d26e06b73c81d5ef68a68c2cdebc870403376c7c3b49e4729c020b552a010ffa47c13ca24743dfe9327b12963000a8892c55b3a4d0477f9170
7
+ data.tar.gz: 1f68b62bc955b2d7a4955a217877d9013c2ab7546740a0b90c452952e5cc241fa2573a7c1766da724e22013dfa176894c4ec81aa2da20bf322f0b84598f2ff63
@@ -12,13 +12,17 @@ module Rasti
12
12
  include Sequel::Inflections
13
13
 
14
14
  def collection_name
15
- @collection_name ||= implicit_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(&:one_to_many?).each do |relation|
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)
@@ -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
@@ -0,0 +1,15 @@
1
+ module Rasti
2
+ module DB
3
+ module Relations
4
+ class OneToOne < OneToMany
5
+
6
+ private
7
+
8
+ def build_graph_result(rows)
9
+ rows.first
10
+ end
11
+
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,5 +1,5 @@
1
1
  module Rasti
2
2
  module DB
3
- VERSION = '0.3.0'
3
+ VERSION = '0.4.0'
4
4
  end
5
5
  end
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'
@@ -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.implicit_foreign_key_name.must_equal :user_id
11
+ Users.foreign_key.must_equal :user_id
12
12
  end
13
13
 
14
14
  it 'Explicit' do
15
- model_class = Rasti::DB::Model[:code, :name]
16
-
17
- collection_class = Class.new(Rasti::DB::Collection) do
18
- set_collection_name :countries
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) { |i| db[:categories].insert name: "Category #{i}" }
316
- 1.upto(2) { |i| db[:users].insert name: "User #{i}" }
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
@@ -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
- /integer/ => ->(value, match) { value.to_i }
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 :db do
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
- db.create_table :posts do
86
- primary_key :id
87
- String :title, null: false, unique: true
88
- String :body, null: false
89
- foreign_key :user_id, :users, null: false, index: true
90
- end
91
-
92
- db.create_table :comments do
93
- primary_key :id
94
- String :text, null: false
95
- foreign_key :user_id, :users, null: false, index: true
96
- foreign_key :post_id, :posts, null: false, index: true
97
- end
98
-
99
- db.create_table :categories do
100
- primary_key :id
101
- String :name, null: false, unique: true
102
- end
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
@@ -42,7 +42,7 @@ describe 'Model' do
42
42
  describe 'To String' do
43
43
 
44
44
  it 'Class' do
45
- User.to_s.must_equal 'User[id, name, posts, comments]'
45
+ User.to_s.must_equal 'User[id, name, posts, comments, person]'
46
46
  end
47
47
 
48
48
  it 'Instance' do
@@ -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.3.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-06 00:00:00.000000000 Z
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
@@ -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