Class: Lotus::Model::Mapping::Collection

Inherits:
Object
  • Object
show all
Defined in:
lib/lotus/model/mapping/collection.rb

Overview

Maps a collection and its attributes.

A collection is a set of homogeneous records. Think of a table of a SQL database or about collection of MongoDB.

This is database independent. It can work with SQL, document, and even with key/value stores.

Examples:

require 'lotus/model'

mapper = Lotus::Model::Mapper.new do
  collection :users do
    entity User

    attribute :id,   Integer
    attribute :name, String
  end
end

See Also:

Since:

  • 0.1.0

Constant Summary collapse

REPOSITORY_SUFFIX =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Repository name suffix

See Also:

Since:

  • 0.1.0

'Repository'.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, coercer_class, &blk) ⇒ Collection

Instantiate a new collection

Parameters:

  • name (Symbol)

    the name of the mapped collection. If used with a SQL database it’s the table name.

  • coercer_class (Class)

    the coercer class

  • blk (Proc)

    the block that maps the attributes of that collection.

See Also:

Since:

  • 0.1.0



74
75
76
77
78
79
# File 'lib/lotus/model/mapping/collection.rb', line 74

def initialize(name, coercer_class, &blk)
  @name = name
  @coercer_class = coercer_class
  @attributes = {}
  instance_eval(&blk) if block_given?
end

Instance Attribute Details

#adapterObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 0.1.0



61
62
63
# File 'lib/lotus/model/mapping/collection.rb', line 61

def adapter
  @adapter
end

#attributesObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 0.1.0



55
56
57
# File 'lib/lotus/model/mapping/collection.rb', line 55

def attributes
  @attributes
end

#coercer_classObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 0.1.0



49
50
51
# File 'lib/lotus/model/mapping/collection.rb', line 49

def coercer_class
  @coercer_class
end

#nameObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 0.1.0



43
44
45
# File 'lib/lotus/model/mapping/collection.rb', line 43

def name
  @name
end

Instance Method Details

#attribute(name, coercer, options = {}) ⇒ Object

Map an attribute.

An attribute defines a property of an object. This is storage independent. For instance, it can map an SQL column, a MongoDB attribute or everything that makes sense for your database.

Each attribute defines a Ruby type, to coerce that value from the database. This fixes a huge problem, because database types don’t match Ruby types. Think of Redis, where everything is stored as a string or integer, the mapper translates values from/to the database.

It supports the following types (coercers):

* Array
* Boolean
* Date
* DateTime
* Float
* Hash
* Integer
* BigDecimal
* Set
* String
* Symbol
* Time

Examples:

Default schema

require 'lotus/model'

# Given the following schema:
#
# CREATE TABLE users (
#   id     integer NOT NULL,
#   name   varchar(64),
# );
#
# And the following entity:
#
# class User
#   include Lotus::Entity
#   attributes :name
# end

mapper = Lotus::Model::Mapper.new do
  collection :users do
    entity User

    attribute :id,   Integer
    attribute :name, String
  end
end

# The first argument (`:name`) always corresponds to the `User`
# attribute.

# The second one (`:coercer`) is the Ruby type coercer that we want
# for our attribute.

# We don't need to use `:as` because the database columns match the
# `User` attributes.

Customized schema

require 'lotus/model'

# Given the following schema:
#
# CREATE TABLE articles (
#   i_id           integer NOT NULL,
#   i_user_id      integer NOT NULL,
#   s_title        varchar(64),
#   comments_count varchar(8) # Not an error: it's for String => Integer coercion
# );
#
# And the following entity:
#
# class Article
#   include Lotus::Entity
#   attributes :user_id, :title, :comments_count
# end

mapper = Lotus::Model::Mapper.new do
  collection :articles do
    entity Article

    attribute :id,             Integer, as: :i_id
    attribute :user_id,        Integer, as: :i_user_id
    attribute :title,          String,  as: :s_title
    attribute :comments_count, Integer

    identity :i_id
  end
end

# The first argument (`:name`) always corresponds to the `Article`
# attribute.

# The second one (`:coercer`) is the Ruby type that we want for our
# attribute.

# The third option (`:as`) is mandatory only when the database
# column doesn't match the name of the mapped attribute.
#
# For instance: we need to use it for translate `:s_title` to
# `:title`, but not for `:comments_count`.

Custom coercer

require 'lotus/model'

# Given the following schema:
#
# CREATE TABLE articles (
#   id     integer NOT NULL,
#   title  varchar(128),
#   tags   text[],
# );
#
# The following entity:
#
# class Article
#   include Lotus::Entity
#   attributes :title, :tags
# end
#
# And the following custom coercer:
#
# require 'lotus/model/coercer'
# require 'sequel/extensions/pg_array'
#
# class PGArray < Lotus::Model::Coercer
#   def self.dump(value)
#     ::Sequel.pg_array(value) rescue nil
#   end
#
#   def self.load(value)
#     ::Kernel.Array(value) unless value.nil?
#   end
# end

mapper = Lotus::Model::Mapper.new do
  collection :articles do
    entity Article

    attribute :id,    Integer
    attribute :title, String
    attribute :tags,  PGArray
  end
end

# When an entity is persisted as record into the database,
# `PGArray.dump` is invoked.

# When an entity is retrieved from the database, it will be
# deserialized as an Array via `PGArray.load`.

Parameters:

  • name (Symbol)

    the name of the attribute, as we want it to be mapped in the object

  • coercer (.load, .dump)

    a class that implements coercer interface

  • options (Hash) (defaults to: {})

    a set of options to customize the mapping

Options Hash (options):

  • :as (Symbol)

    the name of the original column

Raises:

  • (NameError)

    if coercer cannot be found

See Also:

Since:

  • 0.1.0



387
388
389
# File 'lib/lotus/model/mapping/collection.rb', line 387

def attribute(name, coercer, options = {})
  @attributes[name] = Attribute.new(name, coercer, options)
end

#deserialize(records) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Deserialize a set of records fetched from the database.

Parameters:

  • records (Array)

    a set of raw records

Since:

  • 0.1.0



407
408
409
410
411
# File 'lib/lotus/model/mapping/collection.rb', line 407

def deserialize(records)
  records.map do |record|
    @coercer.from_record(record)
  end
end

#deserialize_attribute(attribute, value) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Deserialize only one attribute from a raw value.

Parameters:

  • attribute (Symbol)

    the attribute name

  • value (Object, nil)

    the value to be coerced

Since:

  • 0.1.0



420
421
422
# File 'lib/lotus/model/mapping/collection.rb', line 420

def deserialize_attribute(attribute, value)
  @coercer.public_send(:"deserialize_#{ attribute }", value)
end

#entity(klass = nil) ⇒ Object

Defines the entity that is persisted with this collection.

The entity can be any kind of object as long as it implements the following interface: ‘#initialize(attributes = {})`.

Examples:

Set entity with class name

require 'lotus/model'

mapper = Lotus::Model::Mapper.new do
  collection :articles do
    entity Article
  end
end

mapper.entity #=> Article

Set entity with class name string

require 'lotus/model'

mapper = Lotus::Model::Mapper.new do
  collection :articles do
    entity 'Article'
  end
end

mapper.entity #=> Article

Parameters:

  • klass (Class, String) (defaults to: nil)

    the entity persisted with this collection.

See Also:

Since:

  • 0.1.0



114
115
116
117
118
119
120
# File 'lib/lotus/model/mapping/collection.rb', line 114

def entity(klass = nil)
  if klass
    @entity = klass
  else
    @entity
  end
end

#identity(name = nil) ⇒ Object

Defines the identity for a collection.

An identity is a unique value that identifies a record. If used with an SQL table it corresponds to the primary key.

This is an optional feature. By default the system assumes that your identity is ‘:id`. If this is the case, you can omit the value, otherwise you have to specify it.

Examples:

Default

require 'lotus/model'

# We have an SQL table `users` with a primary key `id`.
#
# This this is compliant to the mapper default, we can omit
# `#identity`.

mapper = Lotus::Model::Mapper.new do
  collection :users do
    entity User

    # attribute definitions..
  end
end

Custom identity

require 'lotus/model'

# We have an SQL table `articles` with a primary key `i_id`.
#
# This schema diverges from the expected default: `id`, that's why
# we need to use #identity to let the mapper to recognize the
# primary key.

mapper = Lotus::Model::Mapper.new do
  collection :articles do
    entity Article

    # attribute definitions..

    identity :i_id
  end
end

Parameters:

  • name (Symbol) (defaults to: nil)

    the name of the identity

Since:

  • 0.1.0



211
212
213
214
215
216
217
# File 'lib/lotus/model/mapping/collection.rb', line 211

def identity(name = nil)
  if name
    @identity = name
  else
    @identity || :id
  end
end

#load!Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Loads the internals of the mapper, in order to guarantee thread safety.

Since:

  • 0.1.0



428
429
430
431
432
433
434
# File 'lib/lotus/model/mapping/collection.rb', line 428

def load!
  _load_entity!
  _load_repository!
  _load_coercer!

  _configure_repository!
end

#repository(klass = nil) ⇒ Object

Defines the repository that interacts with this collection.

Examples:

Set repository with class name

require 'lotus/model'

mapper = Lotus::Model::Mapper.new do
  collection :articles do
    entity Article

    repository RemoteArticleRepository
  end
end

mapper.repository #=> RemoteArticleRepository

Set repository with class name string

require 'lotus/model'

mapper = Lotus::Model::Mapper.new do
  collection :articles do
    entity Article

    repository 'RemoteArticleRepository'
  end
end

mapper.repository #=> RemoteArticleRepository

Parameters:

  • klass (Class, String) (defaults to: nil)

    the repository that interacts with this collection.

See Also:

Since:

  • 0.2.0



155
156
157
158
159
160
161
# File 'lib/lotus/model/mapping/collection.rb', line 155

def repository(klass = nil)
  if klass
    @repository = klass
  else
    @repository ||= default_repository_klass
  end
end

#serialize(entity) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Serializes an entity to be persisted in the database.

Parameters:

  • entity (Object)

    an entity

Since:

  • 0.1.0



397
398
399
# File 'lib/lotus/model/mapping/collection.rb', line 397

def serialize(entity)
  @coercer.to_record(entity)
end