README.md in alba-2.3.0 vs README.md in alba-2.4.0

- old
+ new

@@ -9,10 +9,44 @@ # Alba Alba is a JSON serializer for Ruby, JRuby, and TruffleRuby. +## TL;DR + +Alba allows you to do something like below. + +```ruby +class User + attr_accessor :id, :name, :email + + def initialize(id, name, email) + @id = id + @name = name + @email = email + end +end + +class UserResource + include Alba::Resource + + root_key :user + + attributes :id, :name + + attribute :name_with_email do |resource| + "#{resource.name}: #{resource.email}" + end +end + +user = User.new(1, 'Masafumi OKURA', 'masafumi@example.com') +UserResource.new(user).serialize +# => '{"user":{"id":1,"name":"Masafumi OKURA","name_with_email":"Masafumi OKURA: masafumi@example.com"}}' +``` + +Seems useful? Continue reading! + ## Discussions Alba uses [GitHub Discussions](https://github.com/okuramasafumi/alba/discussions) to openly discuss the project. If you've already used Alba, please consider posting your thoughts and feelings on [Feedback](https://github.com/okuramasafumi/alba/discussions/categories/feedback). The fact that you enjoy using Alba gives me energy to keep developing Alba! @@ -80,11 +114,11 @@ * Key transformation * Root key and association resource name inference * Error handling * Nil handling * Circular associations control -* [Experimental] Types for validation and conversion +* Types for validation and conversion * Layout * No runtime dependencies ## Usage @@ -233,10 +267,76 @@ attribute :some_other_name, &:name end ``` +#### Prefer methods on resource + +By default, Alba prefers methods on the object to methods on the resource. This means if you have a following situation: + +```ruby +class User + attr_accessor :id, :name, :email + + def initialize(id, name, email) + @id = id + @name = name + @email = email + end + + def name_with_email + "dummy!" + end +end + +class UserResource + include Alba::Resource + + root_key :user, :users # Later is for plural + + attributes :id, :name, :name_with_email + + # Same method exists in `User` class! + # This is not called + def name_with_email(user) + "#{user.name}: #{user.email}" + end +end + +user = User.new(1, 'Masafumi OKURA', 'masafumi@example.com') +UserResource.new(user).serialize +# => '{"user":{"id":1,"name":"Masafumi OKURA","name_with_email":"dummy!"}}' +``` + +You can see that `name_with_email` is now `dummy!` from `User#name_with_email`. You cna change this behavior by using `prefer_resource_method!` DSL in a resource class: + +```ruby +# With the same `User` class + +class UserResource + include Alba::Resource + + prefer_resource_method! # This line is important + + root_key :user, :users # Later is for plural + + attributes :id, :name, :name_with_email + + # Same method exists in `User` class! + # But now this is called! + def name_with_email(user) + "#{user.name}: #{user.email}" + end +end + +user = User.new(1, 'Masafumi OKURA', 'masafumi@example.com') +UserResource.new(user).serialize +# => '{"user":{"id":1,"name":"Masafumi OKURA","name_with_email":"Masafumi OKURA: masafumi@example.com"}}' +``` + +The next major version of Alba will change this default behavior to prefer resource methods. In case you want to preserve current behavior, there's `prefer_object_method!` DSL, which does that. + #### Params You can pass a Hash to the resource for internal use. It can be used as "flags" to control attribute content. ```ruby @@ -1068,10 +1168,15 @@ UserResource.new(User.new(1)).serialize # => '{"user":{"id":1,"name":"User1","age":20}}' ``` +Note that `on_nil` does NOT work when the given object itself is `nil`. There are a few possible ways to deal with `nil`. + +- Use `if` statement and avoid using Alba when the object is `nil` +- Use "Null Object" pattern + ### Metadata You can set a metadata with `meta` DSL or `meta` option. ```ruby @@ -1231,37 +1336,80 @@ In case you don't want to have a file for layout, Alba lets you define and apply layouts inline: ```ruby class FooResource include Alba::Resource - layout inline: proc do + layout inline: proc { { header: 'my header', body: serializable_hash } - end + } end ``` 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. You can also use a Proc which returns String, not a Hash, for an inline layout. ```ruby class FooResource include Alba::Resource - layout inline: proc do + layout inline: proc { %({ "header": "my header", "body": #{serialized_json} }) - end + } end ``` It looks similar to file layout but you must use string interpolation for method calls since it's not an ERB. 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. + +### Helper + +Inheritance works well in most of the cases to share behaviors. One of the exceptions is when you want to shared behaviors with inline association. For example: + +```ruby +class ApplicationResource + include Alba::Resource + + def self.with_id + attributes :id + end +end + +class LibraryResource < ApplicationResource + with_id + attributes :created_at + + with_many :library_books do + with_id # This DOES NOT work! + attributes :created_at + end +end +``` + +This doesn't work. Technically, inside of `has_many` is a separate class which doesn't inherit from the base class (`LibraryResource` in this example). + +`helper` solves this problem. It's just a mark for methods that should be shared with inline associations. + +```ruby +class ApplicationResource + include Alba::Resource + + helper do + def with_id + attributes :id + end + end +end +# Now `LibraryResource` works! +``` + +Within `helper` block, all methods should be defined without `self.`. ### Caching 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).