alba 1.3.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,359 @@
1
+ ---
2
+ title: Upgrading from ActiveModelSerializers
3
+ ---
4
+
5
+ <!-- @format -->
6
+
7
+ This guide is aimed at helping ActiveModelSerializers users transition to Alba, and it consists of three parts:
8
+
9
+ 1. Basic serialization
10
+ 2. Complex serialization
11
+ 3. Unsupported features
12
+
13
+ ## Example class
14
+
15
+ Example clsss is inherited `ActiveRecord::Base`, because [serializing PORO with AMS is pretty hard](https://github.com/rails-api/active_model_serializers/blob/0-10-stable/docs/howto/serialize_poro.md).
16
+
17
+ ```rb
18
+ class User < ActiveRecord::Base
19
+ # columns: id, created_at, updated_at
20
+ has_one :profile
21
+ has_many :articles
22
+ end
23
+
24
+ class Profile < ActiveRecord::Base
25
+ # columns: id, user_id, email, created_at, updated_at
26
+ belongs_to :user
27
+ end
28
+
29
+ class Article < ActiveRecord::Base
30
+ # columns: id, user_id, title, body, created_at, updated_at
31
+ belongs_to :user
32
+ end
33
+ ```
34
+
35
+ ## 1. Basic serialization
36
+
37
+ ### #serializable_hash
38
+
39
+ - or #as_json, #to_json
40
+
41
+ #### ActiveModelSerializer
42
+
43
+ ```rb
44
+ # Infer and use by "#{MODEL_NAME}Serializer" in app/serializers/user_serializer.rb
45
+ class UserSerializer < ActiveModel::Serializer
46
+ type :user
47
+ attributes :id, :created_at, :updated_at
48
+ end
49
+
50
+ # serialze
51
+ user = User.create!
52
+ ActiveModelSerializers::SerializableResource.new(
53
+ user
54
+ ).serializable_hash
55
+ # => {
56
+ # user: {
57
+ # id: id,
58
+ # created_at: created_at,
59
+ # updated_at: updated_at
60
+ # }
61
+ # }
62
+ ```
63
+
64
+ #### Alba
65
+
66
+ ```rb
67
+ # Infer and use by "#{MODEL_NAME}Resource"
68
+ # In app/resources/user_resource.rb
69
+ class UserResource
70
+ include Alba::Resource
71
+ attributes :id, :created_at, :updated_at
72
+ end
73
+
74
+ # serialze
75
+ user = User.create!
76
+ UserResource.new(user).serializable_hash
77
+ # => {
78
+ # id: id,
79
+ # created_at: created_at,
80
+ # updated_at: updated_at,
81
+ # }
82
+
83
+ # If want `user key`
84
+ class UserResource
85
+ include Alba::Resource
86
+ root_key :user # Call root_key method like ActiveModel::Serializer#type
87
+ attributes :id, :created_at, :updated_at
88
+ end
89
+
90
+ # serialze
91
+ user = User.create!
92
+ JSON.parse UserResource.new(user).serialize # !!!!serializable_hash does not support root key!!! Must use JSON.parse and serialize
93
+ # => {
94
+ # "user"=>{
95
+ # "id"=>id,
96
+ # "created_at"=>created_at,
97
+ # "updated_at"=>updated_at
98
+ # }
99
+ # }
100
+ # If want symbolize keys with #deep_symbolize_keys in Rails
101
+ user = User.create!
102
+ JSON.parse(UserResource.new(user).serialize).deep_symbolize_keys
103
+ # => {
104
+ # user: {
105
+ # id: id,
106
+ # created_at: created_at,
107
+ # updated_at: updated_at
108
+ # }
109
+ # }
110
+ ```
111
+
112
+ ## 2. Complex serialization
113
+
114
+ ### Serialize collections
115
+
116
+ #### ActiveModelSerializer
117
+
118
+ ```rb
119
+ class UserSerializer < ActiveModel::Serializer
120
+ type :user
121
+ attributes :id, :created_at, :updated_at
122
+ end
123
+ 3.times { User.create! }
124
+ users = User.limit 3
125
+ ActiveModelSerializers::SerializableResource.new(
126
+ users,
127
+ adapter: :attributes # Comment out this line if you want users key
128
+ # Want to specified key to call with root: args
129
+ ).serializable_hash
130
+ # => [{:id=>1, :created_at=>created_at, :updated_at=>updated_at},
131
+ # {:id=>2, :created_at=>created_at, :updated_at=>updated_at},
132
+ # {:id=>3, :created_at=>created_at, :updated_at=>updated_at}]
133
+ ```
134
+
135
+ #### Alba
136
+
137
+ ```rb
138
+ class UserResource
139
+ include Alba::Resource
140
+ attributes :id, :created_at, :updated_at
141
+ end
142
+ 3.times { User.create! }
143
+ users = User.limit 3
144
+ UserResource.new(users).serializable_hash
145
+ # =>[{:id=>1, :created_at=>created_at, :updated_at=>updated_at},
146
+ # {:id=>2, :created_at=>created_at, :updated_at=>updated_at},
147
+ # {:id=>3, :created_at=>created_at, :updated_at=>updated_at}]
148
+ # or
149
+ JSON.parse UserResource.new(users).serialize(root_key: :users)
150
+ # => {"users"=>
151
+ # [{"id"=>1, "created_at"=>created_at, "updated_at"=>updated_at},
152
+ # {"id"=>2, "created_at"=>created_at, "updated_at"=>updated_at},
153
+ # {"id"=>3, "created_at"=>created_at, "updated_at"=>updated_at}]}
154
+ ```
155
+
156
+ ### Nested serialization
157
+
158
+ #### ActiveModelSerializer
159
+
160
+ ```rb
161
+ class ProfileSerializer < ActiveModel::Serializer
162
+ type :profile
163
+ attributes :email
164
+ end
165
+
166
+ class ArticleSerializer < ActiveModel::Serializer
167
+ type :article
168
+ attributes :title, :body
169
+ end
170
+
171
+ class UserSerializer < ActiveModel::Serializer
172
+ type :user
173
+ attributes :id, :created_at, :updated_at
174
+ has_one :profile, serializer: ProfileSerializer # For has_one relation
175
+ has_many :articles, serializer: ArticleSerializer # For has_many relation
176
+ end
177
+ user = User.create!
178
+ user.craete_profile! email: email
179
+ user.articles.create! title: title, body: body
180
+ ActiveModelSerializers::SerializableResource.new(
181
+ user
182
+ ).serializable_hash
183
+ # => {
184
+ # :user=> {
185
+ # :id=>1,
186
+ # :created_at=>created_at,
187
+ # :updated_at=>updated_at,
188
+ # :profile=> {
189
+ # :email=>email
190
+ # },
191
+ # :articles => [
192
+ # {
193
+ # :title=>title,
194
+ # :body=>body
195
+ # }
196
+ # ]
197
+ # }
198
+ # }
199
+ ```
200
+
201
+ #### Alba
202
+
203
+ ```rb
204
+ class ProfileResource
205
+ include Alba::Resource
206
+ root_key :profile
207
+ attributes :email
208
+ end
209
+
210
+ class ArticleResource
211
+ include Alba::Resource
212
+ root_key :article
213
+ attributes :title, :body
214
+ end
215
+
216
+ class UserResource
217
+ include Alba::Resource
218
+ root_key :user
219
+ attributes :id, :created_at, :updated_at
220
+ one :profile, resource: ProfileResource # For has_one relation
221
+ many :articles, resource: ArticleResource # For has_many relation
222
+ end
223
+
224
+ user = User.create!
225
+ user.craete_profile! email: email
226
+ user.articles.create! title: title, body: body
227
+ UserResource.new(user).serializable_hash
228
+ # => {
229
+ # :id=>1,
230
+ # :created_at=>created_at,
231
+ # :updated_at=>updated_at,
232
+ # :profile=> {
233
+ # :email=>email
234
+ # },
235
+ # :articles => [
236
+ # {
237
+ # :title=>title,
238
+ # :body=>body
239
+ # }
240
+ # ]
241
+ # }
242
+ ```
243
+
244
+ ### Serialize with custom serializer
245
+
246
+ #### ActiveModelSerializer
247
+
248
+ ```rb
249
+ class CustomUserSerializer < ActiveModel::Serializer
250
+ type :user
251
+ attribute :email do
252
+ object.profile.email
253
+ end
254
+ end
255
+
256
+ # serialze
257
+ user = User.create!
258
+ user.craete_profile! email: email
259
+ ActiveModelSerializers::SerializableResource.new(
260
+ user,
261
+ serializer: ::CustomUserSerializer # Call with serializer arg
262
+ ).serializable_hash
263
+ # => {
264
+ # user: {
265
+ # email: email
266
+ # }
267
+ # }
268
+ ```
269
+
270
+ #### Alba
271
+
272
+ ```rb
273
+ class CustomUserResource
274
+ include Alba::Resource
275
+ root_key :user
276
+ attribute :email do
277
+ object.profile.email
278
+ end
279
+ end
280
+
281
+ # serialze
282
+ user = User.create!
283
+ user.craete_profile! email: email
284
+ CustomUserResource.new(user).serializable_hash
285
+ # => {
286
+ # email: email
287
+ # }
288
+ ```
289
+
290
+ ### Passing arbitrary options to a serializer
291
+
292
+ #### ActiveModelSerializer
293
+
294
+ ```rb
295
+ class UserSerializer < ApplicationSerializer
296
+ type :user
297
+ attributes :id, :created_at, :updated_at
298
+ attribute :custom_params do
299
+ pp instance_options
300
+ # => given_params: { a: :b }
301
+ instance_options # Access by instance_options method
302
+ end
303
+ end
304
+
305
+ # serialze
306
+ user = User.create!
307
+ ActiveModelSerializers::SerializableResource.new(
308
+ user,
309
+ given_params: { a: :b } # Give with your favorite keyword argument
310
+ ).serializable_hash
311
+ # => {
312
+ # :id=>1,
313
+ # :created_at=>created_at,
314
+ # :updated_at=>updated_at,
315
+ # :custom_params=>{
316
+ # :given_params=>{
317
+ # :a=>:b
318
+ # }
319
+ # }
320
+ # }
321
+ ```
322
+
323
+ #### Alba
324
+
325
+ ```rb
326
+ class UserResource
327
+ include Alba::Resource
328
+ root_key :user
329
+ attributes :id, :created_at, :updated_at
330
+ attribute :custom_params do
331
+ pp params
332
+ # => { :a=>:b }
333
+ params
334
+ end
335
+ end
336
+
337
+ # serialze
338
+ user = User.create!
339
+ UserResource.new(
340
+ user,
341
+ params: { a: :b } # Give with :params keyword argument
342
+ ).serializable_hash
343
+ # => {
344
+ # :id=>1,
345
+ # :created_at=>created_at,
346
+ # :updated_at=>updated_at,
347
+ # :custom_params=>{
348
+ # :a=>:b
349
+ # }
350
+ # }
351
+ ```
352
+
353
+ ## 3. Unsupported features
354
+
355
+ - [RelationshipLinks](https://github.com/rails-api/active_model_serializers/blob/v0.10.6/docs/howto/add_relationship_links.md)
356
+ - [PaginationLinks](https://github.com/rails-api/active_model_serializers/blob/v0.10.6/docs/howto/add_pagination_links.md)
357
+ - [Logging](https://github.com/rails-api/active_model_serializers/blob/v0.10.6/docs/general/logging.md)
358
+ - [Caching](https://github.com/rails-api/active_model_serializers/blob/v0.10.6/docs/general/caching.md)
359
+ - [Rendering](https://github.com/rails-api/active_model_serializers/blob/v0.10.6/docs/general/rendering.md)
@@ -0,0 +1,223 @@
1
+ ---
2
+ title: Upgrading from Jbuilder
3
+ ---
4
+
5
+ <!-- @format -->
6
+
7
+ This guide is aimed at helping Jbuilder users transition to Alba, and it consists of three parts:
8
+
9
+ 1. Basic serialization
10
+ 2. Complex serialization
11
+ 3. Unsupported features
12
+
13
+ ## Example class
14
+
15
+ This example will also be replaced by ActiveReord.
16
+
17
+ ```rb
18
+ class User
19
+ attr_reader :id, :created_at, :updated_at
20
+ attr_accessor :profile, :articles
21
+
22
+ def initialize(id)
23
+ @id = id
24
+ @created_at = Time.now
25
+ @updated_at = Time.now
26
+ @articles = []
27
+ end
28
+ end
29
+
30
+ class Profile
31
+ attr_reader :email
32
+
33
+ def initialize(email)
34
+ @email = email
35
+ end
36
+ end
37
+
38
+ class Article
39
+ attr_accessor :title, :body
40
+
41
+ def initialize(title, body)
42
+ @title = title
43
+ @body = body
44
+ end
45
+ end
46
+ ```
47
+
48
+ ## 1. Basic serialization
49
+
50
+ #### Jbuilder
51
+
52
+ ```rb
53
+ # show.json.jbuilder
54
+ # With block
55
+ @user = User.new(id)
56
+ json.user do |user|
57
+ user.id @user.id
58
+ user.created_at @user.created_at
59
+ user.updated_at @user.updated_at
60
+ end
61
+ # => '{"user":{"id":id, "created_at": created_at, "updated_at": updated_at}'
62
+ # or #extract!
63
+ json.extract! @user, :id, :created_at, :updated_at
64
+ # => '{"id":id, "created_at": created_at, "updated_at": updated_at}'
65
+ ```
66
+
67
+ #### Alba
68
+
69
+ ```rb
70
+ # With block
71
+ user = User.new(id)
72
+ Alba.serialize(user, root_key: :user) do
73
+ attributes :id, :created_at, :updated_at
74
+ end
75
+ # => '{"user":{"id":id, "created_at": created_at, "updated_at": updated_at}'
76
+ # or with resourceClass.
77
+ # Infer and use by "#{MODEL_NAME}Resource"
78
+ class UserResource
79
+ include Alba::Resource
80
+ root_key :user
81
+ attributes :id, :created_at, :updated_at
82
+ end
83
+ UserResource.new(user).serialize
84
+ # => '{"user":{"id":id, "created_at": created_at, "updated_at": updated_at}'
85
+ ```
86
+
87
+ ## 2. Complex serialization
88
+
89
+ ### Serialize collections
90
+
91
+ #### Jbuilder
92
+
93
+ ```rb
94
+ @users = ids.map { |id| User.new(id) }
95
+ # index.json.jbuilder
96
+ json.array! @users, :id, :created_at, :updated_at
97
+ # => '[{"id":id, "created_at": created_at, "updated_at": updated_at}, {"id":id, "created_at": created_at, "updated_at": updated_at}, {"id":id, "created_at": created_at, "updated_at": updated_at}]'
98
+ ```
99
+
100
+ #### Alba
101
+
102
+ ```rb
103
+ class UserResource
104
+ include Alba::Resource
105
+ root_key :user
106
+ attributes :id, :created_at, :updated_at
107
+ end
108
+ users = ids.map { |id| User.new(id) }
109
+ UserResource.new(users).serialize
110
+ # => '[{"id":id, "created_at": created_at, "updated_at": updated_at}, {"id":id, "created_at": created_at, "updated_at": updated_at}, {"id":id, "created_at": created_at, "updated_at": updated_at}]'
111
+
112
+ ```
113
+
114
+ ### Nested serialization
115
+
116
+ #### Jbuilder
117
+
118
+ ```rb
119
+ # show.json.jbuilder
120
+ @user = User.new(id)
121
+ @user.profile = Profile.new(email)
122
+ @user.articles = [Article.new(title, body)]
123
+ json.user do |user|
124
+ user.id @user.id
125
+ user.created_at @user.created_at
126
+ user.updated_at @user.updated_at
127
+ json.profile do
128
+ json.email @user.profile.email
129
+ end
130
+ json.articles do
131
+ json.array! @user.articles, :title, :body
132
+ end
133
+ end
134
+ # => '{"user":{"id":id, "created_at": created_at, "updated_at": updated_at, "profile": {"email": email}, articles: [{"title": title, "body": body}]}'
135
+ # or #merge!
136
+ profile_hash = { profile: { email: @user.profile.email } }
137
+ articles_hash = { articles: @user.articles.map { |article| { title: article.title, body: article.body } } }
138
+ json.user do |user|
139
+ user.id @user.id
140
+ user.created_at @user.created_at
141
+ user.updated_at @user.updated_at
142
+ json.merge! profile_hash
143
+ json.merge! articles_hash
144
+ end
145
+ # => '{"user":{"id":id, "created_at": created_at, "updated_at": updated_at, "profile": {"email": email}, articles: [{"title": title, "body": body}]}'
146
+ # or #partial!
147
+ # profiles/_profile.json.jbuilder
148
+ json.profile do
149
+ json.email @profile.email
150
+ end
151
+ # articles/_article.json.jbuilder
152
+ json.extract! article, :title, :body
153
+ # user/show.json.jbuilder
154
+ json.user do |user|
155
+ user.id @user.id
156
+ user.created_at @user.created_at
157
+ user.updated_at @user.updated_at
158
+ json.partial! @user.profile, as: :profile
159
+ json.articles @user.articles do |article|
160
+ json.partial! article, partial: 'articles/article'
161
+ end
162
+ end
163
+ ```
164
+
165
+ #### Alba
166
+
167
+ ```rb
168
+ # With ResourceClass by each resources
169
+ class ProfileResource
170
+ include Alba::Resource
171
+ root_key :profile
172
+ attributes :email
173
+ end
174
+ class ArticleResource
175
+ include Alba::Resource
176
+ root_key :article
177
+ attributes :title, :body
178
+ end
179
+ class UserResource
180
+ include Alba::Resource
181
+ root_key :user
182
+ attributes :id, :created_at, :updated_at
183
+ one :profile, resource: ProfileResource
184
+ many :articles, resource: ArticleResource
185
+ end
186
+ user = User.new(id)
187
+ user.profile = Profile.new(email)
188
+ user.articles = [Article.new(title, body)]
189
+ UserResource.new(user).serialize
190
+ # => '{"user":{"id":id, "created_at": created_at, "updated_at": updated_at, "profile": {"email": email}, articles: [{"title": title, "body": body}]}'
191
+
192
+ # or #attribute
193
+ class UserResource
194
+ include Alba::Resource
195
+ root_key :user
196
+ attributes :id, :created_at, :updated_at
197
+
198
+ attribute :profile do
199
+ {
200
+ email: object.profile.email # Can access to received resource by #object method
201
+ }
202
+ end
203
+
204
+ attribute :articles do
205
+ object.articles.map do |article|
206
+ {
207
+ title: article.title,
208
+ body: article.body,
209
+ }
210
+ end
211
+ end
212
+ end
213
+ user = User.new(id)
214
+ user.profile = Profile.new(email)
215
+ UserResource.new(user).serialize
216
+ # => '{"user":{"id":id, "created_at": created_at, "updated_at": updated_at, "profile": {"email": email}, articles: [{"title": title, "body": body}]}'
217
+ ```
218
+
219
+ ## 3. Unsupported features
220
+
221
+ - Jbuilder#ignore_nil!
222
+ - Jbuilder#cache!
223
+ - Jbuilder.key_format! and Jbuilder.deep_format_keys!
data/gemfiles/all.gemfile CHANGED
@@ -1,6 +1,7 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gem 'activesupport', require: false # For backend
4
+ gem 'dry-inflector', require: false # For inflector
4
5
  gem 'ffaker', require: false # For testing
5
6
  gem 'minitest', '~> 5.14' # For test
6
7
  gem 'rake', '~> 13.0' # For test and automation
@@ -11,7 +12,7 @@ gem 'rubocop-rake', '>= 0.5.1', require: false # For lint
11
12
  gem 'rubocop-sensible', '~> 0.3.0', require: false # For lint
12
13
  gem 'simplecov', '~> 0.21.0', require: false # For test coverage
13
14
  gem 'simplecov-cobertura', require: false # For test coverage
14
- gem 'yard', require: false
15
+ gem 'yard', require: false # For documentation
15
16
 
16
17
  platforms :ruby do
17
18
  gem 'oj', '~> 3.11', require: false # For backend
@@ -9,7 +9,7 @@ gem 'rubocop-rake', '>= 0.5.1', require: false # For lint
9
9
  gem 'rubocop-sensible', '~> 0.3.0', require: false # For lint
10
10
  gem 'simplecov', '~> 0.21.0', require: false # For test coverage
11
11
  gem 'simplecov-cobertura', require: false # For test coverage
12
- gem 'yard', require: false
12
+ gem 'yard', require: false # For documentation
13
13
 
14
14
  platforms :ruby do
15
15
  gem 'oj', '~> 3.11', require: false # For backend
@@ -10,7 +10,7 @@ gem 'rubocop-rake', '>= 0.5.1', require: false # For lint
10
10
  gem 'rubocop-sensible', '~> 0.3.0', require: false # For lint
11
11
  gem 'simplecov', '~> 0.21.0', require: false # For test coverage
12
12
  gem 'simplecov-cobertura', require: false # For test coverage
13
- gem 'yard', require: false
13
+ gem 'yard', require: false # For documentation
14
14
 
15
15
  platforms :ruby do
16
16
  gem 'ruby-prof', require: false # For performance profiling
@@ -1,21 +1,40 @@
1
1
  module Alba
2
- # Base class for `One` and `Many`
3
- # Child class should implement `to_hash` method
2
+ # Representing association
4
3
  class Association
4
+ @const_cache = {}
5
+ class << self
6
+ attr_reader :const_cache
7
+ end
8
+
5
9
  attr_reader :object, :name
6
10
 
7
- # @param name [Symbol] name of the method to fetch association
8
- # @param condition [Proc] a proc filtering data
9
- # @param resource [Class<Alba::Resource>] a resource class for the association
11
+ # @param name [Symbol, String] name of the method to fetch association
12
+ # @param condition [Proc, nil] a proc filtering data
13
+ # @param resource [Class<Alba::Resource>, nil] a resource class for the association
14
+ # @param nesting [String] a namespace where source class is inferred with
10
15
  # @param block [Block] used to define resource when resource arg is absent
11
16
  def initialize(name:, condition: nil, resource: nil, nesting: nil, &block)
12
17
  @name = name
13
18
  @condition = condition
14
- @block = block
15
19
  @resource = resource
16
20
  return if @resource
17
21
 
18
- assign_resource(nesting)
22
+ assign_resource(nesting, block)
23
+ end
24
+
25
+ # Recursively converts an object into a Hash
26
+ #
27
+ # @param target [Object] the object having an association method
28
+ # @param within [Hash] determines what associations to be serialized. If not set, it serializes all associations.
29
+ # @param params [Hash] user-given Hash for arbitrary data
30
+ # @return [Hash]
31
+ def to_h(target, within: nil, params: {})
32
+ @object = target.public_send(@name)
33
+ @object = @condition.call(object, params) if @condition
34
+ return if @object.nil?
35
+
36
+ @resource = constantize(@resource)
37
+ @resource.new(object, params: params, within: within).to_h
19
38
  end
20
39
 
21
40
  private
@@ -25,30 +44,20 @@ module Alba
25
44
  when Class
26
45
  resource
27
46
  when Symbol, String
28
- Object.const_get(resource)
47
+ self.class.const_cache.fetch(resource) do
48
+ self.class.const_cache[resource] = Object.const_get(resource)
49
+ end
29
50
  end
30
51
  end
31
52
 
32
- def assign_resource(nesting)
33
- @resource = if @block
34
- resource_class
53
+ def assign_resource(nesting, block)
54
+ @resource = if block
55
+ Alba.resource_class(&block)
35
56
  elsif Alba.inferring
36
- resource_class_with_nesting(nesting)
57
+ Alba.infer_resource_class(@name, nesting: nesting)
37
58
  else
38
59
  raise ArgumentError, 'When Alba.inferring is false, either resource or block is required'
39
60
  end
40
61
  end
41
-
42
- def resource_class
43
- klass = Class.new
44
- klass.include(Alba::Resource)
45
- klass.class_eval(&@block)
46
- klass
47
- end
48
-
49
- def resource_class_with_nesting(nesting)
50
- const_parent = nesting.nil? ? Object : Object.const_get(nesting)
51
- const_parent.const_get("#{ActiveSupport::Inflector.classify(@name)}Resource")
52
- end
53
62
  end
54
63
  end