Halcyon

Notice: The website is currently being udpated. Sorry for any inconvenience.

Connecting to Databases

Although Halcyon doesn’t come with any ORM-specific plumbing, getting connected
and building database-centric Halcyon applications is trivial. Well, it’s
certainly not impossible.

As of Halcyon’s 0.5.0 Release (which, as of this writing, will be released any
day now), connecting to databases is surprisingly easy, but requires some
effort. Getting connected to a database is made of a few essential steps:
loading the database configuration, connecting to the database, and,
optionally, loading all models as well as hooking up migrations.

The instructions provided here will be focused on a
Sequel system, but the instructions
should still be relevant for most systems, including
DataMapper and ActiveRecord.

But First

One tiny detail to go ahead and address is that you will need to require the
appropriate ORM library, which can be done from
config/init/requires.rb. You can get by with something like this:

1 ## config/init/requires.rb
2 %w(sample_app sequel).each{|dep|require dep}

Don’t mind the unfamiliar syntax (if, indeed, this is unfamiliar), we’re simply
creating an array of strings separated by spaces and then requiring each
dependency programmatically.

Go ahead and require the sample_app file (which is located in
lib/sample_app.rb file), or, specifically, whatever your
application’s primary module located in lib/, we’ll put additional
functionality here as well as storing the current instance of the database
connection.

Load Database Configuration

Presently, Halcyon doesn’t have any explicit location to load the database, but
it does provide a mechanism for injecting into the initialization process. This
is done by creating a file in the config/init/ folder. This file
will look something like this:

1 ## config/init/database.rb
2 Halcyon.db = Halcyon::Runner.load_config(Halcyon.paths[:config]/'database.yml')
3 Halcyon.db = Halcyon.db[(Halcyon.environment || :development).to_sym]

What’s above appears fairly verbose, but what’s happening is that the file,
located at Halcyon.root/'config'/'database.yml' (which gets
expanded to the application directory’s configuration folder) is getting parsed
by the framework configuration loader (through YAML), processed (through Mash),
and then saved into Halcyon.db which gets mapped to
Halcyon.config[:db]. (This is a common abstraction mechanism for
Halcyon configuration values.)

We’re also telling it to select the current application runtime environment’s
database configuration. Let’s assume a somewhat standard database configuration
file:

 1 ---
 2 ## config/database.yml
 3 development: &defaults
 4   adapter: mysql
 5   database: sample_development
 6   username: sample_user
 7   password: sample_password
 8   host: localhost
 9 
10 test:
11   <<: *defaults
12   database: sample_test
13 
14 production:
15   <<: *defaults
16   database: sample_production

This should look familiar, and the unfamiliar parts should at least be
comprehendible.

Connecting

Once the database configuration has been loaded, we will need to actually
connect to the database in question. We will put this in the
config/init/hooks.rb file since we want this to happen once the
application has been fully initialized with all of its necessary requirements
preloaded.

Go ahead and add this to your startup hook:

1 ## config/init/hooks.rb, in Halcyon::Application.startup block
2 # Connect to DB
3 SampleApp::DB = Sequel.connect(Halcyon.db)
4 SampleApp::DB.logger = Halcyon.logger if $DEBUG
5 logger.info 'Connected to Database'

Note: Be sure to put this inside of the
Halcyon::Application.startup code block. This is not demonstrated
explicitly above, but is essential.

Sequel.connect is specific to the Sequel library, but it is fairly
obvious what purpose it servers. We store the result, an instance of a
connection, as SampleApp::DB to have a common location from which
to refer to the connection, particularly under the SampleApp module
to signify its tight bond to the application domain. We also set the logger
instance to the current, app-wide logger if the $DEBUG flag is set.

While we’re at it, let’s go ahead and load any models we may have stored in
app/models/, the unofficial default location for application
models. Put this directly below the code listed above inside of
config/init/hooks.rb:

1 ## config/init/hooks.rb, in Halcyon::Application.startup block
2 # Load Models
3 Dir.glob([Halcyon.paths[:model]/'*.rb']).each do |model|
4   logger.debug "Load: #{File.basename(model).chomp('.rb').camel_case} Model" if require model
5 end

The above code simply requires each model file found within the model directory,
printing out a debugging message if it succeeds. Any failure should normally
result in application startup failing (which is expected).

If you have questions about how to create models with the Sequel ORM, refer to
their excellent
Sequel Models wiki page
which contains simple instructions for writing your models.

Step One: Done

And that, in turn, connects your application to a database.

Step Two: Polish

Now, to simplify using this database system, you may be interested in a few
conventional portions of code that will simplify your life, particularly with
keeping track of your database schema with migrations.

Migrations

Most developers interested in Halcyon should be familiar with migrations already
since many come from Rails or more modern frameworks like Merb et al, so we
won’t go over them. However, we will learn about the conventions used to
organize and load migrations.

Migrations are often stored in lib/migrations with the standard
001_create_records.rb naming structure. You can find out the
specific migration syntax to use from the Sequel wiki, linked above, or from the
WeeDB sample application, linked at the bottom of this page.

Now, to make your application use migrations is the fun part. In the section
labelled for custom Rake tasks in the application Rakefile, place
this code:

 1 ## Rakefile
 2 desc "Load up the application environment"
 3 task :env do
 4   $log = ''
 5   $logger = Logger.new(StringIO.new($log))
 6   Halcyon.config = {:logger => $logger,
 7     :environment => (ENV['HALCYON_ENV'] || ENV['ENV'] || :development).to_sym}
 8   Halcyon::Runner.new
 9 end
10 
11 namespace(:db) do
12   desc "Migrate the database to the latest version"
13   task :migrate => :env do
14     current = Sequel::Migrator.get_current_migration_version(SampleApp::DB)
15     latest = Sequel::Migrator.apply(SampleApp::DB, Halcyon.paths[:lib]/'migrations')
16     puts "Database successfully migrated to latest version (#{latest})." if current < latest
17     puts "Migrations finished successfully."
18   end
19 end

The first task, though seemingly unneeded, goes through and loads the full
application environment, including the instance of the database connection. This
is used to check the current migration version and also to apply the migrations
to if necessary. It also hides any normal debugger output, though it keeps it
saved for when it’s necessary.

The second task in the db namespace then executes the migrations if
in fact they are out of date.

Putting these tasks into the Rakefile opens up more in the future
and prevents cluttering up more of the application loading process. Also, it’s
at least somewhat familiar because we can run a command like so:


$ rake db:migrate

Step Three: Usage

Accessing data from the database an be trivial, depending on whether you like to
use the models you’ve defined. If you’ve created your models already, you
probably have already read the documentation for accessing rows with the models.
Refer there again if you have questions. And, of course, there’s always the
WeeDB sample app with actual code for connecting to and manipulating the
database inside of a Halcyon app, specifically in the Records
controller.

Conclusion

Hope this helps you get familiar with how to start using databases with your
Halcyon applications sooner than ever.

If you’re looking for a good example of this code in action, check out the WeeDB
sample application located at the GitHUB
repository
which is
where most of this code was pulled from.