Property

Wrap model properties into a single database column and declare properties from within the model.

website: zenadmin.org/635

license: MIT

Status: Beta

The gem works fine, even though it still needs some more features like property definition changes detections and migrations.

Usage

You first need to create a migration to add a 'text' field named 'properties' to your model. Choose a text format that is really long (otherwize your data will be truncated and the property will fail to decode => error). Do something like this:

class AddPropertyToContact < ActiveRecord::Migration
  def self.up
    if ActiveRecord::Base.configurations[RAILS_ENV]['adapter'] == 'mysql'
      execute "ALTER TABLE contacts ADD COLUMN properties LONGTEXT"
    else
      add_column :contacts, :properties, :text
    end
  end

  def self.down
    remove_column :contacts, :properties
  end
end

Once your database is ready, you need to declare the property columns:

class Contact < ActiveRecord::Base
  include Property
  property do |p|
    p.string  'first_name', 'name', 'phone'
    p.datetime 'contacted_at', :default => Proc.new {Time.now}
  end
 end

You can now read property values with:

@contact.prop['first_name']
@contact.first_name

And set them with:

@contact.update_attributes('first_name' => 'Mahatma')
@contact.prop['name'] = 'Gandhi'
@contact.name = 'Gandhi'

Roles

Properties would not be really fun if you could not add new properties to your instances depending on what the object does. First define the roles:

@picture = Property::Role.new do |p|
  p.integer :width,  :default => :get_width
  p.integer :height, :default => :get_height
  p.string  'camera'
  p.string  'location'

  p.actions do
    # Define new methods to insert into model

    def get_width
      image.width
    end

    def get_height
      image.height
    end

    def image
      raise 'Missing file' unless @file
      @image ||= ImageBuilder(@file)
    end
  end
end

And then, either when creating new pictures or updating them, you need to include the role:

@model.has_role @picture

The model now has the picture's properties defined, with accessors like @model.camera, methods like @model.image, get_with, etc and default values will be fetched on save.

Note that you do not need to include a role just to read the data as long as you use the 'prop' accessor.

StoredRole

The dynamic nature of the Property gem goes to the point where you can store your property definitions in the database by using the StoredRole and StoredColumn modules.

External storage

You might need to define properties in a model but store them in another model (versioning). In this case you can simply use 'store_properties_in' class method:

class Contact < ActiveRecord::Base
  include Property
  store_properties_in :version
  property do |p|
    p.string 'name', 'first_name'
    p.string 'childhood', :default => 'happy'
  end
end

Doing so will not touch the storage class. All property definitions, validations and method definitions are executed on the 'Contact' class.

Indexing support

The property gem lets you very easily export content from the packed data to any kind of external table. Using a key/value tables:

class Contact < ActiveRecord::Base
  include Property
  property do |p|
    p.string 'name', :indexed => true
    p.string 'first_name', :index => Proc.new {|rec| { 'fullname' => rec.fullname }}

    p.index(:string) do |record|
      {
        'fulltext' => "name:#{record.name} first_name:#{record.first_name}",
        "name_#{record.lang}" => record.name
      }
    end
  end
end

Using a custom indexer, you can group indexed values together in a single record. This can be interesting if you have some legacy code or queries that need direct access to some values:

class Contact < ActiveRecord::Base
  include Property
  property do |p|
    p.string 'name'
    p.string 'first_name'

    p.index(ContactIndexer) do |record|
      {
        'name'       => record.name,
        'first_name' => record.first_name,
      }
    end
  end
end

Please read the docs for details: zenadmin.org/635

Developping property and testing

As of 2015, you need ruby 1.8.7 and the following gems in order to run the test suite.

gem install rdoc-data; rdoc-data --install
gem install shoulda --version=2.10.3
gem install activerecord --version=2.3.18
gem install sqlite3 --version=1.3.5
gem install json --version=1.5.1
gem install jeweler --version=1.8.3