datasource 0.2.0 → 0.3.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 +6 -237
- data/lib/datasource.rb +6 -7
- data/lib/datasource/adapters/active_record.rb +37 -22
- data/lib/datasource/adapters/sequel.rb +42 -23
- data/lib/datasource/base.rb +10 -9
- data/lib/datasource/configuration.rb +1 -1
- data/lib/generators/datasource/templates/initializer.rb +1 -6
- metadata +2 -17
- data/lib/datasource/consumer_adapters/active_model_serializers.rb +0 -138
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: acadf2996a54023f7ea9ec464ae0b82ad0c57dc9
|
4
|
+
data.tar.gz: 7f1759d99758a076f66787bb152c147cbb0ba714
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a8d565d3e8d3e2e44ee7c3c22f27f8e776b10f3b301c6f252a996b9130d1a39c15da04e92530d1a5eb81b892815d7377b61d2aaadeadd1841c22df1c5199a0ab
|
7
|
+
data.tar.gz: c40d2192fcdaf926b25f4ece6f8378a763e63459d1ff5a673d06a14e32fbe2183c26b43de1a3f49955b5c73eb9ab6a18bfbc6c6e03b3f78dbefa0d6930a83d5f
|
data/README.md
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# Datasource
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
- Write custom preloading logic in a reusable way
|
3
|
+
**Please see gem [active_loaders](https://github.com/kundi/active_loaders)
|
4
|
+
documentation for now, it includes all the necessary information**
|
6
5
|
|
7
|
-
|
6
|
+
Documentation for datasource will be updated later. Active Model Serializer support
|
7
|
+
was extracted into the active_loaders gem.
|
8
8
|
|
9
9
|
#### Install
|
10
10
|
|
@@ -32,237 +32,6 @@ rails g datasource:install
|
|
32
32
|
- ActiveRecord
|
33
33
|
- Sequel
|
34
34
|
|
35
|
-
#### Serializer support
|
36
|
-
|
37
|
-
- active_model_serializers
|
38
|
-
|
39
|
-
### Associations
|
40
|
-
|
41
|
-
The most noticable magic effect of using Datasource is that associations will
|
42
|
-
automatically be preloaded using a single query.
|
43
|
-
|
44
|
-
```ruby
|
45
|
-
class PostSerializer < ActiveModel::Serializer
|
46
|
-
attributes :id, :title
|
47
|
-
end
|
48
|
-
|
49
|
-
class UserSerializer < ActiveModel::Serializer
|
50
|
-
attributes :id
|
51
|
-
has_many :posts
|
52
|
-
end
|
53
|
-
```
|
54
|
-
```sql
|
55
|
-
SELECT users.* FROM users
|
56
|
-
SELECT posts.* FROM posts WHERE id IN (?)
|
57
|
-
```
|
58
|
-
|
59
|
-
This means you **do not** need to call `includes` yourself. It will be done
|
60
|
-
automatically by Datasource.
|
61
|
-
|
62
|
-
### Show action
|
63
|
-
|
64
|
-
If you use the more advanced features like Loaded, you will probably want to
|
65
|
-
reuse the same loading logic in your show action.
|
66
|
-
You will need to call `for_serializer` on the scope before you call `find`.
|
67
|
-
You can optionally give it the serializer class as an argument.
|
68
|
-
|
69
|
-
```ruby
|
70
|
-
class PostsController < ApplicationController
|
71
|
-
def show
|
72
|
-
post = Post.for_serializer.find(params[:id])
|
73
|
-
# more explicit:
|
74
|
-
# post = Post.for_serializer(PostSerializer).find(params[:id])
|
75
|
-
|
76
|
-
render json: post
|
77
|
-
end
|
78
|
-
end
|
79
|
-
```
|
80
|
-
|
81
|
-
You can also use it on an existing record, but doing it this way may result in
|
82
|
-
an additional SQL query (for example if you use query attributes).
|
83
|
-
|
84
|
-
```ruby
|
85
|
-
class UsersController < ApplicationController
|
86
|
-
def show
|
87
|
-
user = current_user.for_serializer
|
88
|
-
|
89
|
-
render json: user
|
90
|
-
end
|
91
|
-
end
|
92
|
-
```
|
93
|
-
|
94
|
-
### Query attribute
|
95
|
-
|
96
|
-
You can specify a SQL fragment for `SELECT` and use that as an attribute on your
|
97
|
-
model. As a simple example you can concatenate 2 strings together in SQL:
|
98
|
-
|
99
|
-
```ruby
|
100
|
-
class User < ActiveRecord::Base
|
101
|
-
datasource_module do
|
102
|
-
query :full_name do
|
103
|
-
"users.first_name || ' ' || users.last_name"
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
class UserSerializer < ActiveModel::Serializer
|
109
|
-
attributes :id, :full_name
|
110
|
-
end
|
111
|
-
```
|
112
|
-
|
113
|
-
```sql
|
114
|
-
SELECT users.*, (users.first_name || ' ' || users.last_name) AS full_name FROM users
|
115
|
-
```
|
116
|
-
|
117
|
-
Note: If you need data from another table, use a join in a loaded value (see below).
|
118
|
-
|
119
|
-
### Standalone Datasource class
|
120
|
-
|
121
|
-
If you are going to have more complex preloading logic (like using Loaded below),
|
122
|
-
then it might be better to put Datasource code into its own class. This is pretty
|
123
|
-
easy, just create a directory `app/datasources` (or whatever you like), and create
|
124
|
-
a file depending on your model name, for example for a `Post` model, create
|
125
|
-
`post_datasource.rb`. The name is important for auto-magic reasons. Example file:
|
126
|
-
|
127
|
-
```ruby
|
128
|
-
class PostDatasource < Datasource::From(Post)
|
129
|
-
query(:full_name) { "users.first_name || ' ' || users.last_name" }
|
130
|
-
end
|
131
|
-
```
|
132
|
-
|
133
|
-
### Loaded
|
134
|
-
|
135
|
-
You might want to have some more complex preloading logic. In that case you can
|
136
|
-
use a method to load values for all the records at once (e.g. with a custom query
|
137
|
-
or even from a cache). The loading methods are only executed if you use the values,
|
138
|
-
otherwise they will be skipped.
|
139
|
-
|
140
|
-
First just declare that you want to have a loaded attribute (the parameters will be explained shortly):
|
141
|
-
|
142
|
-
```ruby
|
143
|
-
class UserDatasource < Datasource::From(User)
|
144
|
-
loaded :post_count, from: :array, default: 0
|
145
|
-
end
|
146
|
-
```
|
147
|
-
|
148
|
-
By default, datasource will look for a method named `load_<name>` for loading
|
149
|
-
the values, in this case `load_newest_comment`. It needs to be defined in the
|
150
|
-
collection block, which has methods to access information about the collection (posts)
|
151
|
-
that are being loaded. These methods are `scope`, `models`, `model_ids`,
|
152
|
-
`datasource`, `datasource_class` and `params`.
|
153
|
-
|
154
|
-
```ruby
|
155
|
-
class UserDatasource < Datasource::From(User)
|
156
|
-
loaded :post_count, from: :array, default: 0
|
157
|
-
|
158
|
-
collection do
|
159
|
-
def load_post_count
|
160
|
-
Post.where(user_id: model_ids)
|
161
|
-
.group(:user_id)
|
162
|
-
.pluck("user_id, COUNT(id)")
|
163
|
-
end
|
164
|
-
end
|
165
|
-
end
|
166
|
-
```
|
167
|
-
|
168
|
-
In this case `load_post_count` returns an array of pairs.
|
169
|
-
For example: `[[1, 10], [2, 5]]`. Datasource can understand this because of
|
170
|
-
`from: :array`. This would result in the following:
|
171
|
-
|
172
|
-
```ruby
|
173
|
-
post_id_1.post_count # => 10
|
174
|
-
post_id_2.post_count # => 5
|
175
|
-
# other posts will have the default value or nil if no default value was given
|
176
|
-
other_post.post_count # => 0
|
177
|
-
```
|
178
|
-
|
179
|
-
Besides `default` and `from: :array`, you can also specify `group_by`, `one`
|
180
|
-
and `source`. Source is just the name of the load method.
|
181
|
-
|
182
|
-
The other two are explained in the following example.
|
183
|
-
|
184
|
-
```ruby
|
185
|
-
class PostDatasource < Datasource::From(Post)
|
186
|
-
loaded :newest_comment, group_by: :post_id, one: true, source: :load_newest_comment
|
187
|
-
|
188
|
-
collection do
|
189
|
-
def load_newest_comment
|
190
|
-
Comment.for_serializer.where(post_id: model_ids)
|
191
|
-
.group("post_id")
|
192
|
-
.having("id = MAX(id)")
|
193
|
-
end
|
194
|
-
end
|
195
|
-
end
|
196
|
-
```
|
197
|
-
|
198
|
-
In this case the load method returns an ActiveRecord relation, which for our purposes
|
199
|
-
acts the same as an Array (so we could also return an Array if we wanted).
|
200
|
-
Using `group_by: :post_id` in the `loaded` call tells datasource to group the
|
201
|
-
results in this array by that attribute (or key if it's an array of hashes instead
|
202
|
-
of model objects). `one: true` means that we only want a single value instead of
|
203
|
-
an array of values (we might want multiple, e.g. `newest_10_comments`).
|
204
|
-
So in this case, if we had a Post with id 1, `post.newest_comment` would be a
|
205
|
-
Comment from the array that has `post_id` equal to 1.
|
206
|
-
|
207
|
-
In this case, in the load method, we also used `for_serializer`, which will load
|
208
|
-
the `Comment`s according to the `CommentSerializer`.
|
209
|
-
|
210
|
-
Note that it's perfectly fine (even good) to already have a method with the same
|
211
|
-
name in your model.
|
212
|
-
If you use that method outside of serializers/datasource, it will work just as
|
213
|
-
it should. But when using datasource, it will be overwritten by the datasource
|
214
|
-
version. Counts is a good example:
|
215
|
-
|
216
|
-
```ruby
|
217
|
-
class User < ActiveRecord::Base
|
218
|
-
has_many :posts
|
219
|
-
|
220
|
-
def post_count
|
221
|
-
posts.count
|
222
|
-
end
|
223
|
-
end
|
224
|
-
|
225
|
-
class UserDatasource < Datasource::From(User)
|
226
|
-
loaded :post_count, from: :array, default: 0
|
227
|
-
|
228
|
-
collection do
|
229
|
-
def load_post_count
|
230
|
-
Post.where(user_id: model_ids)
|
231
|
-
.group(:user_id)
|
232
|
-
.pluck("user_id, COUNT(id)")
|
233
|
-
end
|
234
|
-
end
|
235
|
-
end
|
236
|
-
|
237
|
-
class UserSerializer < ActiveModel::Serializer
|
238
|
-
attributes :id, :post_count # <- post_count will be read from load_post_count
|
239
|
-
end
|
240
|
-
|
241
|
-
User.first.post_count # <- your model method will be called
|
242
|
-
```
|
243
|
-
|
244
|
-
### Params
|
245
|
-
|
246
|
-
You can also specify params that can be read from collection methods. The params
|
247
|
-
can be specified when you call `render`:
|
248
|
-
|
249
|
-
```ruby
|
250
|
-
# controller
|
251
|
-
render json: posts,
|
252
|
-
datasource_params: { include_newest_comments: true }
|
253
|
-
|
254
|
-
# datasource
|
255
|
-
loaded :newest_comments, default: []
|
256
|
-
|
257
|
-
collection do
|
258
|
-
def load_newest_comments
|
259
|
-
if params[:include_newest_comments]
|
260
|
-
# ...
|
261
|
-
end
|
262
|
-
end
|
263
|
-
end
|
264
|
-
```
|
265
|
-
|
266
35
|
### Debugging and logging
|
267
36
|
|
268
37
|
Datasource outputs some useful logs that you can use debugging. By default the log level is
|
@@ -270,11 +39,11 @@ set to warnings only, but you can change it. You can add the following line to y
|
|
270
39
|
`config/initializers/datasource.rb`:
|
271
40
|
|
272
41
|
```ruby
|
273
|
-
Datasource.logger.level = Logger::INFO
|
42
|
+
Datasource.logger.level = Logger::INFO unless Rails.env.production?
|
274
43
|
```
|
275
44
|
|
276
45
|
You can also set it to `DEBUG` for more output. The logger outputs to `stdout` by default. It
|
277
|
-
is not recommended to have this enabled in production.
|
46
|
+
is not recommended to have this enabled in production (simply for performance reasons).
|
278
47
|
|
279
48
|
## Getting Help
|
280
49
|
|
data/lib/datasource.rb
CHANGED
@@ -9,9 +9,7 @@ module Datasource
|
|
9
9
|
AdapterPaths = {
|
10
10
|
activerecord: 'datasource/adapters/active_record',
|
11
11
|
active_record: :activerecord,
|
12
|
-
sequel: 'datasource/adapters/sequel'
|
13
|
-
ams: 'datasource/consumer_adapters/active_model_serializers',
|
14
|
-
active_model_serializers: :ams
|
12
|
+
sequel: 'datasource/adapters/sequel'
|
15
13
|
}
|
16
14
|
|
17
15
|
module_function
|
@@ -26,10 +24,11 @@ module_function
|
|
26
24
|
|
27
25
|
yield(config)
|
28
26
|
|
29
|
-
config.adapters.each do |
|
30
|
-
|
31
|
-
|
32
|
-
|
27
|
+
config.adapters.each do |adapter_name|
|
28
|
+
adapter_path = AdapterPaths[adapter_name]
|
29
|
+
adapter_path = AdapterPaths[adapter_path] if adapter_path.is_a?(Symbol)
|
30
|
+
fail "Unknown Datasource adapter '#{adapter_name}'." unless adapter_path
|
31
|
+
require adapter_path
|
33
32
|
end
|
34
33
|
end
|
35
34
|
|
@@ -11,6 +11,10 @@ module Datasource
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
+
def datasource_get(key)
|
15
|
+
@datasource_info[key]
|
16
|
+
end
|
17
|
+
|
14
18
|
def datasource_set(hash)
|
15
19
|
@datasource_info.merge!(hash)
|
16
20
|
self
|
@@ -33,7 +37,7 @@ module Datasource
|
|
33
37
|
datasource.params(*@datasource_info[:params])
|
34
38
|
if @datasource_info[:serializer_class]
|
35
39
|
select = []
|
36
|
-
|
40
|
+
@datasource_info[:serializer_class].datasource_adapter.to_datasource_select(select, klass.orm_klass, @datasource_info[:serializer_class], nil, datasource.adapter, datasource)
|
37
41
|
|
38
42
|
datasource.select(*select)
|
39
43
|
end
|
@@ -63,31 +67,19 @@ module Datasource
|
|
63
67
|
attr_accessor :_datasource_loaded, :_datasource_instance
|
64
68
|
end
|
65
69
|
|
66
|
-
def for_serializer(
|
67
|
-
|
68
|
-
pk = datasource_class.primary_key.to_sym
|
69
|
-
|
70
|
-
scope = self.class
|
71
|
-
.with_datasource(datasource_class)
|
72
|
-
.for_serializer(serializer)
|
73
|
-
.where(pk => send(pk))
|
74
|
-
|
75
|
-
scope = yield(scope) if block_given?
|
76
|
-
|
77
|
-
datasource = scope.get_datasource
|
78
|
-
if datasource.can_upgrade?(self)
|
79
|
-
datasource.upgrade_records(self).first
|
80
|
-
else
|
81
|
-
scope.first
|
82
|
-
end
|
70
|
+
def for_serializer(serializer_class = nil, datasource_class = nil)
|
71
|
+
self.class.upgrade_for_serializer([self], serializer_class, datasource_class).first
|
83
72
|
end
|
84
73
|
|
85
74
|
module ClassMethods
|
86
75
|
def for_serializer(serializer_class = nil)
|
87
|
-
|
76
|
+
if Datasource::Base.default_consumer_adapter.nil?
|
77
|
+
fail Datasource::Error, "No serializer adapter loaded, see the active_loaders gem."
|
78
|
+
end
|
88
79
|
serializer_class ||=
|
89
|
-
Datasource::Base.
|
90
|
-
Adapters::ActiveRecord.scope_to_class(
|
80
|
+
Datasource::Base.default_consumer_adapter.get_serializer_for(
|
81
|
+
Adapters::ActiveRecord.scope_to_class(all))
|
82
|
+
scope = scope_with_datasource_ext(serializer_class.use_datasource)
|
91
83
|
scope.datasource_set(serializer_class: serializer_class)
|
92
84
|
end
|
93
85
|
|
@@ -95,6 +87,25 @@ module Datasource
|
|
95
87
|
scope_with_datasource_ext(datasource_class)
|
96
88
|
end
|
97
89
|
|
90
|
+
def upgrade_for_serializer(records, serializer_class = nil, datasource_class = nil)
|
91
|
+
scope = with_datasource(datasource_class).for_serializer(serializer_class)
|
92
|
+
records = Array(records)
|
93
|
+
|
94
|
+
pk = scope.datasource_get(:datasource_class).primary_key.to_sym
|
95
|
+
if primary_keys = records.map(&pk)
|
96
|
+
scope = scope.where(pk => primary_keys.compact)
|
97
|
+
end
|
98
|
+
|
99
|
+
scope = yield(scope) if block_given?
|
100
|
+
|
101
|
+
datasource = scope.get_datasource
|
102
|
+
if datasource.can_upgrade?(records)
|
103
|
+
datasource.upgrade_records(records)
|
104
|
+
else
|
105
|
+
scope.to_a
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
98
109
|
def default_datasource
|
99
110
|
@default_datasource ||= begin
|
100
111
|
"#{name}Datasource".constantize
|
@@ -110,7 +121,11 @@ module Datasource
|
|
110
121
|
private
|
111
122
|
def scope_with_datasource_ext(datasource_class = nil)
|
112
123
|
if all.respond_to?(:datasource_set)
|
113
|
-
|
124
|
+
if datasource_class
|
125
|
+
all.datasource_set(datasource_class: datasource_class)
|
126
|
+
else
|
127
|
+
all
|
128
|
+
end
|
114
129
|
else
|
115
130
|
datasource_class ||= default_datasource
|
116
131
|
|
@@ -10,6 +10,10 @@ module Datasource
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
+
def datasource_get(key)
|
14
|
+
@datasource_info[key]
|
15
|
+
end
|
16
|
+
|
13
17
|
def datasource_set(hash)
|
14
18
|
@datasource_info.merge!(hash)
|
15
19
|
self
|
@@ -32,7 +36,7 @@ module Datasource
|
|
32
36
|
datasource.params(*@datasource_info[:params])
|
33
37
|
if @datasource_info[:serializer_class]
|
34
38
|
select = []
|
35
|
-
|
39
|
+
@datasource_info[:serializer_class].datasource_adapter.to_datasource_select(select, klass.orm_klass, @datasource_info[:serializer_class], nil, datasource.adapter, datasource)
|
36
40
|
|
37
41
|
datasource.select(*select)
|
38
42
|
end
|
@@ -58,10 +62,10 @@ module Datasource
|
|
58
62
|
|
59
63
|
dataset_module do
|
60
64
|
def for_serializer(serializer_class = nil)
|
61
|
-
scope = scope_with_datasource_ext
|
62
65
|
serializer_class ||=
|
63
|
-
Datasource::Base.
|
64
|
-
.get_serializer_for(Adapters::Sequel.scope_to_class(
|
66
|
+
Datasource::Base.default_consumer_adapter
|
67
|
+
.get_serializer_for(Adapters::Sequel.scope_to_class(self))
|
68
|
+
scope = scope_with_datasource_ext(serializer_class.use_datasource)
|
65
69
|
scope.datasource_set(serializer_class: serializer_class)
|
66
70
|
end
|
67
71
|
|
@@ -72,7 +76,11 @@ module Datasource
|
|
72
76
|
private
|
73
77
|
def scope_with_datasource_ext(datasource_class = nil)
|
74
78
|
if respond_to?(:datasource_set)
|
75
|
-
|
79
|
+
if datasource_class
|
80
|
+
datasource_set(datasource_class: datasource_class)
|
81
|
+
else
|
82
|
+
self
|
83
|
+
end
|
76
84
|
else
|
77
85
|
datasource_class ||= Adapters::Sequel.scope_to_class(self).default_datasource
|
78
86
|
|
@@ -83,23 +91,8 @@ module Datasource
|
|
83
91
|
end
|
84
92
|
end
|
85
93
|
|
86
|
-
def for_serializer(
|
87
|
-
|
88
|
-
pk = datasource_class.primary_key.to_sym
|
89
|
-
|
90
|
-
scope = self.class
|
91
|
-
.with_datasource(datasource_class)
|
92
|
-
.for_serializer(serializer)
|
93
|
-
.where(pk => send(pk))
|
94
|
-
|
95
|
-
scope = yield(scope) if block_given?
|
96
|
-
|
97
|
-
datasource = scope.get_datasource
|
98
|
-
if datasource.can_upgrade?(self)
|
99
|
-
datasource.upgrade_records(self).first
|
100
|
-
else
|
101
|
-
scope.first
|
102
|
-
end
|
94
|
+
def for_serializer(serializer_class = nil, datasource_class = nil)
|
95
|
+
self.class.upgrade_for_serializer([self], serializer_class, datasource_class).first
|
103
96
|
end
|
104
97
|
|
105
98
|
module ClassMethods
|
@@ -110,6 +103,28 @@ module Datasource
|
|
110
103
|
def datasource_module(&block)
|
111
104
|
default_datasource.instance_exec(&block)
|
112
105
|
end
|
106
|
+
|
107
|
+
def upgrade_for_serializer(records, serializer_class = nil, datasource_class = nil)
|
108
|
+
# must use filter to get a new scope
|
109
|
+
scope = filter.with_datasource(datasource_class).for_serializer(serializer_class)
|
110
|
+
records = Array(records)
|
111
|
+
|
112
|
+
binding.pry if scope.datasource_get(:datasource_class).nil?
|
113
|
+
|
114
|
+
pk = scope.datasource_get(:datasource_class).primary_key.to_sym
|
115
|
+
if primary_keys = records.map(&pk)
|
116
|
+
scope = scope.where(pk => primary_keys.compact)
|
117
|
+
end
|
118
|
+
|
119
|
+
scope = yield(scope) if block_given?
|
120
|
+
|
121
|
+
datasource = scope.get_datasource
|
122
|
+
if datasource.can_upgrade?(records)
|
123
|
+
datasource.upgrade_records(records)
|
124
|
+
else
|
125
|
+
scope.all
|
126
|
+
end
|
127
|
+
end
|
113
128
|
end
|
114
129
|
end
|
115
130
|
|
@@ -193,7 +208,11 @@ module Datasource
|
|
193
208
|
|
194
209
|
def upgrade_records(ds, records)
|
195
210
|
Datasource.logger.debug { "Upgrading records #{records.map(&:class).map(&:name).join(', ')}" }
|
196
|
-
|
211
|
+
# NOTE: this does not guarantee assocs are loaded, there might be a better way
|
212
|
+
assocs_loaded = records.all? { |record|
|
213
|
+
record._datasource_instance
|
214
|
+
}
|
215
|
+
get_final_scope(ds).send :post_load, records unless assocs_loaded
|
197
216
|
ds.results(records)
|
198
217
|
end
|
199
218
|
|
data/lib/datasource/base.rb
CHANGED
@@ -3,6 +3,8 @@ module Datasource
|
|
3
3
|
class << self
|
4
4
|
attr_accessor :_attributes, :_associations, :_update_scope, :_loaders, :_loader_order, :_collection_context
|
5
5
|
attr_writer :orm_klass
|
6
|
+
# Should be set by consumer adapter library (e.g. for ActiveModelSerializers)
|
7
|
+
attr_accessor :default_consumer_adapter
|
6
8
|
|
7
9
|
def inherited(base)
|
8
10
|
base._attributes = (_attributes || {}).dup
|
@@ -18,10 +20,6 @@ module Datasource
|
|
18
20
|
end
|
19
21
|
end
|
20
22
|
|
21
|
-
def consumer_adapter
|
22
|
-
@consumer_adapter = Datasource::ConsumerAdapters::ActiveModelSerializers
|
23
|
-
end
|
24
|
-
|
25
23
|
def orm_klass
|
26
24
|
fail Datasource::Error, "Model class not set for #{name}. You should define it:\nclass YourDatasource\n @orm_klass = MyModelClass\nend"
|
27
25
|
end
|
@@ -45,6 +43,12 @@ module Datasource
|
|
45
43
|
_collection_context.class_exec(&block)
|
46
44
|
end
|
47
45
|
|
46
|
+
def _column_attribute_names
|
47
|
+
column_attributes = _attributes.values.select { |att|
|
48
|
+
att[:klass].nil?
|
49
|
+
}.map { |att| att[:name] }
|
50
|
+
end
|
51
|
+
|
48
52
|
private
|
49
53
|
def attributes(*attrs)
|
50
54
|
attrs.each { |name| attribute(name) }
|
@@ -108,10 +112,7 @@ module Datasource
|
|
108
112
|
end
|
109
113
|
|
110
114
|
def select_all_columns
|
111
|
-
|
112
|
-
att[:klass].nil?
|
113
|
-
end
|
114
|
-
columns = column_attributes.map { |att| att[:name] }
|
115
|
+
columns = self.class._column_attribute_names
|
115
116
|
select(*columns)
|
116
117
|
@select_all_columns = true
|
117
118
|
|
@@ -156,7 +157,7 @@ module Datasource
|
|
156
157
|
end
|
157
158
|
end
|
158
159
|
update_dependencies(newly_exposed_attributes) unless newly_exposed_attributes.empty?
|
159
|
-
fail_missing_attributes(missing_attributes)
|
160
|
+
fail_missing_attributes(missing_attributes) if Datasource.config.raise_error_on_unknown_attribute_select && !missing_attributes.empty?
|
160
161
|
self
|
161
162
|
end
|
162
163
|
|
@@ -1,10 +1,5 @@
|
|
1
1
|
Datasource.setup do |config|
|
2
2
|
# Adapters to load
|
3
3
|
# Available ORM adapters: activerecord, sequel
|
4
|
-
|
5
|
-
config.adapters = [:activerecord, :active_model_serializers]
|
6
|
-
|
7
|
-
# Enable simple mode, which will always select all model database columns,
|
8
|
-
# making Datasource easier to use. See documentation for details.
|
9
|
-
config.simple_mode = true
|
4
|
+
config.adapters = [:activerecord]
|
10
5
|
end
|
metadata
CHANGED
@@ -1,29 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: datasource
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jan Berdajs
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-02-
|
11
|
+
date: 2015-02-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: active_model_serializers
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - ">="
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '0.8'
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - ">="
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '0.8'
|
27
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: activesupport
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -141,7 +127,6 @@ files:
|
|
141
127
|
- lib/datasource/base.rb
|
142
128
|
- lib/datasource/collection_context.rb
|
143
129
|
- lib/datasource/configuration.rb
|
144
|
-
- lib/datasource/consumer_adapters/active_model_serializers.rb
|
145
130
|
- lib/generators/datasource/install_generator.rb
|
146
131
|
- lib/generators/datasource/templates/initializer.rb
|
147
132
|
homepage: https://github.com/kundi/datasource
|
@@ -1,138 +0,0 @@
|
|
1
|
-
require "active_model/serializer"
|
2
|
-
|
3
|
-
module Datasource
|
4
|
-
module ConsumerAdapters
|
5
|
-
module ActiveModelSerializers
|
6
|
-
module ArraySerializer
|
7
|
-
def initialize_with_datasource(objects, options = {})
|
8
|
-
datasource_class = options.delete(:datasource)
|
9
|
-
adapter = Datasource.orm_adapters.find { |a| a.is_scope?(objects) }
|
10
|
-
if adapter && !adapter.scope_loaded?(objects)
|
11
|
-
datasource_class ||= adapter.scope_to_class(objects).default_datasource
|
12
|
-
|
13
|
-
scope = begin
|
14
|
-
objects
|
15
|
-
.with_datasource(datasource_class)
|
16
|
-
.for_serializer(options[:serializer])
|
17
|
-
.datasource_params(*[options[:datasource_params]].compact)
|
18
|
-
rescue NameError
|
19
|
-
if options[:serializer].nil?
|
20
|
-
return initialize_without_datasource(objects, options)
|
21
|
-
else
|
22
|
-
raise
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
records = adapter.scope_to_records(scope)
|
27
|
-
|
28
|
-
initialize_without_datasource(records, options)
|
29
|
-
else
|
30
|
-
initialize_without_datasource(objects, options)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
module_function
|
36
|
-
def get_serializer_for(klass, serializer_assoc = nil)
|
37
|
-
serializer = if serializer_assoc
|
38
|
-
if serializer_assoc.kind_of?(Hash)
|
39
|
-
serializer_assoc[:options].try(:[], :serializer)
|
40
|
-
else
|
41
|
-
serializer_assoc.options[:serializer]
|
42
|
-
end
|
43
|
-
end
|
44
|
-
serializer || "#{klass.name}Serializer".constantize
|
45
|
-
end
|
46
|
-
|
47
|
-
def to_datasource_select(result, klass, serializer = nil, serializer_assoc = nil, adapter = nil)
|
48
|
-
adapter ||= Datasource::Base.default_adapter
|
49
|
-
serializer ||= get_serializer_for(klass, serializer_assoc)
|
50
|
-
result.unshift("*") if Datasource.config.simple_mode
|
51
|
-
if serializer._attributes.respond_to?(:keys) # AMS 0.8
|
52
|
-
result.concat(serializer._attributes.keys)
|
53
|
-
else # AMS 0.9
|
54
|
-
result.concat(serializer._attributes)
|
55
|
-
end
|
56
|
-
result.concat(serializer.datasource_select)
|
57
|
-
result_assocs = serializer.datasource_includes.dup
|
58
|
-
result.push(result_assocs)
|
59
|
-
|
60
|
-
serializer._associations.each_pair do |name, serializer_assoc|
|
61
|
-
# TODO: what if assoc is renamed in serializer?
|
62
|
-
reflection = adapter.association_reflection(klass, name.to_sym)
|
63
|
-
assoc_class = reflection[:klass]
|
64
|
-
|
65
|
-
name = name.to_s
|
66
|
-
result_assocs[name] = []
|
67
|
-
to_datasource_select(result_assocs[name], assoc_class, nil, serializer_assoc, adapter)
|
68
|
-
end
|
69
|
-
rescue Exception => ex
|
70
|
-
if ex.is_a?(SystemStackError) || ex.is_a?(Datasource::RecursionError)
|
71
|
-
fail Datasource::RecursionError, "recursive association (involving #{klass.name})"
|
72
|
-
else
|
73
|
-
raise
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
array_serializer_class = if defined?(ActiveModel::Serializer::ArraySerializer)
|
81
|
-
ActiveModel::Serializer::ArraySerializer
|
82
|
-
else
|
83
|
-
ActiveModel::ArraySerializer
|
84
|
-
end
|
85
|
-
|
86
|
-
array_serializer_class.class_exec do
|
87
|
-
alias_method :initialize_without_datasource, :initialize
|
88
|
-
include Datasource::ConsumerAdapters::ActiveModelSerializers::ArraySerializer
|
89
|
-
def initialize(*args)
|
90
|
-
initialize_with_datasource(*args)
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
module SerializerClassMethods
|
95
|
-
def inherited(base)
|
96
|
-
base.datasource_select(*datasource_select.deep_dup)
|
97
|
-
base.datasource_includes(*datasource_includes.deep_dup)
|
98
|
-
|
99
|
-
super
|
100
|
-
end
|
101
|
-
|
102
|
-
def datasource_select(*args)
|
103
|
-
@datasource_select ||= []
|
104
|
-
@datasource_select.concat(args)
|
105
|
-
|
106
|
-
@datasource_select
|
107
|
-
end
|
108
|
-
|
109
|
-
def datasource_includes(*args)
|
110
|
-
@datasource_includes ||= {}
|
111
|
-
|
112
|
-
args.each do |arg|
|
113
|
-
@datasource_includes.deep_merge!(datasource_includes_to_select(arg))
|
114
|
-
end
|
115
|
-
|
116
|
-
@datasource_includes
|
117
|
-
end
|
118
|
-
|
119
|
-
private
|
120
|
-
def datasource_includes_to_select(arg)
|
121
|
-
if arg.kind_of?(Hash)
|
122
|
-
arg.keys.inject({}) do |memo, key|
|
123
|
-
memo[key.to_sym] = ["*", datasource_includes_to_select(arg[key])]
|
124
|
-
memo
|
125
|
-
end
|
126
|
-
elsif arg.kind_of?(Array)
|
127
|
-
arg.inject({}) do |memo, element|
|
128
|
-
memo.deep_merge!(datasource_includes_to_select(element))
|
129
|
-
end
|
130
|
-
elsif arg.respond_to?(:to_sym)
|
131
|
-
{ arg.to_sym => ["*"] }
|
132
|
-
else
|
133
|
-
fail Datasource::Error, "unknown includes value type #{arg.class}"
|
134
|
-
end
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
ActiveModel::Serializer.singleton_class.send :prepend, SerializerClassMethods
|