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.
- checksums.yaml +4 -4
 - data/.github/workflows/codeql-analysis.yml +70 -0
 - data/.github/workflows/main.yml +3 -1
 - data/.github/workflows/perf.yml +21 -0
 - data/.rubocop.yml +9 -7
 - data/CHANGELOG.md +28 -0
 - data/Gemfile +4 -3
 - data/README.md +469 -60
 - data/alba.gemspec +7 -3
 - data/benchmark/collection.rb +108 -3
 - data/benchmark/single_resource.rb +82 -1
 - data/docs/migrate_from_active_model_serializers.md +359 -0
 - data/docs/migrate_from_jbuilder.md +223 -0
 - data/gemfiles/all.gemfile +2 -1
 - data/gemfiles/without_active_support.gemfile +1 -1
 - data/gemfiles/without_oj.gemfile +1 -1
 - data/lib/alba/association.rb +33 -24
 - data/lib/alba/default_inflector.rb +22 -4
 - data/lib/alba/deprecation.rb +14 -0
 - data/lib/alba/errors.rb +10 -0
 - data/lib/alba/resource.rb +260 -100
 - data/lib/alba/typed_attribute.rb +3 -6
 - data/lib/alba/version.rb +1 -1
 - data/lib/alba.rb +110 -36
 - data/script/perf_check.rb +174 -0
 - metadata +14 -8
 - data/lib/alba/key_transform_factory.rb +0 -33
 - data/lib/alba/many.rb +0 -21
 - data/lib/alba/one.rb +0 -21
 
    
        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,  
     | 
| 
      
 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 
     | 
    
         
            -
            ###  
     | 
| 
      
 28 
     | 
    
         
            +
            ### Easy to use
         
     | 
| 
       29 
29 
     | 
    
         | 
| 
       30 
     | 
    
         
            -
            Alba provides  
     | 
| 
      
 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 
     | 
    
         
            -
            ###  
     | 
| 
      
 32 
     | 
    
         
            +
            ### Extensible
         
     | 
| 
       33 
33 
     | 
    
         | 
| 
       34 
     | 
    
         
            -
            Alba  
     | 
| 
      
 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 
     | 
    
         
            -
            ####  
     | 
| 
      
 94 
     | 
    
         
            +
            #### Encoder configuration
         
     | 
| 
       102 
95 
     | 
    
         | 
| 
       103 
     | 
    
         
            -
            You can  
     | 
| 
      
 96 
     | 
    
         
            +
            You can also set JSON encoder directly with a Proc.
         
     | 
| 
       104 
97 
     | 
    
         | 
| 
       105 
98 
     | 
    
         
             
            ```ruby
         
     | 
| 
       106 
     | 
    
         
            -
            Alba. 
     | 
| 
      
 99 
     | 
    
         
            +
            Alba.encoder = ->(object) { JSON.generate(object) }
         
     | 
| 
       107 
100 
     | 
    
         
             
            ```
         
     | 
| 
       108 
101 
     | 
    
         | 
| 
       109 
     | 
    
         
            -
            You  
     | 
| 
      
 102 
     | 
    
         
            +
            You can consider setting a backend with Symbol as a shortcut to set encoder.
         
     | 
| 
       110 
103 
     | 
    
         | 
| 
       111 
     | 
    
         
            -
            ####  
     | 
| 
      
 104 
     | 
    
         
            +
            #### Inference configuration
         
     | 
| 
       112 
105 
     | 
    
         | 
| 
       113 
     | 
    
         
            -
            You can  
     | 
| 
      
 106 
     | 
    
         
            +
            You can enable inference feature using `enable_inference!` method.
         
     | 
| 
       114 
107 
     | 
    
         | 
| 
       115 
108 
     | 
    
         
             
            ```ruby
         
     | 
| 
       116 
     | 
    
         
            -
            Alba. 
     | 
| 
      
 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 
     | 
    
         
            -
               
     | 
| 
      
 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,  
     | 
| 
      
 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  
     | 
| 
      
 338 
     | 
    
         
            +
            ### Inheritance and attributes filter
         
     | 
| 
       218 
339 
     | 
    
         | 
| 
       219 
     | 
    
         
            -
            You can  
     | 
| 
      
 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  
     | 
| 
       239 
     | 
    
         
            -
               
     | 
| 
      
 361 
     | 
    
         
            +
            class RestrictedFooResource < GenericFooResource
         
     | 
| 
      
 362 
     | 
    
         
            +
              def attributes
         
     | 
| 
      
 363 
     | 
    
         
            +
                super.select { |key, _| key.to_sym == :name }
         
     | 
| 
      
 364 
     | 
    
         
            +
              end
         
     | 
| 
       240 
365 
     | 
    
         
             
            end
         
     | 
| 
       241 
366 
     | 
    
         | 
| 
       242 
     | 
    
         
            -
             
     | 
| 
      
 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 
     | 
    
         
            -
            * ` 
     | 
| 
       283 
     | 
    
         
            -
            * `root` option of `transform_keys` is set to true 
     | 
| 
      
 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 
     | 
    
         
            -
               
     | 
| 
      
 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 
     | 
    
         
            -
             
     | 
| 
       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  
     | 
| 
       323 
     | 
    
         
            -
               
     | 
| 
       324 
     | 
    
         
            -
             
     | 
| 
      
 451 
     | 
    
         
            +
            module CustomInflector
         
     | 
| 
      
 452 
     | 
    
         
            +
              module_function
         
     | 
| 
      
 453 
     | 
    
         
            +
             
     | 
| 
      
 454 
     | 
    
         
            +
              def camelize(string)
         
     | 
| 
       325 
455 
     | 
    
         
             
              end
         
     | 
| 
       326 
456 
     | 
    
         | 
| 
       327 
     | 
    
         
            -
              def camelize_lower( 
     | 
| 
       328 
     | 
    
         
            -
                raise "Not implemented"
         
     | 
| 
      
 457 
     | 
    
         
            +
              def camelize_lower(string)
         
     | 
| 
       329 
458 
     | 
    
         
             
              end
         
     | 
| 
       330 
459 
     | 
    
         | 
| 
       331 
     | 
    
         
            -
              def dasherize( 
     | 
| 
       332 
     | 
    
         
            -
             
     | 
| 
      
 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 
     | 
    
         
            -
             
     | 
| 
       504 
     | 
    
         
            -
            Alba 
     | 
| 
       505 
     | 
    
         
            -
               
     | 
| 
       506 
     | 
    
         
            -
                 
     | 
| 
       507 
     | 
    
         
            -
             
     | 
| 
       508 
     | 
    
         
            -
                 
     | 
| 
      
 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 
     | 
| 
       16 
     | 
    
         
            -
             
     | 
| 
       17 
     | 
    
         
            -
             
     | 
| 
      
 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.
         
     |