datasource 0.0.5 → 0.0.6
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 +88 -19
- data/lib/datasource/adapters/active_record.rb +51 -17
- data/lib/datasource/adapters/sequel.rb +47 -13
- data/lib/datasource/attributes/computed_attribute.rb +8 -1
- data/lib/datasource/attributes/loader.rb +17 -0
- data/lib/datasource/base.rb +84 -37
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 89a34ecb59d8f4183295c43b4badd33aca5f5e62
|
4
|
+
data.tar.gz: b408320273ce49236bd022d7db00c1fce06bdfd5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0db668df6f4ae71223655a544d59b805e6bdd2694ab540f37dacc7962700d427799f00308d0cdf03a180a7e45f7d3891ff795e73def10c180bf9df5d92942df3
|
7
|
+
data.tar.gz: c69ae3fe12467516c7f24d090e7240a398e9e7d341dd82a1c54b459c57dd4fb2f9c796a6180a85e02fc304ea45bbf96361579af3249974df4fd73cc0a02dd997
|
data/README.md
CHANGED
@@ -18,12 +18,12 @@ Run install generator:
|
|
18
18
|
rails g datasource:install
|
19
19
|
```
|
20
20
|
|
21
|
-
|
21
|
+
#### ORM support
|
22
22
|
|
23
23
|
- ActiveRecord
|
24
24
|
- Sequel
|
25
25
|
|
26
|
-
|
26
|
+
#### Serializer support
|
27
27
|
|
28
28
|
- active_model_serializers
|
29
29
|
|
@@ -66,18 +66,16 @@ SELECT id, title, user_id FROM posts WHERE id IN (?)
|
|
66
66
|
```
|
67
67
|
|
68
68
|
### Model Methods / Virtual Attributes
|
69
|
-
You need to
|
70
|
-
The method itself can be either in the serializer or in your model, it doesn't matter.
|
71
|
-
|
72
|
-
You can list multiple dependency columns.
|
69
|
+
You need to use `computed` in a `datasource_module` block to specify what a method depends on. It can depend on database columns, other computed attributes or loaders.
|
73
70
|
|
74
71
|
```ruby
|
75
72
|
class User < ActiveRecord::Base
|
76
73
|
datasource_module do
|
77
74
|
computed :first_name_initial, :first_name
|
78
|
-
computed :
|
75
|
+
computed :both_initials, :first_name, :last_name
|
79
76
|
end
|
80
77
|
|
78
|
+
# method can be in model
|
81
79
|
def first_name_initial
|
82
80
|
first_name[0].upcase
|
83
81
|
end
|
@@ -86,8 +84,9 @@ end
|
|
86
84
|
class UserSerializer < ActiveModel::Serializer
|
87
85
|
attributes :first_name_initial, :last_name_initial
|
88
86
|
|
89
|
-
|
90
|
-
|
87
|
+
# method can also be in serializer
|
88
|
+
def both_initials
|
89
|
+
object.last_name[0].upcase + object.last_name[0].upcase
|
91
90
|
end
|
92
91
|
end
|
93
92
|
```
|
@@ -100,15 +99,15 @@ You will be reminded with an exception if you forget to do this.
|
|
100
99
|
|
101
100
|
### Show action
|
102
101
|
|
103
|
-
You will probably want to reuse the same preloading
|
104
|
-
You
|
105
|
-
the serializer class as an argument.
|
102
|
+
You will probably want to reuse the same preloading logic in your show action.
|
103
|
+
You will need to call `for_serializer` on the scope before you call `find`.
|
104
|
+
You can optionally give it the serializer class as an argument.
|
106
105
|
|
107
106
|
```ruby
|
108
|
-
class
|
107
|
+
class PostsController < ApplicationController
|
109
108
|
def show
|
110
109
|
post = Post.for_serializer.find(params[:id])
|
111
|
-
#
|
110
|
+
# more explicit:
|
112
111
|
# post = Post.for_serializer(PostSerializer).find(params[:id])
|
113
112
|
|
114
113
|
render json: post
|
@@ -116,9 +115,22 @@ class UsersController < ApplicationController
|
|
116
115
|
end
|
117
116
|
```
|
118
117
|
|
118
|
+
You can also use it on an existing record, but doing it this way may result in
|
119
|
+
an additional SQL query (for example if you use query attributes).
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
class UsersController < ApplicationController
|
123
|
+
def show
|
124
|
+
user = current_user.for_serializer
|
125
|
+
|
126
|
+
render json: user
|
127
|
+
end
|
128
|
+
end
|
129
|
+
```
|
130
|
+
|
119
131
|
## Advanced Usage
|
120
132
|
|
121
|
-
### Query
|
133
|
+
### Query attribute
|
122
134
|
|
123
135
|
You can specify a SQL fragment for `SELECT` and use that as an attribute on your
|
124
136
|
model. As a simple example you can concatenate 2 strings together in SQL:
|
@@ -141,11 +153,13 @@ end
|
|
141
153
|
SELECT users.id, (users.first_name || ' ' || users.last_name) AS full_name FROM users
|
142
154
|
```
|
143
155
|
|
144
|
-
|
156
|
+
Note: If you need data from another table, use a join in a loader (see below).
|
157
|
+
|
158
|
+
### Loader
|
145
159
|
|
146
160
|
You might want to have some more complex preloading logic. In that case you can use a loader.
|
147
|
-
|
148
|
-
The key of the hash must be the id of the record for which the
|
161
|
+
A loader will receive ids of the records, and needs to return a hash.
|
162
|
+
The key of the hash must be the id of the record for which the value is.
|
149
163
|
|
150
164
|
A loader will only be executed if a computed attribute depends on it. If an attribute depends
|
151
165
|
on multiple loaders, pass an array of loaders like so `computed :attr, loaders: [:loader1, :loader2]`.
|
@@ -156,7 +170,7 @@ will be nil. However you can use the `default` option for such cases (see below
|
|
156
170
|
```ruby
|
157
171
|
class User < ActiveRecord::Base
|
158
172
|
datasource_module do
|
159
|
-
computed :post_count,
|
173
|
+
computed :post_count, loader: :post_counts
|
160
174
|
loader :post_counts, array_to_hash: true, default: 0 do |user_ids|
|
161
175
|
results = Post
|
162
176
|
.where(user_id: user_ids)
|
@@ -210,3 +224,58 @@ loader :stuff, group_by: "user_id", one: true do |ids|
|
|
210
224
|
# { 10 => { "title" => "Something", "user_id" => 10 } }
|
211
225
|
end
|
212
226
|
```
|
227
|
+
|
228
|
+
### Loaded
|
229
|
+
|
230
|
+
Loaded is the same as loader, but it also creates a computed attribute and defines
|
231
|
+
a method with the same name on your model.
|
232
|
+
|
233
|
+
Here is the previous example with `loaded` instead of `loader`:
|
234
|
+
|
235
|
+
```ruby
|
236
|
+
class User < ActiveRecord::Base
|
237
|
+
datasource_module do
|
238
|
+
loaded :post_count, array_to_hash: true, default: 0 do |user_ids|
|
239
|
+
results = Post
|
240
|
+
.where(user_id: user_ids)
|
241
|
+
.group(:user_id)
|
242
|
+
.pluck("user_id, COUNT(id)")
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
class UserSerializer < ActiveModel::Serializer
|
248
|
+
attributes :id, :post_count
|
249
|
+
# Note that the User now has a generated post_count method
|
250
|
+
end
|
251
|
+
```
|
252
|
+
|
253
|
+
When using `loaded`, if you already have the method with this name defined in your
|
254
|
+
model, datasource will automatically create a 'wrapper' method that will use the
|
255
|
+
loaded value if available (when you are using a serializer/datasource), otherwise
|
256
|
+
it will fallback to your original method. This way you can still use the same
|
257
|
+
method when you are not using a serializer/datasource. For example:
|
258
|
+
|
259
|
+
```ruby
|
260
|
+
class User < ActiveRecord::Base
|
261
|
+
datasource_module do
|
262
|
+
loaded :post_count, array_to_hash: true, default: 0 do |user_ids|
|
263
|
+
results = Post
|
264
|
+
.where(user_id: user_ids)
|
265
|
+
.group(:user_id)
|
266
|
+
.pluck("user_id, COUNT(id)")
|
267
|
+
end
|
268
|
+
|
269
|
+
def post_count
|
270
|
+
posts.count
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
class UserSerializer < ActiveModel::Serializer
|
276
|
+
attributes :id, :post_count # <- post_count will be read from loaded_values
|
277
|
+
end
|
278
|
+
|
279
|
+
User.first.post_count # <- your method will be called
|
280
|
+
|
281
|
+
```
|
@@ -20,17 +20,22 @@ module Datasource
|
|
20
20
|
self
|
21
21
|
end
|
22
22
|
|
23
|
+
def get_datasource
|
24
|
+
datasource = @datasource.new(self)
|
25
|
+
datasource.select(*Array(@datasource_select))
|
26
|
+
if @datasource_serializer
|
27
|
+
select = []
|
28
|
+
Datasource::Base.consumer_adapter.to_datasource_select(select, @datasource.orm_klass, @datasource_serializer, nil, datasource.adapter)
|
29
|
+
|
30
|
+
datasource.select(*select)
|
31
|
+
end
|
32
|
+
datasource
|
33
|
+
end
|
34
|
+
|
23
35
|
private
|
24
36
|
def exec_queries
|
25
37
|
if @datasource
|
26
|
-
datasource =
|
27
|
-
datasource.select(*Array(@datasource_select))
|
28
|
-
if @datasource_serializer
|
29
|
-
select = []
|
30
|
-
Datasource::Base.consumer_adapter.to_datasource_select(select, @datasource.orm_klass, @datasource_serializer, nil, datasource.adapter)
|
31
|
-
|
32
|
-
datasource.select(*select)
|
33
|
-
end
|
38
|
+
datasource = get_datasource
|
34
39
|
|
35
40
|
@loaded = true
|
36
41
|
@records = datasource.results
|
@@ -47,6 +52,22 @@ module Datasource
|
|
47
52
|
attr_accessor :loaded_values
|
48
53
|
end
|
49
54
|
|
55
|
+
def for_serializer(serializer = nil)
|
56
|
+
datasource_class = self.class.default_datasource
|
57
|
+
pk = datasource_class.primary_key.to_sym
|
58
|
+
|
59
|
+
scope = self.class
|
60
|
+
.with_datasource(datasource_class)
|
61
|
+
.for_serializer(serializer).where(pk => send(pk))
|
62
|
+
|
63
|
+
datasource = scope.get_datasource
|
64
|
+
if datasource.can_upgrade?(self)
|
65
|
+
datasource.upgrade_records(self).first
|
66
|
+
else
|
67
|
+
scope.first
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
50
71
|
module ClassMethods
|
51
72
|
def for_serializer(serializer = nil)
|
52
73
|
scope = if all.respond_to?(:use_datasource_serializer)
|
@@ -67,7 +88,7 @@ module Datasource
|
|
67
88
|
end
|
68
89
|
|
69
90
|
def default_datasource
|
70
|
-
@default_datasource ||=
|
91
|
+
@default_datasource ||= Datasource::From(self)
|
71
92
|
end
|
72
93
|
|
73
94
|
def datasource_module(&block)
|
@@ -103,6 +124,10 @@ module Datasource
|
|
103
124
|
scope.loaded?
|
104
125
|
end
|
105
126
|
|
127
|
+
def has_attribute?(record, name)
|
128
|
+
record.attributes.key?(name.to_s)
|
129
|
+
end
|
130
|
+
|
106
131
|
def association_klass(reflection)
|
107
132
|
if reflection.macro == :belongs_to && reflection.options[:polymorphic]
|
108
133
|
fail Datasource::Error, "polymorphic belongs_to not supported, write custom loader"
|
@@ -111,7 +136,7 @@ module Datasource
|
|
111
136
|
end
|
112
137
|
end
|
113
138
|
|
114
|
-
def
|
139
|
+
def load_association(records, name)
|
115
140
|
return if records.empty?
|
116
141
|
return if records.first.association(name.to_sym).loaded?
|
117
142
|
klass = records.first.class
|
@@ -125,7 +150,7 @@ module Datasource
|
|
125
150
|
scope = assoc_class.all
|
126
151
|
datasource = datasource_class.new(scope)
|
127
152
|
datasource_select = serializer_class._attributes.dup
|
128
|
-
Datasource::Base.reflection_select(
|
153
|
+
Datasource::Base.reflection_select(association_reflection(klass, name.to_sym), [], datasource_select)
|
129
154
|
datasource.select(*datasource_select)
|
130
155
|
select_values = datasource.get_select_values
|
131
156
|
|
@@ -139,7 +164,7 @@ module Datasource
|
|
139
164
|
|
140
165
|
assoc_records = records.flat_map { |record| record.send(name) }.compact
|
141
166
|
serializer_class._associations.each_pair do |assoc_name, options|
|
142
|
-
|
167
|
+
load_association(assoc_records, assoc_name)
|
143
168
|
end
|
144
169
|
datasource.results(assoc_records)
|
145
170
|
end
|
@@ -161,10 +186,21 @@ module Datasource
|
|
161
186
|
ds.scope.select(*ds.get_select_values)
|
162
187
|
end
|
163
188
|
|
189
|
+
def upgrade_records(ds, records)
|
190
|
+
load_associations(ds, records)
|
191
|
+
ds.results(records)
|
192
|
+
end
|
193
|
+
|
194
|
+
def load_associations(ds, records)
|
195
|
+
ds.expose_associations.each_pair do |assoc_name, assoc_select|
|
196
|
+
load_association(records, assoc_name)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
164
200
|
def get_rows(ds)
|
165
201
|
append_select = []
|
166
202
|
ds.expose_associations.each_pair do |assoc_name, assoc_select|
|
167
|
-
if reflection =
|
203
|
+
if reflection = association_reflection(ds.class.orm_klass, assoc_name.to_sym)
|
168
204
|
Datasource::Base.reflection_select(reflection, append_select, [])
|
169
205
|
end
|
170
206
|
end
|
@@ -176,9 +212,7 @@ module Datasource
|
|
176
212
|
end
|
177
213
|
scope.includes_values = []
|
178
214
|
scope.to_a.tap do |records|
|
179
|
-
ds
|
180
|
-
Adapters::ActiveRecord.preload_association(records, assoc_name)
|
181
|
-
end
|
215
|
+
load_associations(ds, records)
|
182
216
|
end
|
183
217
|
end
|
184
218
|
|
@@ -214,7 +248,7 @@ module Datasource
|
|
214
248
|
Datasource::Adapters::ActiveRecord
|
215
249
|
end
|
216
250
|
|
217
|
-
|
251
|
+
define_singleton_method(:primary_key) do
|
218
252
|
klass.primary_key.to_sym
|
219
253
|
end
|
220
254
|
end
|
@@ -14,6 +14,18 @@ module Datasource
|
|
14
14
|
self
|
15
15
|
end
|
16
16
|
|
17
|
+
def get_datasource
|
18
|
+
datasource = @datasource.new(self)
|
19
|
+
datasource.select(*Array(@datasource_select))
|
20
|
+
if @datasource_serializer
|
21
|
+
select = []
|
22
|
+
Datasource::Base.consumer_adapter.to_datasource_select(select, @datasource.orm_klass, @datasource_serializer, nil, datasource.adapter)
|
23
|
+
|
24
|
+
datasource.select(*select)
|
25
|
+
end
|
26
|
+
datasource
|
27
|
+
end
|
28
|
+
|
17
29
|
def datasource_select(*args)
|
18
30
|
@datasource_select = Array(@datasource_select) + args
|
19
31
|
self
|
@@ -21,14 +33,7 @@ module Datasource
|
|
21
33
|
|
22
34
|
def each(&block)
|
23
35
|
if @datasource
|
24
|
-
datasource =
|
25
|
-
datasource.select(*Array(@datasource_select))
|
26
|
-
if @datasource_serializer
|
27
|
-
select = []
|
28
|
-
Datasource::Base.consumer_adapter.to_datasource_select(select, @datasource.orm_klass, @datasource_serializer, nil, datasource.adapter)
|
29
|
-
|
30
|
-
datasource.select(*select)
|
31
|
-
end
|
36
|
+
datasource = get_datasource
|
32
37
|
|
33
38
|
datasource.results.each(&block)
|
34
39
|
else
|
@@ -64,9 +69,25 @@ module Datasource
|
|
64
69
|
end
|
65
70
|
end
|
66
71
|
|
72
|
+
def for_serializer(serializer = nil)
|
73
|
+
datasource_class = self.class.default_datasource
|
74
|
+
pk = datasource_class.primary_key.to_sym
|
75
|
+
|
76
|
+
scope = self.class
|
77
|
+
.with_datasource(datasource_class)
|
78
|
+
.for_serializer(serializer).where(pk => send(pk))
|
79
|
+
|
80
|
+
datasource = scope.get_datasource
|
81
|
+
if datasource.can_upgrade?(self)
|
82
|
+
datasource.upgrade_records(self).first
|
83
|
+
else
|
84
|
+
scope.first
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
67
88
|
module ClassMethods
|
68
89
|
def default_datasource
|
69
|
-
@default_datasource ||=
|
90
|
+
@default_datasource ||= Datasource::From(self)
|
70
91
|
end
|
71
92
|
|
72
93
|
def datasource_module(&block)
|
@@ -113,8 +134,12 @@ module Datasource
|
|
113
134
|
false
|
114
135
|
end
|
115
136
|
|
137
|
+
def has_attribute?(record, name)
|
138
|
+
record.values.key?(name.to_sym)
|
139
|
+
end
|
140
|
+
|
116
141
|
def get_assoc_eager_options(klass, name, assoc_select, append_select)
|
117
|
-
if reflection =
|
142
|
+
if reflection = association_reflection(klass, name)
|
118
143
|
self_append_select = []
|
119
144
|
Datasource::Base.reflection_select(reflection, append_select, self_append_select)
|
120
145
|
assoc_class = reflection[:klass]
|
@@ -144,7 +169,12 @@ module Datasource
|
|
144
169
|
ds.scope.select(*get_sequel_select_values(ds.get_select_values))
|
145
170
|
end
|
146
171
|
|
147
|
-
def
|
172
|
+
def upgrade_records(ds, records)
|
173
|
+
get_final_scope(ds).send :post_load, records
|
174
|
+
ds.results(records)
|
175
|
+
end
|
176
|
+
|
177
|
+
def get_final_scope(ds)
|
148
178
|
eager = {}
|
149
179
|
append_select = []
|
150
180
|
ds.expose_associations.each_pair do |assoc_name, assoc_select|
|
@@ -158,7 +188,11 @@ module Datasource
|
|
158
188
|
end
|
159
189
|
scope
|
160
190
|
.select_append(*get_sequel_select_values(append_select.map { |v| primary_scope_table(ds) + ".#{v}" }))
|
161
|
-
.eager(eager)
|
191
|
+
.eager(eager)
|
192
|
+
end
|
193
|
+
|
194
|
+
def get_rows(ds)
|
195
|
+
get_final_scope(ds).all
|
162
196
|
end
|
163
197
|
|
164
198
|
def primary_scope_table(ds)
|
@@ -187,7 +221,7 @@ module Datasource
|
|
187
221
|
Datasource::Adapters::Sequel
|
188
222
|
end
|
189
223
|
|
190
|
-
|
224
|
+
define_singleton_method(:primary_key) do
|
191
225
|
klass.primary_key
|
192
226
|
end
|
193
227
|
end
|
@@ -2,16 +2,23 @@ module Datasource
|
|
2
2
|
module Attributes
|
3
3
|
class ComputedAttribute
|
4
4
|
class << self
|
5
|
-
attr_accessor :_depends
|
5
|
+
attr_accessor :_depends, :_loader_depends
|
6
6
|
|
7
7
|
def inherited(base)
|
8
8
|
base._depends = (_depends || {}).dup # TODO: deep dup?
|
9
|
+
base._loader_depends = (_loader_depends || []).dup # TODO: deep dup?
|
9
10
|
end
|
10
11
|
|
11
12
|
def depends(*args)
|
12
13
|
args.each do |dep|
|
13
14
|
_depends.deep_merge!(dep)
|
14
15
|
end
|
16
|
+
_depends.delete_if do |key, value|
|
17
|
+
if [:loaders, :loader].include?(key.to_sym)
|
18
|
+
self._loader_depends += Array(value).map(&:to_sym)
|
19
|
+
true
|
20
|
+
end
|
21
|
+
end
|
15
22
|
end
|
16
23
|
end
|
17
24
|
end
|
@@ -65,5 +65,22 @@ module Datasource
|
|
65
65
|
end
|
66
66
|
@_loaders[name.to_sym] = klass
|
67
67
|
end
|
68
|
+
|
69
|
+
def self.loaded(name, _options = {}, &block)
|
70
|
+
loader(name, _options, &block)
|
71
|
+
renamed_existing_method = :"#{name}_without_datasource"
|
72
|
+
orm_klass.class_eval do
|
73
|
+
alias_method renamed_existing_method, name if method_defined?(name)
|
74
|
+
fail "#{name} already defined on #{to_s}, would be overridden by datasource loaded method" if method_defined?(name)
|
75
|
+
define_method name do |*args, &block|
|
76
|
+
if loaded_values || !method_defined?(renamed_existing_method)
|
77
|
+
loaded_values[name.to_sym]
|
78
|
+
else
|
79
|
+
send(renamed_existing_method, *args, &block)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
computed name, loader: name
|
84
|
+
end
|
68
85
|
end
|
69
86
|
end
|
data/lib/datasource/base.rb
CHANGED
@@ -24,6 +24,10 @@ module Datasource
|
|
24
24
|
fail Datasource::Error, "Model class not set for #{name}. You should define it:\nclass YourDatasource\n @orm_klass = MyModelClass\nend"
|
25
25
|
end
|
26
26
|
|
27
|
+
def primary_key
|
28
|
+
:id
|
29
|
+
end
|
30
|
+
|
27
31
|
def reflection_select(reflection, parent_select, assoc_select)
|
28
32
|
# append foreign key depending on assoication
|
29
33
|
if reflection[:macro] == :belongs_to
|
@@ -81,16 +85,14 @@ module Datasource
|
|
81
85
|
@expose_associations = {}
|
82
86
|
end
|
83
87
|
|
84
|
-
def primary_key
|
85
|
-
:id
|
86
|
-
end
|
87
|
-
|
88
88
|
def select_all
|
89
89
|
@expose_attributes = self.class._attributes.keys.dup
|
90
90
|
end
|
91
91
|
|
92
92
|
def select(*names)
|
93
93
|
failure = ->(name) { fail Datasource::Error, "attribute or association #{name} doesn't exist for #{self.class.orm_klass.name}, did you forget to call \"computed :#{name}, <dependencies>\" in your datasource_module?" }
|
94
|
+
newly_exposed_attributes = []
|
95
|
+
missing_attributes = []
|
94
96
|
names.each do |name|
|
95
97
|
if name.kind_of?(Hash)
|
96
98
|
name.each_pair do |assoc_name, assoc_select|
|
@@ -100,53 +102,80 @@ module Datasource
|
|
100
102
|
@expose_associations[assoc_name] += Array(assoc_select)
|
101
103
|
@expose_associations[assoc_name].uniq!
|
102
104
|
else
|
105
|
+
missing_attributes << assoc_name
|
103
106
|
failure.call(assoc_name)
|
104
107
|
end
|
105
108
|
end
|
106
109
|
else
|
107
110
|
name = name.to_s
|
108
111
|
if self.class._attributes.key?(name)
|
109
|
-
@expose_attributes.
|
112
|
+
unless @expose_attributes.include?(name)
|
113
|
+
@expose_attributes.push(name)
|
114
|
+
newly_exposed_attributes.push(name)
|
115
|
+
end
|
110
116
|
else
|
117
|
+
missing_attributes << name
|
111
118
|
failure.call(name)
|
112
119
|
end
|
113
120
|
end
|
114
121
|
end
|
115
|
-
|
122
|
+
update_dependencies(newly_exposed_attributes) unless newly_exposed_attributes.empty?
|
123
|
+
fail_missing_attributes(missing_attributes) unless missing_attributes.blank?
|
116
124
|
self
|
117
125
|
end
|
118
126
|
|
127
|
+
def fail_missing_attributes(names)
|
128
|
+
message = if names.size > 1
|
129
|
+
"attributes or associations #{names.join(', ')} don't exist "
|
130
|
+
else
|
131
|
+
"attribute or association #{names.first} doesn't exist "
|
132
|
+
end
|
133
|
+
message += "for #{self.class.orm_klass.name}, "
|
134
|
+
message += "did you forget to call \"computed :#{name}, <dependencies>\" in your datasource_module?"
|
135
|
+
fail Datasource::Error, message
|
136
|
+
end
|
137
|
+
|
138
|
+
def update_dependencies(names)
|
139
|
+
scope_table = adapter.primary_scope_table(self)
|
140
|
+
|
141
|
+
self.class._attributes.values.each do |att|
|
142
|
+
next unless names.include?(att[:name])
|
143
|
+
next unless att[:klass]
|
144
|
+
|
145
|
+
if att[:klass].ancestors.include?(Attributes::ComputedAttribute)
|
146
|
+
att[:klass]._depends.each_pair do |key, value|
|
147
|
+
if key.to_s == scope_table
|
148
|
+
select(*value)
|
149
|
+
else
|
150
|
+
select(key => value)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
elsif att[:klass].ancestors.include?(Attributes::QueryAttribute)
|
154
|
+
att[:klass]._depends.each do |name|
|
155
|
+
next if name == scope_table
|
156
|
+
adapter.ensure_table_join!(self, name, att)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
119
162
|
def get_select_values
|
120
163
|
scope_table = adapter.primary_scope_table(self)
|
164
|
+
|
165
|
+
# SQL select values
|
121
166
|
select_values = Set.new
|
122
|
-
select_values.add("#{scope_table}.#{primary_key}")
|
167
|
+
select_values.add("#{scope_table}.#{self.class.primary_key}")
|
123
168
|
|
124
169
|
self.class._attributes.values.each do |att|
|
125
170
|
if attribute_exposed?(att[:name])
|
126
171
|
if att[:klass] == nil
|
127
172
|
select_values.add("#{scope_table}.#{att[:name]}")
|
128
|
-
elsif att[:klass].ancestors.include?(Attributes::ComputedAttribute)
|
129
|
-
att[:klass]._depends.keys.map(&:to_s).each do |name|
|
130
|
-
next if name == scope_table
|
131
|
-
next if name == "loaders"
|
132
|
-
adapter.ensure_table_join!(self, name, att)
|
133
|
-
end
|
134
|
-
att[:klass]._depends.each_pair do |table, names|
|
135
|
-
next if table.to_sym == :loaders
|
136
|
-
Array(names).each do |name|
|
137
|
-
select_values.add("#{table}.#{name}")
|
138
|
-
end
|
139
|
-
# TODO: handle depends on virtual attribute
|
140
|
-
end
|
141
173
|
elsif att[:klass].ancestors.include?(Attributes::QueryAttribute)
|
142
174
|
select_values.add("(#{att[:klass].select_value}) as #{att[:name]}")
|
143
|
-
att[:klass]._depends.each do |name|
|
144
|
-
next if name == scope_table
|
145
|
-
adapter.ensure_table_join!(self, name, att)
|
146
|
-
end
|
147
175
|
end
|
148
176
|
end
|
149
177
|
end
|
178
|
+
|
150
179
|
select_values.to_a
|
151
180
|
end
|
152
181
|
|
@@ -154,6 +183,27 @@ module Datasource
|
|
154
183
|
@expose_attributes.include?(name.to_s)
|
155
184
|
end
|
156
185
|
|
186
|
+
# assume records have all attributes selected (default ORM record)
|
187
|
+
def can_upgrade?(records)
|
188
|
+
query_attributes = @expose_attributes.select do |name|
|
189
|
+
klass = self.class._attributes[name][:klass]
|
190
|
+
if klass
|
191
|
+
klass.ancestors.include?(Attributes::QueryAttribute)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
return true if query_attributes.empty?
|
196
|
+
Array(records).all? do |record|
|
197
|
+
query_attributes.all? do |name|
|
198
|
+
adapter.has_attribute?(record, name)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def upgrade_records(records)
|
204
|
+
adapter.upgrade_records(self, Array(records))
|
205
|
+
end
|
206
|
+
|
157
207
|
def results(rows = nil)
|
158
208
|
rows ||= adapter.get_rows(self)
|
159
209
|
|
@@ -166,23 +216,20 @@ module Datasource
|
|
166
216
|
next if rows.empty?
|
167
217
|
|
168
218
|
if att[:klass].ancestors.include?(Attributes::ComputedAttribute)
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
if loaded_values = loader.load(rows.map(&primary_key), rows, @scope)
|
174
|
-
unless rows.first.loaded_values
|
175
|
-
rows.each do |row|
|
176
|
-
row.loaded_values = {}
|
177
|
-
end
|
178
|
-
end
|
219
|
+
att[:klass]._loader_depends.each do |name|
|
220
|
+
if loader = self.class._loaders[name]
|
221
|
+
if loaded_values = loader.load(rows.map(&self.class.primary_key), rows, @scope)
|
222
|
+
unless rows.first.loaded_values
|
179
223
|
rows.each do |row|
|
180
|
-
row.loaded_values
|
224
|
+
row.loaded_values = {}
|
181
225
|
end
|
182
226
|
end
|
183
|
-
|
184
|
-
|
227
|
+
rows.each do |row|
|
228
|
+
row.loaded_values[name] = loaded_values[row.send(self.class.primary_key)] || loader.default_value
|
229
|
+
end
|
185
230
|
end
|
231
|
+
else
|
232
|
+
raise Datasource::Error, "loader with name :#{name} could not be found"
|
186
233
|
end
|
187
234
|
end
|
188
235
|
end
|