Build Status Dependency Status Code Climate Gem Version

TracksAttributes

TracksAttributes adds the ability to track ActiveRecord and Object level attributes. Beginning at version 1.1.0, it is possible to re-hydrate complex object structures that contain Plain Old Ruby Objects, or arrays of POROs.

Sometimes you just need to know what your accessors are at runtime, like when you're writing a controller that needs to return JSON or XML. This module extends ActiveRecord::Base with the tracks_attributes class method. Once this has been called the class is extended with the ability to track attributes through attr_accessor, attr_reader, and attr_writer. Plain Old Ruby classes may also use TracksAttributes by including it as a module first.

Note: The necessity for this gem is born out of the clash between ActiveRecord attribute handling and PORO attributes. Using Object::instance_variables just doesn't return the correct list for marshaling data effectively, nor produce values for computed attributes.

Enhanced JSON and XML processing

Beyond the ability to track your attributes, this gem simplifies your use of converting your objects to an from JSON or XML. Once a class has been extended, it can convert to and from JSON or XML without having to explicitly include attributes.

Example:

class Person < ActiveRecordBase
  tracks_attributes

  attr_accessible :name, :email
  attr_accessor :favorite_food
end

fred = Person.find_by_name("Fred")
fred.favorite_food = 'Brontosaurus Burgers'

fred_json = fred.to_json
puts fred_json
# => {"id":1,"name":"Fred","email":"[email protected]","favorite_food":"Brontosaurus Burgers"}

fred2 = Person.new
fred2.from_json(fred_json)
puts "#{fred2.name} loves #{fred2.favorite_food}"
# => Fred loves Brontosaurus Burgers

Both the JSON and XML take the same options as their Hash and ActiveRecord counterparts so you can still use :only and :includes in your code as needed.

Re-hydrating Complex Ruby Objects

Classes that have simple types, like Fixnum or String, can be handled by simply invoking :tracks_attributes within the class definition. More complex objects require additional information to converted from a Hash to the correct type of Object. This is done by providing the class in the calls to attr_accessor, attr_reader and attr_writer.

Specify the class of an attribute by providing the option, :klass, with the target class as the value.

Example:

  attr_accessor :my_poro_var, :klass => MyPoroClass

The target class must then provide class method, :create, taking a Hash of attributes to construct the Object instance.

Here is example from TracksAttributes::Base

  class Base
    include TracksAttributes
    tracks_attributes

    def self.create(attributes = {}, options = {})
      # implentation
    end

    # the rest of the class here...
  end

Add Validations To Non Active Record Attributes

To add ActiveModel::Validations to your class just initialize your class with tracks_attributes as

  tracks_attributes :validates => true

Use TracksAttributes::Base to simplify coding POROs

While developers can continue to roll their own PORO class, TracksAttributes::Base provides a quick implementation that tracks attributes, provides validation and works with TracksAttributes when re-hydrating. Simply inherit from TracksAttributes::Base and you are good to go.

Here's an example that shows how simple it is to define:

class Photo < TracksAttributes::Base
  attr_accessor :title, :filename
end

class Person < ActiveRecord::Base
  tracks_attributes

  attr_accessible :name 
  attr_accessor   :photos, :klass => Photo
end

Once this has been coded up, it is possible to generate JSON/XML that stream the entire array of PhotoLocation. More importantly, it is possible to fully re-hydrate a Person, including the array of Photo. Re-hydration takes place when the Hash of attributes is set on the Object instance.

Continuing...

# Instance Creation
photos = [ 
  Photo.create(:title => 'Hadji and Me', :filename => 'images/hadji_and_me.png'),
  Photo.create(:title => 'Bandit', :filename => 'images/bandit.png')
]

johnny_quest        = Person.new(:name => 'Johnny Quest')
johnny_quest.photos = photos

# Generate the JSON
jq_json = johnny_quest.to_json

# => {"name":"Johnny Quest","photos":[{"title":"Hadji and Me","filename":"images/hadji_and_me.png"},{"title":"Bandit","filename":"images/bandit.png"}]}

# Later Re-hydrate the JSON
json_param = params[:person]
person = Person.new
person.from_json json_param

puts "Name = #{person.name}, 1st image title = #{person.photos[0].title}"
# => Johnny Quest, 1st image title = Hadji and Me

Installation

Add the following to your Gemfile

gem 'tracs-attributes

Or from the git repo for the bleeding edge (feel free to star it :-))

gem 'tracks-attributes', :git => "git://github.com/leopoldodonnell/tracks-attributes"

Then call bundle to install it.

> bundle

License

This project rocks and uses MIT-LICENSE. Copyright 2013 Leopold O'Donnell