<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <generator>Persumi - Level up your writing and blogging with AI</generator>
  <category label="Blog" scheme="http://persumi.com/u/fredwu/tech/e/blog" term="blog"/>
  <category label="Tech" scheme="http://persumi.com/u/fredwu/tech" term="tech"/>
  <link href="http://persumi.com/u/fredwu/tech/e/blog/t/persistence"/>
  <link href="http://persumi.com/u/fredwu/tech/e/blog/t/persistence/feed/rss"/>
  <link rel="self" href="http://persumi.com/u/fredwu/tech/e/blog/t/persistence/feed/atom"/>
  <author>
    <name>Fred Wu</name>
    <email>ifredwu@gmail.com</email>
    <uri>http://persumi.com/u/fredwu</uri>
  </author>
  <subtitle/>
  <id>http://persumi.com/u/fredwu/tech/e/blog/t/persistence</id>
  <title>Blog (persistence) - Fred Wu&apos;s Tech</title>
  <updated>2026-06-21T03:27:15.375564Z</updated>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
&lt;em&gt;This post is about the ruby library we are building - &lt;a href=&quot;https://github.com/fredwu/datamappify&quot;&gt;Datamappify&lt;/a&gt;, please go check it out.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;
At &lt;a href=&quot;http://www.locomote.com.au/&quot;&gt;Locomote&lt;/a&gt; we are building a relatively large web application using Rails. Before we began to lay the foundation, we knew very well that if we wanted the project to be maintainable we had to architect the system with extra care and attention. More specifically, we can’t rely on simply using ActiveRecord which combines behaviour and persistence as our domain models.&lt;/p&gt;
&lt;p&gt;
We began our search for something that would help us decouple our application from the domain layer down to the form handling. We’ve found a couple of gems that are close to what we were after - &lt;a href=&quot;https://github.com/braintree/curator&quot;&gt;Curator&lt;/a&gt;, &lt;a href=&quot;https://github.com/joakimk/minimapper&quot;&gt;Minimapper&lt;/a&gt;, &lt;a href=&quot;https://github.com/nulogy/edr&quot;&gt;Edr&lt;/a&gt; and later on &lt;a href=&quot;https://github.com/apotonick/reform&quot;&gt;Reform&lt;/a&gt;. They are all wonderful gems but unfortunately none of them has everything we need.&lt;/p&gt;
&lt;p&gt;
Here are the things we need:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
Decouple domain logic and data persistence.  &lt;/li&gt;
  &lt;li&gt;
Decouple models and forms.  &lt;/li&gt;
  &lt;li&gt;
Support ActiveRecord (or at the very least, ActiveModel) so we can still use many of the awesome gems like &lt;a href=&quot;https://github.com/plataformatec/devise&quot;&gt;Devise&lt;/a&gt;, &lt;a href=&quot;https://github.com/plataformatec/simple_form&quot;&gt;SimpleForm&lt;/a&gt; and &lt;a href=&quot;https://github.com/carrierwaveuploader/carrierwave&quot;&gt;CarrierWave&lt;/a&gt;.  &lt;/li&gt;
  &lt;li&gt;
Support attributes mapped from different data sources (e.g. remote web services from third-party vendors).  &lt;/li&gt;
  &lt;li&gt;
Support lazy loading so that attributes stored on remote data sources will not get triggered upon loading the entities.  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
All things considered, we bit the bullet and started working on &lt;a href=&quot;https://github.com/fredwu/datamappify&quot;&gt;Datamappify&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
Before I go into details and examples, here is an extremely simplified overview of Datamappify’s architecture:&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/I9GpLds.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
As you can see, Datamappify is loosely based on &lt;a href=&quot;http://martinfowler.com/eaaCatalog/repository.html&quot;&gt;Repository pattern&lt;/a&gt; and &lt;a href=&quot;http://msdn.microsoft.com/en-au/library/ff649505.aspx&quot;&gt;Entity Aggregation pattern&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
Datamappify has three main design goals:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;
To utilise the powerfulness of existing ORMs so that using Datamappify doesn’t interrupt too much of your current workflow. For example, Devise would still work if you use it with a UserAccount ActiveRecord model that is attached to a User entity managed by Datamappify.  &lt;/li&gt;
  &lt;li&gt;
To have a flexible entity model that works great with dealing with form data. For example, SimpleForm would still work with nested attributes from different ORM models if you map entity attributes smartly in your repositories managed by Datamappify.  &lt;/li&gt;
  &lt;li&gt;
To have a set of data providers to encapsulate the handling of how the data is persisted. This is especially useful for dealing with external data sources such as a web service. For example, by calling UserRepository.save(user), certain attributes of the user entity are now persisted on a remote web service. Better yet, dirty tracking and lazy loading are supported out of the box!  &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;
You can read more about Datamappify in the project’s &lt;a href=&quot;https://github.com/fredwu/datamappify/blob/master/README.md&quot;&gt;README&lt;/a&gt;. For this blog post I will focus on how we use Datamappify in our Rails application. We are still early days in our application development and Datamappify still has quirks and issues, but I am hoping this post will illustrate some of the key benefits of Datamappify.&lt;/p&gt;
&lt;h3&gt;
Getting Started&lt;/h3&gt;
&lt;p&gt;
Getting started with using Datamappify is really easy. We simply include it in the &lt;code class=&quot;inline&quot;&gt;Gemfile&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;gem &apos;datamappify&apos;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
To keep things organised, we put entities and repositories in their respective directories under &lt;code class=&quot;inline&quot;&gt;app&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_inline_mp1vigkh0f1qz4rgp.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;h3&gt;
Entities&lt;/h3&gt;
&lt;p&gt;
The reason we wanted Datamappify to utilise existing ORMs like ActiveRecord, is so that we could still use gems like Devise.&lt;/p&gt;
&lt;p&gt;
So, we have a &lt;code class=&quot;inline&quot;&gt;UserAccount&lt;/code&gt; model that handles authentication:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;class UserAccount &lt; ActiveRecord::Base
  devise :database_authenticatable,
          :recoverable, :rememberable, :trackable, :validatable
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
&lt;code class=&quot;inline&quot;&gt;UserAccount&lt;/code&gt; has one and only one purpose - user account authentication. Other user behaviours would be contained in either the &lt;code class=&quot;inline&quot;&gt;User&lt;/code&gt; entity itself or service objects. Speaking of the &lt;code class=&quot;inline&quot;&gt;User&lt;/code&gt; entity, it looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;class User
  include Datamappify::Entity

  attr_accessor :user_account

  attribute :account_username, String
  attribute :account_email, String
  attribute :first_name, String
  attribute :last_name, String
  attribute :activated, Boolean, :default =&gt; true

  attributes_from Contact, prefix_with: :work

  validates :first_name, presence: true
  validates :last_name, presence: true

  references :agency

  def full_name
    &quot;#{first_name} #{last_name}&quot;
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
We include &lt;code class=&quot;inline&quot;&gt;Datamappify::Entity&lt;/code&gt; in the &lt;code class=&quot;inline&quot;&gt;User&lt;/code&gt; class to make it an entity. We set the &lt;code class=&quot;inline&quot;&gt;:user_account&lt;/code&gt; accessor is so that we could attach the &lt;code class=&quot;inline&quot;&gt;UserAccount&lt;/code&gt; object onto the entity.&lt;/p&gt;
&lt;p&gt;
The &lt;code class=&quot;inline&quot;&gt;attribute&lt;/code&gt; DSL is from &lt;a href=&quot;https://github.com/solnic/virtus&quot;&gt;Virtus&lt;/a&gt; - we get attribute type coercion for free, awesome!&lt;/p&gt;
&lt;p&gt;
&lt;code class=&quot;inline&quot;&gt;attributes_from&lt;/code&gt; is a DSL provided by Datamappify - it essentially “mounts” all the attributes from another entity, in this case the &lt;code class=&quot;inline&quot;&gt;Contact&lt;/code&gt; entity, which looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;class Contact
  include Datamappify::Entity

  attribute :email, String
  attribute :phone_number, String
  attribute :fax_number, String

  validates :phone_number, presence: true
  validates :fax_number, presence: true
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
All the attributes and validation rules from &lt;code class=&quot;inline&quot;&gt;Contact&lt;/code&gt; are now “mounted” on &lt;code class=&quot;inline&quot;&gt;User&lt;/code&gt;. &lt;code class=&quot;inline&quot;&gt;attributes_from Contact, prefix_with: :work&lt;/code&gt; is equivalent to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;attribute :work_email, String
attribute :work_phone_number, String
attribute :work_fax_number, String

validates :work_phone_number, presence: true
validates :work_fax_number, presence: true&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Validation DSL is provided by &lt;a href=&quot;http://api.rubyonrails.org/classes/ActiveModel/Validations.html&quot;&gt;ActiveModel::Validations&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
&lt;code class=&quot;inline&quot;&gt;references :agency&lt;/code&gt; is a convenient DSL provided by Datamappify. It is equivalent to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;attr_accessor :agency

attribute :agency_id, Integer&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Now that we have entities, we need a way to retrieve and store them. For that we need repositories.&lt;/p&gt;
&lt;h3&gt;
Repositories&lt;/h3&gt;
&lt;p&gt;
Here is the user repository:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;class UserRepository
  include Datamappify::Repository

  for_entity User

  default_provider :ActiveRecord

  map_attribute :account_username, &apos;ActiveRecord::UserAccount#username&apos;
  map_attribute :account_email, &apos;ActiveRecord::UserAccount#email&apos;

  map_attribute :work_email, &apos;ActiveRecord::Contact#email&apos;
  map_attribute :work_phone_number, &apos;ActiveRecord::Contact#phone_number&apos;
  map_attribute :work_fax_number, &apos;ActiveRecord::Contact#fax_number&apos;
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Similarly to an entity, we include &lt;code class=&quot;inline&quot;&gt;Datamappify::Repository&lt;/code&gt; to make the class a repository. We specify &lt;code class=&quot;inline&quot;&gt;for_entity&lt;/code&gt; to link the repository to an entity, and &lt;code class=&quot;inline&quot;&gt;default_provider&lt;/code&gt; to use a specific data provider for unmapped attributes.&lt;/p&gt;
&lt;p&gt;
Unmapped attributes are the ones not specified in &lt;code class=&quot;inline&quot;&gt;map_attribute&lt;/code&gt;, in this case they are &lt;code class=&quot;inline&quot;&gt;first_name&lt;/code&gt;, &lt;code class=&quot;inline&quot;&gt;last_name&lt;/code&gt; and &lt;code class=&quot;inline&quot;&gt;activated&lt;/code&gt;. Unmapped attributes are actually automatically mapped by Datamappify, so the user repository essentially does this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;map_attribute :first_name, &apos;ActiveRecord::User#first_name&apos;
map_attribute :last_name, &apos;ActiveRecord::User#last_name&apos;
map_attribute :activated, &apos;ActiveRecord::User#activated&apos;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
The first argument of &lt;code class=&quot;inline&quot;&gt;map_attribute&lt;/code&gt; is the name of the attribute from the &lt;code class=&quot;inline&quot;&gt;User&lt;/code&gt; entity (e.g. &lt;code class=&quot;inline&quot;&gt;:first_name&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;
The second argument is a string containing three things:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;
&lt;code class=&quot;inline&quot;&gt;ActiveRecord&lt;/code&gt; is the data provider.  &lt;/li&gt;
  &lt;li&gt;
&lt;code class=&quot;inline&quot;&gt;::User&lt;/code&gt; is the ActiveRecord model class.  &lt;/li&gt;
  &lt;li&gt;
&lt;code class=&quot;inline&quot;&gt;#first_name&lt;/code&gt; is the ActiveRecord attribute from the model class.  &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;
Even though the &lt;code class=&quot;inline&quot;&gt;User&lt;/code&gt; entity is a representation of a user on the domain level, the underlying data structure does not necessary have to be. As you can see from the user repository example, we are mapping &lt;code class=&quot;inline&quot;&gt;:account_username&lt;/code&gt; and &lt;code class=&quot;inline&quot;&gt;:account_email&lt;/code&gt; to the &lt;code class=&quot;inline&quot;&gt;UserAccount&lt;/code&gt; ActiveRecord model we’ve seen before. And we have a bunch of contact details attributes mapped to the &lt;code class=&quot;inline&quot;&gt;Contact&lt;/code&gt; ActiveRecord model.&lt;/p&gt;
&lt;p&gt;
The database schema therefore looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;create_table &quot;contacts&quot;, force: true do |t|
  t.string &quot;email&quot;
  t.string &quot;phone_number&quot;
  t.string &quot;fax_number&quot;
  t.integer &quot;user_id&quot;
end

create_table &quot;user_accounts&quot;, force: true do |t|
  t.string &quot;username&quot;, default: &quot;&quot;, null: false
  t.string &quot;email&quot;, default: &quot;&quot;, null: false
  t.string &quot;encrypted_password&quot;, default: &quot;&quot;, null: false
  t.string &quot;reset_password_token&quot;
  t.datetime &quot;reset_password_sent_at&quot;
  t.datetime &quot;remember_created_at&quot;
  t.integer &quot;sign_in_count&quot;, default: 0
  t.datetime &quot;current_sign_in_at&quot;
  t.datetime &quot;last_sign_in_at&quot;
  t.string &quot;current_sign_in_ip&quot;
  t.string &quot;last_sign_in_ip&quot;
  t.integer &quot;user_id&quot;
  t.datetime &quot;created_at&quot;
  t.datetime &quot;updated_at&quot;
end

create_table &quot;users&quot;, force: true do |t|
  t.string &quot;first_name&quot;
  t.string &quot;last_name&quot;
  t.boolean &quot;activated&quot;
  t.integer &quot;agency_id&quot;
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Note that because the &lt;code class=&quot;inline&quot;&gt;Contact&lt;/code&gt; entity is mounted on the &lt;code class=&quot;inline&quot;&gt;User&lt;/code&gt; entity, we need a foreign key &lt;code class=&quot;inline&quot;&gt;user_id&lt;/code&gt; in the &lt;code class=&quot;inline&quot;&gt;contacts&lt;/code&gt; table to link them.&lt;/p&gt;
&lt;h4&gt;
Data providers&lt;/h4&gt;
&lt;p&gt;
Because we are allowed to specify a data provider (i.e. &lt;code class=&quot;inline&quot;&gt;ActiveRecord&lt;/code&gt;) for each attribute, we can map attributes to entirely different data providers! For instance, we could have:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;map_attribute :first_name, &apos;ActiveRecord::User#first_name&apos;
map_attribute :last_name, &apos;ActiveRecord::User#last_name&apos;
map_attribute :activated, &apos;Sequel::UserActivation#activated&apos;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Now the &lt;code class=&quot;inline&quot;&gt;activated&lt;/code&gt; attribute is mapped to the &lt;code class=&quot;inline&quot;&gt;UserActivation&lt;/code&gt; Sequel model. This powerful feature would allow us to develop data providers that communicate with remote web services. :)&lt;/p&gt;
&lt;h4&gt;
Repository APIs&lt;/h4&gt;
&lt;p&gt;
Datamappify provides a bunch of APIs for retrieving and storing data. These APIs are being developed as needed during our application development.&lt;/p&gt;
&lt;p&gt;
For instance, to find a particular user entity by ID:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;user = UserRepository.find(1)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
To get all the users:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;users = UserRepository.all&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
To search for users:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;users = UserRepository.find(:first_name =&gt; &apos;Fred&apos;, :activated =&gt; true)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
To save a user:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;UserRepository.save(user)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
To delete a user:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;UserRepository.destroy(user)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
There are some limitations for certain APIs, you can &lt;a href=&quot;https://github.com/fredwu/datamappify/blob/master/README.md#repository-apis&quot;&gt;read more about them here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
When saving an entity, because Datamappify has an internal pool for tracking dirty attributes, only those dirty attributes will get saved.&lt;/p&gt;
&lt;p&gt;
Also, Datamappify supports attribute lazy loading, all we have to do is to tell our entity to become lazy aware:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;class User
  include Datamappify::Entity
  include Datamappify::Lazy
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Once an entity is lazy aware, a repository will inject the lazy loading mechanism onto the entity when it retrieves such entity (via &lt;code class=&quot;inline&quot;&gt;find&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;
Both repositories and entities support inheritance, again, you may read more about them in the &lt;a href=&quot;https://github.com/fredwu/datamappify/blob/master/README.md&quot;&gt;project’s README&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
Putting Things Together With A Controller&lt;/h3&gt;
&lt;p&gt;
Remember we have the &lt;code class=&quot;inline&quot;&gt;UserAccount&lt;/code&gt; for authentication? So how does that work? Well, here’s the piece of code in our application controller:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;class ApplicationController &lt; ActionController::Base
  before_filter :authenticate_user_account!

  private

  def current_user
    unless current_user_account.blank?
      @current_user ||= UserRepository.find(current_user_account.user_id)
    end
  end

  helper_method :current_user
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Not too different from the normal Devise workflow hey? ;)&lt;/p&gt;
&lt;h3&gt;
Forms&lt;/h3&gt;
&lt;p&gt;
Web applications typically have lots of forms - ours is no different. It turns out, Datamappify can help build form objects too!&lt;/p&gt;
&lt;p&gt;
Here’s the view portion of one of our forms (we use &lt;a href=&quot;https://github.com/slim-template/slim&quot;&gt;Slim&lt;/a&gt; templates):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;# our standard form layout

= simple_form_for submission_target, url: submission_path, signed: true do |f|

  = yield(f)

  .form-actions
    .pull-left
      - if archivable? &amp;&amp; resource.persisted?
        = link_to &apos;Archive&apos;, &apos;#&apos;, method: :delete, class: &apos;btn-archive&apos;

    = f.submit &apos;Save&apos;
    = link_to &apos;Cancel&apos;, cancel_path

# actual form content

= render layout: &apos;forms/resource&apos; do |f|
  h3 Account Details
  .row-fluid
    .span4 = f.input :account_type, collection: BankAccount::ACCOUNT_TYPES.invert
    .span4 = f.input :account_code
    .span4 = f.input :account_name
  .row-fluid
    .span4 = f.input :account_number
    .span4 = f.input :bank_name
    .span4 = f.input :bank_branch
  .row-fluid
    .span4 = f.input :bank_bsb, label: &quot;Bank BSB&quot;
    .span4 = f.input :activated, as: :boolean

  h3 Branch Details
  .row-fluid
    .span4 = f.input :branch_address_line_1, label: &quot;Address 1&quot;
    .span4 = f.input :branch_address_line_2, label: &quot;Address 2&quot;
    .span4 = f.input :branch_address_line_3, label: &quot;Address 3&quot;
  .row-fluid
    .span4 = f.input :branch_state, label: &quot;State&quot;
    .span4 = f.input :branch_postcode, label: &quot;Postcode&quot;
    .span4
      = f.input :branch_country_code, label: &quot;Country&quot; do
        = f.country_select :branch_country_code, [&apos;au&apos;, &apos;us&apos;, &apos;gb&apos;], value: &apos;au&apos;
      - f.add_signed_fields :branch_country_code
  .row-fluid
    .span4 = f.input :branch_phone_number, label: &quot;Phone number&quot;
    .span4 = f.input :branch_fax_number, label: &quot;Fax number&quot;

  h3 Merchant Details
  .row-fluid
    .span4 = f.input :merchant_number
    .span4 = f.input :merchant_name
    .span4 = f.input :merchant_address
  .row-fluid
    .span4 = f.input :merchant_biller_code, label: &quot;Biller code&quot;
    .span4 = f.input :merchant_bpay_method, collection: Merchant::BPAY_METHODS.invert, label: &quot;BPAY method&quot;

  h3 Payment Details
  .row-fluid
    .span4 = f.input :payment_number
    .span4 = f.input :payment_merchant, label: &quot;Merchant description&quot;
    .span4 = f.input :payment_reference_type, collection: PaymentAccount::REFERENCE_TYPES.invert, label: &apos;Reference type&apos;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
As you can see, it’s a form containing details from bank account, branch address, branch contact information, merchant and payment. And indeed we do have &lt;code class=&quot;inline&quot;&gt;bank_accounts&lt;/code&gt;, &lt;code class=&quot;inline&quot;&gt;addresses&lt;/code&gt;, &lt;code class=&quot;inline&quot;&gt;contacts&lt;/code&gt;, &lt;code class=&quot;inline&quot;&gt;merchants&lt;/code&gt; and &lt;code class=&quot;inline&quot;&gt;payment_accounts&lt;/code&gt; tables in our database. Yet, the form still remains flat-structured and submits via &lt;code class=&quot;inline&quot;&gt;simple_form_for&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;
This is thanks to our &lt;code class=&quot;inline&quot;&gt;BankAccount&lt;/code&gt; entity and &lt;code class=&quot;inline&quot;&gt;BankAccountRepository&lt;/code&gt; repository:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;class BankAccount
  include Datamappify::Entity
  include Concerns::Archivable

  ACCOUNT_TYPES = {
    &apos;general&apos; =&gt; &apos;General&apos;,
    &apos;trust&apos; =&gt; &apos;Trust&apos;
  }

  attribute :account_type, String
  attribute :account_code, String
  attribute :account_name, String
  attribute :account_number, String
  attribute :bank_name, String
  attribute :bank_branch, String
  attribute :bank_bsb, String
  attribute :activated, Boolean, default: true

  attributes_from Address, prefix_with: :branch
  attributes_from Contact, prefix_with: :branch
  attributes_from Merchant, prefix_with: :merchant
  attributes_from PaymentAccount, prefix_with: :payment

  references :agency

  validates :account_type, Mos::Validation.in(ACCOUNT_TYPES)
  validates :account_code, Mos::Validation::STANDARD_TEXT
  validates :account_name, Mos::Validation::STANDARD_TEXT
  validates :account_number, Mos::Validation::STANDARD_TEXT
  validates :bank_name, Mos::Validation::STANDARD_TEXT
  validates :bank_branch, Mos::Validation::STANDARD_TEXT_OPTIONAL
  validates :bank_bsb, Mos::Validation::BSB
  validates :merchant_bpay_method, Mos::Validation.presence_depend_on(:merchant_biller_code)
  validates :payment_reference_type, Mos::Validation.presence_depend_on(:payment_number)
end

class BankAccountRepository
  include Datamappify::Repository

  for_entity BankAccount

  default_provider :ActiveRecord

  map_attribute :branch_address_line_1, &apos;ActiveRecord::Address#address_line_1&apos;
  map_attribute :branch_address_line_2, &apos;ActiveRecord::Address#address_line_2&apos;
  map_attribute :branch_address_line_3, &apos;ActiveRecord::Address#address_line_3&apos;
  map_attribute :branch_state, &apos;ActiveRecord::Address#state&apos;
  map_attribute :branch_postcode, &apos;ActiveRecord::Address#postcode&apos;
  map_attribute :branch_country_code, &apos;ActiveRecord::Address#country_code&apos;

  map_attribute :branch_phone_number, &apos;ActiveRecord::Contact#phone_number&apos;
  map_attribute :branch_fax_number, &apos;ActiveRecord::Contact#fax_number&apos;
  map_attribute :branch_email, &apos;ActiveRecord::Contact#email&apos;
  map_attribute :branch_website, &apos;ActiveRecord::Contact#website&apos;

  map_attribute :merchant_number, &apos;ActiveRecord::Merchant#number&apos;
  map_attribute :merchant_name, &apos;ActiveRecord::Merchant#name&apos;
  map_attribute :merchant_address, &apos;ActiveRecord::Merchant#address&apos;
  map_attribute :merchant_biller_code, &apos;ActiveRecord::Merchant#biller_code&apos;
  map_attribute :merchant_bpay_method, &apos;ActiveRecord::Merchant#bpay_method&apos;

  map_attribute :payment_number, &apos;ActiveRecord::PaymentAccount#number&apos;
  map_attribute :payment_merchant, &apos;ActiveRecord::PaymentAccount#merchant&apos;
  map_attribute :payment_reference_type, &apos;ActiveRecord::PaymentAccount#reference_type&apos;
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Pretty clean and straightforward. What do you think?&lt;/p&gt;
&lt;h3&gt;
Datamappify - Keeping Your Domain Layer Sane&lt;/h3&gt;
&lt;p&gt;
Datamappify’s concept isn’t new, but there simply isn’t anything in the ruby community that solves everything Datamappify tries to solve.&lt;/p&gt;
&lt;p&gt;
I hope that this post has not only introduced you to Datamappify, but has also made you think about how to make &lt;strong&gt;your&lt;/strong&gt; application’s domain layer more decoupled.&lt;/p&gt;
&lt;p&gt;
Thanks for reading, and if you have any feedback for Datamappify please get in touch with me!&lt;/p&gt;
]]&gt;</content>
    <published>2013-06-27T12:09:00.000000Z</published>
    <category label="Blog" scheme="http://persumi.com/u/fredwu/tech/e/blog" term="blog"/>
    <category label="Tech" scheme="http://persumi.com/u/fredwu/tech" term="tech"/>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/datamappify-a-new-take-on-decoupling-domain-form-and-persistence-in-rails"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/datamappify-a-new-take-on-decoupling-domain-form-and-persistence-in-rails</id>
    <title>Datamappify - A New Take on Decoupling Domain, Form and Persistence in Rails</title>
    <updated>2013-06-27T12:09:00.000000Z</updated>
  </entry>
</feed>