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.
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":"fred@bedrock.com","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.
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
To add ActiveModel::Validations to your class just initialize your class with tracks_attributes as
tracks_attributes :validates => true
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
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
This project rocks and uses MIT-LICENSE. Copyright 2013 Leopold O'Donnell