Strange Symphonies Don’t worry, be happy

Related tags

21May/072

ActiveRecord Without Rails

Some of you must be wondering, I must be insane to use ActiveRecord outside of Rails. But there are some very compelling reasons.

  • ActiveRecord is an awesome library for Object Relational Mapping (ORM).
  • Other projects might not involve the use of Rails.
  • You want to experiment with ActiveRecord prior to creating a plugin for your Rails project.
  • A library for ORM is already written (why reinvent the wheel?)
  • Your just very bored.

Whatever the case may be, it's best to be prepared.

Installing ActiveRecord

If you already have Rails installed via RubyGems, then your already set to go. Else install it in the terminal via:

sudo gem install activerecord

Playing with ActiveRecord

Open up a file, and insert the following:

require 'rubygems'
require 'active_record'

ActiveRecord::Base.establish_connection(
  :adapter  => 'mysql',
  :database => 'database',
  :username => 'user',
  :password => 'password',
  :host     => 'localhost')

Now if you are familiar with Rails, this is the same configuration as seen in your database.yml file ( RAILS_ROOT/config/database.yml File). There are more options for how to connect to a database, just check the ActiveRecord::Base.establish_connection documentation.

Moving Database Configuration to Another File

Note: It's not safe to save your database configuration in the same file as you code. In case you distribute your code, and forget to remove your password. That wouldn't be a cool scenario. (Well it would also be pointless if you sent your database configuration file as well).

So lets make a database.yml and dump the information in there.

adapter: mysql
database:  database
username: user
password: password
host:      localhost

Now our previous file that utilizes ActiveRecord will look like such:

require 'rubygems'
require 'active_record'
require 'yaml'

dbconfig = YAML::load(File.open('database.yml'))
ActiveRecord::Base.establish_connection(dbconfig)

Playing with ActiveRecord Safely!

And now your set to playing around with ActiveRecord without Rails!

require 'rubygems'
require 'active_record'
require 'yaml'

dbconfig = YAML::load(File.open('database.yml'))
ActiveRecord::Base.establish_connection(dbconfig)

class User < ActiveRecord::Base
end

puts User.count
# 6

Debugging ActiveRecord SQL Queries

Sometimes you run into problems, and suspect your SQL isn't generated as it's supposed to be. But how do we debug this?

require 'rubygems'
require 'active_record'
require 'yaml'
require 'logger'

dbconfig = YAML::load(File.open('database.yml'))
ActiveRecord::Base.establish_connection(dbconfig)
ActiveRecord::Base.logger = Logger.new(STDERR)

class User < ActiveRecord::Base
end

puts User.count
# SQL (0.000277)   SELECT count(*) AS count_all FROM users
# 6

This will output it to standard error (in which most cases its) your screen. Sadly this can also clutter your screen.

Debugging ActiveRecord SQL Queries to a File

Now if you know your shell well, you can pipe it to a file via:

ruby connect.rb 2&gt;&gt; database.log

But you may forget to pipe it, or what not.

So replace the line containing ActiveRecord::Base.logger with

ActiveRecord::Base.logger = Logger.new(File.open('database.log', 'a'))

Warning: I chose to append ('a') the output to the file. This may result in very large files. You can chose to use overwrite ('w') instead.

From here on you can watch the file with: tail -f database.log [image16x16:terminal].

Funky/Colorized Logs

Note: If you view the database.log file, you'll notice the following. This is because ActiveRecord colorizes output for viewing in a terminal. But when viewed as a normal file, you get garbage.

  ^[[4;36;1mSQL (0.000273)^[[0m   ^[[0;1mSELECT count(*) AS count_all FROM users ^[[0m

To disable colorize logging, enter:

ActiveRecord.colorize_logging = false
16May/073

ActiveRecord Associations Extensions

Here is a little known feature of ActiveRecord's Associations that rarely gets mentioned. Even though it has been documented, it rarely makes any news. I love this feature.

Basic Associations

So lets assume I have two models: Student and Assignment, where a Student has many Assignments, and thus the Assignment belongs to the Student.

class Student < ActiveRecord::Base
  has_many :assignments
end
class Assignment < ActiveRecord::Base
  belongs_to :student
end

Lets also assume that the Assignment has a field called 'state', indicating whether it is submitted, finished, hasn't started, procrastinating, or eaten by the dog.

The general way to find assignments of a particular state is to use the find method with a set of conditions.

student.assignments.find :all, :conditions => {:state => 'submitted' }
student.assignments.find :all, :conditions => ["state NOT IN (?, ?)", 'submitted', 'finished']

The first line, finds all submitted assignments, and the second one, all incomplete assignments.

But it's tedious to type this up each and every time we want to find this sort of assignments. Also you can make mistakes, and it makes your code very messy.

Extending Associations

So let's move these finder methods into the association itself. I present to you our new Student class!

class Student < ActiveRecord::Base
  has_many :assignments do
    def submitted
      find :all, :conditions => {:state => 'submitted'}
    end

    def incomplete
      find :all, :conditions => ["state NOT IN (?, ?)", 'submitted', 'finished']
    end
  end
end

And now we can find all submitted assignments and all incomplete assignments like such:

student.assignments.submitted
student.assignments.incomplete

Doesn't that just feel good?

But if you are like me, you'll keep adding more and more methods to the extension. Which will start making it once again, messy and difficult to read. Damn

Extending Associations into a Module

So once again we can extract it out, and this time store it into a module.

class Student < ActiveRecord::Base
  has_many :assignments, :extend => AssociationExtensions::Student
end
module AssociationExtensions
  module Student
    def submitted
      find :all, :conditions => {:state => 'submitted'}
    end

    def incomplete
      find :all, :conditions => ["state NOT IN (?, ?)", 'submitted', 'finished']
    end
  end
end

You can keep this file wherever you like, but just to keep things simple, I suggest leaving the extension in the model's file.

Note: The extension/module is not specific to the model. If other model's have the same needs, you can reuse this module.