Related tags
Finder for Workflow States
Now a days for all my Finite State Machine needs on Ruby on Rails I use geekq's workflow. I find it much better to use than the previous popular Finite State Machine plugin on Rails, acts_as_state_machine.
named_scope
I need to locate all the models that are of a certain state, and thanks to named_scope. This can be done quite easily. Except when we have a lot of states I'd rather not write a named_scope for each individual state. For example:
class Order < ActiveRecord::Base
named_scope :completed, :conditions => { :workflow_state => 'completed' }
end
It would be encumbersome to write this multiple times. I'd rather have a named_scope created for each state. This is my quick hack.
class Order < ActiveRecord::Base
include Workflow
workflow do
state :new do
...
end
...
end
(self.workflow_spec.states.keys - [:new]).each do |state|
named_scope state, :conditions => { :workflow_state => state.to_s }
end
end
After the workflow code you place you write:
(self.workflow_spec.states.keys - [:new]).each do |state|
named_scope state, :conditions => { :workflow_state => state.to_s }
end
This iterates through each of our workflow states, and creates a named_scope for each one based on the name of the state. So you can easily do Order.completed and find all completed Orders.
Note: I had to remove :new (on line 12) from the array of keys as it would cause problems with my code. This is caused from overwriting the new method. You can solve this by probably putting a prefix for the named_scope name.
I know it can be extracted to use alias_method_chain and modify the Workflow sourcecode, but I enjoy my hack.
Fixtures Without Rails
So continuing my series of ActiveRecord Without Rails, I thought I would inspect another popular feature of ActiveRecord, Fixtures.
Fixtures are a great way to populate your database with an expected result set. It makes it easy to prepare your database for testing and presentation to your client.
Why would you do this? Well you've already got ActiveRecord without Rails working, and probably are using the Migrations without Rails. So why not continue with fixtures?
Note:
- I won't go into getting ActiveRecord working without Rails, please refer to that post. It's dead simple.
- All these commands are executed from the root directory of your non-rails application.
Creating the Fixtures Directory
The fixtures will need to be stored somewhere, so we'll just create the necessary directory for them to enjoy a rather peaceful life.
mkdir -p test/fixtures
Setting up the Rakefile
Now we'll need a Rakefile. If your not familiar with Rakefiles just create a file called: Rakefile 
Note:
- If you were following my ActiveRecord Migrations Without Rails, this is the same
Rakefile. I've changed the default task to execute to load the fixtures instead. You can opt to continue using themigratetask if you want. Just remember to call eitherrake migrateorrake fixtures, depending on which task you would like call, that isn't your default task. - If you were not following my ActiveRecord Migrations Without Rails, you can ignore the migrate task.
require 'active_record'
require 'active_record/fixtures'
require 'yaml'
require 'erb'
task :default => :fixtures # Your choice
desc "Load fixtures into the current database. Load specific fixtures using FIXTURES=x,y"
task :fixtures => :environment do
fixtures = ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir.glob(File.join(File.dirname(__FILE__), 'test', 'fixtures', '*.{yml,csv}'))
fixtures.each do |fixture_file|
Fixtures.create_fixtures('test/fixtures', File.basename(fixture_file, '.*'))
end
end
desc "Migrate the database through scripts in db/migrate. Target specific version with VERSION=x"
task :migrate => :environment do
ActiveRecord::Migrator.migrate('db/migrate', ENV["VERSION"] ? ENV["VERSION"].to_i : nil )
end
task :environment do
ActiveRecord::Base.establish_connection(YAML::load(File.open('database.yml')))
ActiveRecord::Base.logger = Logger.new(File.open('database.log', 'a'))
end
Your First Fixture
Now we'll create our first fixture, and save it into test/fixtures/users.yml
. The filename corresponds to the table for where the fixtures will be loaded into.
aizatto: name: Ezwan Aizat Bin Abdullah Faiz
Warning:
- Loading fixtures is destructive. It will destroy everything in the table.
- This task loads all fixtures into the database. We'll cover specifying individual fixtures later.
Now the fixture is ready for loading and we execute:
rake
Loading Specific Fixtures
By default all fixtures are loaded into the database. Rather than loading all the fixtures, you can also specify which fixtures you want to load by appending them to a FIXTURES argument.
rake FIXTURES=users,roles
Embedded Ruby in Fixtures
In the same way like Rails, you can also continue to embed ruby inside your fixtures, giving you more flexibility to your hour models will be loaded.
aizatto: name: Ezwan Aizat Bin Abdullah Faiz created_at: <%= 1.day.ago %>
Conclusion
So by now you've probably accumulated quite a bit of knowledge in using ActiveRecord and its features outside of Rails.
So lets hear it, what else would you like to see?
ActiveRecord Migrations Without Rails
Continuing exploring ActiveRecord without Rails, I thought I'd look into using Migrations without Rails.
Once again, why?
- You already are using ActiveRecord without Rails
- Versionable instances of how your database looked like
- Database agnostic
- You forget how to crack out SQL code (happens to me, maybe too much ORM)
- You want migrations in non Ruby Projects
- boredom
One thing I'd like to emphasize is that you don't have to use Migrations for Ruby Projects. You can use them in non Ruby projects as well. Just use them to create a versioned of the database.
By using Migrations, your (Ruby and non Ruby) developers will appreciate that they can easily see what changes were made to the database.
Note:
- I won't go into getting ActiveRecord working without Rails, please refer to that post. It's dead simple.
- All these commands are executed from the root directory of your non-rails application.
Creating the Migrations Directory
First we need a directory where the migrations will be stored, in this case I'll use the name 'db/migrate
'
mkdir -p db/migrate
Now we'll create our first migration, and save it as db/migrate/001_create_users.rb
:
class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.column :name, :string, :null => false
end
end
def self.down
drop_table :users
end
end
Wonderful
Warning:
- The numbers prefixed to the migration file's name (in this case 001) are very important. DO NOT OMMIT THIS.
- Notice the class name is CreateUsers, this should correspond to a camel case representation of the filename.
Setting up the Rakefile
Now we'll need a Rakefile. If your not familiar with Rakefiles just create a file called: Rakefile 
require 'active_record'
require 'yaml'
task :default => :migrate
desc "Migrate the database through scripts in db/migrate. Target specific version with VERSION=x"
task :migrate => :environment do
ActiveRecord::Migrator.migrate('db/migrate', ENV["VERSION"] ? ENV["VERSION"].to_i : nil )
end
task :environment do
ActiveRecord::Base.establish_connection(YAML::load(File.open('database.yml')))
ActiveRecord::Base.logger = Logger.new(File.open('database.log', 'a'))
end
The :environment task sets up the ActiveRecord configurations, configure as desired.
Your First Migration
Executing the migration is dead easy:
rake
If you chose to log the output you'd get something like this:
SQL (0.004480) CREATE TABLE schema_info (version integer)
SQL (0.001514) INSERT INTO schema_info (version) VALUES(0)
SQL (0.000000) SQLite::Exceptions::SQLException: table schema_info already exists: CREATE TABLE schema_info (version integer)
SQL (0.000246) SELECT version FROM schema_info
Migrating to CreateUsers (1)
== CreateUsers: migrating =====================================================
-- create_table(:users)
SQL (0.002094) CREATE TABLE users ("id" INTEGER PRIMARY KEY NOT NULL, "name" varchar(255) NOT NULL)
-> 0.0026s
== CreateUsers: migrated (0.0028s) ============================================SQL (0.001499) UPDATE schema_info SET version = 1
Perfect
Note: I am using SQLite in this example, your results may come out differently.
Your Second Migration
Knowing that our first migration works, lets create a second migration and test it out!
Now let's create another migration: db/migrate/002_create_roles.rb 
class CreateRoles < ActiveRecord::Migration
def self.up
create_table :roles do |t|
t.column :name, :string, :null => false
end
end
def self.down
drop_table :roles
end
end
And when executing rake
, you should see the new table roles being created!
Wizard!
Moving Between Versions
At this stage we are at at version 2 of the database. But how do we go down to say version 0, before everything existed?
rake VERSION=0
What? You only want to go to version 1?
rake VERSION=1
Okay, enough showing off
Conclusion
That's it, your ready to use and enjoy ActiveRecord's Migrations Without Rails!
The Migration's have a ton of features which you can and should utilize, which including adding indexes, controlling verbosity, renaming tables, dropping tables, and modifying existing tables (adding, renaming, modifying, deleting columns).
Just a whole alot of features, do check out the API for it!
