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 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