Skip to content

nativestranger/cerializable

Repository files navigation

Cerializable

Plain old Ruby serialization.

Rather than using something like jbuilder, you could use cerializable to generate hashes for your model instances and then render the hashes as JSON. This can aid in performance.

It also gives you the option to customize the process via custom serialization options.

Installation

For use with ActiveRecord, simply add cerializable to your Gemfile:

gem 'cerializable', '~> 0.2.0'

For other ORMs, you'll also need to include Cerializable::Model in your ORM's base class.

Usage

Call cerializable in the models you wish to use Cerializable with:

class Comment < ApplicationRecord
  cerializable
end

Define corresponding serializer modules:

module CommentSerializer

  def run(comment, options)
    { id: comment.id,
      userId: comment.user_id,
      body: comment.body,
      ancestorCount: comment.ancestor_count,
      childComments: options[:children] || comment.child_comments.map { |c| c.cerializable_hash }
  end

end

If you wish, you can specify a serializer module to use:

class Comment < ApplicationRecord
  cerializable serialize_with: SomeSerializerModule
end

In these serializer modules, you can define methods and include other modules without polluting the corresponding models.

The only requirement is that the serializer modules have a run method which accepts two arguments and returns a hash detailing how instances of your model should be serialized.

Your model's cerializable_hash methods will use the run method of their serializers to produce their results.

Like serializable_hash, cerializable_hash accepts :only, :except, and :methods options which can be passed as a symbol or as an array of symbols.

Using the :only option will return a hash that only has the specified keys:

> comment.cerializable_hash(only: :id)
=> { id: 1 }

Using the :except option will return a hash that has all default keys except those specified:

> comment.cerializable_hash(except: [:user_id, :ancestorCount, :childComments])
=> { id: 1, body: '...sushi? ' }

Using the :methods option add will add a key and value for each method specified.

The key is the method name and the value is the return value given when calling the method on the model instance.

The :methods option is processed after the :only and :except options:

> comment.cerializable_hash(only: id, methods: :hash])
=> { id: 1, hash: -2535926706119161824 }

Custom Serialization Options

You can pass in custom options that your serializer modules make use of.

Common use cases for custom options might be for handling roles or versions.

In the example serializer above, we're using a custom :children option.

This allows us to build a comment thread's hierarchy without querying for each comment's children:

class CommentTreeService
  def self.call(comment_thread, options = { order: { ancestor_count: :asc, id: :desc } })
    self.new.for_thread(comment_thread, options)
  end

  def initialize
    @comments_hash = {}
    @result = []

    @children_resolver = proc do |parent_comment|
      children_array = []

      @comments_hash.each_pair do |id, comment|
        if comment.parent_type == 'Comment' && comment.parent_id == parent_comment.id
          @comments_hash.delete(id)
          children_array << comment.cerializable_hash(children: @children_resolver.call(comment))
        end
      end

      children_array
    end
  end

  def for_thread(comment_thread, options)
    comment_thread.comments
      .order(options[:order])
      .each { |comment| @comments_hash[comment.id] = comment }

    @comments_hash.each_pair do |id, comment|
      if comment.parent_type == 'CommentThread'
        @comments_hash.delete(id)
        @result << comment.cerializable_hash(children: @children_resolver.call(comment))
      end
    end

    @result
  end
end