Snapi
Section 4
Mapper
Mapper
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 magic
# 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
Anatomy of a mapper
- Cartographer.mapper
- table
- model
- mapping
- lazy_load
Cartographer.mapper includes
the following functions into the mapper:
- find
- find_by_ids
- search
- count
- save
- destroy
- reload
Cartographer.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.
table
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.
model
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
-
model
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.
mapping
# 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.
lazy_loading
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.
lazy_loading
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
lazy_loading
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
Mapper
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
Thank you!
Snapi Section 04 Mapper
By Dustin McCraw
Snapi Section 04 Mapper
- 845