Class: Barley::Serializer

Inherits:
Object
  • Object
show all
Defined in:
lib/barley/serializer.rb

Constant Summary collapse

EMPTY_ARRAY =
[].freeze
EMPTY_HASH =
{}.freeze

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(object, cache: false, root: false, context: nil) ⇒ Serializer

Returns a new instance of Serializer.

Examples:

with cache

Barley::Serializer.new(object, cache: true)

with cache and expires_in

Barley::Serializer.new(object, cache: {expires_in: 1.hour})

Parameters:

  • object (Object)

    the object to serialize

  • cache (Boolean, Hash<Symbol, ActiveSupport::Duration>) (defaults to: false)

    a boolean to cache the result, or a hash with options for the cache

  • root (Boolean) (defaults to: false)

    whether to include the root key in the hash

  • context (Object) (defaults to: nil)

    an optional context object to pass additional data to the serializer



251
252
253
254
255
256
257
258
259
260
# File 'lib/barley/serializer.rb', line 251

def initialize(object, cache: false, root: false, context: nil)
  @object = object
  @context = context
  @root = root
  @cache, @expires_in = if cache.is_a?(Hash)
                          [true, cache[:expires_in]]
                        else
                          [cache, nil]
                        end
end

Class Attribute Details

.defined_attributesObject

Returns the value of attribute defined_attributes.



11
12
13
# File 'lib/barley/serializer.rb', line 11

def defined_attributes
  @defined_attributes
end

Instance Attribute Details

#contextObject

Returns the value of attribute context.



8
9
10
# File 'lib/barley/serializer.rb', line 8

def context
  @context
end

#objectObject

Returns the value of attribute object.



8
9
10
# File 'lib/barley/serializer.rb', line 8

def object
  @object
end

Class Method Details

.attribute(key, key_name: nil, type: nil, &block) ⇒ Object

Defines a single attribute for the serializer

Type checking is done with Dry::Types. If a type is not provided, the value is returned as is. Dry::Types can be used to coerce the value to the desired type and to check constraints.

Examples:

simple attribute

attribute :id
# => {id: 1234}

attribute with a different key name

attribute :name, key_name: :full_name
# => {full_name: "John Doe"}

attribute with a type

attribute :email, type: Types::Strict::String
# => {email: "john.doe@example"}

attribute with a type and a block

attribute :email, type: Types::Strict::String do
  object.email.upcase
end
# => {email: "JOHN.DOE@EXAMPLE"}

Parameters:

  • key (Symbol)

    the attribute name

  • key_name (Symbol) (defaults to: nil)

    the key name in the hash

  • type (Dry::Types) (defaults to: nil)

    the type to use, or coerce the value to

  • block (Proc)

    a block to use to compute the value

Raises:

  • (Dry::Types::ConstraintError)

    if the type does not match

  • (Barley::InvalidAttributeError)

    if the value does not match the type - when a type is provided

See Also:



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/barley/serializer.rb', line 79

def attribute(key, key_name: nil, type: nil, &block)
  key_name ||= key
  define_method(key_name) do
    value = block ? instance_eval(&block) : object.send(key)
    if type.nil?
      value
    else
      unless type.valid?(value)
        raise Barley::InvalidAttributeError,
              "Invalid value type found for attribute #{key_name}::#{type.name}: #{value}::#{value.class}"
      end

      type[value]
    end
  end

  self.defined_attributes = (defined_attributes || []) << key_name
end

.attributes(*keys) ⇒ Object

Defines attributes for the serializer

Accepts either a list of symbols or a hash of symbols and Dry::Types, or a mix of both

Examples:

only symbols

attributes :id, :name, :email
# => {id: 1234, name: "John Doe", email: "john.doe@example"}

with types

attributes id: Types::Strict::Integer, name: Types::Strict::String, email: Types::Strict::String
# => {id: 1234, name: "John Doe", email: "john.doe@example"}

with types and symbols

attributes :id, name: Types::Strict::String, email: Types::Strict::String
# => {id: 1234, name: "John Doe", email: "john.doe@example"}

Parameters:

  • keys (Hash<Symbol, Dry::Types>, Array<Symbol>)

    mix of symbols and hashes of symbols and Dry::Types

See Also:



32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/barley/serializer.rb', line 32

def attributes(*keys)
  if keys.last.is_a?(Hash)
    keys.pop.each do |key, type|
      attribute(key, type: type)
    end
  end
  keys.each do |key|
    if key.is_a?(Hash)
      attribute(key.keys.first, type: key.values.first)
    else
      attribute(key)
    end
  end
end

.many(key, key_name: nil, serializer: nil, cache: false, scope: nil, &block) ⇒ Object

Defines a collection association for the serializer

Examples:

using the default serializer of the associated model

many :groups
# => {groups: [{id: 1234, name: "Group 1"}, {id: 5678, name: "Group 2"}]}

using a custom serializer

many :groups, serializer: MyCustomGroupSerializer
# => {groups: [{id: 1234, name: "Group 1"}, {id: 5678, name: "Group 2"}]}

using a block with an inline serializer definition

many :groups do
  attributes :id, :name
end
# => {groups: [{id: 1234, name: "Group 1"}, {id: 5678, name: "Group 2"}]}

using a different key name

many :groups, key_name: :my_groups
# => {my_groups: [{id: 1234, name: "Group 1"}, {id: 5678, name: "Group 2"}]}

using cache

many :groups, cache: true
# => {groups: [{id: 1234, name: "Group 1"}, {id: 5678, name: "Group 2"}]}

using cache and expires_in

many :groups, cache: {expires_in: 1.hour}
# => {groups: [{id: 1234, name: "Group 1"}, {id: 5678, name: "Group 2"}]}

using a named scope

many :groups, scope: :active # given the scope `active` is defined in the Group model
# => {groups: [{id: 5678, name: "Group 2"}]}

using a lambda scope

many :groups, scope: -> { order(id: :asc).limit(1) }
# => {groups: [{id: 1234, name: "Group 1"}]}

Parameters:

  • key (Symbol)

    the association name

  • key_name (Symbol) (defaults to: nil)

    the key name in the hash

  • serializer (Class) (defaults to: nil)

    the serializer to use

  • cache (Boolean, Hash<Symbol, ActiveSupport::Duration>) (defaults to: false)

    whether to cache the result, or a hash with options for the cache

  • scope (Symbol, Proc) (defaults to: nil)

    the scope to use to fetch the elements

  • block (Proc)

    a block to use to define the serializer inline



193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/barley/serializer.rb', line 193

def many(key, key_name: nil, serializer: nil, cache: false, scope: nil, &block)
  key_name ||= key
  resolved_serializer = serializer
  resolved_serializer_from_block = nil
  if block && !serializer
    resolved_serializer_from_block = Class.new(Barley::Serializer) do
      instance_eval(&block)
    end
  end

  define_method(key_name) do
    elements = object.send(key)
    return EMPTY_ARRAY if elements.nil? || (elements.respond_to?(:empty?) && elements.empty?)

    if scope
      elements = if scope.is_a?(Symbol)
                   elements.send(scope)
                 else
                   (if scope.arity == 1
                      elements.instance_exec(@context, &scope)
                    else
                      elements.instance_exec(&scope)
                    end)
                 end
    end
    return EMPTY_ARRAY if elements.empty?

    el_serializer_class = resolved_serializer || resolved_serializer_from_block || element.serializer.class
    serializer_instance = el_serializer_class.new(nil, cache: cache, context: @context)

    result = []
    elements.each do |element|
      serializer_instance.object = element
      # This assumes _serializable_hash primarily depends on @object,
      # @context, and @effective_options (set during its own init).
      serialized = serializer_instance.send(:_serializable_hash) # Use send for private method

      next if serialized.nil? || (serialized.respond_to?(:empty?) && serialized.empty?)

      result << serialized
    end

    result
  end
  self.defined_attributes = (defined_attributes || []) << key_name
end

.one(key, key_name: nil, serializer: nil, cache: false, &block) ⇒ Object

Defines a single association for the serializer

Examples:

using the default serializer of the associated model

one :group
# => {group: {id: 1234, name: "Group 1"}}

using a custom serializer

one :group, serializer: MyCustomGroupSerializer
# => {group: {id: 1234, name: "Group 1"}}

using a block with an inline serializer definition

one :group do
  attributes :id, :name
end
# => {group: {id: 1234, name: "Group 1"}}

using a different key name

one :group, key_name: :my_group
# => {my_group: {id: 1234, name: "Group 1"}}

using cache

one :group, cache: true
# => {group: {id: 1234, name: "Group 1"}}

using cache and expires_in

one :group, cache: {expires_in: 1.hour}
# => {group: {id: 1234, name: "Group 1"}}

Parameters:

  • key (Symbol)

    the association name

  • key_name (Symbol) (defaults to: nil)

    the key name in the hash

  • serializer (Class) (defaults to: nil)

    the serializer to use

  • cache (Boolean, Hash<Symbol, ActiveSupport::Duration>) (defaults to: false)

    whether to cache the result, or a hash with options for the cache

  • block (Proc)

    a block to use to define the serializer inline



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/barley/serializer.rb', line 131

def one(key, key_name: nil, serializer: nil, cache: false, &block)
  key_name ||= key
  resolved_serializer = serializer
  resolved_serializer_from_block = nil
  if block && !serializer
    resolved_serializer_from_block = Class.new(Barley::Serializer) do
      instance_eval(&block)
    end
  end

  define_method(key_name) do
    element = object.send(key)
    return EMPTY_HASH if element.nil?

    el_serializer = resolved_serializer || resolved_serializer_from_block || element.serializer.class

    el_serializer.new(element, cache: cache, context: @context).serializable_hash
  end
  self.defined_attributes = (defined_attributes || []) << key_name
end

Instance Method Details

#clear_cache(key: cache_base_key) ⇒ Boolean

Clears the cache for the object

Parameters:

  • key (String) (defaults to: cache_base_key)

    the cache key

Returns:

  • (Boolean)

    whether the cache was cleared



281
282
283
# File 'lib/barley/serializer.rb', line 281

def clear_cache(key: cache_base_key)
  Barley::Cache.delete(key)
end

#serializable_hashHash

Serializes the object

Returns:

  • (Hash)

    the serializable hash



265
266
267
268
269
270
271
272
273
274
# File 'lib/barley/serializer.rb', line 265

def serializable_hash
  # Cache check first
  if @cache
    Barley::Cache.fetch(cache_base_key, expires_in: @expires_in) do
      _serializable_hash
    end
  else
    _serializable_hash
  end
end

#with_context(**args) ⇒ Barley::Serializer

Sets the context object for the serializer

The context object is a Struct built from the given arguments. It can be used to pass additional data to the serializer. The context object is accessible in the serializer with the ‘context` attribute.

Examples:

serializer.with_context(current_user: current_user, locale: I18n.locale)
# => #<Barley::Serializer:0x00007f8f3b8b3e08 @object=#<Product id: 1, name: "Product 1">, @context=#<struct current_user=1, locale=:en>>
# In the serializer:
attribute :name do
  "#{object.name[context.locale]}" # Will use the locale from the context
end

Parameters:

  • args (Hash)

    the context object attributes

Returns:



298
299
300
301
302
# File 'lib/barley/serializer.rb', line 298

def with_context(**args)
  @context = Struct.new(*args.keys).new(*args.values)

  self
end