Responsibilities
Maps database row into a ruby object
Adds find/search/create/update/delete to the curator
"lazy_loads" calculate/derived data into the ruby object
Uses the data mapper pattern
# include # curator # mapper
include Omicron.destruction(ContactMapper) => delete => destroy
include Mufasa.find(ContactMapper) => find => find
include Mufasa.search(ContactMapper) => search => search
include Omicron.persistance(ContactMapper) => create/update => save
We use the include magic to inject functions into the curator which calls functions on the mapper.
These functions will modify or use response.collection.
class Response
include Virtus.value_object
values do
attribute :status, Integer, :default => 200
attribute :headers, Hash, :default => {}
attribute :collection, Array, :default => []
attribute :original_collection, Array, :default => []
attribute :page_information, Hash
attribute :error_message, String
end
end
class ContactMapper
include Cartographer.mapper
private
def model
Contact
end
def table
:contacts
end
def mapping
{
:id => :id,
:label => :label,
:address_street1 => :address,
:address_city => :city,
:address_state => :state,
:address_zip => :zip,
:created_at => :created_at,
:updated_at => :updated_at,
:first_name => :first,
:last_name => :last,
:is_address_hidden => :hide_address,
:allow_shared_access => :allow_shared_access,
:invitation_code => :activation,
:invitation_declined => :invitation_declined,
:member_id => :roster_id,
:user_id => :user_id
}
end
end
Cartographer.mapper includes
the following functions into the mapper:
find: Finds the rows in the database using the table value and then converts them into ruby objects using the model.
This is the name of the table in the database in symbol form used by the Sequel gem.
def table
:contacts
end
class ContactMapper
include Cartographer.mapper
private
def model
Contact
end
def table
:contacts
end
def mapping
{
:id => :id,
:label => :label,
:address_street1 => :address,
:address_city => :city,
:address_state => :state,
:address_zip => :zip,
:created_at => :created_at,
:updated_at => :updated_at,
:first_name => :first,
:last_name => :last,
:is_address_hidden => :hide_address,
:allow_shared_access => :allow_shared_access,
:invitation_code => :activation,
:invitation_declined => :invitation_declined,
:member_id => :roster_id,
:user_id => :user_id
}
end
end
This is the name of the ruby model that will get mapped into.
def model
Contact
end
What's the ruby model?
class ContactMapper
include Cartographer.mapper
private
def model
Contact
end
def table
:contacts
end
def mapping
{
:id => :id,
:label => :label,
:address_street1 => :address,
:address_city => :city,
:address_state => :state,
:address_zip => :zip,
:created_at => :created_at,
:updated_at => :updated_at,
:first_name => :first,
:last_name => :last,
:is_address_hidden => :hide_address,
:allow_shared_access => :allow_shared_access,
:invitation_code => :activation,
:invitation_declined => :invitation_declined,
:member_id => :roster_id,
:user_id => :user_id
}
end
end
class Contact
include Cartographer.value_object
values do
attribute :id, Integer
attribute :label, String
attribute :address_street1, String
attribute :address_city, String
attribute :address_state, String
attribute :address_zip, String
attribute :created_at, DateTime
attribute :updated_at, DateTime
attribute :first_name, String
attribute :last_name, String
attribute :is_address_hidden, Boolean, :default => false
attribute :allow_shared_access, Boolean, :default => false
end
lazy_attribute :user_first_name
lazy_attribute :user_last_name
lazy_attribute :team_id
lazy_attribute :division_id
lazy_attribute :is_alertable
lazy_attribute :is_emailable
lazy_attribute :is_invitable
def is_invited
@is_invited ||= invitation_code && !user_id
end
end
attribute
has a corresponding column in the database
attribute :id, Integer
attribute :label, String
attribute :is_address_hidden, Boolean, :default => false
lazy_attribute :user_first_name
lazy_attribute :user_last_name
lazy_attribute :team_id
lazy_attribute :division_id
lazy_attribute :is_alertable
lazy_attribute :is_emailable
lazy_attribute :is_invitable
def is_invited
@is_invited ||= invitation_code && !user_id
end
end
lazy_attribute :user_first_name
lazy_attribute :team_id
lazy_attribute :is_alertable
lazy_attribute :is_emailable
lazy_attribute :is_invitable
lazy_attribute
has to be calculated or derived
Maps the model attributes
from the database table columns.
# model => table column
def mapping
{
:id => :id,
:label => :label,
:address_street1 => :address,
:address_city => :city,
:address_state => :state,
:address_zip => :zip,
:created_at => :created_at,
:updated_at => :updated_at,
:is_address_hidden => :hide_address,
:allow_shared_access => :allow_shared_access,
:invitation_code => :activation,
:invitation_declined => :invitation_declined,
:member_id => :roster_id,
:user_id => :user_id
}
end
class ContactMapper
include Cartographer.mapper
private
def model
Contact
end
def table
:contacts
end
def mapping
{
:id => :id,
:label => :label,
:address_street1 => :address,
:address_city => :city,
:address_state => :state,
:address_zip => :zip,
:created_at => :created_at,
:updated_at => :updated_at,
:first_name => :first,
:last_name => :last,
:is_address_hidden => :hide_address,
:allow_shared_access => :allow_shared_access,
:invitation_code => :activation,
:invitation_declined => :invitation_declined,
:member_id => :roster_id,
:user_id => :user_id
}
end
end
We have to calculate all the lazy_attributes
in the mapper.
class Contact
lazy_attribute :user_first_name
lazy_attribute :user_last_name
lazy_attribute :team_id
lazy_attribute :division_id
lazy_attribute :is_alertable
lazy_attribute :is_emailable
lazy_attribute :is_invitable
end
Cartographer.mapper will call lazy_load on the mapper with list of objects in response.collection.
class ContactMapper
include Cartographer.mapper
private
def lazy_load(objects)
lazy_load_user_first_and_last_name(objects)
lazy_load_team_ids_division_ids_and_is_owner(objects)
lazy_load_is_alertable(objects)
lazy_load_is_emailable(objects)
lazy_load_is_invitable(objects)
lazy_load_is_pushable(objects)
end
def lazy_load_user_first_and_last_name(objects)
user_ids = objects.map(&:user_id)
if user_ids.any?
users = DB[:users]
.where(:id => user_ids)
.select_hash(:id, [:first, :last])
objects.each do |object|
user = users.fetch(object.user_id) { [nil, nil] }
object.user_first_name = user[0]
object.user_last_name = user[1]
end
end
end
end
class ContactMapper
def lazy_load_is_alertable(objects)
contact_ids = objects.map(&:id)
if contact_ids.any?
entries = DB[:telephones]
.where(:contact_id => contact_ids)
.where(:enable_sms => true)
.group_and_count(:contact_id)
.to_hash(:contact_id, :count)
objects.each do |object|
object.is_alertable = !!entries.fetch(object.id) { false }
end
end
end
end
Responsibilities
Maps database row into a ruby object
Adds find/search/create/update/delete to the curator
"lazy_loads" calculate/derived data into the ruby object
Curator calls mapper functions to modify response.collection