rasti-db 1.4.0 → 1.5.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
  SHA256:
3
- metadata.gz: ed5332eda8adfde54d7465d8f3f631d9b542f54d17c78644703d2f9054cb1bca
4
- data.tar.gz: 5452a867c32c4083f10149c3496093e731d6f8d64a6b13fafd57ebcc0d7e38c8
3
+ metadata.gz: b801c94dd51c4ee11f624cb542d784a42e445622e0090c42cb9d47322b87e855
4
+ data.tar.gz: 518c58daf408e477f80cdac52837a0e2d3256938352c888a73e369f3eca5b7a2
5
5
  SHA512:
6
- metadata.gz: 31a039adaeb64c98fa7f5c38b2155a009f78e2a824d9c8d4e9969bd2d9ff7599afc899d5b10c3864694a22540061cf0f439f681792f2f89e9eb9da030aa7c28e
7
- data.tar.gz: 393c7b91108ff8ab2c8effed2016b4ea912747baf65194c05614a01ef9d4d4447d41f350e996f0bdf8b14b9de102ced09bd023ce9cef590306068c28a8ae051c
6
+ metadata.gz: ae1452c9b77c6d50f9aa8b05b85f9970c801369e7e3a67a7a88436762fbacea4191390e114be066458d7bc2dc563f834cda93a364185354c74267db86798a889
7
+ data.tar.gz: 2707b44943ea63f17cd0f613c4fa08bdfc0663e8fde17f604c8c6ce9ba8f1fff23b4fa1641dfe42f77810a55fda6794491f5e8d8cb756c4fc19adb1cbb8cba76
data/.travis.yml CHANGED
@@ -1,7 +1,6 @@
1
1
  language: ruby
2
2
 
3
3
  rvm:
4
- - 2.0
5
4
  - 2.1
6
5
  - 2.2
7
6
  - 2.3
@@ -9,8 +8,7 @@ rvm:
9
8
  - 2.5
10
9
  - 2.6
11
10
  - 2.7
12
- - jruby-9.1.7.0
13
- - jruby-9.1.16.0
11
+ - jruby-9.2.9.0
14
12
  - ruby-head
15
13
  - jruby-head
16
14
 
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
 
@@ -108,7 +107,7 @@ class Posts < Rasti::DB::Collection
108
107
  chainable do
109
108
  dataset.join(with_schema(:comments), post_id: :id)
110
109
  .where(with_schema(:comments, :user_id) => user_id)
111
- .select_all(with_schema(:posts))
110
+ .select_all(:posts)
112
111
  .distinct
113
112
  end
114
113
  end
@@ -162,17 +161,49 @@ end
162
161
  posts.all # => [Post, ...]
163
162
  posts.first # => Post
164
163
  posts.count # => 1
164
+
165
165
  posts.where(id: [1,2]) # => [Post, ...]
166
166
  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, ...]
167
+
171
168
  posts.where(id: [1,2]).raw # => [{id:1, ...}, {id:2, ...}]
172
169
  posts.where(id: [1,2]).primary_keys # => [1,2]
173
170
  posts.where(id: [1,2]).pluck(:id) # => [1,2]
174
171
  posts.where(id: [1,2]).pluck(:id, :title) # => [[1, ...], [2, ...]]
172
+
173
+ posts.created_by(1) # => [Post, ...]
174
+ posts.created_by(1).entitled('...').commented_by(2) # => [Post, ...]
175
+ posts.with_categories([1,2]) # => [Post, ...]
176
+
177
+ posts.graph(:user, :categories, 'comments.user') # => [Post(User, [Categories, ...], [Comments(User)]), ...]
178
+
175
179
  posts.join(:user).where(name: 'User 4') # => [Post, ...]
180
+
181
+ posts.select_attributes(:id, :title) # => [Post, ...]
182
+ posts.exclude_attributes(:id, :title) # => [Post, ...]
183
+ posts.all_attributes # => [Post, ...]
184
+
185
+ posts.graph('user.person').select_graph_attributes(user: [:id], 'user.person': [:last_name, :user_id]) # => [Post, ...]
186
+ posts.graph('user.person').exclude_graph_attributes(user: [:name], 'user.person': [:first_name, :last_name]) # => [Post, ...]
187
+ posts.graph('user.person').all_graph_attributes('user.person') # => [Post, ...]
188
+ ```
189
+ ### Natural Query Language
190
+
191
+ ```ruby
192
+ posts.nql('id = 1') # => Equal
193
+ posts.nql('id != 1') # => Not equal
194
+ posts.nql('title: My post') # => Include
195
+ posts.nql('title !: My post') # => Not include
196
+ posts.nql('title ~ My post') # => Insensitive like
197
+ posts.nql('id > 1') # => Greater
198
+ posts.nql('id >= 1') # => Greater or equal
199
+ posts.nql('id < 10') # => Less
200
+ posts.nql('id <= 10') # => Less or equal
201
+
202
+ posts.nql('id = 1 | id = 2') # => Or
203
+ posts.nql('id > 1 & title: "My post"') # => And
204
+ posts.nql('(id > 3 & id < 10) | title: "My post"') # => Precedence
205
+
206
+ posts.nql('comments.user.person.document_number = 7') # => Nested
176
207
  ```
177
208
 
178
209
  ## Development
@@ -2,9 +2,8 @@ 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
5
+ QUERY_METHODS = Query.public_instance_methods - Object.public_instance_methods
6
6
 
7
- include Enumerable
8
7
  include Helpers::WithSchema
9
8
 
10
9
  class << self
@@ -16,8 +15,8 @@ module Rasti
16
15
  @collection_name ||= underscore(demodulize(name)).to_sym
17
16
  end
18
17
 
19
- def collection_fields
20
- @collection_fields ||= model.attributes - relations.keys
18
+ def collection_attributes
19
+ @collection_attributes ||= model.attributes - relations.keys
21
20
  end
22
21
 
23
22
  def primary_key
@@ -80,7 +79,7 @@ module Rasti
80
79
  queries[name] = lambda || block
81
80
 
82
81
  define_method name do |*args|
83
- query.instance_exec(*args, &self.class.queries[name])
82
+ default_query.instance_exec(*args, &self.class.queries.fetch(name))
84
83
  end
85
84
  end
86
85
 
@@ -93,6 +92,12 @@ module Rasti
93
92
  @schema = schema ? schema.to_sym : nil
94
93
  end
95
94
 
95
+ QUERY_METHODS.each do |method|
96
+ define_method method do |*args, &block|
97
+ default_query.public_send method, *args, &block
98
+ end
99
+ end
100
+
96
101
  def dataset
97
102
  db[qualified_collection_name]
98
103
  end
@@ -172,12 +177,6 @@ module Rasti
172
177
  where(self.class.primary_key => primary_key).graph(*relations).first
173
178
  end
174
179
 
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
180
  def exists?(filter=nil, &block)
182
181
  build_query(filter, &block).any?
183
182
  end
@@ -199,16 +198,19 @@ module Rasti
199
198
  schema.nil? ? Sequel[self.class.collection_name] : Sequel[schema][self.class.collection_name]
200
199
  end
201
200
 
202
- def query
203
- Query.new self.class, dataset, [], schema
201
+ def default_query
202
+ Query.new collection_class: self.class,
203
+ dataset: dataset.select_all(self.class.collection_name),
204
+ schema: schema
204
205
  end
205
206
 
206
207
  def build_query(filter=nil, &block)
207
208
  raise ArgumentError, 'must specify filter hash or block' if filter.nil? && block.nil?
209
+
208
210
  if filter
209
- query.where filter
211
+ default_query.where filter
210
212
  else
211
- block.arity == 0 ? query.instance_eval(&block) : block.call(query)
213
+ block.arity == 0 ? default_query.instance_eval(&block) : block.call(default_query)
212
214
  end
213
215
  end
214
216
 
@@ -7,19 +7,25 @@ module Rasti
7
7
  include Enumerable
8
8
  include Helpers::WithSchema
9
9
 
10
- def initialize(collection_class, dataset, relations=[], schema=nil)
10
+ def initialize(collection_class:, dataset:, relations_graph:nil, schema:nil)
11
11
  @collection_class = collection_class
12
12
  @dataset = dataset
13
- @relations = relations
13
+ @relations_graph = relations_graph || Relations::Graph.new(dataset.db, schema, collection_class)
14
14
  @schema = schema
15
15
  end
16
16
 
17
+ DATASET_CHAINED_METHODS.each do |method|
18
+ define_method method do |*args, &block|
19
+ build_query dataset: dataset.public_send(method, *args, &block)
20
+ end
21
+ end
22
+
17
23
  def raw
18
24
  dataset.all
19
25
  end
20
26
 
21
27
  def pluck(*attributes)
22
- ds = dataset.select(*attributes.map { |attr| Sequel[collection_class.collection_name][attr] })
28
+ ds = dataset.select(*attributes.map { |a| Sequel[collection_class.collection_name][a] })
23
29
  attributes.count == 1 ? ds.map { |r| r[attributes.first] } : ds.map(&:values)
24
30
  end
25
31
 
@@ -27,6 +33,31 @@ module Rasti
27
33
  pluck collection_class.primary_key
28
34
  end
29
35
 
36
+ def select_attributes(*attributes)
37
+ build_query dataset: dataset.select(*attributes.map { |a| Sequel[collection_class.collection_name][a] })
38
+ end
39
+
40
+ def exclude_attributes(*excluded_attributes)
41
+ attributes = collection_class.collection_attributes - excluded_attributes
42
+ select_attributes(*attributes)
43
+ end
44
+
45
+ def all_attributes
46
+ build_query dataset: dataset.select_all(collection_class.collection_name)
47
+ end
48
+
49
+ def select_graph_attributes(selected_attributes)
50
+ build_query relations_graph: relations_graph.merge(selected_attributes: selected_attributes)
51
+ end
52
+
53
+ def exclude_graph_attributes(excluded_attributes)
54
+ build_query relations_graph: relations_graph.merge(excluded_attributes: excluded_attributes)
55
+ end
56
+
57
+ def all_graph_attributes(*relations)
58
+ build_query relations_graph: relations_graph.with_all_attributes_for(relations)
59
+ end
60
+
30
61
  def all
31
62
  with_graph(dataset.all).map do |row|
32
63
  collection_class.model.new row
@@ -38,27 +69,18 @@ module Rasti
38
69
  all.each(&block)
39
70
  end
40
71
 
41
- DATASET_CHAINED_METHODS.each do |method|
42
- define_method method do |*args, &block|
43
- Query.new collection_class,
44
- dataset.public_send(method, *args, &block),
45
- relations,
46
- schema
47
- end
48
- end
49
-
50
- def graph(*rels)
51
- Query.new collection_class,
52
- dataset,
53
- (relations | rels),
54
- schema
72
+ def graph(*relations)
73
+ build_query relations_graph: relations_graph.merge(relations: relations)
55
74
  end
56
75
 
57
- def join(*rels)
58
- Query.new collection_class,
59
- Relations::GraphBuilder.joins_to(dataset, rels, collection_class, schema),
60
- relations,
61
- schema
76
+ def join(*relations)
77
+ graph = Relations::Graph.new dataset.db, schema, collection_class, relations
78
+
79
+ ds = graph.add_joins(dataset)
80
+ .distinct
81
+ .select_all(collection_class.collection_name)
82
+
83
+ build_query dataset: ds
62
84
  end
63
85
 
64
86
  def count
@@ -105,25 +127,41 @@ module Rasti
105
127
 
106
128
  private
107
129
 
130
+ attr_reader :collection_class, :dataset, :relations_graph, :schema
131
+
132
+ def build_query(**args)
133
+ current_args = {
134
+ collection_class: collection_class,
135
+ dataset: dataset,
136
+ relations_graph: relations_graph,
137
+ schema: schema
138
+ }
139
+
140
+ Query.new(**current_args.merge(args))
141
+ end
142
+
108
143
  def chainable(&block)
109
- ds = instance_eval(&block)
110
- Query.new collection_class, ds, relations, schema
144
+ build_query dataset: instance_eval(&block)
111
145
  end
112
146
 
113
147
  def with_related(relation_name, primary_keys)
114
148
  ds = collection_class.relations[relation_name].apply_filter dataset, schema, primary_keys
115
- Query.new collection_class, ds, relations, schema
149
+ build_query dataset: ds
116
150
  end
117
151
 
118
152
  def with_graph(data)
119
153
  rows = data.is_a?(Array) ? data : [data]
120
- Relations::GraphBuilder.graph_to rows, relations, collection_class, dataset.db, schema
154
+ relations_graph.fetch_graph rows
121
155
  data
122
156
  end
123
157
 
158
+ def nql_parser
159
+ NQL::SyntaxParser.new
160
+ end
161
+
124
162
  def method_missing(method, *args, &block)
125
- if collection_class.queries.key?(method)
126
- instance_exec(*args, &collection_class.queries[method])
163
+ if collection_class.queries.key? method
164
+ instance_exec(*args, &collection_class.queries.fetch(method))
127
165
  else
128
166
  super
129
167
  end
@@ -133,12 +171,6 @@ module Rasti
133
171
  collection_class.queries.key?(method) || super
134
172
  end
135
173
 
136
- def nql_parser
137
- NQL::SyntaxParser.new
138
- end
139
-
140
- attr_reader :collection_class, :dataset, :relations, :schema
141
-
142
174
  end
143
175
  end
144
176
  end
@@ -0,0 +1,135 @@
1
+ module Rasti
2
+ module DB
3
+ module Relations
4
+ class Graph
5
+
6
+ def initialize(db, schema, collection_class, relations=[], selected_attributes={}, excluded_attributes={})
7
+ @db = db
8
+ @schema = schema
9
+ @collection_class = collection_class
10
+ @graph = build_graph relations,
11
+ Hash::Indifferent.new(selected_attributes),
12
+ Hash::Indifferent.new(excluded_attributes)
13
+ end
14
+
15
+ def merge(relations:[], selected_attributes:{}, excluded_attributes:{})
16
+ Graph.new db,
17
+ schema,
18
+ collection_class,
19
+ (flat_relations | relations),
20
+ flat_selected_attributes.merge(selected_attributes),
21
+ flat_excluded_attributes.merge(excluded_attributes)
22
+ end
23
+
24
+ def with_all_attributes_for(relations)
25
+ relations_with_all_attributes = relations.map { |r| [r, nil] }.to_h
26
+
27
+ merge selected_attributes: relations_with_all_attributes,
28
+ excluded_attributes: relations_with_all_attributes
29
+ end
30
+
31
+ def apply_to(query)
32
+ query.graph(*flat_relations)
33
+ .select_graph_attributes(flat_selected_attributes)
34
+ .exclude_graph_attributes(flat_excluded_attributes)
35
+ end
36
+
37
+ def fetch_graph(rows)
38
+ return if rows.empty?
39
+
40
+ graph.roots.each do |node|
41
+ relation_of(node).fetch_graph rows,
42
+ db,
43
+ schema,
44
+ node[:selected_attributes],
45
+ node[:excluded_attributes] ,
46
+ subgraph_of(node)
47
+ end
48
+ end
49
+
50
+ def add_joins(dataset, prefix=nil)
51
+ graph.roots.each do |node|
52
+ relation = relation_of node
53
+ dataset = relation.add_join dataset, schema, prefix
54
+ dataset = subgraph_of(node).add_joins dataset, relation.join_relation_name(prefix)
55
+ end
56
+
57
+ dataset
58
+ end
59
+
60
+ private
61
+
62
+ attr_reader :db, :schema, :collection_class, :graph
63
+
64
+ def relation_of(node)
65
+ collection_class.relations.fetch(node[:name])
66
+ end
67
+
68
+ def flat_relations
69
+ graph.map(&:id)
70
+ end
71
+
72
+ def flat_selected_attributes
73
+ graph.each_with_object(Hash::Indifferent.new) do |node, hash|
74
+ hash[node.id] = node[:selected_attributes]
75
+ end
76
+ end
77
+
78
+ def flat_excluded_attributes
79
+ graph.each_with_object(Hash::Indifferent.new) do |node, hash|
80
+ hash[node.id] = node[:excluded_attributes]
81
+ end
82
+ end
83
+
84
+ def subgraph_of(node)
85
+ relations = []
86
+ selected = Hash::Indifferent.new
87
+ excluded = Hash::Indifferent.new
88
+
89
+ node.descendants.each do |descendant|
90
+ id = descendant.id[node[:name].length+1..-1]
91
+ relations << id
92
+ selected[id] = descendant[:selected_attributes]
93
+ excluded[id] = descendant[:excluded_attributes]
94
+ end
95
+
96
+ Graph.new db,
97
+ schema,
98
+ relation_of(node).target_collection_class,
99
+ relations,
100
+ selected,
101
+ excluded
102
+ end
103
+
104
+ def build_graph(relations, selected_attributes, excluded_attributes)
105
+ HierarchicalGraph.new.tap do |graph|
106
+ flatten(relations).each do |relation|
107
+ sections = relation.split('.')
108
+
109
+ graph.add_node relation, name: sections.last.to_sym,
110
+ selected_attributes: selected_attributes[relation],
111
+ excluded_attributes: excluded_attributes[relation]
112
+
113
+ if sections.count > 1
114
+ parent_id = sections[0..-2].join('.')
115
+ graph.add_relation parent_id: parent_id,
116
+ child_id: relation
117
+ end
118
+ end
119
+ end
120
+ end
121
+
122
+ def flatten(relations)
123
+ relations.flat_map do |relation|
124
+ parents = []
125
+ relation.to_s.split('.').map do |section|
126
+ parents << section
127
+ parents.compact.join('.')
128
+ end
129
+ end.uniq.sort
130
+ end
131
+
132
+ end
133
+ end
134
+ end
135
+ end
@@ -19,7 +19,7 @@ module Rasti
19
19
  schema.nil? ? Sequel[relation_collection_name] : Sequel[schema][relation_collection_name]
20
20
  end
21
21
 
22
- def graph_to(rows, db, schema=nil, relations=[])
22
+ def fetch_graph(rows, db, schema=nil, selected_attributes=nil, excluded_attributes=nil, relations_graph=nil)
23
23
  pks = rows.map { |row| row[source_collection_class.primary_key] }
24
24
 
25
25
  target_collection = target_collection_class.new db, schema
@@ -29,11 +29,11 @@ module Rasti
29
29
  join_rows = target_collection.dataset
30
30
  .join(relation_name, target_foreign_key => target_collection_class.primary_key)
31
31
  .where(Sequel[relation_name][source_foreign_key] => pks)
32
- .select_all(qualified_target_collection_name(schema))
32
+ .select_all(target_collection_class.collection_name)
33
33
  .select_append(Sequel[relation_name][source_foreign_key].as(:source_foreign_key))
34
34
  .all
35
35
 
36
- GraphBuilder.graph_to join_rows, relations, target_collection_class, db, schema
36
+ relations_graph.fetch_graph join_rows if relations_graph
37
37
 
38
38
  relation_rows = join_rows.each_with_object(Hash.new { |h,k| h[k] = [] }) do |row, hash|
39
39
  attributes = row.select { |attr,_| target_collection_class.model.attributes.include? attr }
@@ -45,7 +45,7 @@ module Rasti
45
45
  end
46
46
  end
47
47
 
48
- def join_to(dataset, schema=nil, prefix=nil)
48
+ def add_join(dataset, schema=nil, prefix=nil)
49
49
  many_to_many_relation_alias = with_prefix prefix, "#{relation_collection_name}_#{SecureRandom.base64}"
50
50
 
51
51
  qualified_relation_source = prefix ? Sequel[prefix] : qualified_source_collection_name(schema)
@@ -69,7 +69,7 @@ module Rasti
69
69
 
70
70
  dataset.join(relation_name, source_foreign_key => target_collection_class.primary_key)
71
71
  .where(Sequel[relation_name][target_foreign_key] => primary_keys)
72
- .select_all(qualified_source_collection_name(schema))
72
+ .select_all(source_collection_class.collection_name)
73
73
  .distinct
74
74
  end
75
75
 
@@ -7,23 +7,26 @@ module Rasti
7
7
  @foreign_key ||= options[:foreign_key] || target_collection_class.foreign_key
8
8
  end
9
9
 
10
- def graph_to(rows, db, schema=nil, relations=[])
10
+ def fetch_graph(rows, db, schema=nil, selected_attributes=nil, excluded_attributes=nil, relations_graph=nil)
11
11
  fks = rows.map { |row| row[foreign_key] }.uniq
12
12
 
13
13
  target_collection = target_collection_class.new db, schema
14
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
15
+ query = target_collection.where(source_collection_class.primary_key => fks)
16
+ query = query.exclude_attributes(*excluded_attributes) if excluded_attributes
17
+ query = query.select_attributes(*selected_attributes) if selected_attributes
18
+ query = relations_graph.apply_to query if relations_graph
19
+
20
+ relation_rows = query.each_with_object({}) do |row, hash|
21
+ hash[row.public_send(source_collection_class.primary_key)] = row
22
+ end
20
23
 
21
24
  rows.each do |row|
22
25
  row[name] = relation_rows[row[foreign_key]]
23
26
  end
24
27
  end
25
28
 
26
- def join_to(dataset, schema=nil, prefix=nil)
29
+ def add_join(dataset, schema=nil, prefix=nil)
27
30
  relation_alias = join_relation_name prefix
28
31
 
29
32
  qualified_relation_source = prefix ? Sequel[prefix] : qualified_source_collection_name(schema)
@@ -7,21 +7,24 @@ module Rasti
7
7
  @foreign_key ||= options[:foreign_key] || source_collection_class.foreign_key
8
8
  end
9
9
 
10
- def graph_to(rows, db, schema=nil, relations=[])
10
+ def fetch_graph(rows, db, schema=nil, selected_attributes=nil, excluded_attributes=nil, relations_graph=nil)
11
11
  pks = rows.map { |row| row[source_collection_class.primary_key] }.uniq
12
12
 
13
13
  target_collection = target_collection_class.new db, schema
14
14
 
15
- relation_rows = target_collection.where(foreign_key => pks)
16
- .graph(*relations)
17
- .group_by { |r| r.public_send(foreign_key) }
15
+ query = target_collection.where(foreign_key => pks)
16
+ query = query.exclude_attributes(*excluded_attributes) if excluded_attributes
17
+ query = query.select_attributes(*selected_attributes) if selected_attributes
18
+ query = relations_graph.apply_to query if relations_graph
19
+
20
+ relation_rows = query.group_by(&foreign_key)
18
21
 
19
22
  rows.each do |row|
20
23
  row[name] = build_graph_result relation_rows.fetch(row[source_collection_class.primary_key], [])
21
24
  end
22
25
  end
23
26
 
24
- def join_to(dataset, schema=nil, prefix=nil)
27
+ def add_join(dataset, schema=nil, prefix=nil)
25
28
  relation_alias = join_relation_name prefix
26
29
 
27
30
  qualified_relation_source = prefix ? Sequel[prefix] : qualified_source_collection_name(schema)
@@ -1,5 +1,5 @@
1
1
  module Rasti
2
2
  module DB
3
- VERSION = '1.4.0'
3
+ VERSION = '1.5.0'
4
4
  end
5
5
  end
data/lib/rasti/db.rb CHANGED
@@ -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
data/rasti-db.gemspec CHANGED
@@ -24,8 +24,10 @@ Gem::Specification.new do |spec|
24
24
  spec.add_runtime_dependency 'timing', '~> 0.1', '>= 0.1.3'
25
25
  spec.add_runtime_dependency 'class_config', '~> 0.0', '>= 0.0.2'
26
26
  spec.add_runtime_dependency 'multi_require', '~> 1.0'
27
+ spec.add_runtime_dependency 'hierarchical_graph', '~> 1.0'
28
+ spec.add_runtime_dependency 'hash_ext', '~> 0.5'
27
29
 
28
- spec.add_development_dependency 'rake', '~> 11.0'
30
+ spec.add_development_dependency 'rake', '~> 12.3'
29
31
  spec.add_development_dependency 'minitest', '~> 5.0', '< 5.11'
30
32
  spec.add_development_dependency 'minitest-colorin', '~> 0.1'
31
33
  spec.add_development_dependency 'minitest-line', '~> 0.6'
@@ -38,10 +40,4 @@ Gem::Specification.new do |spec|
38
40
  else
39
41
  spec.add_development_dependency 'sqlite3', '~> 1.3'
40
42
  end
41
-
42
- if RUBY_VERSION < '2'
43
- spec.add_development_dependency 'term-ansicolor', '~> 1.3.0'
44
- spec.add_development_dependency 'tins', '~> 1.6.0'
45
- spec.add_development_dependency 'json', '~> 1.8'
46
- end
47
43
  end
@@ -6,7 +6,7 @@ describe 'Collection' do
6
6
 
7
7
  it 'Implicit' do
8
8
  Users.collection_name.must_equal :users
9
- Users.collection_fields.must_equal [:id, :name]
9
+ Users.collection_attributes.must_equal [:id, :name]
10
10
  Users.model.must_equal User
11
11
  Users.primary_key.must_equal :id
12
12
  Users.foreign_key.must_equal :user_id
@@ -14,7 +14,7 @@ describe 'Collection' do
14
14
 
15
15
  it 'Explicit' do
16
16
  People.collection_name.must_equal :people
17
- People.collection_fields.must_equal [:document_number, :first_name, :last_name, :birth_date, :user_id]
17
+ People.collection_attributes.must_equal [:document_number, :first_name, :last_name, :birth_date, :user_id]
18
18
  People.model.must_equal Person
19
19
  People.primary_key.must_equal :document_number
20
20
  People.foreign_key.must_equal :document_number
@@ -244,6 +244,24 @@ describe 'Collection' do
244
244
  users.find_graph(user_id, :posts).must_equal User.new id: user_id, name: 'User 1', posts: posts.all
245
245
  end
246
246
 
247
+ it 'Select attributes' do
248
+ id = db[:users].insert name: 'User 1'
249
+
250
+ users.select_attributes(:id).all.must_equal [User.new(id: id)]
251
+ end
252
+
253
+ it 'Exclude attributes' do
254
+ db[:users].insert name: 'User 1'
255
+
256
+ users.exclude_attributes(:id).all.must_equal [User.new(name: 'User 1')]
257
+ end
258
+
259
+ it 'All attributes' do
260
+ id = db[:users].insert name: 'User 1'
261
+
262
+ users.select_attributes(:id).all_attributes.all.must_equal [User.new(id: id, name: 'User 1')]
263
+ end
264
+
247
265
  it 'Count' do
248
266
  1.upto(10) { |i| db[:users].insert name: "User #{i}" }
249
267
 
@@ -430,21 +448,21 @@ describe 'Collection' do
430
448
  stubs = Proc.new do |sql|
431
449
  case sql
432
450
 
433
- when 'SELECT * FROM custom_schema.users',
434
- 'SELECT * FROM custom_schema.users WHERE (id IN (2, 1))'
451
+ when 'SELECT users.* FROM custom_schema.users',
452
+ 'SELECT users.* FROM custom_schema.users WHERE (id IN (2, 1))'
435
453
  [
436
454
  {id: 1},
437
455
  {id: 2}
438
456
  ]
439
457
 
440
- when 'SELECT * FROM custom_schema.posts',
441
- 'SELECT * FROM custom_schema.posts WHERE (user_id IN (1, 2))'
458
+ when 'SELECT posts.* FROM custom_schema.posts',
459
+ 'SELECT posts.* FROM custom_schema.posts WHERE (user_id IN (1, 2))'
442
460
  [
443
461
  {id: 3, user_id: 1},
444
462
  {id: 4, user_id: 2}
445
463
  ]
446
464
 
447
- when 'SELECT * FROM custom_schema.comments WHERE (post_id IN (3, 4))'
465
+ when 'SELECT comments.* FROM custom_schema.comments WHERE (post_id IN (3, 4))'
448
466
  [
449
467
  {id: 5, user_id: 2, post_id: 3},
450
468
  {id: 6, user_id: 1, post_id: 3},
@@ -506,33 +524,34 @@ describe 'Collection' do
506
524
 
507
525
  it 'Chained query' do
508
526
  stub_users.where(id: [1,2]).limit(1).order(:name).all
509
- stub_db.sqls.must_equal ['SELECT * FROM custom_schema.users WHERE (id IN (1, 2)) ORDER BY name LIMIT 1']
527
+ stub_db.sqls.must_equal ['SELECT users.* FROM custom_schema.users WHERE (id IN (1, 2)) ORDER BY name LIMIT 1']
510
528
  end
511
529
 
512
530
  it 'Graph' do
513
531
  stub_posts.graph(:user, :categories, 'comments.user.posts.categories').all
532
+
514
533
  stub_db.sqls.must_equal [
515
- 'SELECT * FROM custom_schema.posts',
516
- 'SELECT * FROM custom_schema.users WHERE (id IN (1, 2))',
517
- 'SELECT custom_schema.categories.*, custom_schema.categories_posts.post_id AS source_foreign_key FROM custom_schema.categories INNER JOIN custom_schema.categories_posts ON (custom_schema.categories_posts.category_id = custom_schema.categories.id) WHERE (custom_schema.categories_posts.post_id IN (3, 4))',
518
- 'SELECT * FROM custom_schema.comments WHERE (post_id IN (3, 4))',
519
- 'SELECT * FROM custom_schema.users WHERE (id IN (2, 1))',
520
- 'SELECT * FROM custom_schema.posts WHERE (user_id IN (1, 2))',
521
- 'SELECT custom_schema.categories.*, custom_schema.categories_posts.post_id AS source_foreign_key FROM custom_schema.categories INNER JOIN custom_schema.categories_posts ON (custom_schema.categories_posts.category_id = custom_schema.categories.id) WHERE (custom_schema.categories_posts.post_id IN (3, 4))'
534
+ 'SELECT posts.* FROM custom_schema.posts',
535
+ 'SELECT categories.*, custom_schema.categories_posts.post_id AS source_foreign_key FROM custom_schema.categories INNER JOIN custom_schema.categories_posts ON (custom_schema.categories_posts.category_id = custom_schema.categories.id) WHERE (custom_schema.categories_posts.post_id IN (3, 4))',
536
+ 'SELECT comments.* FROM custom_schema.comments WHERE (post_id IN (3, 4))',
537
+ 'SELECT users.* FROM custom_schema.users WHERE (id IN (2, 1))',
538
+ 'SELECT posts.* FROM custom_schema.posts WHERE (user_id IN (1, 2))',
539
+ 'SELECT categories.*, custom_schema.categories_posts.post_id AS source_foreign_key FROM custom_schema.categories INNER JOIN custom_schema.categories_posts ON (custom_schema.categories_posts.category_id = custom_schema.categories.id) WHERE (custom_schema.categories_posts.post_id IN (3, 4))',
540
+ 'SELECT users.* FROM custom_schema.users WHERE (id IN (1, 2))'
522
541
  ]
523
542
  end
524
543
 
525
544
  it 'Named query' do
526
545
  stub_posts.commented_by(1).all
527
546
  stub_db.sqls.must_equal [
528
- 'SELECT DISTINCT custom_schema.posts.* FROM custom_schema.posts INNER JOIN custom_schema.comments ON (custom_schema.comments.post_id = custom_schema.posts.id) WHERE (custom_schema.comments.user_id = 1)'
547
+ 'SELECT DISTINCT posts.* FROM custom_schema.posts INNER JOIN custom_schema.comments ON (custom_schema.comments.post_id = custom_schema.posts.id) WHERE (custom_schema.comments.user_id = 1)'
529
548
  ]
530
549
  end
531
550
 
532
551
  it 'Custom query' do
533
552
  stub_comments.posts_commented_by(2)
534
553
  stub_db.sqls.must_equal [
535
- 'SELECT custom_schema.posts.* FROM custom_schema.comments INNER JOIN custom_schema.posts ON (custom_schema.posts.id = custom_schema.comments.post_id) WHERE (comments.user_id = 2)'
554
+ 'SELECT posts.* FROM custom_schema.comments INNER JOIN custom_schema.posts ON (custom_schema.posts.id = custom_schema.comments.post_id) WHERE (comments.user_id = 2)'
536
555
  ]
537
556
  end
538
557
 
@@ -2,6 +2,7 @@ require 'coverage_helper'
2
2
  require 'rasti-db'
3
3
  require 'minitest/autorun'
4
4
  require 'minitest/colorin'
5
+ require 'minitest/line/describe_track'
5
6
  require 'pry-nav'
6
7
  require 'logger'
7
8
  require 'sequel/extensions/pg_hstore'
@@ -40,7 +41,7 @@ class Posts < Rasti::DB::Collection
40
41
  chainable do
41
42
  dataset.join(with_schema(:comments), post_id: :id)
42
43
  .where(with_schema(:comments, :user_id) => user_id)
43
- .select_all(with_schema(:posts))
44
+ .select_all(:posts)
44
45
  .distinct
45
46
  end
46
47
  end
@@ -53,7 +54,7 @@ class Comments < Rasti::DB::Collection
53
54
  def posts_commented_by(user_id)
54
55
  dataset.where(Sequel[:comments][:user_id] => user_id)
55
56
  .join(with_schema(:posts), id: :post_id)
56
- .select_all(with_schema(:posts))
57
+ .select_all(:posts)
57
58
  .map { |row| Post.new row }
58
59
  end
59
60
  end
data/spec/query_spec.rb CHANGED
@@ -3,18 +3,38 @@ require 'minitest_helper'
3
3
  describe 'Query' do
4
4
 
5
5
  before do
6
- 1.upto(10) { |i| db[:users].insert name: "User #{i}" }
6
+ 1.upto(10) do |i|
7
+ db[:users].insert name: "User #{i}"
8
+
9
+ db[:people].insert user_id: i,
10
+ document_number: i,
11
+ first_name: "Name #{i}",
12
+ last_name: "Last Name #{i}",
13
+ birth_date: Time.now
14
+ end
7
15
 
8
16
  db[:posts].insert user_id: 2, title: 'Sample post', body: '...'
9
17
  db[:posts].insert user_id: 1, title: 'Another post', body: '...'
10
18
  db[:posts].insert user_id: 4, title: 'Best post', body: '...'
11
- end
12
19
 
13
- let(:users_query) { Rasti::DB::Query.new Users, db[:users] }
20
+ 1.upto(3) { |i| db[:categories].insert name: "Category #{i}" }
21
+
22
+ db[:comments].insert post_id: 1, user_id: 5, text: 'Comment 1'
23
+ db[:comments].insert post_id: 1, user_id: 7, text: 'Comment 2'
24
+ db[:comments].insert post_id: 2, user_id: 2, text: 'Comment 3'
25
+
26
+ db[:categories_posts].insert post_id: 1, category_id: 1
27
+ db[:categories_posts].insert post_id: 1, category_id: 2
28
+ db[:categories_posts].insert post_id: 2, category_id: 2
29
+ db[:categories_posts].insert post_id: 2, category_id: 3
30
+ db[:categories_posts].insert post_id: 3, category_id: 3
31
+ end
14
32
 
15
- let(:posts_query) { Rasti::DB::Query.new Posts, db[:posts] }
33
+ let(:users_query) { Rasti::DB::Query.new collection_class: Users, dataset: db[:users] }
34
+
35
+ let(:posts_query) { Rasti::DB::Query.new collection_class: Posts, dataset: db[:posts] }
16
36
 
17
- let(:comments_query) { Rasti::DB::Query.new Comments, db[:comments] }
37
+ let(:comments_query) { Rasti::DB::Query.new collection_class: Comments, dataset: db[:comments] }
18
38
 
19
39
  it 'Count' do
20
40
  users_query.count.must_equal 10
@@ -37,6 +57,61 @@ describe 'Query' do
37
57
  users_query.primary_keys.must_equal db[:users].map { |u| u[:id] }
38
58
  end
39
59
 
60
+ it 'Select attributes' do
61
+ posts_query.select_attributes(:id, :user_id).all.must_equal db[:posts].select(:id, :user_id).map { |r| Post.new r }
62
+ end
63
+
64
+ it 'Exclude attributes' do
65
+ posts_query.exclude_attributes(:body).all.must_equal db[:posts].select(:id, :user_id, :title).map { |r| Post.new r }
66
+ end
67
+
68
+ it 'All attributes' do
69
+ posts_query.exclude_attributes(:body).all_attributes.all.must_equal db[:posts].map { |r| Post.new r }
70
+ end
71
+
72
+ it 'Select graph attributes' do
73
+ person = Person.new db[:people].where(document_number: 2).select(:first_name, :last_name, :user_id).first
74
+
75
+ user = User.new db[:users].where(id: 2).select(:id).first.merge(person: person)
76
+
77
+ post = Post.new db[:posts].where(id: 1).first.merge(user: user)
78
+
79
+ posts_query.where(id: 1)
80
+ .graph('user.person')
81
+ .select_graph_attributes(user: [:id], 'user.person' => [:first_name, :last_name, :user_id])
82
+ .all
83
+ .must_equal [post]
84
+ end
85
+
86
+ it 'Exclude graph attributes' do
87
+ person = Person.new db[:people].where(document_number: 2).select(:document_number, :last_name, :user_id).first
88
+
89
+ user = User.new db[:users].where(id: 2).select(:id).first.merge(person: person)
90
+
91
+ post = Post.new db[:posts].where(id: 1).first.merge(user: user)
92
+
93
+ posts_query.where(id: 1)
94
+ .graph('user.person')
95
+ .exclude_graph_attributes(user: [:name], 'user.person' => [:first_name, :birth_date])
96
+ .all
97
+ .must_equal [post]
98
+ end
99
+
100
+ it 'All graph attributes' do
101
+ person = Person.new db[:people].where(document_number: 2).first
102
+
103
+ user = User.new db[:users].where(id: 2).select(:id).first.merge(person: person)
104
+
105
+ post = Post.new db[:posts].where(id: 1).first.merge(user: user)
106
+
107
+ posts_query.where(id: 1)
108
+ .graph('user.person')
109
+ .exclude_graph_attributes(user: [:name], 'user.person' => [:birth_date, :first_name, :last_name])
110
+ .all_graph_attributes('user.person')
111
+ .all
112
+ .must_equal [post]
113
+ end
114
+
40
115
  it 'Map' do
41
116
  users_query.map(&:name).must_equal db[:users].map(:name)
42
117
  end
@@ -96,12 +171,14 @@ describe 'Query' do
96
171
  users_query.graph(:posts).where(id: 1).first.must_equal User.new(id: 1, name: 'User 1', posts: [Post.new(id: 2, user_id: 1, title: 'Another post', body: '...')])
97
172
  end
98
173
 
99
- it 'Empty?' do
174
+ it 'Any?' do
100
175
  users_query.empty?.must_equal false
101
176
  users_query.any?.must_equal true
102
177
  end
103
178
 
104
- it 'Any?' do
179
+ it 'Empty?' do
180
+ db[:comments].truncate
181
+
105
182
  comments_query.empty?.must_equal true
106
183
  comments_query.any?.must_equal false
107
184
  end
@@ -126,28 +203,6 @@ describe 'Query' do
126
203
 
127
204
  describe 'Join' do
128
205
 
129
- before do
130
- 1.upto(10) do |i|
131
- db[:people].insert user_id: i,
132
- document_number: i,
133
- first_name: "Name #{i}",
134
- last_name: "Last Name #{i}",
135
- birth_date: Time.now
136
- end
137
-
138
- 1.upto(3) { |i| db[:categories].insert name: "Category #{i}" }
139
-
140
- db[:comments].insert post_id: 1, user_id: 5, text: 'Comment 1'
141
- db[:comments].insert post_id: 1, user_id: 7, text: 'Comment 2'
142
- db[:comments].insert post_id: 2, user_id: 2, text: 'Comment 3'
143
-
144
- db[:categories_posts].insert post_id: 1, category_id: 1
145
- db[:categories_posts].insert post_id: 1, category_id: 2
146
- db[:categories_posts].insert post_id: 2, category_id: 2
147
- db[:categories_posts].insert post_id: 2, category_id: 3
148
- db[:categories_posts].insert post_id: 3, category_id: 3
149
- end
150
-
151
206
  it 'One to Many' do
152
207
  users_query.join(:posts).where(title: 'Sample post').all.must_equal [User.new(id: 2, name: 'User 2')]
153
208
  end
@@ -179,55 +234,37 @@ describe 'Query' do
179
234
 
180
235
  describe 'NQL' do
181
236
 
182
- before do
183
- 1.upto(10) do |i|
184
- db[:people].insert user_id: i,
185
- document_number: i,
186
- first_name: "Name #{i}",
187
- last_name: "Last Name #{i}",
188
- birth_date: Time.now
189
- end
190
-
191
- 1.upto(3) { |i| db[:categories].insert name: "Category #{i}" }
192
-
193
- db[:comments].insert post_id: 1, user_id: 5, text: 'Comment 1'
194
- db[:comments].insert post_id: 1, user_id: 7, text: 'Comment 2'
195
- db[:comments].insert post_id: 2, user_id: 2, text: 'Comment 3'
196
-
197
- db[:categories_posts].insert post_id: 1, category_id: 1
198
- db[:categories_posts].insert post_id: 1, category_id: 2
199
- db[:categories_posts].insert post_id: 2, category_id: 2
200
- db[:categories_posts].insert post_id: 2, category_id: 3
201
- db[:categories_posts].insert post_id: 3, category_id: 3
202
- end
203
-
204
237
  it 'Invalid expression' do
205
238
  error = proc { posts_query.nql('a + b') }.must_raise Rasti::DB::NQL::InvalidExpressionError
206
239
  error.message.must_equal 'Invalid filter expression: a + b'
207
240
  end
208
241
 
209
242
  it 'Filter to self table' do
210
- people_query = Rasti::DB::Query.new People, db[:people]
211
-
212
- people_query.nql('user_id > 7')
213
- .map(&:user_id)
214
- .sort
215
- .must_equal [8, 9, 10]
243
+ posts_query.nql('user_id > 1')
244
+ .pluck(:user_id)
245
+ .sort
246
+ .must_equal [2, 4]
216
247
  end
217
248
 
218
249
  it 'Filter to join table' do
219
250
  posts_query.nql('categories.name = Category 2')
220
- .map(&:id)
251
+ .pluck(:id)
221
252
  .sort
222
253
  .must_equal [1, 2]
223
254
  end
224
255
 
225
256
  it 'Filter to 2nd order relation' do
226
257
  posts_query.nql('comments.user.person.document_number = 7')
227
- .map(&:id)
258
+ .pluck(:id)
228
259
  .must_equal [1]
229
260
  end
230
261
 
262
+ it 'Filter combined' do
263
+ posts_query.nql('(categories.id = 1 | categories.id = 3) & comments.user.person.document_number = 2')
264
+ .pluck(:id)
265
+ .must_equal [2]
266
+ end
267
+
231
268
  end
232
269
 
233
270
  end
@@ -37,7 +37,7 @@ describe 'Relations' do
37
37
  1.upto(2) { |i| db[:posts].insert user_id: user_id, title: "Post #{i}", body: '...' }
38
38
  rows = db[:users].all
39
39
 
40
- Users.relations[:posts].graph_to rows, db
40
+ Users.relations[:posts].fetch_graph rows, db
41
41
 
42
42
  rows[0][:posts].must_equal posts.where(user_id: user_id).all
43
43
  end
@@ -79,7 +79,7 @@ describe 'Relations' do
79
79
  db[:posts].insert user_id: user_id, title: 'Post 1', body: '...'
80
80
  rows = db[:posts].all
81
81
 
82
- Posts.relations[:user].graph_to rows, db
82
+ Posts.relations[:user].fetch_graph rows, db
83
83
 
84
84
  rows[0][:user].must_equal users.first
85
85
  end
@@ -136,7 +136,7 @@ describe 'Relations' do
136
136
 
137
137
  rows = db[:posts].all
138
138
 
139
- Posts.relations[:categories].graph_to rows, db
139
+ Posts.relations[:categories].fetch_graph rows, db
140
140
 
141
141
  rows[0][:categories].must_equal categories.where(id: [1,2]).all
142
142
  rows[1][:categories].must_equal categories.where(id: [3,4]).all
@@ -186,7 +186,7 @@ describe 'Relations' do
186
186
 
187
187
  rows = db[:users].all
188
188
 
189
- Users.relations[:person].graph_to rows, db
189
+ Users.relations[:person].fetch_graph rows, db
190
190
 
191
191
  2.times do |i|
192
192
  rows[i][:person].must_equal people.find("document_#{i}")
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: 1.4.0
4
+ version: 1.5.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: 2020-01-08 00:00:00.000000000 Z
11
+ date: 2020-04-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sequel
@@ -112,20 +112,48 @@ dependencies:
112
112
  - - "~>"
113
113
  - !ruby/object:Gem::Version
114
114
  version: '1.0'
115
+ - !ruby/object:Gem::Dependency
116
+ name: hierarchical_graph
117
+ requirement: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - "~>"
120
+ - !ruby/object:Gem::Version
121
+ version: '1.0'
122
+ type: :runtime
123
+ prerelease: false
124
+ version_requirements: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - "~>"
127
+ - !ruby/object:Gem::Version
128
+ version: '1.0'
129
+ - !ruby/object:Gem::Dependency
130
+ name: hash_ext
131
+ requirement: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - "~>"
134
+ - !ruby/object:Gem::Version
135
+ version: '0.5'
136
+ type: :runtime
137
+ prerelease: false
138
+ version_requirements: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - "~>"
141
+ - !ruby/object:Gem::Version
142
+ version: '0.5'
115
143
  - !ruby/object:Gem::Dependency
116
144
  name: rake
117
145
  requirement: !ruby/object:Gem::Requirement
118
146
  requirements:
119
147
  - - "~>"
120
148
  - !ruby/object:Gem::Version
121
- version: '11.0'
149
+ version: '12.3'
122
150
  type: :development
123
151
  prerelease: false
124
152
  version_requirements: !ruby/object:Gem::Requirement
125
153
  requirements:
126
154
  - - "~>"
127
155
  - !ruby/object:Gem::Version
128
- version: '11.0'
156
+ version: '12.3'
129
157
  - !ruby/object:Gem::Dependency
130
158
  name: minitest
131
159
  requirement: !ruby/object:Gem::Requirement
@@ -279,7 +307,7 @@ files:
279
307
  - lib/rasti/db/nql/syntax.treetop
280
308
  - lib/rasti/db/query.rb
281
309
  - lib/rasti/db/relations/base.rb
282
- - lib/rasti/db/relations/graph_builder.rb
310
+ - lib/rasti/db/relations/graph.rb
283
311
  - lib/rasti/db/relations/many_to_many.rb
284
312
  - lib/rasti/db/relations/many_to_one.rb
285
313
  - lib/rasti/db/relations/one_to_many.rb
@@ -1,60 +0,0 @@
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_name, nested_relations|
11
- relation = get_relation collection_class, relation_name
12
- relation.graph_to rows, db, schema, nested_relations
13
- end
14
- end
15
-
16
- def joins_to(dataset, relations, collection_class, schema=nil)
17
- ds = recursive_joins dataset, recursive_parse(relations), collection_class, schema
18
- qualified_collection_name = schema ? Sequel[schema][collection_class.collection_name] : Sequel[collection_class.collection_name]
19
- ds.distinct.select_all(qualified_collection_name)
20
- end
21
-
22
- private
23
-
24
- def get_relation(collection_class, relation_name)
25
- raise "Undefined relation #{relation_name} for #{collection_class}" unless collection_class.relations.key? relation_name
26
- collection_class.relations[relation_name]
27
- end
28
-
29
- def parse(relations)
30
- relations.each_with_object({}) do |relation, hash|
31
- tail = relation.to_s.split '.'
32
- head = tail.shift.to_sym
33
- hash[head] ||= []
34
- hash[head] << tail.join('.') unless tail.empty?
35
- end
36
- end
37
-
38
- def recursive_parse(relations)
39
- parse(relations).each_with_object({}) do |(key, value), hash|
40
- hash[key] = recursive_parse value
41
- end
42
- end
43
-
44
- def recursive_joins(dataset, joins, collection_class, schema, prefix=nil)
45
- joins.each do |relation_name, nested_joins|
46
- relation = get_relation collection_class, relation_name
47
-
48
- dataset = relation.join_to dataset, schema, prefix
49
-
50
- dataset = recursive_joins dataset, nested_joins, relation.target_collection_class, schema, relation.join_relation_name(prefix) unless nested_joins.empty?
51
- end
52
-
53
- dataset
54
- end
55
-
56
- end
57
- end
58
- end
59
- end
60
- end