Alba JSON Serializer

repository·main·Indexed 20 days ago

https://github.com/okuramasafumi/alba

A high-performance, dependency-free JSON serializer for Ruby, JRuby, and TruffleRuby. Alba provides a feature-rich DSL for defining resource classes with support for attributes, associations, nested structures, traits, and key transformations. It supports multiple backends including Oj and ActiveSupport, and offers tools for migrating from ActiveModelSerializers.

Tokens
16.4K
Snippets
77
Records
86
Agent score
77%

What's inside Alba

  1. Configure root key and association name inference

    main

    When Alba.inflector is configured (e.g., to :active_support), Alba can automatically infer root keys and association resource names. To enable this, you must call root_key! in your resource class. This allows many associations to automatically find the correct resource class based on the association name and handles pluralization/singularization of the root key based on whether the input is a collection or a single object.

    Alba.inflector = :active_support
    
    class ArticleResource
      include Alba::Resource
      attributes :title
    end
    
    class UserResource
      include Alba::Resource
      root_key! # Required for inferred root key
    
      attributes :id
      many :articles
    end
    
    user = User.new(1)
    user.articles << Article.new(1, 'The title')
    
    # Single object results in singular root key
    UserResource.new(user).serialize 
    # => '{"user":{"id":1,"articles":[{"title":"The title"}]}}'
    
    # Collection results in plural root key
    UserResource.new([user]).serialize 
    # => '{"users":[{"id":1,"articles":[{"title":"The title"}]}]}'
  2. How Alba's core architecture works

    main

    Alba follows a simple design pattern centered around the Alba::Resource module. To use Alba in a class, you simply include Alba::Resource.

    This inclusion provides two layers of functionality:

    1. Class Methods (DSL): Used to define the structure of the serialized output (e.g., defining which attributes or associations to include).
    2. Instance Methods: Used to perform the actual serialization of a target object based on the configuration defined by the class methods.

    When a class includes Alba::Resource, the class methods collect metadata that the instance methods later use during the serialization process.

    class User
      include Alba::Resource
    
      attributes :id, :name
    end
  3. Inherit and extend Alba resources

    main

    Since Alba resources are standard Ruby classes, you can use traditional OOP inheritance. Subclasses can add new attributes or override existing configurations like root_key.

    class FooResource
      include Alba::Resource
      root_key :foo
      attributes :bar
    end
    
    class ExtendedFooResource < FooResource
      root_key :foofoo
      attributes :baz
    end
  4. Share behaviors with inline associations using `helper`

    main

    Standard class inheritance does not work for sharing behaviors with inline associations (like has_many or one) because associations are implemented as separate internal classes that do not inherit from your base resource class. To share methods with these associations, use the helper block. All methods defined within a helper block must be defined without self..

    class ApplicationResource
      include Alba::Resource
    
      helper do
        def with_id
          attributes(:id)
        end
      end
    end
    
    class LibraryResource < ApplicationResource
      with_id
      attributes :created_at
    
      with_many :library_books do
        with_id # This now works because of the helper block
        attributes :created_at
      end
    end
  5. Control circular associations with `within`

    main
    To prevent infinite loops in complex, bidirectional data structures, use the within option in associations. The within option accepts a nested Hash that tracks the serialization path (e.g., {book: {authors: books}}). This allows Alba to recognize when it is traversing back to a previously visited resource type and stop the recursion.
  6. Handle method conflicts between Resource and Target

    main

    By default, if a method name exists in both the Resource class and the target object, Alba prioritizes the method defined in the Resource class.

    If you want Alba to prioritize the method on the target object (the object being serialized), use the prefer_object_method! macro.

    class FooResource
      include Alba::Resource
    
      prefer_object_method! # Prioritizes Foo#bar over FooResource#bar
    
      attributes :bar
    end
  7. Understand how Alba resolves attribute values

    main

    Alba handles different types of attribute definitions by applying specific resolution logic to the target object (@object). Depending on how you define an attribute, Alba uses different internal mechanisms:

    Attribute TypeDefinition MethodResolution Logic
    SymbolattributesCalls the method name on the object via __send__
    ProcattributeExecutes the provided block via instance_exec
    AssociationassociationCalls to_h on the associated object
    TypedAttributetype: ... optionCalls the value method on the object
    NestedAttributenestedHandles nested structures
    ConditionalAttributeif: ... optionEvaluates the condition before including the attribute

    Note: @object can represent either a single object or a collection of objects.

  8. Quickstart: Create a JSON serializer with Alba

    main

    To use Alba, include Alba::Resource in your serializer class. You can define a root_key, specify multiple attributes at once, or define a single attribute with a block for custom logic. Use .new(resource).serialize to generate the JSON string.

    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"}}'
  9. Configure Alba in Rails via Initializer

    main

    To configure Alba's behavior in a Rails application, create an initializer file (e.g., config/initializers/alba.rb).

    As of Alba 2.2, Rails integration is built-in, meaning you do not need an initializer to set the inflector to :active_support. However, you still need an initializer if you want to:

    1. Change the backend (e.g., to :oj_rails).
    2. Configure the inflector to something other than :active_support.
    # config/initializers/alba.rb
    Alba.backend = :active_support
    Alba.inflector = :active_support
    
    # Or using Oj for backend
    Alba.backend = :oj_rails
  10. Install Alba via Gemfile or gem command

    main

    You can install Alba by adding it to your application's Gemfile or by installing the gem directly via the command line.

    Using Bundler: Add this to your Gemfile:

    gem 'alba'

    Then run:

    bundle install

    Direct Installation:

    gem install alba
  11. Handle nested relationships in Alba

    main

    Alba uses one and many to define associations, whereas AMS uses has_one and has_many. You must explicitly specify the resource class to use for the nested serialization.

    class ProfileResource
      include Alba::Resource
      root_key :profile
      attributes :email
    end
    
    class UserResource
      include Alba::Resource
      root_key :user
      attributes :id, :created_at, :updated_at
      
      # For has_one relations
      one :profile, resource: ProfileResource
      
      # For has_many relations
      many :articles, resource: ArticleResource
    end