Ruby on Rails: Override Attribute Assignment 1

Posted by Tadman Thu, 05 Jul 2007 20:07:00 GMT

ActiveRecord models used to provide access to their internal attribute data through the attributes method, but this doesn’t seem to be the case any longer. Have a look at the current source code and you can see why. The attribute hash is cloned before being returned, so changes aren’t applied.

So the old method of reassigning is no longer applicable:

m = MyModel.create(:name => 'test')
m.attributes[:name] = 'value'   # Assigns to a clone of m's @attributes
m.name
 # => 'test'

This limits your options when trying to re-define an assignment method, but there is still a way. For example, to trigger some behaviour when a value is assigned, you can do this:

def name=(value)
        # Always force to lower-case
        super(value.downcase)
end

Calling the base class method of the same name will allow ActiveRecord to handle the assignment properly, and there’s still a way to extend the basic functionality in a model.

Ruby on Rails + Daemons 6

Posted by Tadman Sat, 25 Nov 2006 03:46:00 GMT

Getting a Rails script to run in the background shouldn’t be hard, but there’s a few issues with the stock configuration that need to be fixed before anything will work properly.

First, the Daemons GEM will switch your current working directory to be ‘/’ which will mean that whatever RAILS_ROOT you have will likely be incorrect. Usually it’s something like “../config/../” which works only if you’re in the “correct” location.

A quick fix for that can be put into your environment.rb file right up at the top, after RAILSGEMVERSION.

RAILS_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'), Dir.getwd)

Then you need to create a controller script in some directory. scripts/ is probably fine, but you may have a preference for something else.

Here’s an example that launches myscript.rb which resides in the same directory as the controller script.

#!/usr/bin/env ruby

require 'rubygems'
require 'daemons'

Daemons.run(File.join(File.dirname(__FILE__), 'myscript.rb'),
    :dir => "../log",
    :dir_mode => :script,
    :backtrace => true,
    :monitor => true
)

Nothing special needs to be done in myscript.rb but you must keep in mind that all output will be lost. To log it, you might do something like this before sending any output to STDOUT:

$stdout.reopen(File.open("#{RAILS_ROOT}/log/agent.log", "w"))
$stderr.reopen(File.open("#{RAILS_ROOT}/log/agent.err", "w"))

Rails Migrations with Fixtures 4

Posted by Tadman Tue, 14 Nov 2006 22:51:00 GMT

When creating a model, in addition to creating the base class, Rails will create a fixture file in YAML format and a very rough migration class. In most cases you will want to import some data at first, for example, to create the “Administrator” user.

Here’s a simple way to automatically load in the fixtures within the migration file:

require 'active_record/fixtures'

class CreateMyModel < ActiveRecord::Migration
    def self.up
        begin
            ActiveRecord::Base.transaction do
                create_table :my_models do |t|
                    t.column :name, :string
                    # ...
                end

                Fixtures.create_fixtures("test/fixtures", :my_models)
            end
        rescue
            self.down
            raise
        end
    end

    def self.down
        drop_table : my_models if (MyModel.table_exists?)
    end
end

Note that if there’s an error in your YAML file you’ll get a failure that might not make any sense. I spent a good fifteen minutes trying to track down a problem that was related to having an “empty” entry. In this case I had a value that looked like:

test: # ...

With no data in there, the Fixture instance was being given ‘nil’ instead of some data and threw an exception as a result. I think it’s valid YAML, but not valid enough for Fixture.

Quick Ruby on Rails SQL Shell Tool 25

Posted by Tadman Tue, 10 Oct 2006 20:30:00 GMT

This is a simple Ruby script that I put together to launch a MySQL shell for a rails application. It looks within the current directory and parent directories for a database.yml configuration file to read.

#!/usr/bin/env ruby

require 'yaml'
require 'optparse'

config = nil

path = "."
while (File.exists?(path) and (path.length < 255) and !config)
        if (File.exists?("#{path}/config/database.yml"))
                File.open("#{path}/config/database.yml") do |f|
                        config = YAML.load(f)
                end
        else
                path = "../#{path}"
        end
end

unless (config)
        puts "Could not find database.yml"
        exit(-1)
end

command = "mysql"
verbose = false

opts = OptionParser.new
opts.on("-v", "--verbose") { verbose = true }
opts.on("-d", "--dump") { command = "mysqldump" }

args = opts.parse(*ARGV)

environ = (args[0] or ENV['RAILS_ENV'] or 'development')

if (config and config[environ])
        config = config[environ]

        puts config.inspect if (verbose)

        exec(command,
                "--user=#{config['username']}",
                "--password=#{config['password']}",
                "--host=#{config['host']}",
                "--port=#{config['port'] or 3306}",
                config['database']
        )
elsif (config)
        print "Could not find environment #{environ} in configuration file\n"
        exit(-2)
else
        print "Could not find or read Rails configuration file\n"
        exit(-1)
end

For example, to save a database backup:

% cd svn/myapp/db
% rsql --dump > dev.sql

Ruby on Rails Migrations 3

Posted by Tadman Tue, 10 Oct 2006 18:50:00 GMT

Although I’m a big fan of the concept of migrations, one of the things I’m not especially enamoured with is the way Rails implements them. Each migration is an atomic thing that proceeds, in specific sequence, from others. There’s no room for branching or conditions.

When I’m working in a group development environment I might be producing code that requires a migration alongside another developer doing much the same. Without some degree of co-ordination regarding migration numbers, this will cause a conflict, even if the actual migrations themselves are entirely independent..

It would be nice if the migrations had not only some kind of sequence identifier number but a prerequisites column so that two could share a number and yet be applied correctly.

I wonder if developing an improved migrations system would yield any tangible benefits.

Mongrel Rails Launcher 4

Posted by Tadman Mon, 09 Oct 2006 04:01:00 GMT

Here’s a quick init.d-type script to launch Mongrel with the appropriate options:

#!/bin/sh

case "$1" in
        start)
                mongrel_rails start -d -e demo -p 4101 -P log/mongrel-dev.pid
                ;;
        stop)
                mongrel_rails stop -P log/mongrel-dev.pid
                ;;
        *)
                echo $"Invalid option"
                exit 1
esac

This is useful if there’s more than one instance of mongrel running in a particular directory.