alba 1.3.0 → 1.6.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.
@@ -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