Ruby on Rails: Override Attribute Assignment 1
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)
endCalling 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
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
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
endNote 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
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)
endFor example, to save a database backup:
% cd svn/myapp/db % rsql --dump > dev.sql
Ruby on Rails Migrations 3
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
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.
