Attributes

A serializer is a class that lists attributes. Each attribute adds a key to the result, and its options say where the value comes from.

class UserSerializer < Serega
  attribute :first_name
  attribute :last_name
end

UserSerializer.to_h(user) # => {first_name: "Bruce", last_name: "Wayne"}

Base serializer

Define one base serializer with the plugins and config your app uses. Every serializer inherits plugins, config and attributes from its superclass.

class AppSerializer < Serega
  plugin :activerecord_preloads
  config.auto_preload = true
  config.base_serializer = self # nested serializers defined with a block inherit AppSerializer
end

class UserSerializer < AppSerializer
  attribute :first_name
end

class CommentSerializer < AppSerializer
  attribute :text
end

Locked serializers

Configure a serializer fully before it serializes anything. A serializer is locked after its first serialization, directly or as a nested serializer.

UserSerializer.to_h(user)

UserSerializer.attribute :nickname
# => Serega::SeregaError: UserSerializer can not be changed after it was used for serialization

UserSerializer.config.auto_preload = false
# => FrozenError

class AdminSerializer < UserSerializer
  attribute :nickname # subclasses of a locked serializer can be changed
end

A locked serializer raises Serega::SeregaError on new attributes, plugins, batch loaders, presenter methods, meta attributes, preload_with and prepare_initial_objects handlers, and FrozenError on config changes.

Attribute values

By default, the value is the result of the method with the attribute name: attribute :first_name reads user.first_name. The options below change that.

:method

Reads the value from another method.

attribute :name, method: :first_name # user.first_name
# => {name: "Bruce"}

With :hash_access, :method names a Hash key.

:value

Computes the value with a Proc or any object that responds to #call.

attribute :full_name, value: proc { |user| "#{user.first_name} #{user.last_name}" }
attribute :current, value: proc { |user, ctx| user == ctx[:current_user] }
attribute :current, value: proc { |user, ctx:| user == ctx[:current_user] }
attribute :api_version, value: proc { 2 }
attribute :full_name, value: FullName # FullName.call(user)

The callable receives the object to serialize and, optionally, the context as a second parameter or the :ctx keyword. The :batches keyword gives the results of the attribute’s batch loaders.

:delegate

Reads the value from an associated object.

attribute :city, delegate: {to: :profile}                          # user.profile.city
attribute :profile_name, delegate: {to: :profile, method: :first_name} # user.profile.first_name
attribute :city, delegate: {to: :profile, allow_nil: true}         # nil when user.profile is nil

Without allow_nil: true, a nil associated object raises NoMethodError. config.delegate_default_allow_nil = true makes allow_nil: true the default. Delegates also read Hash records, see Hash records.

:const

Serializes a constant value.

attribute :type, const: "user"
# => {type: "user"}

:default

Replaces a nil value.

attribute :nickname, default: ""
attribute :active, default: false
attribute :city, delegate: {to: :profile, allow_nil: true}, default: "Unknown"

Other sources

:method, :value and :const can not be combined, and :batch can not be combined with :method or :const.

Hidden attributes

hide: true excludes an attribute unless it is requested with :with or :only. See Selecting fields.

class UserSerializer < Serega
  attribute :first_name
  attribute :last_name, hide: true
end

UserSerializer.to_h(user)                   # => {first_name: "Bruce"}
UserSerializer.to_h(user, with: :last_name) # => {first_name: "Bruce", last_name: "Wayne"}

Relations

:serializer

Serializes the value with another serializer. Use a String or a Proc when serializers refer to each other.

attribute :posts, serializer: PostSerializer
attribute :posts, serializer: "PostSerializer"
attribute :posts, serializer: -> { PostSerializer }
# => {posts: [{title: "Gotham at night"}]}

:many

Serega serializes a value as a list when it is an Enumerable, except Hash and Struct. many: true marks a list explicitly and serializes nil as [].

attribute :posts, serializer: PostSerializer, many: true
# => {posts: []} when user.posts is nil

config.require_many = true requires :many on every relation, an attribute with :serializer or a block. A relation without :many raises Serega::SeregaError.

class AppSerializer < Serega
  config.require_many = true
  config.base_serializer = self
end

class PostSerializer < AppSerializer
  attribute :comments, serializer: CommentSerializer, many: true
  attribute :author, many: false do
    attribute :name
  end
end

:preload

Names the associations to preload for the value, so the value is read without N+1 queries. See Preloads.

attribute :email, preload: :emails, value: proc { |user| user.emails.find(&:verified?) }

Nested serializers defined with a block

A block defines an anonymous nested serializer for the attribute value. The value is found as usual, by the attribute name or the :method, :value, :delegate or :batch option.

class UserSerializer < AppSerializer
  attribute :first_name

  # user.profile
  attribute :profile do
    attribute :city
  end

  # The user itself, with only its counters
  attribute :statistics, method: :itself do
    attribute :likes_count
    attribute :comments_count
  end
end

UserSerializer.to_h(user)
# => {first_name: "Bruce", profile: {city: "Gotham"}, statistics: {likes_count: 10, comments_count: 3}}

The nested serializer is a subclass of a base serializer, set with config.base_serializer or the base_serializer: attribute option. The attribute option takes precedence. An attribute block raises Serega::SeregaError when neither is set.

attribute :statistics, method: :itself, base_serializer: StatisticsSerializer do
  attribute :likes_count
end

The nested serializer inherits everything from its base serializer: plugins, config, attributes, batch loaders and the preload handler. Anything else can be declared inside the block:

attribute :statistics, method: :itself do
  batch :statistics, ->(users) { Statistics.for_users(users) } # => {user_id => statistics}

  attribute :likes_count, batch: :statistics,
    value: proc { |user, batches:| batches[:statistics][user.id].likes }
end

Good to know:

text (when serializing the 'likes_count' attribute in UserSerializer.<statistics>, app/serializers/user_serializer.rb:12)

Conditions and formatting

attribute :email, if: proc { |user, ctx| user == ctx[:current_user] }
attribute :email, if_value: :present?
attribute :created_at, format: :iso8601

See Conditional attributes for :if, :unless, :if_value and :unless_value, and Formatters for :format.

Attribute names

Attribute names may contain only a-z, A-Z, 0-9, _, - and ~, so they can be used in URLs without escaping. Other names raise Serega::SeregaError. config.check_attribute_name = false turns the check off.

The name is the serialized key as is. A camelCase key reads its value with :method:

attribute :firstName, method: :first_name
# => {firstName: "Bruce"}

Listing attributes

.attributes returns the attributes by name, hidden and inherited ones too:

class UserSerializer < Serega
  attribute :username
  attribute :email, hide: true
end

class AdminSerializer < UserSerializer
  attribute :role
end

AdminSerializer.attributes.keys # => [:username, :email, :role]

All options

Option Purpose
method Read another method
value Compute the value
delegate Read from an associated object
const Serialize a constant
default Replace nil
batch Load values for all objects at once
hash_access Read Hash keys
hide Exclude unless requested
serializer Serialize with another serializer
many Mark a list
preload Preload associations
base_serializer Base of a block-defined serializer
if, unless, if_value, unless_value Conditional attributes
format Format the value