今さらRails3メモ - その1 : DB操作、Model確認周りを中心に -

前回をその0とします。正直、盛り込みすぎました。

railsコマンド

  • rails コマンドはものすごくよく使うので alias r=rails するらしい
  • rails console が便利

特に Model の動作を確認するのに rails console が便利。要するに rails 環境が丸ごと載った irb だと思う。上の alias と合わせて

r c

で呼べるようにしておく。またこの console 上で

reload!

すると model などの変更に追随できる。

ただし、文字通り reload するだけなので新たに追加されたものは反映されない。

  • rails server でサーバを起こす

凝った使い方はしたことがない。普通に port 3000 で WEBRick を使ってる。

-u --debugger

オプションはのちのち便利。

development のうちは基本的にファイルの変更には自動で追随してくれるんだけど、

config/locals/ 以下のファイルが増減したときには再起動しないとダメ

なのはちょっと忘れがち。他にもそういうのあるかもしれないけど、ちょっとよく分かってない。とりあえず「あれー反映されてないな?」と思ったら再起動オススメ。

  • rails generate はやたら使う

だから

r g

で呼び出す。model, helper, migration, 他のツールなどかなりのものがここから作業を開始する。

  • rails dbconsole は適切な設定で sqlite コマンドや psql コマンドなどを叩いてくれる

Model からではなく生の DB の様子を確認するのに使う。こうしたツールに不慣れな人はあまり嬉しい機能じゃないかな。SQLite くらいなら GUI のツールが充実してるので development の間はそれで押すのも手かも。

最近自分は Rinari の rinari-sql を経由して使ってることが多いかも。

rvm で複数の ruby を入れている場合

あんまりこんなことやらないと思うけど、複数の ruby を入れて検証を行う場合、

  1. rvm use で ruby の切り替え
  2. bundle install
  3. source ~/.zshrc ( bash でもなんでも )

が必要。最後の source をやらないと alias の r から呼び出される rails コマンドがそれまで使っていた ruby で動くのでややこしくなる。

rvm wrapper を使えばもしかしたら解決する問題かもしれないけど、やったことないので分からない。

rakeタスク

とりあえず rake -T db の部分。

  • create, drop, migrate, rollback は(端折って)いいよね
  • db:fixtures:load で fixture ファイルを読み込める

基本的に fixtures は test 環境で利用するものでパスも

test/fixtures/

以下になっている(デフォルト)。これを development 環境で読み込むのに

rake db:fixtures:load

が使える。ただし、対象のテーブルにそれまで入っていたデータはクリアされる。

  • db:setup が便利

基本は db:migrate でいいんだけど、何回もやり直してるうちに一度きれいにして初期データを入れ直したいと思うことがある。そんなとき setup が便利。

datebase.rake の中で

 task :setup => [ 'db:create', 'db:schema:load', 'db:seed' ]

と定義されているので、rake db:setup だけで、create して

db/schema.rb
db/seeds.rb

を実行してくれる。

てなことを2ヶ月前に Twitter で教えてもらっていた。

Twitter / @ゆーけー/赤松 祐希: @wtnabe rake db:setup でdb: …

db/seeds.rb

seeds.rb は Model 作って自分で save しろということらしい。少なくとも rails new したばかりの db/seeds.rb には何も書かれていない。そこでこんなものを作ってみた。

# -*- mode: ruby; coding: utf-8 -*-
class SeedImporter
def initialize
@model = nil
@with_id = false
@dragonfly_fields = []
@paperclip_fields = []
end
def run
Dir.glob( File.join( seed_dir, '**/*.{yml,csv}' ) ).sort.each { |seed|
send( "import_#{File.extname( seed )[1..-1]}_seed", seed )
}
end
def seed_dir
ENV['SEED_DIR'] || File.dirname(__FILE__) + '/seeds'
end
def fcsv_opts
{ :headers => true,
:header_converters => :downcase }
end
def import_csv_seed( file )
init_model( file, '.csv' )
FasterCSV.table( file, fcsv_opts ) { |csv|
@with_id = csv.headers.include?( 'id' )
}
if ( @with_id )
_import_csv( file ) { |row| create_and_save_with_id( row.to_hash ) }
else
_import_csv( file ) { |row| create_and_save( row.to_hash ) }
end
end
def _import_csv( file, &block )
FasterCSV.open( file, fcsv_opts ).each { |csv| block.call( csv ) }
end
def import_yml_seed( file )
init_model( file, '.yml' )
YAML.load_file( file ).each_pair { |k, v|
if ( v.has_key?( 'id' ) )
create_and_save_with_id( v )
else
create_and_save( v )
end
}
end
def to_model( file, suffix )
File.basename( file, suffix ).sub( /\A[0-9]+(_\.)?/, '' ).classify
end
def init_model( file, suffix )
@model = Object.const_get( to_model( file, suffix ) )
init_file_fields
end
def create_and_save( data )
save( create( data ) )
end
def create_and_save_with_id( data )
record = create( data )
record['id'] = data['id']
save( record )
end
def create( data )
attach_file_when_need( data )
end
def attach_file_when_need( data )
record = @model.new( data )
if ( @dragonfly_fields.size > 0 )
@dragonfly_fields.each { |e|
col = e.to_s
file = data[e] || data[e.to_s]
path = File.join( seed_dir, file )
record.send( "#{col}=",
open( path ).read ) if file and File.exist?( path )
}
end
if ( @paperclip_fields.size > 0 )
@paperclip_fields.each { |e|
col = e.to_s
file = data[e] || data[e.to_s]
path = File.join( seed_dir, file )
record.send( "#{col}=",
open( path ) ) if file and File.exist?( path )
}
end
record
end
def save( record )
begin
record.save!
rescue => e
p record
p record.errors
raise e
end
end
def init_file_fields
if ( defined? ::Dragonfly )
@dragonfly_fields =
if @model.respond_to? :dragonfly_apps_for_attributes # v0.8
@model.dragonfly_apps_for_attributes.keys
elsif @model.respond_to? :dragonfly_attachment_classes # v0.9
@model.dragonfly_attachment_classes.map { |c|
c.attribute
}
end
end
if ( defined? ::Paperclip )
@paperclip_fields = @model.attachment_definitions.keys if @model.attachment_definitions
end
end
end
SeedImporter.new.run
view raw seeds.rb hosted with ❤ by GitHub

結構便利。

cf.

Twitter / @Dai Akatsuka: @wtnabe Model.create(:id = …

fixtureのパスはデフォルトの方に合わせた方がいいかも?

  • 基本は test/fixtures/ 以下
  • rspec の基本は spec/fixtures/ 以下
  • rspec の設定は spec_helper.rb 内で変更可能

Rails 全体の設定をする場所はたぶんなくて、RAILS_ENV と FIXTURES_PATH などの環境変数経由で設定する感じっぽい。Rails の環境設定の外で指定するのはちょっと再現性に難があるので、rspec の方で

 config.fixture_path = "#{::Rails.root}/test/fixtures"

して設定を変えておいた方が楽な気がする。例えば前述の

rake db:fixtures:load

test/fixtures/

以下を見にいく。

ところで fixture は Model の制約を無視して DB に直接データを INSERT INTO してるみたい。ということは Model の制約を無視した(DBMS の制約にだけ従う)データを突っ込むことができる。ちょっと注意が必要かも。

More