前回はCGIをやったので、今回はCGIから一歩出てSinatraを使ったものをやろうと思ったのですが。

それはやめて、ActiveRecordをRailsではなくSinatraから使うことで、ほとんどコードを書かずにデータベース操作できるというところをやってみたいと思います。

今回は最初に「Bundler」というパッケージというかライブラリというかを利用するため、インストールからいきます。

Bundler


http://bundler.io/



rubyには「gem」というパッケージマネージャがあるのですが、こいつはあくまでもrubyのバージョンごとにパッケージを管理するものです。
「Bundler」がしてくれるのは、プロジェクトごとのパッケージ管理です。

そのほかにも便利なことがいくつもありますので、今回はこれを導入していきます。
手元の環境はcrubyの2.2.3、Mac OSXです。

環境を準備する



まず、なにはともあれBundlerを入れましょう。

gem install bundler


ちょっと待っていたらインストールが完了します。

そしたら次はディレクトリ(フォルダ)を作りましょう。方法は問いません。
できあがったら、そのフォルダ内へ移動します。
コマンドラインから操作して移動しましょうね。

移動してきたら、Bundlerの管理元を作ります。

bundle init


これで「Gemfile」というファイルが作成されます。
ここへ、今回利用するパッケージを並べていきます。

# A sample Gemfile
source "https://rubygems.org"

# gem "rails"
gem 'sinatra'
gem 'sinatra-contrib'
gem 'sqlite3'
gem 'activerecord'
gem 'sinatra-activerecord'
gem 'rake'

gem 'tux'


シャープ記号がコメントなのはrubyと同じようです。
一番上から、本題に利用するための「Sinatra」、次がリロード用のものですが今回のエントリでは触れません。
次がデータベース「SQLite3」を利用するためのパッケージです。
「SQLite3」はこのブログでも頻繁に登場する組み込みデータベースですね。
Windowsだとインストールすら不要でデータベース利用できるので、サラリーマン時代には大変お世話になりました。

それから、本題の2つめの「ActiveRecord」です。その次の「sinatra-acriverecord」は読んで字の如く、SinatraからActiveRecordを使いやすくするためのものです。

そのほか、本題に直接関係あるのが「rake」です。
これはタスクランナーでして、今回はデータベースのテーブル作成に利用します。

最後の「tux」ですが、こんな便利なものがあるとは…今回初めて知りました。

cldwalker/tux - Sinatra dressed for interactive ruby - a sinatra shell


https://github.com/cldwalker/tux



なんとSinatraを対話形式シェルで扱うことができるというものです。
Railsにそんなのがあるのは聞いたことがありましたが、なんでもありますね、rubyは…

さ、ここまで記述ができたらこれらをBundlerを利用して一括でインストールします。

bundler install --path vendor/bundle


ちょっとだけ時間がかかりますが、その間に。
これによって先ほど「Gemfile」に記述したパッケージ群が現在のプロジェクトフォルダ内の「vendor/bundle」配下へインストールされます。
便利ですね。ひとつずつインストールしなくてもいいのですから。

Sinatraからデータベースを操作する、しかも対話形式で



準備はもうすぐ完了します。
次は初めてコードを書きますよ。

と言っても、ほとんど書きません。

require 'bundler'
Bundler.require

module MyHandling
class Application < Sinatra::Base
configure do
register Sinatra::ActiveRecordExtension
set :database, {adapter: "sqlite3", database: "db/booking.db"}
end
end

class Book < ActiveRecord::Base
end
end


これは「app.rb」というファイルに記述しました。
現在のフォルダ内に保存しています。

一行目からいきますが、まずはBundlerを読み込んでいます。なぜかというと、「Gemfile」にあるパッケージをすべてrequireしたいからです。
それが二行目で、「Bundler.require」ですね。
この時点で私はマジかー…と思ってしまいました。
そんなに楽チンできるだなんて。

次からがモジュールの定義です。
モジュールの中には操作をするためのSinatraアプリケーションクラス「Application」とActiveRecordクラスの「Book」しか書いてありません。

しかも、「Book」については外側というかしかありません。
これでもActiveRecordを継承しているので、そこで定義されたものはすべて使うことができます。

「Application」クラスには「configure」というのがありまして、ここで「sinatra-activerecord」による拡張を登録しています。
これをしておくと、「set」でデータベース設定を記述することができます。
今回は「set :database」のほうを利用していますが、次のようにすればYAMLファイルを使った設定もできるようです。

set :database_file, "path/to/database.yml"


そしてデータベースファイルは「db」フォルダ内に「booking.db」という名前で作成することにしました。

では準備の最終段階です。
Rakeを使ってデータベースの作成やテーブルの作成をしていきたいと思います。

まずはRakeを利用するために「Rakefile」を作成します。

require 'sinatra/activerecord'
require 'sinatra/activerecord/rake'

require './app'


3行しかありません。最後のrequireはさきほど記述した「app.rb」です。

ではタスクを実行していきたいと思います。
作成した「Rakefile」に「sinatra/activerecord/rake」が記述されているため、ActiveRecordを操作するためのタスクが自動的に登録された形になります。

bundle exec rake -T


リポジトリのreadmeには以下のように記述されています。

rake db:create            # Create the database from DATABASE_URL or config/database.yml for the current Rails.env (use db:create:all to create all dbs in the config)
rake db:create_migration # Create a migration (parameters: NAME, VERSION)
rake db:drop # Drops the database using DATABASE_URL or the current Rails.env (use db:drop:all to drop all databases)
rake db:fixtures:load # Load fixtures into the current environment's database
rake db:migrate # Migrate the database (options: VERSION=x, VERBOSE=false)
rake db:migrate:status # Display status of migrations
rake db:rollback # Rolls the schema back to the previous version (specify steps w/ STEP=n)
rake db:schema:dump # Create a db/schema.rb file that can be portably used against any DB supported by AR
rake db:schema:load # Load a schema.rb file into the database
rake db:seed # Load the seed data from db/seeds.rb
rake db:setup # Create the database, load the schema, and initialize with the seed data (use db:reset to also drop the db first)
rake db:structure:dump # Dump the database structure to db/structure.sql
rake db:version # Retrieves the current schema version number


今回はとりあえずデータベース作成をするための「db:create_migration」タスク、それからテーブルを作成するための「db:migrate」タスクを使います。

なお、上記の例に英語で記述されているとおり、パラメータは大文字で記述します。
大文字でないと弾かれて実行できません。

では、データベース作成のためのタスクを実行してみます。
パラメータには「NAME」としてクラスの名前をつけます。「VERSION」は便宜上とりあえず「001」にしておきます。
タスクを実行すると、NAMEにつけた名前はキャメルケース(先頭1文字が大文字)に変換されてクラス名になります。

bundle exec rake db:create_migration NAME=create_books VERSION=001


このタスクによって、「db/migrate/001_create_books.rb」というファイルが作成されたと思います。

そうしたら、次はこの作成されたファイルにテーブルの記述を書いていきます。
class CreateBooks < ActiveRecord::Migration
def change
end
end


このchangeメソッド内にテーブル作成のための記述を追記してやればいいのです。

class CreateBooks < ActiveRecord::Migration
def change
create_table :books do |b|
b.string :bookname
b.string :content
b.timestamps
end
end
end


これが後ほど「db:migrate」タスク実行することで実行されてデータベース内のテーブルになります。
「id」という列がActiveRecordによって自動的に追加され、これがプライマリキーになります。
「timestamps」をメンバーに書いておくと、createatなど登録日時や更新日時の列を自動的に追加してくれます。
また、テーブル名は複数形の「books」にしてあります。

クラス名は「Book」で単数です。
これはクラスの実体であるオブジェクトが1つのレコードデータを表すためで、そのレコードの集合だからテーブル名は複数形になるわけです。

ここまでできたら準備はもう一息です!

bundle exec rake db:migrate


ここでもしもこんなエラーになっていたらデータベース接続設定が誤っていると思われます。

rake aborted!
ActiveRecord::ConnectionNotEstablished: No connection pool for ActiveRecord::Base
1 require 'bundler'


sqlite3コマンドをインストールしていれば、テーブルが作成されていることが見て取れると思います。

コマンドラインから対話形式にSinatraを操作する



それでは最後に「config.ru」ファイルを準備しましょう。

require './app'
run MyHandling::Application


これで全ての準備が完了です!

tuxを使って操作してみましょう。

bundle exec tux


うまくいったら内部でRackアプリケーション(SinatraもRailsもRackアプリケーションです)が起動してきます。

Loading development environment (Rack 1.3)
Bond has detected EditLine and may not work with it. See the README's Limitations section.
>>


ここへコードを直接入力していくと操作することができます。

まずはいわゆる「select * from books」をやってみます。

MyHandling::Books.all


この時点ではデータは何も返されないはずです。
レコードが存在しないためですね。

では次はレコードの登録をしましょう。

book1 = MyHandling::Books.new(:bookname=>'excel entries', :content=>'this is excel vba!')


これで再度「all」メソッドを呼ぶとなにやら表示されます。

=> #<MyHandling::Book id: nil, bookname: "excel entries", content: "this is excel vba!", created_at: nil, updated_at: nil>


でもちょっと変です。
idやcreated_atなどが「nil」です。
なぜでしょうか?当たり前と言えば当たり前なのですが、コミットしてないだけでなく、まだメモリ内にあるだけだからですね:)

データをデータベースに送ってコミットするには「save」メソッドを使います。

book1.save


これで再度見てみると、nilの部分が埋まった状態になります。

最後に



長々と書いてきましたが、個人的にrubyの苦手なところはココにあると思います。

ちょっとしたことをするために最小の労力でたどり着くことができるのですが、それらがどういう目的でそうしているのかが見えにくいのですよ。
だからこそ、長々と説明していくことになるわけです。

今回のエントリではできるだけ説明を書いていますが、それでも大幅にスキップしています。
「gem」とはなんなのか、なぜ「bundle exec」と前置きしないとダメなのか、「ActiveRecord」のタスクでなぜ最初にrubyファイルが作成されるのか、「config.ru」とはなんなのか、「Rackアプリケーション」とはなんなのか…

そしてActiveRecordを操作するときに「モジュール名::クラス名.メソッド名」でアクセスしているのはなぜなのか。

Railsがバージョン2の頃に初めて見たときの「ちょっとなに言ってるかわからない」感覚は今でも残っています。
これもrubyに再入門する動機のひとつかもしれません。

ということで、次回も長々となりそうですがお付き合いくださると幸いです。