Project

extend_at

0.0
No commit activity in last 3 years
No release in over 3 years
This gem allows you to extend rails models without migrations: This way you can, i.e., develop your own content types, like in Drupal.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

Runtime

~> 3.1
 Project Readme

Extend at

This gem allows you to extend models without migrations: This way you can, i.e., develop your own content types, like in Drupal.

For example, if you want to create an administration panel to add columns to a model, for example, you are working on a CMS, and you want to create a "content type" and you need to set the "columns" but you don't want to migrate the database, then, you can see this little tutorial.

Status:

Build Status

Installation

gem install extend_at

Rails 3

Add in your Gemfile:

gem 'extend_at'

After that, you need execute:

rails generate extend_at:install

This will generate one migration with all necessary tables. Now you need migrate your database.

rake db:migrate

Usage

You don't need an extra column in your model. Only you need is put next code in your model.

extend_at :extra

For example:

class User < ActiveRecord::Base
  extend_at :extra
end

Now you can create extra attributes:

user.extra.private_photos = true
user.extra.subscribe_to_news = false
user.extra.profile_description = ''
user.save

This is the same:

user.extra_private_photos = true
user.extra_subscribe_to_news = false
user.extra_profile_description = ''
user.save

Or:

user[:extra_private_photos] = true
user[:extra_subscribe_to_news] = false
user[:extra_profile_description] = ''
user.save

Columns configuration

You can configure each column.

Set column type

You can set the colum's type.

class User < ActiveRecord::Base
  extend_at :extra, :columns => {
    :private_photos => {
      :type => :boolean
    }, :age => {
      :type => :get_type
    }, :profile_description => {
      :type => lambda {
        String
      }
    }, :last_loggin => {
      :type => Time.now.class
    }, :subscribe_to_rss => :get_rss_config
  }

  protected
  def get_type
    Fixnum
  end

  def get_rss_config
    {
      :type => :boolean
    }
  end
end
Valid types

Valid symbols:

  • :any
  • :binary
  • :boolean
  • :date
  • :datetime
  • :decimal
  • :float
  • :integer
  • :string
  • :text
  • :time
  • :timestamp

But you can use classes.

  • Float: :any
  • Fixnum: :integer
  • String: :text
  • Time: :timestamp
  • Date: :datetime

Else, return :any

Set default value

class User < ActiveRecord::Base
  extend_at :extra, :columns => {
    :private_photos => {
      :type => :boolean,
      :default => true
    }, :age => {
      :type => :get_type,
      :default => 1
    }, :profile_description => {
      :type => lambda {
        String
      },
      :default => :get_default_profile_description
    }, :last_loggin => {
      :type => Time.now.class,
      :default => lambda {
        self.created_at.time
      }
    }, :subscribe_to_rss => :get_rss_config
  }

  protected
  def get_type
    Fixnum
  end

  def get_rss_config
    {
      :type => :boolean,
      :default => true
    }
  end

  def get_default_profile_description
    Description.where(:user_id => self.id).default
  end
end

Set validation

class User < ActiveRecord::Base
  extend_at :extra, :columns => {
    :private_photos => {
      :type => :boolean,
      :default => true
    }, :age => {
      :type => :get_type,
      :default => 1,
      :validate => lambda {
        |age|
        errors.add :extra_age, "Are you Matusalen?" if age > 150
        errors.add :extra_age, "Are you a fetus?" if age <= 0
      }
    }, :profile_description => {
      :type => lambda {
        String
      },
      :default => :get_default_profile_description,
      :lambda => :must_not_use_strong_language
    }, :last_loggin => {
      :type => Time.now.class,
      :default => lambda {
        self.created_at.time
      },
      :validate => lambda {
        |time|
        errors.add :extra_last_loggin, "You can't loggin on the future" if time > Time.now
      }
    }, :subscribe_to_rss => :get_rss_config
  }

  protected
  STRONG_WORD = [
    #...
  ]
  
  def get_type
    Fixnum
  end

  def get_rss_config
    {
      :type => :boolean,
      :default => true
    }
  end

  def get_default_profile_description
    Description.where(:user_id => self.id).default
  end

  def must_not_use_strong_language(desc)
    errors.add :cofig_profile_description, "You must not use strong language" if desc =~ /(#{STRONG_WORD.join('|')})/
  end
end

Static columns

If you like to restrict the existent extended columns, you should use :static => true

class User < ActiveRecord::Base
  extend_at :extra, :columns => {
    :private_photos => {
      :type => :boolean,
      :default => true
    }
  }, :static => true
end

Now, User.extra only accept private_photos column

Scopes

You can use scope like:

User.extra_last_loggin_gt_eq(1.week.ago).extra_age_gt_eq(18).where(:column => "value").all

Valid scopes:

<extention>_<column_name>_<comparation>

Comparations:

  • lt
  • lt_eq
  • eq
  • gt_eq
  • gt
  • match

Belongs to

If you like to add a belongs_to relationship, you can do it in this way:

# app/models/toolbox.rb
class Toolbox
end

# app/model/tool.rb
class Tool
  extend_at extra, columns => {}, :belongs_to => :toolbox
end

:belongs_to parametter accept

  • One name

    :belongs_to => :toolbox

  • Array of names

    :belongs_to => [:toolbox, :owner]

  • Hash

    :belongs_to => {:onwer => {:class_name => "User"}}

For now, hash only accept

  • class_name
  • polymorphic
  • foreign_key

Note, this new feature is under heavy development, use it under your own risk.

Integration in the views

If you like to use some configuration variable in your views you only need put the name of the input like :extra_name, for example:

<% form_for(@user) do |f| %>
  ...
  <div class="field">
    <%= f.label :extra_private_photos %><br />
    <%= f.check_box :extra_private_photos %>
  </div>
  ...
<% end %>

More

For more documentation go to wiki.

Tips

If you like to do something more dynamic, like create columns and validations depending of some model or configuration, then you can do something like this:

class User < ActiveRecord::Base
  extend_at :extra, :columns => :get_columns
  serialize :columns_name

  protected
  def get_columns
    columns = {}
    columns_name.each do |name|
      config = ColumConfig.where(:user_id => self.id, :column => name).first
      columns[name.to_sym] = {
        :type => eval(config.class_type),
        :default => config.default_value,
        :validate => get_validation(config)
      }
    end
    
    columns
  end

  # Accept a name of a validation and return the Proc with the validation code
  def get_validation(validation_type)
    # ...
  end
end

How works?

serialize :columns_name

This make columns_name column work like YAML serialized object. In this case, is used to sotore an array of names of each column name. (See ActiveRecord::Base)

extend_at :extra, :columns => :get_columns

This line will use the function get_columns to get the information about each column dynamically. This function returns a hash with the information about each column.

columns_name.each do |name|
    #...
end

Iterate through each column stored in the column columns_name.

config = ColumConfig.where(:user_id => self.id, :column => name).first

Search the column configuration stored in a separated model. By this way, we can configura each column easily, we can create a view to create columns and configure it easily.

columns[name.to_sym] = {
    :type => eval(config.class_type),
    :default => config.default_value,
    :validate => get_validation(config)
  }

This lines configure the column.

:type => eval(config.class_type),

The model ColumConfig have a string column named class_type, can be ":integer" or "Fixnum".

:default => config.default_value,

The model ColumConfig have a serialized column named default_valuee, in this way we can sotre integer values, boolean, strings or datetime and time values without problems.

:validate => get_validation(config.validation)

This line execute the function get_validation to get a Proc with the validation code. This function can use a case/when for select the correct function or use the data of the model config to create a function.

columns

Finally we return the colums configuration.

Bugs, recomendation, etc

If you found a bug, create an issue. If you have a recomendation, idea, etc., create a request or fork the project.

License

This gem is under MIT license.