rasti-db 1.3.0 → 2.0.1

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
  SHA256:
3
- metadata.gz: aab2813290f3080c9a50cfed449183cc5da14e04915f6593fa9ab7fa5e5f24a7
4
- data.tar.gz: 3d46e38905af30b1a50a8b186b5ebf3f3253db29b58eed2526859c12862cc8af
3
+ metadata.gz: 8b41f6198798c9bee6da1f94742378639bb3b900f3bda6471f791b42a71a24c7
4
+ data.tar.gz: f470e1f1ef5673546e54e6c144e16ebba577b0f848b8fd59c8822489808a7cbf
5
5
  SHA512:
6
- metadata.gz: 5ae412446ff6d62b5cd1043e1958aaafe7dbc391ba98173adcc6ab5b31ce0d1daa8dc7086b62ae27ca50b7c03e6c2a1f6ac905a06a30a932a5e367e30698d8cd
7
- data.tar.gz: 68e99deac8ffb297263a56a1e3f4deff341518f94aba48bbbb8a760983e86d9268a9a7d96a36626ca7fcf4173c512f23c3585bcbae047dc964de493b2a7256e4
6
+ metadata.gz: f5e836d580a42656efe1d78ee2e193449edd1adf45dbccd0a4b4c9c4d6f263a0d92e844a142abb908a683a8dcad4865d5926aec10b5a0260c0240425dcd063dd
7
+ data.tar.gz: 71148abafe56a6fe60613f2ebfa38138df2aaa656bdbe7116a834d41f3920fb49b97bfffac3235a520a6ffbb293184310d2d7fcefa552178d1cb767b94ce8e4d
@@ -1,15 +1,14 @@
1
1
  language: ruby
2
2
 
3
3
  rvm:
4
- - 2.0
5
4
  - 2.1
6
5
  - 2.2
7
- - 2.3.0
8
- - 2.4.0
9
- - 2.5.0
10
- - 2.6.0
11
- - jruby-9.1.7.0
12
- - jruby-9.1.16.0
6
+ - 2.3
7
+ - 2.4
8
+ - 2.5
9
+ - 2.6
10
+ - 2.7
11
+ - jruby-9.2.9.0
13
12
  - ruby-head
14
13
  - jruby-head
15
14
 
@@ -20,8 +19,4 @@ matrix:
20
19
  - rvm: jruby-head
21
20
 
22
21
  jdk:
23
- - openjdk8
24
-
25
- before_install:
26
- - rvm all-gemsets do gem uninstall bundler -ax || true
27
- - gem install bundler -v "< 2"
22
+ - openjdk8
data/README.md CHANGED
@@ -4,7 +4,6 @@
4
4
  [![Build Status](https://travis-ci.org/gabynaiman/rasti-db.svg?branch=master)](https://travis-ci.org/gabynaiman/rasti-db)
5
5
  [![Coverage Status](https://coveralls.io/repos/github/gabynaiman/rasti-db/badge.svg?branch=master)](https://coveralls.io/github/gabynaiman/rasti-db?branch=master)
6
6
  [![Code Climate](https://codeclimate.com/github/gabynaiman/rasti-db.svg)](https://codeclimate.com/github/gabynaiman/rasti-db)
7
- [![Dependency Status](https://gemnasium.com/gabynaiman/rasti-db.svg)](https://gemnasium.com/gabynaiman/rasti-db)
8
7
 
9
8
  Database collections and relations
10
9
 
@@ -29,49 +28,62 @@ Or install it yourself as:
29
28
  ### Database connection
30
29
 
31
30
  ```ruby
32
- DB = Sequel.connect ...
31
+ DB_1 = Sequel.connect ...
32
+ DB_2 = Sequel.connect ...
33
33
  ```
34
34
 
35
35
  ### Database schema
36
36
 
37
37
  ```ruby
38
- DB.create_table :users do
38
+ DB_1.create_table :users do
39
39
  primary_key :id
40
40
  String :name, null: false, unique: true
41
41
  end
42
42
 
43
- DB.create_table :posts do
43
+ DB_1.create_table :posts do
44
44
  primary_key :id
45
45
  String :title, null: false, unique: true
46
46
  String :body, null: false
47
+ Integer :language_id, null: false, index: true
47
48
  foreign_key :user_id, :users, null: false, index: true
48
49
  end
49
50
 
50
- DB.create_table :comments do
51
+ DB_1.create_table :comments do
51
52
  primary_key :id
52
53
  String :text, null: false
53
54
  foreign_key :user_id, :users, null: false, index: true
54
55
  foreign_key :post_id, :posts, null: false, index: true
55
56
  end
56
57
 
57
- DB.create_table :categories do
58
+ DB_1.create_table :categories do
58
59
  primary_key :id
59
60
  String :name, null: false, unique: true
60
61
  end
61
62
 
62
- DB.create_table :categories_posts do
63
+ DB_1.create_table :categories_posts do
63
64
  foreign_key :category_id, :categories, null: false, index: true
64
65
  foreign_key :post_id, :posts, null: false, index: true
65
66
  primary_key [:category_id, :post_id]
66
67
  end
67
68
 
68
- DB.create_table :people do
69
+ DB_1.create_table :people do
69
70
  String :document_number, null: false, primary_key: true
70
71
  String :first_name, null: false
71
72
  String :last_name, null: false
72
73
  Date :birth_date, null: false
73
74
  foreign_key :user_id, :users, null: false, unique: true
74
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
75
87
  ```
76
88
 
77
89
  ### Models
@@ -82,6 +94,7 @@ Post = Rasti::DB::Model[:id, :title, :body, :user_id, :user, :comments, :cat
82
94
  Comment = Rasti::DB::Model[:id, :text, :user_id, :user, :post_id, :post]
83
95
  Category = Rasti::DB::Model[:id, :name, :posts]
84
96
  Person = Rasti::DB::Model[:document_number, :first_name, :last_name, :birth_date, :user_id, :user]
97
+ Language = Rasti::DB::Model[:id, :name, :people]
85
98
  ```
86
99
 
87
100
  ### Collections
@@ -106,9 +119,9 @@ class Posts < Rasti::DB::Collection
106
119
 
107
120
  query :commented_by do |user_id|
108
121
  chainable do
109
- dataset.join(with_schema(:comments), post_id: :id)
110
- .where(with_schema(:comments, :user_id) => user_id)
111
- .select_all(with_schema(:posts))
122
+ dataset.join(qualify(:comments), post_id: :id)
123
+ .where(Sequel[:comments][:user_id] => user_id)
124
+ .select_all(:posts)
112
125
  .distinct
113
126
  end
114
127
  end
@@ -130,19 +143,30 @@ class People < Rasti::DB::Collection
130
143
  set_model Person
131
144
 
132
145
  many_to_one :user
146
+ many_to_many :languages
147
+ end
148
+
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
133
154
  end
134
155
 
135
- users = Users.new DB
136
- posts = Posts.new DB
137
- comments = Comments.new DB
138
- categories = Categories.new DB
139
- people = People.new DB
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
140
164
  ```
141
165
 
142
166
  ### Persistence
143
167
 
144
168
  ```ruby
145
- DB.transaction do
169
+ DB_1.transaction do
146
170
  id = users.insert name: 'User 1'
147
171
  users.update id, name: 'User updated'
148
172
  users.delete id
@@ -162,17 +186,49 @@ end
162
186
  posts.all # => [Post, ...]
163
187
  posts.first # => Post
164
188
  posts.count # => 1
189
+
165
190
  posts.where(id: [1,2]) # => [Post, ...]
166
191
  posts.where{id > 1}.limit(10).offset(20) } # => [Post, ...]
167
- posts.graph(:user, :categories, 'comments.user') # => [Post(User, [Categories, ...], [Comments(User)]), ...]
168
- posts.created_by(1) # => [Post, ...]
169
- posts.created_by(1).entitled('...').commented_by(2) # => [Post, ...]
170
- posts.with_categories([1,2]) # => [Post, ...]
192
+
171
193
  posts.where(id: [1,2]).raw # => [{id:1, ...}, {id:2, ...}]
172
194
  posts.where(id: [1,2]).primary_keys # => [1,2]
173
195
  posts.where(id: [1,2]).pluck(:id) # => [1,2]
174
196
  posts.where(id: [1,2]).pluck(:id, :title) # => [[1, ...], [2, ...]]
197
+
198
+ posts.created_by(1) # => [Post, ...]
199
+ posts.created_by(1).entitled('...').commented_by(2) # => [Post, ...]
200
+ posts.with_categories([1,2]) # => [Post, ...]
201
+
202
+ posts.graph(:user, :language, :categories, 'comments.user') # => [Post(User, Language, [Categories, ...], [Comments(User)]), ...]
203
+
175
204
  posts.join(:user).where(name: 'User 4') # => [Post, ...]
205
+
206
+ posts.select_attributes(:id, :title) # => [Post, ...]
207
+ posts.exclude_attributes(:id, :title) # => [Post, ...]
208
+ posts.all_attributes # => [Post, ...]
209
+
210
+ posts.graph('user.person').select_graph_attributes(user: [:id], 'user.person': [:last_name, :user_id]) # => [Post, ...]
211
+ posts.graph('user.person').exclude_graph_attributes(user: [:name], 'user.person': [:first_name, :last_name]) # => [Post, ...]
212
+ posts.graph('user.person').all_graph_attributes('user.person') # => [Post, ...]
213
+ ```
214
+ ### Natural Query Language
215
+
216
+ ```ruby
217
+ posts.nql('id = 1') # => Equal
218
+ posts.nql('id != 1') # => Not equal
219
+ posts.nql('title: My post') # => Include
220
+ posts.nql('title !: My post') # => Not include
221
+ posts.nql('title ~ My post') # => Insensitive like
222
+ posts.nql('id > 1') # => Greater
223
+ posts.nql('id >= 1') # => Greater or equal
224
+ posts.nql('id < 10') # => Less
225
+ posts.nql('id <= 10') # => Less or equal
226
+
227
+ posts.nql('id = 1 | id = 2') # => Or
228
+ posts.nql('id > 1 & title: "My post"') # => And
229
+ posts.nql('(id > 3 & id < 10) | title: "My post"') # => Precedence
230
+
231
+ posts.nql('comments.user.person.document_number = 7') # => Nested
176
232
  ```
177
233
 
178
234
  ## Development
@@ -3,7 +3,9 @@ require 'consty'
3
3
  require 'time'
4
4
  require 'timing'
5
5
  require 'treetop'
6
+ require 'hierarchical_graph'
6
7
  require 'class_config'
8
+ require 'hash_ext'
7
9
  require 'multi_require'
8
10
 
9
11
  module Rasti
@@ -12,7 +14,6 @@ module Rasti
12
14
  extend MultiRequire
13
15
  extend ClassConfig
14
16
 
15
- require_relative 'db/helpers'
16
17
  require_relative 'db/query'
17
18
  require_relative_pattern 'db/relations/*'
18
19
  require_relative_pattern 'db/type_converters/postgres_types/*'
@@ -2,10 +2,7 @@ module Rasti
2
2
  module DB
3
3
  class Collection
4
4
 
5
- QUERY_METHODS = (Query::DATASET_CHAINED_METHODS + [:graph, :join, :count, :all, :each, :first, :pluck, :primary_keys, :any?, :empty?, :raw, :nql]).freeze
6
-
7
- include Enumerable
8
- include Helpers::WithSchema
5
+ QUERY_METHODS = Query.public_instance_methods - Object.public_instance_methods
9
6
 
10
7
  class << self
11
8
 
@@ -16,8 +13,8 @@ module Rasti
16
13
  @collection_name ||= underscore(demodulize(name)).to_sym
17
14
  end
18
15
 
19
- def collection_fields
20
- @collection_fields ||= model.attributes - relations.keys
16
+ def collection_attributes
17
+ @collection_attributes ||= model.attributes - relations.keys
21
18
  end
22
19
 
23
20
  def primary_key
@@ -38,12 +35,16 @@ module Rasti
38
35
  end
39
36
  end
40
37
 
38
+ def data_source_name
39
+ @data_source_name ||= superclass.respond_to?(:data_source_name) ? superclass.data_source_name : :default
40
+ end
41
+
41
42
  def relations
42
- @relations ||= {}
43
+ @relations ||= Hash::Indifferent.new
43
44
  end
44
45
 
45
46
  def queries
46
- @queries ||= {}
47
+ @queries ||= Hash::Indifferent.new
47
48
  end
48
49
 
49
50
  private
@@ -64,11 +65,17 @@ module Rasti
64
65
  @model = model
65
66
  end
66
67
 
68
+ def set_data_source_name(name)
69
+ @data_source_name = name.to_sym
70
+ end
71
+
67
72
  [Relations::OneToMany, Relations::ManyToOne, Relations::ManyToMany, Relations::OneToOne].each do |relation_class|
68
73
  define_method underscore(demodulize(relation_class.name)) do |name, options={}|
69
74
  relations[name] = relation_class.new name, self, options
70
75
 
71
- query "with_#{pluralize(name)}".to_sym do |primary_keys|
76
+ query_name = relations[name].to_many? ? name : pluralize(name)
77
+
78
+ query "with_#{query_name}" do |primary_keys|
72
79
  with_related name, primary_keys
73
80
  end
74
81
  end
@@ -80,25 +87,24 @@ module Rasti
80
87
  queries[name] = lambda || block
81
88
 
82
89
  define_method name do |*args|
83
- query.instance_exec(*args, &self.class.queries[name])
90
+ default_query.instance_exec(*args, &self.class.queries.fetch(name))
84
91
  end
85
92
  end
86
93
 
87
94
  end
88
95
 
89
- attr_reader :db, :schema
90
-
91
- def initialize(db, schema=nil)
92
- @db = db
93
- @schema = schema ? schema.to_sym : nil
96
+ def initialize(environment)
97
+ @environment = environment
94
98
  end
95
99
 
96
- def dataset
97
- db[qualified_collection_name]
100
+ QUERY_METHODS.each do |method|
101
+ define_method method do |*args, &block|
102
+ default_query.public_send method, *args, &block
103
+ end
98
104
  end
99
105
 
100
106
  def insert(attributes)
101
- db.transaction do
107
+ data_source.db.transaction do
102
108
  db_attributes = transform_attributes_to_db attributes
103
109
  collection_attributes, relations_primary_keys = split_related_attributes db_attributes
104
110
  primary_key = dataset.insert collection_attributes
@@ -121,7 +127,7 @@ module Rasti
121
127
  end
122
128
 
123
129
  def update(primary_key, attributes)
124
- db.transaction do
130
+ data_source.db.transaction do
125
131
  db_attributes = transform_attributes_to_db attributes
126
132
  collection_attributes, relations_primary_keys = split_related_attributes db_attributes
127
133
  dataset.where(self.class.primary_key => primary_key).update(collection_attributes) unless collection_attributes.empty?
@@ -147,7 +153,7 @@ module Rasti
147
153
  end
148
154
 
149
155
  def delete_relations(primary_key, relations)
150
- db.transaction do
156
+ data_source.db.transaction do
151
157
  relations.each do |relation_name, relation_primary_keys|
152
158
  relation = self.class.relations[relation_name]
153
159
  delete_relation_table relation, primary_key, relation_primary_keys
@@ -157,7 +163,7 @@ module Rasti
157
163
  end
158
164
 
159
165
  def delete_cascade(*primary_keys)
160
- db.transaction do
166
+ data_source.db.transaction do
161
167
  delete_cascade_relations primary_keys
162
168
  bulk_delete { |q| q.where self.class.primary_key => primary_keys }
163
169
  end
@@ -172,12 +178,6 @@ module Rasti
172
178
  where(self.class.primary_key => primary_key).graph(*relations).first
173
179
  end
174
180
 
175
- QUERY_METHODS.each do |method|
176
- define_method method do |*args, &block|
177
- query.public_send method, *args, &block
178
- end
179
- end
180
-
181
181
  def exists?(filter=nil, &block)
182
182
  build_query(filter, &block).any?
183
183
  end
@@ -188,27 +188,45 @@ module Rasti
188
188
 
189
189
  private
190
190
 
191
- def transform_attributes_to_db(attributes)
192
- attributes.each_with_object({}) do |(attribute_name, value), result|
193
- transformed_value = Rasti::DB.to_db db, qualified_collection_name, attribute_name, value
194
- result[attribute_name] = transformed_value
195
- end
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]
196
199
  end
197
200
 
198
201
  def qualified_collection_name
199
- schema.nil? ? Sequel[self.class.collection_name] : Sequel[schema][self.class.collection_name]
202
+ data_source.qualify self.class.collection_name
200
203
  end
201
204
 
202
- def query
203
- Query.new self.class, dataset, [], schema
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
+
210
+ def default_query
211
+ Query.new collection_class: self.class,
212
+ dataset: dataset.select_all(self.class.collection_name),
213
+ environment: environment
204
214
  end
205
215
 
206
216
  def build_query(filter=nil, &block)
207
217
  raise ArgumentError, 'must specify filter hash or block' if filter.nil? && block.nil?
218
+
208
219
  if filter
209
- query.where filter
220
+ default_query.where(filter)
210
221
  else
211
- block.arity == 0 ? query.instance_eval(&block) : block.call(query)
222
+ block.arity == 0 ? default_query.instance_eval(&block) : block.call(default_query)
223
+ end
224
+ end
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
212
230
  end
213
231
  end
214
232
 
@@ -237,18 +255,22 @@ module Rasti
237
255
  end
238
256
 
239
257
  relations.select { |r| r.one_to_many? || r.one_to_one? }.each do |relation|
240
- relation_collection_name = with_schema(relation.target_collection_class.collection_name)
241
- relations_ids = db[relation_collection_name].where(relation.foreign_key => primary_keys)
242
- .select(relation.target_collection_class.primary_key)
243
- .map(relation.target_collection_class.primary_key)
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
244
260
 
245
- target_collection = relation.target_collection_class.new db, schema
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)
265
+
266
+ target_collection = relation.target_collection_class.new environment
246
267
  target_collection.delete_cascade(*relations_ids) unless relations_ids.empty?
247
268
  end
248
269
  end
249
270
 
250
271
  def insert_relation_table(relation, primary_key, relation_primary_keys)
251
- relation_collection_name = relation.qualified_relation_collection_name(schema)
272
+ relation_data_source = environment.data_source relation.relation_data_source_name
273
+ relation_collection_name = relation_data_source.qualify relation.relation_collection_name
252
274
 
253
275
  values = relation_primary_keys.map do |relation_pk|
254
276
  {
@@ -257,12 +279,14 @@ module Rasti
257
279
  }
258
280
  end
259
281
 
260
- db[relation_collection_name].multi_insert values
282
+ relation_data_source.db[relation_collection_name].multi_insert values
261
283
  end
262
284
 
263
285
  def delete_relation_table(relation, primary_keys, relation_primary_keys=nil)
264
- relation_collection_name = relation.qualified_relation_collection_name(schema)
265
- ds = db[relation_collection_name].where(relation.source_foreign_key => primary_keys)
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)
266
290
  ds = ds.where(relation.target_foreign_key => relation_primary_keys) if relation_primary_keys
267
291
  ds.delete
268
292
  end