datasource 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|