rasti-db 1.5.0 → 2.0.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/README.md +41 -16
- data/lib/rasti/db.rb +0 -1
- data/lib/rasti/db/collection.rb +57 -35
- data/lib/rasti/db/data_source.rb +18 -0
- data/lib/rasti/db/environment.rb +32 -0
- data/lib/rasti/db/query.rb +13 -9
- data/lib/rasti/db/relations/base.rb +22 -8
- data/lib/rasti/db/relations/graph.rb +10 -16
- data/lib/rasti/db/relations/many_to_many.rb +57 -23
- data/lib/rasti/db/relations/many_to_one.rb +9 -7
- data/lib/rasti/db/relations/one_to_many.rb +21 -13
- data/lib/rasti/db/version.rb +1 -1
- data/spec/collection_spec.rb +202 -50
- data/spec/minitest_helper.rb +47 -12
- data/spec/model_spec.rb +3 -1
- data/spec/query_spec.rb +104 -33
- data/spec/relations_spec.rb +27 -7
- metadata +4 -3
- data/lib/rasti/db/helpers.rb +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 210581882359ef05a1b09c69b172f259b0b5010b61a254442e9844158a4f9456
|
4
|
+
data.tar.gz: 1dd3f0fcc8399e0145abc50166495a87826bb1b32e52de4acb45ea114f7650d5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2c209c735eb50327d4bb7b54754530cd155dec884e32661f046de73dfb53fb036877e1e25f1ade565ef51a0517254096d24934f3d6e47393febc99318ea71118
|
7
|
+
data.tar.gz: a5a8326418216e1558ffea4b93e52a2369420fed2c8d9633dda5ffd8ee34b457e06aaaea2018001ba8725e51c1ef44386a78e2abaa51819628eba93cec38da99
|
data/README.md
CHANGED
@@ -28,49 +28,62 @@ Or install it yourself as:
|
|
28
28
|
### Database connection
|
29
29
|
|
30
30
|
```ruby
|
31
|
-
|
31
|
+
DB_1 = Sequel.connect ...
|
32
|
+
DB_2 = Sequel.connect ...
|
32
33
|
```
|
33
34
|
|
34
35
|
### Database schema
|
35
36
|
|
36
37
|
```ruby
|
37
|
-
|
38
|
+
DB_1.create_table :users do
|
38
39
|
primary_key :id
|
39
40
|
String :name, null: false, unique: true
|
40
41
|
end
|
41
42
|
|
42
|
-
|
43
|
+
DB_1.create_table :posts do
|
43
44
|
primary_key :id
|
44
45
|
String :title, null: false, unique: true
|
45
46
|
String :body, null: false
|
47
|
+
Integer :language_id, null: false, index: true
|
46
48
|
foreign_key :user_id, :users, null: false, index: true
|
47
49
|
end
|
48
50
|
|
49
|
-
|
51
|
+
DB_1.create_table :comments do
|
50
52
|
primary_key :id
|
51
53
|
String :text, null: false
|
52
54
|
foreign_key :user_id, :users, null: false, index: true
|
53
55
|
foreign_key :post_id, :posts, null: false, index: true
|
54
56
|
end
|
55
57
|
|
56
|
-
|
58
|
+
DB_1.create_table :categories do
|
57
59
|
primary_key :id
|
58
60
|
String :name, null: false, unique: true
|
59
61
|
end
|
60
62
|
|
61
|
-
|
63
|
+
DB_1.create_table :categories_posts do
|
62
64
|
foreign_key :category_id, :categories, null: false, index: true
|
63
65
|
foreign_key :post_id, :posts, null: false, index: true
|
64
66
|
primary_key [:category_id, :post_id]
|
65
67
|
end
|
66
68
|
|
67
|
-
|
69
|
+
DB_1.create_table :people do
|
68
70
|
String :document_number, null: false, primary_key: true
|
69
71
|
String :first_name, null: false
|
70
72
|
String :last_name, null: false
|
71
73
|
Date :birth_date, null: false
|
72
74
|
foreign_key :user_id, :users, null: false, unique: true
|
73
75
|
end
|
76
|
+
|
77
|
+
DB_1.create_table :languages_people do
|
78
|
+
Integer :language_id, null: false, index: true
|
79
|
+
foreign_key :document_number, :people, type: String, null: false, index: true
|
80
|
+
primary_key [:language_id, :document_number]
|
81
|
+
end
|
82
|
+
|
83
|
+
DB_2.create_table :languages do
|
84
|
+
primary_key :id
|
85
|
+
String :name, null: false, unique: true
|
86
|
+
end
|
74
87
|
```
|
75
88
|
|
76
89
|
### Models
|
@@ -81,6 +94,7 @@ Post = Rasti::DB::Model[:id, :title, :body, :user_id, :user, :comments, :cat
|
|
81
94
|
Comment = Rasti::DB::Model[:id, :text, :user_id, :user, :post_id, :post]
|
82
95
|
Category = Rasti::DB::Model[:id, :name, :posts]
|
83
96
|
Person = Rasti::DB::Model[:document_number, :first_name, :last_name, :birth_date, :user_id, :user]
|
97
|
+
Language = Rasti::DB::Model[:id, :name, :people]
|
84
98
|
```
|
85
99
|
|
86
100
|
### Collections
|
@@ -105,8 +119,8 @@ class Posts < Rasti::DB::Collection
|
|
105
119
|
|
106
120
|
query :commented_by do |user_id|
|
107
121
|
chainable do
|
108
|
-
dataset.join(
|
109
|
-
.where(
|
122
|
+
dataset.join(qualify(:comments), post_id: :id)
|
123
|
+
.where(Sequel[:comments][:user_id] => user_id)
|
110
124
|
.select_all(:posts)
|
111
125
|
.distinct
|
112
126
|
end
|
@@ -129,19 +143,30 @@ class People < Rasti::DB::Collection
|
|
129
143
|
set_model Person
|
130
144
|
|
131
145
|
many_to_one :user
|
146
|
+
many_to_many :languages
|
132
147
|
end
|
133
148
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
people
|
149
|
+
class Languages < Rasti::DB::Collection
|
150
|
+
set_data_source_name :other
|
151
|
+
|
152
|
+
one_to_many :posts
|
153
|
+
many_to_many :people, collection: People, relation_data_source_name: :default
|
154
|
+
end
|
155
|
+
|
156
|
+
environment = Rasti::DB::Environment.new default: Rasti::DB::DataSource.new(DB_1),
|
157
|
+
other: Rasti::DB::DataSource.new(DB_2, 'custom_schema')
|
158
|
+
|
159
|
+
users = Users.new environment
|
160
|
+
posts = Posts.new environment
|
161
|
+
comments = Comments.new environment
|
162
|
+
categories = Categories.new environment
|
163
|
+
people = People.new environment
|
139
164
|
```
|
140
165
|
|
141
166
|
### Persistence
|
142
167
|
|
143
168
|
```ruby
|
144
|
-
|
169
|
+
DB_1.transaction do
|
145
170
|
id = users.insert name: 'User 1'
|
146
171
|
users.update id, name: 'User updated'
|
147
172
|
users.delete id
|
@@ -174,7 +199,7 @@ posts.created_by(1) # => [Post, ...]
|
|
174
199
|
posts.created_by(1).entitled('...').commented_by(2) # => [Post, ...]
|
175
200
|
posts.with_categories([1,2]) # => [Post, ...]
|
176
201
|
|
177
|
-
posts.graph(:user, :categories, 'comments.user') # => [Post(User, [Categories, ...], [Comments(User)]), ...]
|
202
|
+
posts.graph(:user, :language, :categories, 'comments.user') # => [Post(User, Language, [Categories, ...], [Comments(User)]), ...]
|
178
203
|
|
179
204
|
posts.join(:user).where(name: 'User 4') # => [Post, ...]
|
180
205
|
|
data/lib/rasti/db.rb
CHANGED
data/lib/rasti/db/collection.rb
CHANGED
@@ -4,8 +4,6 @@ module Rasti
|
|
4
4
|
|
5
5
|
QUERY_METHODS = Query.public_instance_methods - Object.public_instance_methods
|
6
6
|
|
7
|
-
include Helpers::WithSchema
|
8
|
-
|
9
7
|
class << self
|
10
8
|
|
11
9
|
extend Sequel::Inflections
|
@@ -37,12 +35,16 @@ module Rasti
|
|
37
35
|
end
|
38
36
|
end
|
39
37
|
|
38
|
+
def data_source_name
|
39
|
+
@data_source_name ||= superclass.respond_to?(:data_source_name) ? superclass.data_source_name : :default
|
40
|
+
end
|
41
|
+
|
40
42
|
def relations
|
41
|
-
@relations ||=
|
43
|
+
@relations ||= Hash::Indifferent.new
|
42
44
|
end
|
43
45
|
|
44
46
|
def queries
|
45
|
-
@queries ||=
|
47
|
+
@queries ||= Hash::Indifferent.new
|
46
48
|
end
|
47
49
|
|
48
50
|
private
|
@@ -63,11 +65,17 @@ module Rasti
|
|
63
65
|
@model = model
|
64
66
|
end
|
65
67
|
|
68
|
+
def set_data_source_name(name)
|
69
|
+
@data_source_name = name.to_sym
|
70
|
+
end
|
71
|
+
|
66
72
|
[Relations::OneToMany, Relations::ManyToOne, Relations::ManyToMany, Relations::OneToOne].each do |relation_class|
|
67
73
|
define_method underscore(demodulize(relation_class.name)) do |name, options={}|
|
68
74
|
relations[name] = relation_class.new name, self, options
|
69
75
|
|
70
|
-
|
76
|
+
query_name = relations[name].to_many? ? name : pluralize(name)
|
77
|
+
|
78
|
+
query "with_#{query_name}" do |primary_keys|
|
71
79
|
with_related name, primary_keys
|
72
80
|
end
|
73
81
|
end
|
@@ -85,11 +93,8 @@ module Rasti
|
|
85
93
|
|
86
94
|
end
|
87
95
|
|
88
|
-
|
89
|
-
|
90
|
-
def initialize(db, schema=nil)
|
91
|
-
@db = db
|
92
|
-
@schema = schema ? schema.to_sym : nil
|
96
|
+
def initialize(environment)
|
97
|
+
@environment = environment
|
93
98
|
end
|
94
99
|
|
95
100
|
QUERY_METHODS.each do |method|
|
@@ -98,12 +103,8 @@ module Rasti
|
|
98
103
|
end
|
99
104
|
end
|
100
105
|
|
101
|
-
def dataset
|
102
|
-
db[qualified_collection_name]
|
103
|
-
end
|
104
|
-
|
105
106
|
def insert(attributes)
|
106
|
-
db.transaction do
|
107
|
+
data_source.db.transaction do
|
107
108
|
db_attributes = transform_attributes_to_db attributes
|
108
109
|
collection_attributes, relations_primary_keys = split_related_attributes db_attributes
|
109
110
|
primary_key = dataset.insert collection_attributes
|
@@ -126,7 +127,7 @@ module Rasti
|
|
126
127
|
end
|
127
128
|
|
128
129
|
def update(primary_key, attributes)
|
129
|
-
db.transaction do
|
130
|
+
data_source.db.transaction do
|
130
131
|
db_attributes = transform_attributes_to_db attributes
|
131
132
|
collection_attributes, relations_primary_keys = split_related_attributes db_attributes
|
132
133
|
dataset.where(self.class.primary_key => primary_key).update(collection_attributes) unless collection_attributes.empty?
|
@@ -152,7 +153,7 @@ module Rasti
|
|
152
153
|
end
|
153
154
|
|
154
155
|
def delete_relations(primary_key, relations)
|
155
|
-
db.transaction do
|
156
|
+
data_source.db.transaction do
|
156
157
|
relations.each do |relation_name, relation_primary_keys|
|
157
158
|
relation = self.class.relations[relation_name]
|
158
159
|
delete_relation_table relation, primary_key, relation_primary_keys
|
@@ -162,7 +163,7 @@ module Rasti
|
|
162
163
|
end
|
163
164
|
|
164
165
|
def delete_cascade(*primary_keys)
|
165
|
-
db.transaction do
|
166
|
+
data_source.db.transaction do
|
166
167
|
delete_cascade_relations primary_keys
|
167
168
|
bulk_delete { |q| q.where self.class.primary_key => primary_keys }
|
168
169
|
end
|
@@ -187,33 +188,48 @@ module Rasti
|
|
187
188
|
|
188
189
|
private
|
189
190
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
191
|
+
attr_reader :environment
|
192
|
+
|
193
|
+
def data_source
|
194
|
+
@data_source ||= environment.data_source_of self.class
|
195
|
+
end
|
196
|
+
|
197
|
+
def dataset
|
198
|
+
data_source.db[qualified_collection_name]
|
195
199
|
end
|
196
200
|
|
197
201
|
def qualified_collection_name
|
198
|
-
|
202
|
+
data_source.qualify self.class.collection_name
|
199
203
|
end
|
200
204
|
|
205
|
+
def qualify(collection_name, data_source_name: nil)
|
206
|
+
data_source_name ||= self.class.data_source_name
|
207
|
+
environment.qualify data_source_name, collection_name
|
208
|
+
end
|
209
|
+
|
201
210
|
def default_query
|
202
211
|
Query.new collection_class: self.class,
|
203
212
|
dataset: dataset.select_all(self.class.collection_name),
|
204
|
-
|
213
|
+
environment: environment
|
205
214
|
end
|
206
215
|
|
207
216
|
def build_query(filter=nil, &block)
|
208
217
|
raise ArgumentError, 'must specify filter hash or block' if filter.nil? && block.nil?
|
209
218
|
|
210
219
|
if filter
|
211
|
-
default_query.where
|
220
|
+
default_query.where(filter)
|
212
221
|
else
|
213
222
|
block.arity == 0 ? default_query.instance_eval(&block) : block.call(default_query)
|
214
223
|
end
|
215
224
|
end
|
216
225
|
|
226
|
+
def transform_attributes_to_db(attributes)
|
227
|
+
attributes.each_with_object({}) do |(attribute_name, value), result|
|
228
|
+
transformed_value = Rasti::DB.to_db data_source.db, qualified_collection_name, attribute_name, value
|
229
|
+
result[attribute_name] = transformed_value
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
217
233
|
def split_related_attributes(attributes)
|
218
234
|
relation_names = self.class.relations.values.select(&:many_to_many?).map(&:name)
|
219
235
|
|
@@ -239,18 +255,22 @@ module Rasti
|
|
239
255
|
end
|
240
256
|
|
241
257
|
relations.select { |r| r.one_to_many? || r.one_to_one? }.each do |relation|
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
258
|
+
relation_data_source = environment.data_source_of relation.target_collection_class
|
259
|
+
relation_collection_name = relation_data_source.qualify relation.target_collection_class.collection_name
|
260
|
+
|
261
|
+
relations_ids = relation_data_source.db[relation_collection_name]
|
262
|
+
.where(relation.foreign_key => primary_keys)
|
263
|
+
.select(relation.target_collection_class.primary_key)
|
264
|
+
.map(relation.target_collection_class.primary_key)
|
246
265
|
|
247
|
-
target_collection = relation.target_collection_class.new
|
266
|
+
target_collection = relation.target_collection_class.new environment
|
248
267
|
target_collection.delete_cascade(*relations_ids) unless relations_ids.empty?
|
249
268
|
end
|
250
269
|
end
|
251
270
|
|
252
271
|
def insert_relation_table(relation, primary_key, relation_primary_keys)
|
253
|
-
|
272
|
+
relation_data_source = environment.data_source relation.relation_data_source_name
|
273
|
+
relation_collection_name = relation_data_source.qualify relation.relation_collection_name
|
254
274
|
|
255
275
|
values = relation_primary_keys.map do |relation_pk|
|
256
276
|
{
|
@@ -259,12 +279,14 @@ module Rasti
|
|
259
279
|
}
|
260
280
|
end
|
261
281
|
|
262
|
-
db[relation_collection_name].multi_insert values
|
282
|
+
relation_data_source.db[relation_collection_name].multi_insert values
|
263
283
|
end
|
264
284
|
|
265
285
|
def delete_relation_table(relation, primary_keys, relation_primary_keys=nil)
|
266
|
-
|
267
|
-
|
286
|
+
relation_data_source = environment.data_source relation.relation_data_source_name
|
287
|
+
relation_collection_name = relation_data_source.qualify relation.relation_collection_name
|
288
|
+
|
289
|
+
ds = relation_data_source.db[relation_collection_name].where(relation.source_foreign_key => primary_keys)
|
268
290
|
ds = ds.where(relation.target_foreign_key => relation_primary_keys) if relation_primary_keys
|
269
291
|
ds.delete
|
270
292
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Rasti
|
2
|
+
module DB
|
3
|
+
class DataSource
|
4
|
+
|
5
|
+
attr_reader :db, :schema
|
6
|
+
|
7
|
+
def initialize(db, schema=nil)
|
8
|
+
@db = db
|
9
|
+
@schema = schema ? schema.to_sym : nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def qualify(collection_name)
|
13
|
+
schema ? Sequel[schema][collection_name] : Sequel[collection_name]
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Rasti
|
2
|
+
module DB
|
3
|
+
class Environment
|
4
|
+
|
5
|
+
def initialize(data_sources)
|
6
|
+
@data_sources = data_sources
|
7
|
+
end
|
8
|
+
|
9
|
+
def data_source(name)
|
10
|
+
raise "Undefined data source #{name}" unless data_sources.key? name
|
11
|
+
data_sources[name]
|
12
|
+
end
|
13
|
+
|
14
|
+
def data_source_of(collection_class)
|
15
|
+
data_source collection_class.data_source_name
|
16
|
+
end
|
17
|
+
|
18
|
+
def qualify(data_source_name, collection_name)
|
19
|
+
data_source(data_source_name).qualify collection_name
|
20
|
+
end
|
21
|
+
|
22
|
+
def qualify_collection(collection_class)
|
23
|
+
data_source_of(collection_class).qualify collection_class.collection_name
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :data_sources
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/rasti/db/query.rb
CHANGED
@@ -5,13 +5,12 @@ module Rasti
|
|
5
5
|
DATASET_CHAINED_METHODS = [:where, :exclude, :or, :order, :reverse_order, :limit, :offset].freeze
|
6
6
|
|
7
7
|
include Enumerable
|
8
|
-
include Helpers::WithSchema
|
9
8
|
|
10
|
-
def initialize(collection_class:, dataset:, relations_graph:nil
|
9
|
+
def initialize(environment:, collection_class:, dataset:, relations_graph:nil)
|
10
|
+
@environment = environment
|
11
11
|
@collection_class = collection_class
|
12
12
|
@dataset = dataset
|
13
|
-
@relations_graph = relations_graph || Relations::Graph.new(
|
14
|
-
@schema = schema
|
13
|
+
@relations_graph = relations_graph || Relations::Graph.new(environment, collection_class)
|
15
14
|
end
|
16
15
|
|
17
16
|
DATASET_CHAINED_METHODS.each do |method|
|
@@ -74,7 +73,7 @@ module Rasti
|
|
74
73
|
end
|
75
74
|
|
76
75
|
def join(*relations)
|
77
|
-
graph = Relations::Graph.new
|
76
|
+
graph = Relations::Graph.new environment, collection_class, relations
|
78
77
|
|
79
78
|
ds = graph.add_joins(dataset)
|
80
79
|
.distinct
|
@@ -127,14 +126,14 @@ module Rasti
|
|
127
126
|
|
128
127
|
private
|
129
128
|
|
130
|
-
attr_reader :collection_class, :dataset, :relations_graph
|
129
|
+
attr_reader :environment, :collection_class, :dataset, :relations_graph
|
131
130
|
|
132
131
|
def build_query(**args)
|
133
132
|
current_args = {
|
133
|
+
environment: environment,
|
134
134
|
collection_class: collection_class,
|
135
135
|
dataset: dataset,
|
136
|
-
relations_graph: relations_graph
|
137
|
-
schema: schema
|
136
|
+
relations_graph: relations_graph
|
138
137
|
}
|
139
138
|
|
140
139
|
Query.new(**current_args.merge(args))
|
@@ -145,7 +144,7 @@ module Rasti
|
|
145
144
|
end
|
146
145
|
|
147
146
|
def with_related(relation_name, primary_keys)
|
148
|
-
ds = collection_class.relations[relation_name].apply_filter
|
147
|
+
ds = collection_class.relations[relation_name].apply_filter environment, dataset, primary_keys
|
149
148
|
build_query dataset: ds
|
150
149
|
end
|
151
150
|
|
@@ -155,6 +154,11 @@ module Rasti
|
|
155
154
|
data
|
156
155
|
end
|
157
156
|
|
157
|
+
def qualify(collection_name, data_source_name: nil)
|
158
|
+
data_source_name ||= collection_class.data_source_name
|
159
|
+
environment.qualify data_source_name, collection_name
|
160
|
+
end
|
161
|
+
|
158
162
|
def nql_parser
|
159
163
|
NQL::SyntaxParser.new
|
160
164
|
end
|