As Rails is built up of many modular components, these components can be used individually just as they can be used as a framework. Here we will see how the pieces that make up Rails can be used in other Ruby code. We will walk through two modular components of Rails, ActiveRecord and ActionMailer, and see how to use them in standalone applications.
ActiveRecord is perhaps the easiest component to
decouple from the rest of Rails, as it fulfills a purpose
(object-relational mapping) that can be used in many different places.
The basic procedure for loading ActiveRecord is simple; just define
the connection, and then create the classes that inherit from ActiveRecord::Base:
require 'rubygems' require 'active_record' ActiveRecord::Base.establish_connection( # connection hash ) class Something < ActiveRecord::Base # DB table: somethings end
The establish_connection
function takes a hash of parameters needed to set up the connection.
This hash is the same one that is loaded from
database.yml when using Rails, so you could just
pick up that file and load it:
require 'yaml' # Ruby standard library ActiveRecord::Base.establish_connection(YAML.load_file('database.yml'))
If you are used to the features of edge Rails, you may not want to stick with the latest gem version of ActiveRecord. To use the latest edge, first check out ActiveRecord's trunk from Subversion:
$ svn co http://svn.rubyonrails.org/rails/trunk/activerecord \ vendor/activerecord
Then, just require the active_record.rb file from that directory:
require 'vendor/activerecord/lib/active_record'
ActiveRecord can be a useful tool to load data into and extract data from databases. It can be used for anything from one-off migration scripts to hourly data transformation jobs. The following is a representative example, using James Edward Gray II's FasterCSV library:
require 'rubygems' require 'fastercsv' # gem install fastercsv require 'active_record' # Set up AR connection and define User class ActiveRecord::Base.establish_connection( # (connection spec)... ) class User # The table we're importing into doesn't use Rails conventions, # so we'll override some defaults. set_table_name 'user' set_primary_key 'userid' end FasterCSV.foreach('users.csv', :headers => true) do |row| # The CSV header fields correspond to the database column names, # so we can do this directly, with no mapping. User.create! row.to_hash end
ActiveRecord's migration methods can be used as a
portable abstraction for SQL data definition language (DDL). The
ActiveRecord::Schema.define
function allows you to use the ActiveRecord schema definition
statements within a block to perform operations on a database. The
full set of DDL operations is documented in ActiveRecord::ConnectionAdapters::SchemaStatements
.
require 'rubygems' require 'active_record' ActiveRecord::Base.establish_connection( # (connection spec)... ) ActiveRecord::Schema.define do create_table :sites do |t| t.string :name, :null => false t.string :city t.string :state end add_column :users, :site_id, :integer add_index :users, :site_id end
Often, a console or desktop application needs to store
persistent data, whether it be preference data or application data
itself. A common solution is to use YAML, which can marshal and unmarshal most Ruby objects (round trip), while also
being human-readable. However, YAML is verbose compared to binary
data formats, which may be an issue when storing larger amounts of
data. SOAP::Marshal
from Ruby's
standard library is similar; it can serialize objects into an (often
quite verbose) XML representation. This approach has similar
benefits and drawbacks to YAML.
Another option is to use Ruby's Marshal
module, which dumps Ruby objects
into a more concise byte stream. This uses less space, but it can be
brittle. Though efforts are made to maintain backward compatibility
across major Ruby versions, Ruby 1.9 has a new Marshal format that
is not completely interoperable with Ruby 1.8.
For a more structured approach to persistent data storage, SQLite and ActiveRecord can provide a helpful balance. The data schema must be defined first and acted upon by a constrained set of operations (those permitted by SQL DML). But these constraints pay off; as the data store is completely separated from the application, the two halves can evolve separately. There is no need to recode data when an application is upgraded, save for application-level data changes.
Using ActiveRecord for this purpose is simple; just open a connection to a SQLite file (which will be created if it does not exist), and define the appropriate ActiveRecord classes.
require 'rubygems' require 'active_record' ActiveRecord::Base.establish_connection( :adapter => :sqlite3, :database => "db.sqlite3" ) class Client < ActiveRecord::Base has_many :conversations end class Conversation < ActiveRecord::Base belongs_to :client end # Sample usage: def time_log Client.find_all_by_active(true).each do |client| # this uses ActiveRecord::Calculations to grab the sum # in one SQL query hours = client.conversations.sum('hours') # format string gives us a nice table: # First Client 5.00 # Another Client 12.40 printf "%-20s%5.2f", client.name, hours end end
ActiveRecord can be used as a library in any Ruby application, and it is great for rapidly prototyping simple interfaces to a database. The database interface can be self-contained, which makes it easy to integrate with existing applications; it will coexist with other libraries such as Ruby-DBI.
The rapid prototyping aspect is key; ActiveRecord provides a consistent interface to many database management systems, and you can use this interface to abstract away the database details while building an application. An application can theoretically be developed on a laptop with SQLite and deployed on a big-iron server running Oracle (in practice, this is not a perfect transition, but it is somewhat easier than working with the individual database libraries).
Gregory Brown wrote an article that walks through the process of building a to-do list console application from the ground up with Ruby and ActiveRecord, without using code generation. The article is available from http://www.oreillynet.com/pub/a/ruby/2007/06/21/how-to-build-simple-console-apps-with-ruby-and-activerecord.html.
Using ActionMailer to send emails from outside of Rails is a simple process as well. It requires slightly more configuration, but not by much. First, load the framework, either using RubyGems or a newer Subversion checkout:
# gem version require 'rubygems' require 'action_mailer' # or edge version require 'vendor/actionmailer/lib/action_mailer'
Next, set the outgoing mail server settings. All settings are optional.
# Default is :smtp; also accepts :sendmail or :test ActionMailer::Base.delivery_method = :smtp ActionMailer::Base.server_settings = { :address => 'localhost', :port => 25, :domain => 'example.com', # HELO example.com :authentication => :cram_md5, :user_name => 'me', :password => 'secret' }
ActionMailer needs a template directory in which to look for email templates:
ActionMailer::Base.template_root = 'views'
Mailer classes and their email templates are defined just as they are in Rails:
class Mailer < ActionMailer::Base def quota_exceeded_notification(user) from "System Administrator <root@example.com>" recipients name_and_email(user) subject "Your account is over the quota" body {:user => user} end private # "John Smith <jsmith@example.com>" def name_and_email(user) "#{user.full_name} <#{user.email}>" end end
The template follows the usual pattern, and is located under our template root, in views/mailer/quota_exceeded_notification.erb:
Dear <%= @user.name %>, Your account is over its storage quota. You are currently using <%= human_size(user.storage_used) %>, and your limit is <%= human_size(user.account.quota) %>. Please reduce your usage within 5 days or we will reduce it for you. Regards, The Management
Now, this Mailer class can be used just as if it were inside a Rails application. We'll look at one possible application for this next.
Rake is best known in Rails for its purposes in testing. Rake is used to kick off Rails tests, but also to perform administrative functionality (database maintenance and migrations, managing temporary files and sessions, and the like). We can easily extend Rake to handle any application-specific maintenance we need to do; in this case, to find users who are over their quota and send them a nasty email. (For simplicity, we will abstract away some of the details of finding those users.)
Here is a custom Rakefile that provides the email functionality:
require 'rake' # Mailer setup commands from above require 'mailer_config' # ActiveRecord setup, not shown require 'ar_users' # User administration tasks go in a separate namespace namespace :users do desc "Send a nasty email to over-quota users" task :send_quota_warnings do users = User.find(:all).select{|u| u.storage_used > u.account.quota } users.each do |user| Mailer.deliver_quota_exceeded_notification(user) end end end
We can now kick off the email with one command from the project's directory:
rake users:send_quota_warnings
The ActionMailer documentation includes instructions on how to receive incoming email using Rails and ActionMailer. This method involves having your MTA pipe incoming mail to a command such as this:
script/runner 'Mailer.receive(STDIN.read)'
Do not do this except in the absolute simplest of cases. Using
script/runner
is very
computationally expensive, because it loads the entire Rails
environment on each invocation. Loading a Ruby interpreter with all
of Rails for each incoming email is ridiculous.
The standard method for processing incoming mail is to batch
email in a mailbox and have a process retrieve that mail at a
regular interval. The mail can be retrieved using Net::POP3 or Net::IMAP
from the Ruby
standard library.
If mail really needs to be processed immediately upon receipt, a custom solution, even using Ruby and ActionMailer, will still be much faster than the preceding example that loads all of Rails. But if you need immediate delivery, you should probably first consider a solution like SMS or Jabber rather than SMTP.