今さら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 を入れて検証を行う場合、
- rvm use で ruby の切り替え
- bundle install
- 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 |
結構便利。
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 の制約にだけ従う)データを突っ込むことができる。ちょっと注意が必要かも。