alba 1.3.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -19,19 +19,19 @@ If you have feature requests or interesting ideas, join us with [Ideas](https://
19
19
 
20
20
  ## Why Alba?
21
21
 
22
- Because it's fast, flexible and well-maintained!
22
+ Because it's fast, easy-to-use and extensible!
23
23
 
24
24
  ### Fast
25
25
 
26
26
  Alba is faster than most of the alternatives. We have a [benchmark](https://github.com/okuramasafumi/alba/tree/master/benchmark).
27
27
 
28
- ### Flexible
28
+ ### Easy to use
29
29
 
30
- Alba provides a small set of DSL to define your serialization logic. It also provides methods you can override to alter and filter serialized hash so that you have full control over the result.
30
+ Alba provides four DSLs, `attributes` to fetch attribute with its name, `attribute` to execute block for the attribute, `one` to seriaize single attribute with another resource, and `many` to serialize collection attribute with another resource. When you want to do something complex, there are many examples in this README so you can mimic them to get started.
31
31
 
32
- ### Maintained
32
+ ### Extensible
33
33
 
34
- Alba is well-maintained and adds features quickly. [Coverage Status](https://coveralls.io/github/okuramasafumi/alba?branch=master) and [CodeClimate Maintainability](https://codeclimate.com/github/okuramasafumi/alba/maintainability) show the code base is quite healthy.
34
+ Alba embraces extensibility through common techniques such as class inheritance and module inclusion. Alba provides its capacity with one module so you can still have your own class hierarchy.
35
35
 
36
36
  ## Installation
37
37
 
@@ -64,20 +64,13 @@ You can find the documentation on [RubyDoc](https://rubydoc.info/github/okuramas
64
64
  * Key transformation
65
65
  * Root key inference
66
66
  * Error handling
67
+ * Nil handling
67
68
  * Resource name inflection based on association name
68
69
  * Circular associations control
69
70
  * [Experimental] Types for validation and conversion
71
+ * Layout
70
72
  * No runtime dependencies
71
73
 
72
- ## Anti features
73
-
74
- * Sorting keys
75
- * Class level support of parameters
76
- * Supporting all existing JSON encoder/decoder
77
- * Cache
78
- * [JSON:API](https://jsonapi.org) support
79
- * And many others
80
-
81
74
  ## Usage
82
75
 
83
76
  ### Configuration
@@ -98,27 +91,35 @@ You can set a backend like this:
98
91
  Alba.backend = :oj
99
92
  ```
100
93
 
101
- #### Inference configuration
94
+ #### Encoder configuration
102
95
 
103
- You can enable inference feature using `enable_inference!` method.
96
+ You can also set JSON encoder directly with a Proc.
104
97
 
105
98
  ```ruby
106
- Alba.enable_inference!
99
+ Alba.encoder = ->(object) { JSON.generate(object) }
107
100
  ```
108
101
 
109
- You must install `ActiveSupport` to enable inference.
102
+ You can consider setting a backend with Symbol as a shortcut to set encoder.
110
103
 
111
- #### Error handling configuration
104
+ #### Inference configuration
112
105
 
113
- You can configure error handling with `on_error` method.
106
+ You can enable inference feature using `enable_inference!` method.
114
107
 
115
108
  ```ruby
116
- Alba.on_error :ignore
109
+ Alba.enable_inference!(with: :active_support)
117
110
  ```
118
111
 
112
+ You can choose which inflector Alba uses for inference. Possible value for `with` option are:
113
+
114
+ - `:active_support` for `ActiveSupport::Inflector`
115
+ - `:dry` for `Dry::Inflector`
116
+ - any object which responds to some methods (see [below](#custom-inflector))
117
+
119
118
  For the details, see [Error handling section](#error-handling)
120
119
 
121
- ### Simple serialization with key
120
+ ### Simple serialization with root key
121
+
122
+ You can define attributes with (yes) `attributes` macro with attribute names. If your attribute need some calculations, you can use `attribute` with block.
122
123
 
123
124
  ```ruby
124
125
  class User
@@ -135,7 +136,7 @@ end
135
136
  class UserResource
136
137
  include Alba::Resource
137
138
 
138
- key :user
139
+ root_key :user
139
140
 
140
141
  attributes :id, :name
141
142
 
@@ -146,7 +147,34 @@ end
146
147
 
147
148
  user = User.new(1, 'Masafumi OKURA', 'masafumi@example.com')
148
149
  UserResource.new(user).serialize
149
- # => "{\"id\":1,\"name\":\"Masafumi OKURA\",\"name_with_email\":\"Masafumi OKURA: masafumi@example.com\"}"
150
+ # => "{\"user\":{\"id\":1,\"name\":\"Masafumi OKURA\",\"name_with_email\":\"Masafumi OKURA: masafumi@example.com\"}}"
151
+ ```
152
+
153
+ You can define instance methods on resources so that you can use it as attribute name in `attributes`.
154
+
155
+ ```ruby
156
+ # The serialization result is the same as above
157
+ class UserResource
158
+ include Alba::Resource
159
+
160
+ root_key :user, :users # Later is for plural
161
+
162
+ attributes :id, :name, :name_with_email
163
+
164
+ # Attribute methods must accept one argument for each serialized object
165
+ def name_with_email(user)
166
+ "#{user.name}: #{user.email}"
167
+ end
168
+ end
169
+ ```
170
+
171
+ This even works with users collection.
172
+
173
+ ```ruby
174
+ user1 = User.new(1, 'Masafumi OKURA', 'masafumi@example.com')
175
+ user2 = User.new(2, 'Test User', 'test@example.com')
176
+ UserResource.new([user1, user2]).serialize
177
+ # => "{\"users\":[{\"id\":1,\"name\":\"Masafumi OKURA\",\"name_with_email\":\"Masafumi OKURA: masafumi@example.com\"},{\"id\":2,\"name\":\"Test User\",\"name_with_email\":\"Test User: test@example.com\"}]}"
150
178
  ```
151
179
 
152
180
  ### Serialization with associations
@@ -198,12 +226,98 @@ UserResource.new(user).serialize
198
226
  # => '{"id":1,"articles":[{"title":"Hello World!"},{"title":"Super nice"}]}'
199
227
  ```
200
228
 
229
+ You can define associations inline if you don't need a class for association.
230
+
231
+ ```ruby
232
+ class ArticleResource
233
+ include Alba::Resource
234
+
235
+ attributes :title
236
+ end
237
+
238
+ class UserResource
239
+ include Alba::Resource
240
+
241
+ attributes :id
242
+
243
+ many :articles, resource: ArticleResource
244
+ end
245
+
246
+ # This class works the same as `UserResource`
247
+ class AnotherUserResource
248
+ include Alba::Resource
249
+
250
+ attributes :id
251
+
252
+ many :articles do
253
+ attributes :title
254
+ end
255
+ end
256
+ ```
257
+
258
+ You can "filter" association using second proc argument. This proc takes association object and `params`.
259
+
260
+ This feature is useful when you want to modify association, such as adding `includes` or `order` to ActiveRecord relations.
261
+
262
+ ```ruby
263
+ class User
264
+ attr_reader :id
265
+ attr_accessor :articles
266
+
267
+ def initialize(id)
268
+ @id = id
269
+ @articles = []
270
+ end
271
+ end
272
+
273
+ class Article
274
+ attr_accessor :id, :title, :body
275
+
276
+ def initialize(id, title, body)
277
+ @id = id
278
+ @title = title
279
+ @body = body
280
+ end
281
+ end
282
+
283
+ class ArticleResource
284
+ include Alba::Resource
285
+
286
+ attributes :title
287
+ end
288
+
289
+ class UserResource
290
+ include Alba::Resource
291
+
292
+ attributes :id
293
+
294
+ # Second proc works as a filter
295
+ many :articles,
296
+ proc { |articles, params|
297
+ filter = params[:filter] || :odd?
298
+ articles.select {|a| a.id.send(filter) }
299
+ },
300
+ resource: ArticleResource
301
+ end
302
+
303
+ user = User.new(1)
304
+ article1 = Article.new(1, 'Hello World!', 'Hello World!!!')
305
+ user.articles << article1
306
+ article2 = Article.new(2, 'Super nice', 'Really nice!')
307
+ user.articles << article2
308
+
309
+ UserResource.new(user).serialize
310
+ # => '{"id":1,"articles":[{"title":"Hello World!"}]}'
311
+ UserResource.new(user, params: {filter: :even?}).serialize
312
+ # => '{"id":1,"articles":[{"title":"Super nice"}]}'
313
+ ```
314
+
201
315
  ### Inline definition with `Alba.serialize`
202
316
 
203
317
  `Alba.serialize` method is a shortcut to define everything inline.
204
318
 
205
319
  ```ruby
206
- Alba.serialize(user, key: :foo) do
320
+ Alba.serialize(user, root_key: :foo) do
207
321
  attributes :id
208
322
  many :articles do
209
323
  attributes :title, :body
@@ -212,11 +326,20 @@ end
212
326
  # => '{"foo":{"id":1,"articles":[{"title":"Hello World!","body":"Hello World!!!"},{"title":"Super nice","body":"Really nice!"}]}}'
213
327
  ```
214
328
 
329
+ `Alba.serialize` can be used when you don't know what kind of object you serialize. For example:
330
+
331
+ ```ruby
332
+ Alba.serialize(something)
333
+ # => Same as `FooResource.new(something).serialize` when `something` is an instance of `Foo`.
334
+ ```
335
+
215
336
  Although this might be useful sometimes, it's generally recommended to define a class for Resource.
216
337
 
217
- ### Inheritance and Ignorance
338
+ ### Inheritance and attributes filter
218
339
 
219
- You can `exclude` or `ignore` certain attributes using `ignoring`.
340
+ You can filter out certain attributes by overriding `attributes` instance method. This is useful when you want to customize existing resource with inheritance.
341
+
342
+ You can access raw attributes via `super` call. It returns a Hash whose keys are the name of the attribute and whose values are the body. Usually you need only keys to filter out, like below.
220
343
 
221
344
  ```ruby
222
345
  class Foo
@@ -235,13 +358,14 @@ class GenericFooResource
235
358
  attributes :id, :name, :body
236
359
  end
237
360
 
238
- class RestrictedFooResouce < GenericFooResource
239
- ignoring :id, :body
361
+ class RestrictedFooResource < GenericFooResource
362
+ def attributes
363
+ super.select { |key, _| key.to_sym == :name }
364
+ end
240
365
  end
241
366
 
242
- RestrictedFooResouce.new(foo).serialize
367
+ RestrictedFooResource.new(foo).serialize
243
368
  # => '{"name":"my foo"}'
244
- end
245
369
  ```
246
370
 
247
371
  ### Key transformation
@@ -276,14 +400,22 @@ UserResourceCamel.new(user).serialize
276
400
  # => '{"id":1,"firstName":"Masafumi","lastName":"Okura"}'
277
401
  ```
278
402
 
403
+ Possible values for `transform_keys` argument are:
404
+
405
+ * `:camel` for CamelCase
406
+ * `:lower_camel` for lowerCamelCase
407
+ * `:dash` for dash-case
408
+ * `:snake` for snake_case
409
+ * `:none` for not transforming keys
410
+
279
411
  You can also transform root key when:
280
412
 
281
413
  * `Alba.enable_inference!` is called
282
- * `key!` is called in Resource class
283
- * `root` option of `transform_keys` is set to true or `Alba.enable_root_key_transformation!` is called.
414
+ * `root_key!` is called in Resource class
415
+ * `root` option of `transform_keys` is set to true
284
416
 
285
417
  ```ruby
286
- Alba.enable_inference!
418
+ Alba.enable_inference!(with: :active_support) # with :dry also works
287
419
 
288
420
  class BankAccount
289
421
  attr_reader :account_number
@@ -296,7 +428,7 @@ end
296
428
  class BankAccountResource
297
429
  include Alba::Resource
298
430
 
299
- key!
431
+ root_key!
300
432
 
301
433
  attributes :account_number
302
434
  transform_keys :dash, root: true
@@ -313,30 +445,29 @@ Supported transformation types are :camel, :lower_camel and :dash.
313
445
 
314
446
  #### Custom inflector
315
447
 
316
- A custom inflector can be plugged in as follows...
317
- ```ruby
318
- Alba.inflector = MyCustomInflector
319
- ```
320
- ...and has to implement following interface (the parameter `key` is of type `String`):
448
+ A custom inflector can be plugged in as follows.
449
+
321
450
  ```ruby
322
- module InflectorInterface
323
- def camelize(key)
324
- raise "Not implemented"
451
+ module CustomInflector
452
+ module_function
453
+
454
+ def camelize(string)
325
455
  end
326
456
 
327
- def camelize_lower(key)
328
- raise "Not implemented"
457
+ def camelize_lower(string)
329
458
  end
330
459
 
331
- def dasherize(key)
332
- raise "Not implemented"
460
+ def dasherize(string)
461
+ end
462
+
463
+ def underscore(string)
464
+ end
465
+
466
+ def classify(string)
333
467
  end
334
468
  end
335
469
 
336
- ```
337
- For example you could use `Dry::Inflector`, which implements exactly the above interface. If you are developing a `Hanami`-Application `Dry::Inflector` is around. In this case the following would be sufficient:
338
- ```ruby
339
- Alba.inflector = Dry::Inflector.new
470
+ Alba.enable_inference!(with: CustomInflector)
340
471
  ```
341
472
 
342
473
  ### Filtering attributes
@@ -402,12 +533,26 @@ user = User.new(1, nil, nil)
402
533
  UserResource.new(user).serialize # => '{"id":1}'
403
534
  ```
404
535
 
536
+ ### Default
537
+
538
+ Alba doesn't support default value for attributes, but it's easy to set a default value.
539
+
540
+ ```ruby
541
+ class FooResource
542
+ attribute :bar do |foo|
543
+ foo.bar || 'default bar'
544
+ end
545
+ end
546
+ ```
547
+
548
+ We believe this is clearer than using some (not implemented yet) DSL such as `default` because there are some conditions where default values should be applied (`nil`, `blank?`, `empty?` etc.)
549
+
405
550
  ### Inference
406
551
 
407
552
  After `Alba.enable_inference!` called, Alba tries to infer root key and association resource name.
408
553
 
409
554
  ```ruby
410
- Alba.enable_inference!
555
+ Alba.enable_inference!(with: :active_support) # with :dry also works
411
556
 
412
557
  class User
413
558
  attr_reader :id
@@ -455,8 +600,6 @@ This resource automatically sets its root key to either "users" or "user", depen
455
600
 
456
601
  Also, you don't have to specify which resource class to use with `many`. Alba infers it from association name.
457
602
 
458
- Note that to enable this feature you must install `ActiveSupport` gem.
459
-
460
603
  ### Error handling
461
604
 
462
605
  You can set error handler globally or per resource using `on_error`.
@@ -500,16 +643,118 @@ There are four possible arguments `on_error` method accepts.
500
643
  The block receives five arguments, `error`, `object`, `key`, `attribute` and `resource class` and must return a two-element array. Below is an example.
501
644
 
502
645
  ```ruby
503
- # Global error handling
504
- Alba.on_error do |error, object, key, attribute, resource_class|
505
- if resource_class == MyResource
506
- ['error_fallback', object.error_fallback]
507
- else
508
- [key, error.message]
646
+ class ExampleResource
647
+ include Alba::Resource
648
+ on_error do |error, object, key, attribute, resource_class|
649
+ if resource_class == MyResource
650
+ ['error_fallback', object.error_fallback]
651
+ else
652
+ [key, error.message]
653
+ end
509
654
  end
510
655
  end
511
656
  ```
512
657
 
658
+ ### Nil handling
659
+
660
+ Sometimes we want to convert `nil` to different values such as empty string. Alba provides a flexible way to handle `nil`.
661
+
662
+ ```ruby
663
+ class User
664
+ attr_reader :id, :name, :age
665
+
666
+ def initialize(id, name = nil, age = nil)
667
+ @id = id
668
+ @name = name
669
+ @age = age
670
+ end
671
+ end
672
+
673
+ class UserResource
674
+ include Alba::Resource
675
+
676
+ on_nil { '' }
677
+
678
+ root_key :user, :users
679
+
680
+ attributes :id, :name, :age
681
+ end
682
+
683
+ UserResource.new(User.new(1)).serialize
684
+ # => '{"user":{"id":1,"name":"","age":""}}'
685
+ ```
686
+
687
+ You can get various information via block parameters.
688
+
689
+ ```ruby
690
+ class UserResource
691
+ include Alba::Resource
692
+
693
+ on_nil do |object, key|
694
+ if key == age
695
+ 20
696
+ else
697
+ "User#{object.id}"
698
+ end
699
+ end
700
+
701
+ root_key :user, :users
702
+
703
+ attributes :id, :name, :age
704
+ end
705
+
706
+ UserResource.new(User.new(1)).serialize
707
+ # => '{"user":{"id":1,"name":"User1","age":20}}'
708
+ ```
709
+
710
+ ### Metadata
711
+
712
+ You can set a metadata with `meta` DSL or `meta` option.
713
+
714
+ ```ruby
715
+ class UserResource
716
+ include Alba::Resource
717
+
718
+ root_key :user, :users
719
+
720
+ attributes :id, :name
721
+
722
+ meta do
723
+ if object.is_a?(Enumerable)
724
+ {size: object.size}
725
+ else
726
+ {foo: :bar}
727
+ end
728
+ end
729
+ end
730
+
731
+ user = User.new(1, 'Masafumi OKURA', 'masafumi@example.com')
732
+ UserResource.new([user]).serialize
733
+ # => '{"users":[{"id":1,"name":"Masafumi OKURA"}],"meta":{"size":1}}'
734
+
735
+ # You can merge metadata with `meta` option
736
+
737
+ UserResource.new([user]).serialize(meta: {foo: :bar})
738
+ # => '{"users":[{"id":1,"name":"Masafumi OKURA"}],"meta":{"size":1,"foo":"bar"}}'
739
+
740
+ # You can set metadata with `meta` option alone
741
+
742
+ class UserResourceWithoutMeta
743
+ include Alba::Resource
744
+
745
+ root_key :user, :users
746
+
747
+ attributes :id, :name
748
+ end
749
+
750
+ UserResource.new([user]).serialize(meta: {foo: :bar})
751
+ # => '{"users":[{"id":1,"name":"Masafumi OKURA"}],"meta":{"foo":"bar"}}'
752
+ ```
753
+
754
+ You can use `object` method to access the underlying object and `params` to access the params in `meta` block.
755
+
756
+ Note that setting root key is required when setting a metadata.
757
+
513
758
  ### Circular associations control
514
759
 
515
760
  **Note that this feature works correctly since version 1.3. In previous versions it doesn't work as expected.**
@@ -559,10 +804,174 @@ UserResource.new(user).serialize
559
804
 
560
805
  Note that this feature is experimental and interfaces are subject to change.
561
806
 
807
+ ### Layout
808
+
809
+ Sometimes we'd like to serialize JSON into a template. In other words, we need some structure OUTSIDE OF serialized JSON. IN HTML world, we call it a "layout".
810
+
811
+ Alba supports serializing JSON in a layout. You need a file for layout and then to specify file with `layout` method.
812
+
813
+ ```erb
814
+ {
815
+ "header": "my_header",
816
+ "body": <%= serialized_json %>
817
+ }
818
+ ```
819
+
820
+ ```ruby
821
+ class FooResource
822
+ include Alba::Resource
823
+ layout file: 'my_layout.json.erb'
824
+ end
825
+ ```
826
+
827
+ Note that layout files are treated as `json` and `erb` and evaluated in a context of the resource, meaning
828
+
829
+ * A layout file must be a valid JSON
830
+ * You must write `<%= serialized_json %>` in a layout to put serialized JSON string into a layout
831
+ * You can access `params` in a layout so that you can add virtually any objects to a layout
832
+ * When you access `params`, it's usually a Hash. You can use `encode` method in a layout to convert `params` Hash into a JSON with the backend you use
833
+ * You can also access `object`, the underlying object for the resource
834
+
835
+ In case you don't want to have a file for layout, Alba lets you define and apply layouts inline:
836
+
837
+ ```ruby
838
+ class FooResource
839
+ include Alba::Resource
840
+ layout inline: proc do
841
+ {
842
+ header: 'my header',
843
+ body: serializable_hash
844
+ }
845
+ end
846
+ end
847
+ ```
848
+
849
+ In the example above, we specify a Proc which returns a Hash as an inline layout. In the Proc we can use `serializable_hash` method to access a Hash right before serialization.
850
+
851
+ You can also use a Proc which returns String, not a Hash, for an inline layout.
852
+
853
+ ```ruby
854
+ class FooResource
855
+ include Alba::Resource
856
+ layout inline: proc do
857
+ %({
858
+ "header": "my header",
859
+ "body": #{serialized_json}
860
+ })
861
+ end
862
+ end
863
+ ```
864
+
865
+ It looks similar to file layout but you must use string interpolation for method calls since it's not an ERB.
866
+
867
+ Also note that we use percentage notation here to use double quotes. Using single quotes in inline string layout causes the error which might be resolved in other ways.
868
+
562
869
  ### Caching
563
870
 
564
871
  Currently, Alba doesn't support caching, primarily due to the behavior of `ActiveRecord::Relation`'s cache. See [the issue](https://github.com/rails/rails/issues/41784).
565
872
 
873
+ ### Extend Alba
874
+
875
+ Sometimes we have shared behaviors across resources. In such cases we can have a module for common logic.
876
+
877
+ In `attribute` block we can call instance method so we can improve the code below:
878
+
879
+ ```ruby
880
+ class FooResource
881
+ include Alba::Resource
882
+ # other attributes
883
+ attribute :created_at do |foo|
884
+ foo.created_at.strftime('%m/%d/%Y')
885
+ end
886
+
887
+ attribute :updated_at do |foo|
888
+ foo.updated_at.strftime('%m/%d/%Y')
889
+ end
890
+ end
891
+
892
+ class BarResource
893
+ include Alba::Resource
894
+ # other attributes
895
+ attribute :created_at do |bar|
896
+ bar.created_at.strftime('%m/%d/%Y')
897
+ end
898
+
899
+ attribute :updated_at do |bar|
900
+ bar.updated_at.strftime('%m/%d/%Y')
901
+ end
902
+ end
903
+ ```
904
+
905
+ to:
906
+
907
+ ```ruby
908
+ module SharedLogic
909
+ def format_time(time)
910
+ time.strftime('%m/%d/%Y')
911
+ end
912
+ end
913
+
914
+ class FooResource
915
+ include Alba::Resource
916
+ include SharedLogic
917
+ # other attributes
918
+ attribute :created_at do |foo|
919
+ format_time(foo.created_at)
920
+ end
921
+
922
+ attribute :updated_at do |foo|
923
+ format_time(foo.updated_at)
924
+ end
925
+ end
926
+
927
+ class BarResource
928
+ include Alba::Resource
929
+ include SharedLogic
930
+ # other attributes
931
+ attribute :created_at do |bar|
932
+ format_time(bar.created_at)
933
+ end
934
+
935
+ attribute :updated_at do |bar|
936
+ format_time(bar.updated_at)
937
+ end
938
+ end
939
+ ```
940
+
941
+ We can even add our own DSL to serialize attributes for readability and removing code duplications.
942
+
943
+ To do so, we need to `extend` our module. Let's see how we can achieve the same goal with this approach.
944
+
945
+ ```ruby
946
+ module AlbaExtension
947
+ # Here attrs are an Array of Symbol
948
+ def formatted_time_attributes(*attrs)
949
+ attrs.each do |attr|
950
+ attribute attr do |object|
951
+ time = object.send(attr)
952
+ time.strftime('%m/%d/%Y')
953
+ end
954
+ end
955
+ end
956
+ end
957
+
958
+ class FooResource
959
+ include Alba::Resource
960
+ extend AlbaExtension
961
+ # other attributes
962
+ formatted_time_attributes :created_at, :updated_at
963
+ end
964
+
965
+ class BarResource
966
+ include Alba::Resource
967
+ extend AlbaExtension
968
+ # other attributes
969
+ formatted_time_attributes :created_at, :updated_at
970
+ end
971
+ ```
972
+
973
+ In this way we have shorter and cleaner code. Note that we need to use `send` or `public_send` in `attribute` block to get attribute data.
974
+
566
975
  ## Rails
567
976
 
568
977
  When you use Alba in Rails, you can create an initializer file with the line below for compatibility with Rails JSON encoder.
data/alba.gemspec CHANGED
@@ -12,9 +12,13 @@ Gem::Specification.new do |spec|
12
12
  spec.license = 'MIT'
13
13
  spec.required_ruby_version = Gem::Requirement.new('>= 2.5.0')
14
14
 
15
- spec.metadata['homepage_uri'] = spec.homepage
16
- spec.metadata['source_code_uri'] = 'https://github.com/okuramasafumi/alba'
17
- spec.metadata['changelog_uri'] = 'https://github.com/okuramasafumi/alba/blob/master/CHANGELOG.md'
15
+ spec.metadata = {
16
+ 'bug_tracker_uri' => 'https://github.com/okuramasafumi/issues',
17
+ 'changelog_uri' => 'https://github.com/okuramasafumi/alba/blob/main/CHANGELOG.md',
18
+ 'documentation_uri' => 'https://rubydoc.info/github/okuramasafumi/alba',
19
+ 'source_code_uri' => 'https://github.com/okuramasafumi/alba',
20
+ 'rubygems_mfa_required' => 'true'
21
+ }
18
22
 
19
23
  # Specify which files should be added to the gem when it is released.
20
24
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.