I'm creating a Ruby gem and would like to extend ActiveRecord::Migration with my own helpers for creating the necessary columns. (This is similar to what Devise does when creating migrations for their various authentication strategies.) I realize the functionality I'm adding is pretty trivial itself and there are probably better/more efficient ways of doing this - I'm attempting this as a learning experience rather than as something with practical application. I just want to understand how to do something as invasive as adding new migration capabilities in Rails.
What I have so far builds into a gem successfully and installs, but when I attempt to run a migration like:
class CreatePosts < ActiveRecord::Migration
  def self.up
    create_table :posts do |t|
      t.string :name
      t.string :title
      t.text :content
      t.hideable
      t.tracks_hidden_at
      t.timestamps
    end
  end
end
... it fails saying that hideable isn't defined.
I've looked into the way that Devise has done this and I have to admit I'm a little lost, but I've tried to fumble through it. I've done the following:
Extended ActiveRecord with my new model additions and created a method to apply the schema changes based on my new migration methods
require 'orm_adapter/adapters/active_record'
module HiddenRecord
  module Orm
    # This module contains some helpers and handle schema (migrations):
    #
    #   create_table :accounts do |t|
    #     t.hideable
    #     t.tracks_hidden_timestamp
    #   end
    #
    module ActiveRecord
      module Schema
        include HiddenRecord::Schema
        # Tell how to apply schema methods.
        def apply_hiddenrecord_schema(name, type, options={})
          column name, type.to_s.downcase.to_sym, options
        end
      end
    end
  end
end
ActiveRecord::Base.extend HiddenRecord::Models
ActiveRecord::ConnectionAdapters::Table.send :include, HiddenRecord::Orm::ActiveRecord::Schema
ActiveRecord::ConnectionAdapters::TableDefinition.send :include, HiddenRecord::Orm::ActiveRecord::Schema
Created a Schema module similar to Devise's schema.rb that defines the methods I want to use in the migration and calls a method to apply the schema
module HiddenRecord
  # Holds schema definition for hiddenrecord model options.
  module Schema
    # Sets the model as having hidable rows
    #
    # == Options
    # * :null - When true, allows the hidden row flag to be null
    # * :default - Used to set default hidden status to true. If not set, default is false (rows are not hidden)
    def hideable(options={})
      null = options[:null] || false
      default = options[:default] || false
      apply_hiddenrecord_schema :hiddenrecord_is_row_hidden, Boolean, :null => null, :default => default
    end
    # Sets the model to record the timestamp when a row was hidden
    def tracks_hidden_timestamp()
      apply_hiddenrecord_schema :hiddenrecord_hidden_at, DateTime
    end
  end
end
Added methods for the models to support the new fields
module HiddenRecord
  module Models
    # This module implements the hideable API
    module Hideable
      def self.included(base)
        base.class_eval do
          extend ClassMethods
        end
      end
      scope :visible, where(:hiddenrecord_is_row_hidden => true)
      def hidden?
        return hiddenrecord_is_row_hidden || false
      end
      def hide
        hiddenrecord_is_row_hidden = true
      end
      def hide!
        hiddenrecord_is_row_hidden = true
        save!
      end
      def unhide
        hiddenrecord_is_row_hidden = false
      end
      def unhide!
        hiddenrecord_is_row_hidden = false
        save!
      end
    end
  end
end
Load the schema and model files and in the main module of the gem
module HiddenRecord
  autoload :Schema, 'hiddenrecord/schema'
  autoload :Models, 'hiddenrecord/models'
  ...
end
require 'hiddenrecord/models/hideable'
require 'hiddenrecord/models/tracks_hidden_timestamp'
Again, recognizing that this is primarily a learning experience, I'm hoping someone can point me in the right direction on how to do this. I'm attempting this on Rails 3.
Rails uses this timestamp to determine which migration should be run and in what order, so if you're copying a migration from another application or generate a file yourself, be aware of its position in the order. This generator can do much more than append a timestamp to the file name.
To undo a rails generate command, run a rails destroy command. You can then edit the file and run rake db:migrate again. (See how to roll back a Migration file to rollback a specific migration or multiple migrations.)
A Rails migration is a tool for changing an application's database schema. Instead of managing SQL scripts, you define database changes in a domain-specific language (DSL). The code is database-independent, so you can easily move your app to a new platform.
Here's how I added custom migration fields with Rails 2 and MySQL for a previous project. Works great.
I don't know how much of this applies to your exact need, so feel free to ask me questions.
I put this code in Rails.root/lib/dbd_migration_helper.rb
module Ddb
  module MigrationHelper
    def self.included(base) # :nodoc:
      base.send(:include, InstanceMethods)
    end
    module InstanceMethods
      def active    (column_name=:active)     column(column_name, :boolean, :default=>true) end
      def email     (column_name=:email)      column(column_name, :string)     end
      def latitude  (column_name=:latitude)   column(column_name, :float)      end
      def longitude (column_name=:longitude)  column(column_name, :float)      end
      def position  (column_name=:position)   column(column_name, :integer)    end
    end
  end
end
require 'activerecord'
if defined?(ActiveRecord::ConnectionAdapters::TableDefinition)
   ActiveRecord::ConnectionAdapters::TableDefinition.send(:include, Ddb::MigrationHelper)
end
                        Quick note, these lines:
ActiveRecord::ConnectionAdapters::Table.send :include, HiddenRecord::Orm::ActiveRecord::Schema
ActiveRecord::ConnectionAdapters::TableDefinition.send :include, HiddenRecord::Orm::ActiveRecord::Schema
don't appear to be include'ing the correct module. I think they should be:
ActiveRecord::ConnectionAdapters::Table.send :include, HiddenRecord::Schema
ActiveRecord::ConnectionAdapters::TableDefinition.send :include, HiddenRecord::Schema
but then, you don't appear to have #tracks_hidden_at defined anywhere either.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With