datasource 0.1.1 → 0.2.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 +129 -102
- data/lib/datasource/adapters/active_record.rb +106 -41
- data/lib/datasource/adapters/sequel.rb +53 -30
- data/lib/datasource/attributes/loaded.rb +101 -0
- data/lib/datasource/base.rb +90 -29
- data/lib/datasource/collection_context.rb +32 -0
- data/lib/datasource/consumer_adapters/active_model_serializers.rb +66 -4
- data/lib/datasource.rb +13 -12
- metadata +9 -9
- data/lib/datasource/attributes/loader.rb +0 -86
- data/lib/datasource/serializer.rb +0 -117
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6c4654c0cc97e0cba3fd8ce3d9b99a520d590afa
|
4
|
+
data.tar.gz: a37d72813c8e2d0a6ef6430a380115126f28dfad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6d68a095c8b8af62be19f3fbb7048b634b5d0fb5d113f3e81d6e1430959d69bc9f93cce68d02977066ff782361c7915b0a3cab0a9003b85598caa019a5bb5c7e
|
7
|
+
data.tar.gz: 4cf182d351a3bfc21f6dfcd2062eb97f21f36dd39ccf9ef4bfaf6cca418aea7b7fc9105fe613936f95f19a4a32d74714e51b27ebccfc15fa9de9a7b716f25dab
|
data/README.md
CHANGED
@@ -4,12 +4,16 @@
|
|
4
4
|
- Specify custom SQL snippets for virtual attributes (Query attributes)
|
5
5
|
- Write custom preloading logic in a reusable way
|
6
6
|
|
7
|
+
** Note: the API of this gem is still unstable and may change a lot between versions! This project uses semantic versioning (until version 1.0.0, minor version changes may include API changes, but patch version will not) **
|
8
|
+
|
7
9
|
#### Install
|
8
10
|
|
9
|
-
|
11
|
+
Requires Ruby 2.0 or higher.
|
12
|
+
|
13
|
+
Add to Gemfile (recommended to use github version until API is stable)
|
10
14
|
|
11
15
|
```
|
12
|
-
gem 'datasource'
|
16
|
+
gem 'datasource', github: 'kundi/datasource'
|
13
17
|
```
|
14
18
|
|
15
19
|
```
|
@@ -32,18 +36,10 @@ rails g datasource:install
|
|
32
36
|
|
33
37
|
- active_model_serializers
|
34
38
|
|
35
|
-
## Simple Mode
|
36
|
-
|
37
|
-
Datasource is configured to run in Simple mode by default, which makes it easier
|
38
|
-
to start with, but disables some advanced optimizations. See
|
39
|
-
[Advanced mode](https://github.com/mrbrdo/datasource/wiki/Advanced-mode) for more
|
40
|
-
information after you understand Simple mode.
|
41
|
-
|
42
39
|
### Associations
|
43
40
|
|
44
|
-
The most noticable magic effect of using Datasource
|
45
|
-
|
46
|
-
single query.
|
41
|
+
The most noticable magic effect of using Datasource is that associations will
|
42
|
+
automatically be preloaded using a single query.
|
47
43
|
|
48
44
|
```ruby
|
49
45
|
class PostSerializer < ActiveModel::Serializer
|
@@ -65,7 +61,8 @@ automatically by Datasource.
|
|
65
61
|
|
66
62
|
### Show action
|
67
63
|
|
68
|
-
|
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.
|
69
66
|
You will need to call `for_serializer` on the scope before you call `find`.
|
70
67
|
You can optionally give it the serializer class as an argument.
|
71
68
|
|
@@ -117,96 +114,120 @@ end
|
|
117
114
|
SELECT users.*, (users.first_name || ' ' || users.last_name) AS full_name FROM users
|
118
115
|
```
|
119
116
|
|
120
|
-
Note: If you need data from another table, use a join in a
|
117
|
+
Note: If you need data from another table, use a join in a loaded value (see below).
|
121
118
|
|
122
|
-
###
|
119
|
+
### Standalone Datasource class
|
123
120
|
|
124
|
-
|
125
|
-
|
126
|
-
|
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:
|
127
126
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
If an attribute depends on multiple loaders, pass an array of loaders like
|
134
|
-
so `computed :attr, loaders: [:loader1, :loader2]`.
|
127
|
+
```ruby
|
128
|
+
class PostDatasource < Datasource::From(Post)
|
129
|
+
query(:full_name) { "users.first_name || ' ' || users.last_name" }
|
130
|
+
end
|
131
|
+
```
|
135
132
|
|
136
|
-
|
137
|
-
|
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):
|
138
141
|
|
139
142
|
```ruby
|
140
|
-
class
|
141
|
-
|
142
|
-
computed :post_count, loader: :post_counts
|
143
|
-
loader :post_counts, array_to_hash: true, default: 0 do |user_ids|
|
144
|
-
results = Post
|
145
|
-
.where(user_id: user_ids)
|
146
|
-
.group(:user_id)
|
147
|
-
.pluck("user_id, COUNT(id)")
|
148
|
-
end
|
149
|
-
end
|
143
|
+
class UserDatasource < Datasource::From(User)
|
144
|
+
loaded :post_count, from: :array, default: 0
|
150
145
|
end
|
146
|
+
```
|
151
147
|
|
152
|
-
|
153
|
-
|
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`.
|
154
153
|
|
155
|
-
|
156
|
-
|
157
|
-
|
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
|
158
164
|
end
|
159
165
|
end
|
160
166
|
```
|
161
167
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
```
|
166
|
-
|
167
|
-
Datasource provides shortcuts to transform your data into a hash. Here are examples:
|
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:
|
168
171
|
|
169
172
|
```ruby
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
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
|
+
```
|
175
178
|
|
176
|
-
|
177
|
-
|
178
|
-
# will be transformed into
|
179
|
-
# { 1 => [#<Post>, #<Post>, ...], 2 => [ ... ], ... }
|
180
|
-
end
|
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
181
|
|
182
|
-
|
183
|
-
Post.where(user_id: ids)
|
184
|
-
# will be transformed into
|
185
|
-
# { 1 => #<Post>, 2 => #<Post>, ... }
|
186
|
-
end
|
182
|
+
The other two are explained in the following example.
|
187
183
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
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
|
194
195
|
end
|
195
196
|
```
|
196
197
|
|
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.
|
198
206
|
|
199
|
-
|
200
|
-
|
207
|
+
In this case, in the load method, we also used `for_serializer`, which will load
|
208
|
+
the `Comment`s according to the `CommentSerializer`.
|
201
209
|
|
202
|
-
|
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:
|
203
215
|
|
204
216
|
```ruby
|
205
217
|
class User < ActiveRecord::Base
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
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)
|
210
231
|
.group(:user_id)
|
211
232
|
.pluck("user_id, COUNT(id)")
|
212
233
|
end
|
@@ -214,50 +235,56 @@ class User < ActiveRecord::Base
|
|
214
235
|
end
|
215
236
|
|
216
237
|
class UserSerializer < ActiveModel::Serializer
|
217
|
-
attributes :id, :post_count
|
218
|
-
# Note that the User now has a generated post_count method
|
238
|
+
attributes :id, :post_count # <- post_count will be read from load_post_count
|
219
239
|
end
|
240
|
+
|
241
|
+
User.first.post_count # <- your model method will be called
|
220
242
|
```
|
221
243
|
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
method when you are not using a serializer/datasource. For example:
|
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`:
|
227
248
|
|
228
249
|
```ruby
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
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
|
236
262
|
end
|
237
263
|
end
|
264
|
+
```
|
238
265
|
|
239
|
-
|
240
|
-
posts.count
|
241
|
-
end
|
242
|
-
end
|
243
|
-
|
244
|
-
class UserSerializer < ActiveModel::Serializer
|
245
|
-
attributes :id, :post_count # <- post_count will be read from loaded_values
|
246
|
-
end
|
266
|
+
### Debugging and logging
|
247
267
|
|
248
|
-
|
268
|
+
Datasource outputs some useful logs that you can use debugging. By default the log level is
|
269
|
+
set to warnings only, but you can change it. You can add the following line to your
|
270
|
+
`config/initializers/datasource.rb`:
|
249
271
|
|
272
|
+
```ruby
|
273
|
+
Datasource.logger.level = Logger::INFO
|
250
274
|
```
|
251
275
|
|
276
|
+
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.
|
278
|
+
|
252
279
|
## Getting Help
|
253
280
|
|
254
|
-
If you find a bug, please report an [Issue](https://github.com/
|
281
|
+
If you find a bug, please report an [Issue](https://github.com/kundi/datasource/issues/new).
|
255
282
|
|
256
283
|
If you have a question, you can also open an Issue.
|
257
284
|
|
258
285
|
## Contributing
|
259
286
|
|
260
|
-
1. Fork it ( https://github.com/
|
287
|
+
1. Fork it ( https://github.com/kundi/datasource/fork )
|
261
288
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
262
289
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
263
290
|
4. Push to the branch (`git push origin my-new-feature`)
|
@@ -5,27 +5,35 @@ module Datasource
|
|
5
5
|
module Adapters
|
6
6
|
module ActiveRecord
|
7
7
|
module ScopeExtensions
|
8
|
-
def
|
9
|
-
|
10
|
-
|
8
|
+
def self.extended(mod)
|
9
|
+
mod.instance_exec do
|
10
|
+
@datasource_info ||= { select: [], params: [] }
|
11
|
+
end
|
11
12
|
end
|
12
13
|
|
13
|
-
def
|
14
|
-
@
|
14
|
+
def datasource_set(hash)
|
15
|
+
@datasource_info.merge!(hash)
|
15
16
|
self
|
16
17
|
end
|
17
18
|
|
18
19
|
def datasource_select(*args)
|
19
|
-
@
|
20
|
+
@datasource_info[:select] += args
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def datasource_params(*args)
|
25
|
+
@datasource_info[:params] += args
|
20
26
|
self
|
21
27
|
end
|
22
28
|
|
23
29
|
def get_datasource
|
24
|
-
|
25
|
-
datasource.
|
26
|
-
|
30
|
+
klass = @datasource_info[:datasource_class]
|
31
|
+
datasource = klass.new(self)
|
32
|
+
datasource.select(*@datasource_info[:select])
|
33
|
+
datasource.params(*@datasource_info[:params])
|
34
|
+
if @datasource_info[:serializer_class]
|
27
35
|
select = []
|
28
|
-
Datasource::Base.consumer_adapter.to_datasource_select(select,
|
36
|
+
Datasource::Base.consumer_adapter.to_datasource_select(select, klass.orm_klass, @datasource_info[:serializer_class], nil, datasource.adapter)
|
29
37
|
|
30
38
|
datasource.select(*select)
|
31
39
|
end
|
@@ -34,9 +42,12 @@ module Datasource
|
|
34
42
|
|
35
43
|
private
|
36
44
|
def exec_queries
|
37
|
-
if @
|
45
|
+
if @datasource_info[:datasource_class]
|
38
46
|
datasource = get_datasource
|
39
47
|
|
48
|
+
Datasource.logger.debug { "exec_queries expose_attributes: #{datasource.expose_attributes.inspect}" }
|
49
|
+
Datasource.logger.debug { "exec_queries expose_associations: #{datasource.expose_associations.inspect}" }
|
50
|
+
|
40
51
|
@loaded = true
|
41
52
|
@records = datasource.results
|
42
53
|
else
|
@@ -49,7 +60,7 @@ module Datasource
|
|
49
60
|
extend ActiveSupport::Concern
|
50
61
|
|
51
62
|
included do
|
52
|
-
attr_accessor :
|
63
|
+
attr_accessor :_datasource_loaded, :_datasource_instance
|
53
64
|
end
|
54
65
|
|
55
66
|
def for_serializer(serializer = nil)
|
@@ -58,7 +69,10 @@ module Datasource
|
|
58
69
|
|
59
70
|
scope = self.class
|
60
71
|
.with_datasource(datasource_class)
|
61
|
-
.for_serializer(serializer)
|
72
|
+
.for_serializer(serializer)
|
73
|
+
.where(pk => send(pk))
|
74
|
+
|
75
|
+
scope = yield(scope) if block_given?
|
62
76
|
|
63
77
|
datasource = scope.get_datasource
|
64
78
|
if datasource.can_upgrade?(self)
|
@@ -69,37 +83,47 @@ module Datasource
|
|
69
83
|
end
|
70
84
|
|
71
85
|
module ClassMethods
|
72
|
-
def for_serializer(
|
73
|
-
scope =
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
scope.use_datasource_serializer(serializer || Datasource::Base.consumer_adapter.get_serializer_for(Adapters::ActiveRecord.scope_to_class(scope)))
|
86
|
+
def for_serializer(serializer_class = nil)
|
87
|
+
scope = scope_with_datasource_ext
|
88
|
+
serializer_class ||=
|
89
|
+
Datasource::Base.consumer_adapter.get_serializer_for(
|
90
|
+
Adapters::ActiveRecord.scope_to_class(scope))
|
91
|
+
scope.datasource_set(serializer_class: serializer_class)
|
79
92
|
end
|
80
93
|
|
81
|
-
def with_datasource(
|
82
|
-
|
83
|
-
all
|
84
|
-
else
|
85
|
-
all.extending(ScopeExtensions)
|
86
|
-
end
|
87
|
-
scope.use_datasource(datasource || default_datasource)
|
94
|
+
def with_datasource(datasource_class = nil)
|
95
|
+
scope_with_datasource_ext(datasource_class)
|
88
96
|
end
|
89
97
|
|
90
98
|
def default_datasource
|
91
|
-
@default_datasource ||=
|
99
|
+
@default_datasource ||= begin
|
100
|
+
"#{name}Datasource".constantize
|
101
|
+
rescue NameError
|
102
|
+
Datasource::From(self)
|
103
|
+
end
|
92
104
|
end
|
93
105
|
|
94
106
|
def datasource_module(&block)
|
95
107
|
default_datasource.instance_exec(&block)
|
96
108
|
end
|
109
|
+
|
110
|
+
private
|
111
|
+
def scope_with_datasource_ext(datasource_class = nil)
|
112
|
+
if all.respond_to?(:datasource_set)
|
113
|
+
all
|
114
|
+
else
|
115
|
+
datasource_class ||= default_datasource
|
116
|
+
|
117
|
+
all.extending(ScopeExtensions)
|
118
|
+
.datasource_set(datasource_class: datasource_class)
|
119
|
+
end
|
120
|
+
end
|
97
121
|
end
|
98
122
|
end
|
99
123
|
|
100
124
|
module_function
|
101
125
|
def association_reflection(klass, name)
|
102
|
-
if reflection = klass.
|
126
|
+
if reflection = klass.reflect_on_association(name)
|
103
127
|
{
|
104
128
|
klass: reflection.klass,
|
105
129
|
macro: reflection.macro,
|
@@ -124,6 +148,10 @@ module Datasource
|
|
124
148
|
scope.loaded?
|
125
149
|
end
|
126
150
|
|
151
|
+
def scope_to_records(scope)
|
152
|
+
scope.to_a
|
153
|
+
end
|
154
|
+
|
127
155
|
def has_attribute?(record, name)
|
128
156
|
record.attributes.key?(name.to_s)
|
129
157
|
end
|
@@ -136,22 +164,50 @@ module Datasource
|
|
136
164
|
end
|
137
165
|
end
|
138
166
|
|
139
|
-
def
|
167
|
+
def association_loaded?(records, name, assoc_select)
|
168
|
+
if records.first.association(name).loaded?
|
169
|
+
all_loaded = records.all? { |record| record.association(name).loaded? }
|
170
|
+
if assoc_select == ["*"]
|
171
|
+
all_loaded
|
172
|
+
elsif all_loaded
|
173
|
+
records.all? do |record|
|
174
|
+
assoc_sample = Array(record.send(name)).first
|
175
|
+
assoc_sample.nil? || assoc_sample._datasource_instance
|
176
|
+
end
|
177
|
+
else
|
178
|
+
false
|
179
|
+
end
|
180
|
+
else
|
181
|
+
false
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def load_association(records, name, assoc_select, params)
|
140
186
|
return if records.empty?
|
141
|
-
|
187
|
+
name = name.to_sym
|
142
188
|
klass = records.first.class
|
143
|
-
if reflection = klass.
|
189
|
+
if reflection = klass.reflect_on_association(name)
|
144
190
|
assoc_class = association_klass(reflection)
|
145
191
|
datasource_class = assoc_class.default_datasource
|
146
192
|
|
147
193
|
scope = assoc_class.all
|
148
194
|
datasource = datasource_class.new(scope)
|
149
195
|
assoc_select_attributes = assoc_select.reject { |att| att.kind_of?(Hash) }
|
150
|
-
assoc_select_associations = assoc_select.
|
151
|
-
|
196
|
+
assoc_select_associations = assoc_select.inject({}) do |hash, att|
|
197
|
+
hash.deep_merge!(att) if att.kind_of?(Hash)
|
198
|
+
hash
|
199
|
+
end
|
200
|
+
Datasource::Base.reflection_select(association_reflection(klass, name), [], assoc_select_attributes)
|
201
|
+
datasource.params(params)
|
202
|
+
|
203
|
+
Datasource.logger.debug { "load_association #{records.first.try!(:class)} #{name}: #{assoc_select_attributes.inspect}" }
|
152
204
|
datasource.select(*assoc_select_attributes)
|
153
205
|
select_values = datasource.get_select_values
|
154
206
|
|
207
|
+
# TODO: manually load associations, and load them all at once for
|
208
|
+
# nested associations, eg. in following, load all Users in 1 query:
|
209
|
+
# {"user"=>["*"], "players"=>["*"], "picked_players"=>["*",
|
210
|
+
# {:position=>["*"]}], "parent_picked_team"=>["*", {:user=>["*"]}]}
|
155
211
|
begin
|
156
212
|
::ActiveRecord::Associations::Preloader
|
157
213
|
.new.preload(records, name, assoc_class.select(*select_values))
|
@@ -161,12 +217,16 @@ module Datasource
|
|
161
217
|
end
|
162
218
|
|
163
219
|
assoc_records = records.flat_map { |record| record.send(name) }.compact
|
164
|
-
|
165
|
-
|
166
|
-
|
220
|
+
unless assoc_records.empty?
|
221
|
+
if Datasource.logger.info? && !assoc_select_associations.empty?
|
222
|
+
Datasource.logger.info { "Loading associations " + assoc_select_associations.keys.map(&:to_s).join(", ") + " for #{assoc_records.first.try!(:class)}s" }
|
167
223
|
end
|
224
|
+
assoc_select_associations.each_pair do |assoc_name, assoc_select|
|
225
|
+
Datasource.logger.debug { "load_association nested association #{assoc_name}: #{assoc_select.inspect}" }
|
226
|
+
load_association(assoc_records, assoc_name, assoc_select, params)
|
227
|
+
end
|
228
|
+
datasource.results(assoc_records)
|
168
229
|
end
|
169
|
-
datasource.results(assoc_records)
|
170
230
|
end
|
171
231
|
rescue Exception => ex
|
172
232
|
if ex.is_a?(SystemStackError) || ex.is_a?(Datasource::RecursionError)
|
@@ -187,13 +247,18 @@ module Datasource
|
|
187
247
|
end
|
188
248
|
|
189
249
|
def upgrade_records(ds, records)
|
250
|
+
Datasource.logger.debug { "Upgrading records #{records.map(&:class).map(&:name).join(', ')}" }
|
190
251
|
load_associations(ds, records)
|
191
252
|
ds.results(records)
|
192
253
|
end
|
193
254
|
|
194
255
|
def load_associations(ds, records)
|
256
|
+
if Datasource.logger.info? && !ds.expose_associations.empty?
|
257
|
+
Datasource.logger.info { "Loading associations " + ds.expose_associations.keys.map(&:to_s).join(", ") + " for #{records.first.try!(:class)}s" }
|
258
|
+
end
|
259
|
+
Datasource.logger.debug { "load_associations (#{records.size} #{records.first.try!(:class)}): #{ds.expose_associations.inspect}" }
|
195
260
|
ds.expose_associations.each_pair do |assoc_name, assoc_select|
|
196
|
-
load_association(records, assoc_name, assoc_select)
|
261
|
+
load_association(records, assoc_name, assoc_select, ds.params)
|
197
262
|
end
|
198
263
|
end
|
199
264
|
|
@@ -207,8 +272,8 @@ module Datasource
|
|
207
272
|
ds.select(*append_select)
|
208
273
|
|
209
274
|
scope = select_scope(ds)
|
210
|
-
if scope.respond_to?(:
|
211
|
-
scope = scope.spawn.
|
275
|
+
if scope.respond_to?(:datasource_set)
|
276
|
+
scope = scope.spawn.datasource_set(datasource_class: nil)
|
212
277
|
end
|
213
278
|
scope.includes_values = []
|
214
279
|
scope.to_a.tap do |records|
|