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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6c4654c0cc97e0cba3fd8ce3d9b99a520d590afa
4
- data.tar.gz: a37d72813c8e2d0a6ef6430a380115126f28dfad
3
+ metadata.gz: acadf2996a54023f7ea9ec464ae0b82ad0c57dc9
4
+ data.tar.gz: 7f1759d99758a076f66787bb152c147cbb0ba714
5
5
  SHA512:
6
- metadata.gz: 6d68a095c8b8af62be19f3fbb7048b634b5d0fb5d113f3e81d6e1430959d69bc9f93cce68d02977066ff782361c7915b0a3cab0a9003b85598caa019a5bb5c7e
7
- data.tar.gz: 4cf182d351a3bfc21f6dfcd2062eb97f21f36dd39ccf9ef4bfaf6cca418aea7b7fc9105fe613936f95f19a4a32d74714e51b27ebccfc15fa9de9a7b716f25dab
6
+ metadata.gz: a8d565d3e8d3e2e44ee7c3c22f27f8e776b10f3b301c6f252a996b9130d1a39c15da04e92530d1a5eb81b892815d7377b61d2aaadeadd1841c22df1c5199a0ab
7
+ data.tar.gz: c40d2192fcdaf926b25f4ece6f8378a763e63459d1ff5a673d06a14e32fbe2183c26b43de1a3f49955b5c73eb9ab6a18bfbc6c6e03b3f78dbefa0d6930a83d5f
data/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Datasource
2
2
 
3
- - Automatically preload associations for your serializers
4
- - Specify custom SQL snippets for virtual attributes (Query attributes)
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
- ** 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) **
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 |adapter|
30
- adapter = AdapterPaths[adapter]
31
- adapter = AdapterPaths[adapter] if adapter.is_a?(Symbol)
32
- require adapter
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
- Datasource::Base.consumer_adapter.to_datasource_select(select, klass.orm_klass, @datasource_info[:serializer_class], nil, datasource.adapter)
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(serializer = nil)
67
- datasource_class = self.class.default_datasource
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
- scope = scope_with_datasource_ext
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.consumer_adapter.get_serializer_for(
90
- Adapters::ActiveRecord.scope_to_class(scope))
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
- all
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
- Datasource::Base.consumer_adapter.to_datasource_select(select, klass.orm_klass, @datasource_info[:serializer_class], nil, datasource.adapter)
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.consumer_adapter
64
- .get_serializer_for(Adapters::Sequel.scope_to_class(scope))
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
- self
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(serializer = nil)
87
- datasource_class = self.class.default_datasource
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
- get_final_scope(ds).send :post_load, records
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
 
@@ -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
- column_attributes = self.class._attributes.values.select do |att|
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) unless Datasource.config.simple_mode || missing_attributes.empty?
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
 
@@ -5,7 +5,7 @@ module Datasource
5
5
 
6
6
  included do |base|
7
7
  base.config.adapters = Configuration.default_adapters
8
- base.config.simple_mode = false
8
+ base.config.raise_error_on_unknown_attribute_select = false
9
9
  end
10
10
 
11
11
  def self.default_adapters
@@ -1,10 +1,5 @@
1
1
  Datasource.setup do |config|
2
2
  # Adapters to load
3
3
  # Available ORM adapters: activerecord, sequel
4
- # Available Serializer adapters: active_model_serializers
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.2.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-04 00:00:00.000000000 Z
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