mapped-record

github.com/hsume2/mapped-record

Auto-magically map Hash to ActiveRecord.attributes.

Suppose you have a hash a_hash = { 'FullName' => 'Your Name' }, and an Active Record object with a :full_name attribute to create with ‘Your Name’. It’s easy to deal with this on a one-time basis:

model.create( :full_name => a_hash['FullName'] )

However, add in 20 other keys and it gets tiresome.

Suppose you could define hash keys and their target ActiveRecord attribute in one place; then, initialize Active Record objects with the corresponding data. Continue reading.

Getting Started

All you have to do is add attr_mapped to your model.

class Person < ActiveRecord::Base
  attr_mapped 'FullName' => :full_name
end

Then you can create and update like so:

p = Person.create_with(h)
h['FullName'] = 'Mr. Name'
p.update_with(h)

mapped-record is more powerful than that. See Mapping Types for efficient ways to assign mappings. See Mapping Helpers for extra-added features (e.g. post-processing data).

Mapping Types

Mappings can be created in the following ways:

Automatic mappings (implicit)

If you use,

attr_mapped 'FullName'

attr_mapped will automatically downcase_and_underscore the key to :full_name, which is often useful. Whenever used, specify these first.

Manual mappings (explicit)

To manually set which Active Record attribute you want a key to map to, add manual mappings in the options hash.

attr_mapped 'FullName' => :full_name
attr_mapped { 'FullName' => :full_name } # same as above
attr_mapped 'FullName' => :full_name, 'Email' # will fail, because the options hash is considered the last argument

Namespace mappings

Suppose you have a lot of keys starting with PBType which you want removed. Then add :namespace => 'PBType' to remove the prefix and then map automatically (also in the options hash)

attr_mapped 'PBTypeName', 'PBTypeAddress', 'PBTypeCode', { :namespace => 'PBType' }

will map PBTypeName to :name, PBTypeAddress to :address, etc.

Namespaces only apply to the keys for each attr_mapped call. So

class PBPerson < ActiveRecord::Base
  attr_mapped 'PBTypeName', { :namespace => 'PBType' }
  attr_mapped 'PBTypeAddr'
end

will map PBTypeName to :name, but PBTypeAddr to :pb_type_addr.

Mapping priority

Regardless of how many times you call attr_mapped, mappings are overridden in increasing order of priority:

  • implicit

  • namespace

  • explicit

That means explicit will always override namespace and implicit, regardless of the order in which #attr_mapped is called. To illustrate this behavior:

class PBPerson < ActiveRecord::Base
  attr_mapped 'PBTypeName', { :namespace => 'PBType' }
  attr_mapped 'PBTypeName'
  attr_mapped { 'PBTypeName' => :actually_this }

  attr_mapped 'PBTypeName', { :namespace => 'PBType', 'PBTypeName' => :actually_this } # even in this confusing example
end

will map to :actually_this.

Mapping Helpers

:id

If one of the hash keys should map to the Active Record id, setting it like attr_mapped :key => :id won’t work. Active Record won’t let you mass assign :id anyway. Instead

attr_mapped 'PBTypeName', { :namespace => 'PBType', :id => 'PBKey' }

to force creation with PBKey‘s value.

:serialize

You can also specify which keys to serialize after they’ve been mapped. Using,

attr_mapped 'PBArray', { :namespace => 'PB', :serialize => 'PBArray' }

will map PBArray to :array and call serialize :array in the Active Record.

:filter

You can add proc filters to process data from hashes before it’s used by Active Record.

Suppose all the dates are in the wrong format, then,

MYDATE = Proc.new { |p| Time.at(p + 978307200) }
attr_mapped 'PBDate', { :filter => { 'PBDate' => PBPerson::MYDATE } }

Named Mappings

If for some reason, you want to use multiple mappings on the same model, you can create named mappings with attr_mapped_named, where the first argument is the mapping name, followed by the same as attr_mapped.

class Person < ActiveRecord::Base
  attr_mapped_named :phone_record, 'FullName'
end

Dynamic methods

mapped-record will then dynamically create methods so you can:

p = Person.create_with_phone_record(h)
p.update_with_phone_record(h)

Credit

… where deserved. A lot of cues from thoughtbot/paperclip on how to set up the gem and testing, so thanks.