tech-dig

新しい技術が見つかるブログ

rails generate で使われるコマンドと用途について

Rails には、機能を開発する上で雛形になるソースコードを自動生成するためのコマンドとして、 generate コマンドが用意されています。
非常に便利なコマンドではあるのですが、正しく使い分けないと不必要なソースコードが生成されて後から消す必要が出てきたりします。

generate でできることはたくさんありますが、ここでは主要と思われる用途に絞ってご紹介します。
(出力例では、テンプレートエンジンに slim、Javascript の記述に CoffeeScript、テストに RSpec を利用しています)

generate コマンドの基本形

基本的なコマンドのフォーマットは下記のようになっています。

Usage: rails generate GENERATOR [args] [options]

generate コマンドには g というショートカットが用意されており、下記のように実行できます。

rails g scaffold

また、dry-run などの便利なコマンドもあるので、ぜひ意識して使ってみてください。
オプションを忘れた場合は、rails g scaffold -hrails g model -h などで確認できます。

General options:
  -h, [--help]     # ヘルプを表示する
  -p, [--pretend]  # ドライランを実行する(ファイルは生成されない)
  -f, [--force]    # すでに生成予定のファイルが存在する場合に上書きする
  -s, [--skip]     # すでに生成予定のファイルが存在する場合にスキップする
  -q, [--quiet]    # 生成時のプロセスを標準出力しない

ちなみに生成予定のファイルがすでに存在するが -f-s を指定していなかった場合、下記のように対話的に確認されます。

Overwrite /path/to/file (enter "h" for help) [Ynaqdh]

generate コマンドの用途と種類

基本形についておさえたところで、generate コマンドでよく使われる scaffold, controller, model, migration の4つの用途に絞ってご紹介します。

一通りのCRUDを実現する (rails g scaffold)

Usage:
  rails generate scaffold NAME [field[:type][:index] field[:type][:index]] [options]
migration model routing controller view helper asset test

scaffold ("足場" の意)を使うと、モデル・ビュー・ルーティング・マイグレーション・コントローラーまで、テスト込みで一括でファイルを作成します。

例えば、

rails g scaffold User

を実行すると、ルーティングに resources :users が追加され、アプリケーションにおける基本的な機能である一覧(index)、詳細(show)、新規作成(new/create)、編集(edit/update)、削除(destroy) を実現するために必要なファイルが追加されます。

$ rails g scaffold User -p | grep invoke
      invoke  active_record
      invoke    rspec
      invoke  resource_route
      invoke  scaffold_controller
      invoke    slim
      invoke    rspec
      invoke      rspec
      invoke    helper
      invoke      rspec
      invoke    jbuilder
      invoke  assets
      invoke    coffee
      invoke    scss
      invoke  scss

コントローラーを追加したい (rails g controller / scaffold_controller)

コントローラーを追加する場合は、下記の2種類のコマンドがあります。

Usage:
  rails generate controller NAME [action action] [options]

Usage:
  rails generate scaffold_controller NAME [field:type field:type] [options]
COMMAND migration model routing controller view helper asset test
controller × × ×
scaffold_controller × × × ×

アセットを生成するかどうか、という違いがありますが、より大きな差としては下記のような デフォルトアクションの差 です。

  • controller: デフォルトのアクションが設定されない
  • scaffold_controller: scaffold で設定されるデフォルトアクションが設定される

Rails における一般的な RESTful コントローラーを定義したい場合は scaffold_controller を、そうでない場合は controller を使うのが良いかと思います。

$ rails generate scaffold_controller User -p | grep invoke
      invoke  slim
      invoke  rspec
      invoke    rspec
      invoke  helper
      invoke    rspec
      invoke  jbuilder

$ rails generate controller User -p | grep invoke
      invoke  slim
      invoke  rspec
      invoke  helper
      invoke    rspec
      invoke  assets
      invoke    coffee
      invoke    scss

モデルのみ追加したい (rails g model)

Usage:
  rails generate model NAME [field[:type][:index] field[:type][:index]] [options]
migration model routing controller view helper asset test
× × × × ×

モデルのみを作成するコマンドも用意されています。 開発の中でモデルのみ先に作ることもあるかと思いますが、そういった場合に使いやすいコマンドです。

$ rails generate model Article
      invoke  active_record
      create    db/migrate/20160918233116_create_articles.rb
      create    app/models/article.rb
      invoke    rspec
      create      spec/models/article_spec.rb

model コマンドでは、コマンドの中で migration ファイルの中身を指定することができます。

$ rails generate model User name:string age:integer
class CreateUsers < ActiveRecord::Migration[5.0]
  def change
    create_table :users do |t|
      t.string :name
      t.integer :age

      t.timestamps
    end
  end
end

また、下記のように reference を用いて association を指定することもできます。

$ rails generate model book asin:string title:string author:reference
class CreateBooks < ActiveRecord::Migration[5.0]
  def change
    create_table :books do |t|
      t.string :asin
      t.string :title
      t.reference :author

      t.timestamps
    end
  end
end

マイグレーションのみ追加したい (rails g migration)

Usage:
  rails generate model NAME [field[:type][:index] field[:type][:index]] [options]
migration model routing controller view helper asset test
× × × × × × ×

単純にマイグレーションファイルのみを足したい場合はこちらのコマンドで OK です。
モデルと同様に、コマンドの引数としてカラム名などを指定することができます。

$ be rails generate migration AddCategoryToBook category:string | grep invoke
      invoke  active_record
class AddCategoryToBook < ActiveRecord::Migration[5.0]
  def change
    add_column :books, :category, :string
  end
end

Ruby における hash 操作の逆引きまとめ

Ruby の hash に関係する操作でよく使われるメソッドをまとめました。
用途別にグループ分けをしているので、逆引きとしてご覧ください。

※ 想定している ruby のバージョンは ruby 2.3.1p112 です。

要素を追加する

ハッシュに要素(キーと値のペア)を追加したい時は、下記のようなやり方があります。

一つずつ追加する

[]=

単純な要素追加の場合には []= を使うのが一般的です。

hash = {}
hash[:hoge] = 1

hash
# {:hoge=>1}

store

[]= の別名のメソッドとして store も使うことができます。

hash = {}
hash.store(:fuga, 2)

hash
# {:fuga=>2}

ハッシュに別のハッシュを加える

merge, merge!

merge を使うと、すでに存在するハッシュの要素をもう一つのハッシュに統合できます。 同じキーが存在した場合は、引数で与えたハッシュの値で上書きされます。

hash1 = { a: 1, b: 1 }
# {:a=>1, :b=>1}
hash2 = { b: 2, c: 2 }
# {:b=>2, :c=>2}

hash1.merge(hash2)
# {:a=>1, :b=>2, :c=>2}

merge! の場合はレシーバ自身が上書きされます。

## merge はレシーバ自身は変更しない
hash1.merge(hash2)
# {:a=>1, :b=>2, :c=>2}
hash1
# {:a=>1, :b=>1}

## merge! はレシーバ自身が変更される
hash1.merge!(hash2)
# {:a=>1, :b=>2, :c=>2}
hash1
# {:a=>1, :b=>2, :c=>2}

update

merge! の別名のメソッドとして update があります。

hash = { hoge: 1 }
# {:hoge=>1}

hash.update({ fuga: 2})
# {:hoge=>1, :fuga=>2}

hash
# {:hoge=>1, :fuga=>2}

値を検索する

特定の値が存在するか調べる

value?

特定の値が存在するかどうかを判定し、Boolean で返します。

hash = { name: 'Tom', age: 21 }
# {:name=>"Tom", :age=>21}

hash.value?('Tom')
# true
hash.value?('Bob')
# false

has_value?

value? の同名のメソッドとして has_value? があります。

hash = { name: 'Tom', city: 'Tokyo' }
# {:name=>"Tom", :city=>"Tokyo"}

hash.has_value?('Tokyo')
# true
hash.has_value?('Osaka')
# false

値の一覧を返す

values

ハッシュの中に値として存在しているものを配列で返します。

hash = { a: 1, b: 2, c: 3 }
# {:a=>1, :b=>2, :c=>3}

hash.values
# [1, 2, 3]

特定のキー(単一)に紐づく値を取り出す

[]

あるキーに紐づく値を参照する場合には [] を用いるのが一般的です。

hash = { hoge: 1 }
# {:hoge=>1}

hash[:hoge]
# 1

fetch

[] と同様に値を取り出すことができますが、該当のキーが存在しない場合に KeyError を返します。

hash = { hoge: 1 }

hash.fetch(:hoge)
# 1
hash.fetch(:fuga)
# KeyError: key not found: :fuga

第2引数を渡すことで、キーが存在しなかった場合のデフォルト値を設定することができます。

hash.fetch(:fuga)
# KeyError: key not found: :fuga

hash.fetch(:fuga, "NotFound")
# "NotFound"

特定のキー(複数可)が持つ値を配列で返す

fetch_values

引数としてキーを可変長な引数として渡すことで、それらのキーが持つ値を配列で取得できます。

hash = { a: 1, b: 2, c: 3 }
# {:a=>1, :b=>2, :c=>3}

hash.fetch_values(:a, :c)
# [1, 3]

ネストした要素を参照する

dig

ruby 2.3 で追加された dig メソッドを使えば、ネストした要素を安全に取得できます。

hash = { a: { b: { c: 3 } } }
# {:a=>{:b=>{:c=>3}}}

hash[:a][:b][:c]
# 3
hash.dig(:a, :b, :c)
# 3

## []のチェーンだと、途中でキーがない場合にエラーになる
hash[:a][:not_b][:c]
# NoMethodError: undefined method `[]' for nil:NilClass

## dig の場合は途中でキーがない場合はエラーにならず nil が返る
hash.dig(:a, :not_b, :c)
# nil

特定の値(単一)に紐づく値を取り出す

特定の値を持つキーが存在する場合はそのキー名を返し、存在しない場合は nil を返します。

key

hash = { name: 'Tom', city: 'Tokyo' }
# {:name=>"Tom", :city=>"Tokyo"}

hash.key('Tokyo')
# :city
hash.key('Osaka')
# nil

キーを検索する

特定のキーが存在するか調べる

key?

特定のキーが存在するかどうかを判定し、Boolean で返します。

hash = { a: 1, b: 2, c: 3 }
# {:a=>1, :b=>2, :c=>3}

hash.key?(:b)
# true
hash.key?(:d)
# false

has_key?

key? の同名のメソッドとして has_key? があります。

hash = { a: 1, b: 2, c: 3 }
# {:a=>1, :b=>2, :c=>3}

hash.has_key?(:b)
# true
hash.has_key?(:d)
# false

キーの一覧を返す

keys

ハッシュの中に存在するキーを配列にして返します。

hash = { name: 'Tom', city: 'Tokyo' }
# {:name=>"Tom", :city=>"Tokyo"}

hash.keys
# [:name, :city]

繰り返し処理する

キーごとに処理をする

each_key

ハッシュ内に存在するキーをブロック変数にしてイテレーションを回します。

hash = { name: 'Tom', city: 'Tokyo', age: 21 }
# {:name=>"Tom", :city=>"Tokyo", :age=>21}

hash.each_key { |key| puts key }
# name
# city
# age

値ごとに処理をする

each_value

ハッシュ内に存在する値をブロック変数にしてイテレーションを回します。

hash = { name: 'Tom', city: 'Tokyo', age: 21 }
# {:name=>"Tom", :city=>"Tokyo", :age=>21}

hash.each_value { |value| puts value }
# Tom
# Tokyo
# 21

キーと値のペアで処理をする

each_pair

ハッシュ内に存在するキーと値の2つをブロック変数にしてイテレーションを回します。

hash = { name: 'Tom', city: 'Tokyo', age: 21 }
# {:name=>"Tom", :city=>"Tokyo", :age=>21}

hash.each_pair { |key, value| puts "key: #{key}, value :#{value}" }
# key: name, value: Tom
# key: city, value: Tokyo
# key: age, value: 21

その他の操作

キーと値を入れ替える

invert

ハッシュのキーと値を入れ替えたハッシュを生成します。
キーは一意性を担保する必要があるため、値が重複している状態で invert した場合は後ろのキーが残ります。
Ruby 1.9 以降はキーにも順序が存在します)

hash = { A: "Tom", B: "Bob", C: "Green" }
# {:A=>"Tom", :B=>"Bob", :C=>"Green"}

hash.invert
# {"Tom"=>:A, "Bob"=>:B, "Green"=>:C}

## 同じ値がある状態で invert した場合は後ろのキーが採用される
price = { orange: 150, apple: 200, banana: 150 }
# {:orange=>150, :apple=>200, :banana=>150}
price.invert
# {150=>:banana, 200=>:apple}

特定の条件を満たす要素を削除する

delete

引数として渡したキーの要素を削除します。
レシーバは変更されたうえで、戻り値としては削除された要素の値が返ります。

hash = { A: "Tom", B: "Bob", C: "Green" }
# {:A=>"Tom", :B=>"Bob", :C=>"Green"}

hash.delete(:A)
# "Tom"
hash
# {"Bob"=>:B, "Green"=>:C}

delete_if

キーと値を引数にした繰り返しブロックを実行し、真となる要素のみを削除します。

price = { orange: 150, apple: 200, banana: 80 }
# {:orange=>150, :apple=>200, :banana=>80}
price.delete_if { |_, price| price > 100 }
# {:banana=>80}

price
# {:banana=>80}

keep_if

delete_if とは逆に、繰り返しブロックを実行して真となった要素のみを残します。

price = { orange: 150, apple: 200, banana: 80 }
# {:orange=>150, :apple=>200, :banana=>80}

price.keep_if { |_, price| price > 100 }
# {:orange=>150, :apple=>200}

Ruby における正規表現の使い方

正規表現とは

文字列の集合を、「繰り返し」や「否定」など特殊な意味を持つ メタ文字 を使いながら、一つの文字列として表現したものです。

正規表現を用いることで、特定の文字列を含んでいるかどうかを判別したり、文字列から特定のパターンにマッチする部分を探し出すことができます。

Regexp クラス

Ruby では、正規表現を扱うクラスとして Regexp クラスが存在します。

下記のように Regexp オブジェクトを生成することができます。

pattern = Regexp.new("hoge")
# /hoge/
pattern = /hoge/
# /hoge/

pattern.class 
# Regexp < Object

また、%記法(パーセント記法) を用いることもできます。

pattern = %r{https?://}
# /https?:\/\//

%記法では、丸括弧 () 以外の括弧で囲うことができ、出現頻度の高いスラッシュ / などをエスケープしてくれるため、すっきりと書ける場合が多いです。

マッチしたかどうかを判定する

文字列があるパターンにマッチするかどうかを確認する場合、下記のように Regexp#=== または Regexp#=~ を用いることが多いです。

pattern = %r{[123]}

pattern === "abc123"
# true
pattern =~ "abc123"
# 3

Regexp#=== の場合は true または false、Regexp#=~ の場合はマッチした位置を返すので、期待される戻り値に合わせて使い分けましょう。

マッチした箇所を抜き出す

マッチした箇所を抜き出したい場合は、() によるグルーピング を行います。 こうすることで、正規表現内では \1 や \2 といった形、Ruby 内部では $1, $2 といった形で参照することができます。

実際にマッチさせた文字列を取り出す場合は、Regexp#match を使うと便利です。 パターンにマッチした場合は MatchData オブジェクトが返され、マッチした文字列に関する情報を得ることができます。

matched = %r{(\d+)}.match("出席番号10番")
# #<MatchData "10番" 1:"10">

matched[0] # マッチした文字列
# "10番"
matched[1] # マッチした正規表現の中で1つめの括弧にマッチした文字列
# "10"
matched.regexp # 使用した正規表現
# /(\d+)番/

マッチした箇所を置き換える

マッチした部分を別の文字列に置き換える場合、String#subString#gsub を使うことができます。 sub の場合は置換は1度だけ、gsub の場合はマッチした全ての箇所において置換を行います。

下記のように、第一引数に Regexp オブジェクトを、第二引数にマッチした箇所に置き換える文字列を渡します。

"abcabc".sub(/a/, "A")
# "Abcabc"

"abcabc".gsub(/a/, "A")
# "AbcAbc"

正規表現の記法

正規表現には、集合を効果的に表現するための様々な記法が用意されています。

ある文字集合を表す「文字クラス」

[ ] で文字を囲むことで、文字クラス を指定することができます。

例えば [123] は 1, 2, 3 のいずれか1文字にマッチし、ハイフン - を利用することで、[a-z] で a から z までのうちのいずれか、といったような範囲指定ができるようになります。

組み込みで用意されている文字クラスには下記のようなものがあり、それらを組み合わせて使うことで様々な正規表現を簡潔に書くことができます。

文字クラス 意味
\w 英数字 [0-1A-Za-z] にマッチする
\W 英数字 [0-1A-Za-z] 以外にマッチする
\s 空白文字 [\t\r\n\f] にマッチする
\S 空白文字 [\t\r\n\f] 以外にマッチする
\d 数字 [0-9] にマッチする
\D 数字 [0-9] 以外にマッチする

例) 空白を含む文字列 かどうかを判別したい場合

pattern = %r{\s}
# /\s/

pattern === "aaa bbb"
# true
pattern === "aaabbb"
# false

繰り返しを表す「量指定子」

「*」「+」などは 量指定子 と呼ばれており、直前のパターンの繰り返しを表現することができます。

例えば、改行を除く任意の1文字にマッチするドット . と、直前のパターンの1回以上の繰り返しを表す「+」を組み合わせて [.+] とすることで、任意の1文字以上のパターンを表現することができます。

量指定子 意味
* 直前の正規表現の0回以上の反復
+ 直前の正規表現の1回以上の反復
? 直前の正規表現の0回または1回の反復
{m, n} 直前の正規表現のm回~n回までの反復

例)11桁の電話番号 かどうかを判別したい場合

pattern = %r{\d{3}-\d{4}-\d{4}}
# /\d{3}-\d{4}-\d{4}/

pattern === "080-0000-0000"
# true
pattern === "05-0000-0000" 
# false

先頭と末尾の表現

文字列の先頭や末尾を表現するための記号にはいくつか種類があります。 それぞれ微妙な違いがあるため、対象の文字列に改行が含まれる場合は、特に注意が必要です。

記号 意味
^ 行頭、改行文字の直後にマッチする
$ 行末、改行文字の直前にマッチ
\A 文字列の先頭にマッチする
\Z 文字列の末尾にマッチするが、末尾が改行であれば改行の直前にマッチする
\z 文字列の末尾にマッチするが、末尾が改行であっても常に末尾にマッチする

例えば、文字列の途中に改行文字が存在する場合 は、下記のようになります。

## $ であれば行末と改行文字の直前の両方にマッチする
/hoge$/ =~ "hoge\nfuga"
# 0
/fuga$/ =~ "hoge\nfuga"
# 5

## \Zと \z は末尾(行末)にしかマッチしない
/hoge\Z/ =~ "hoge\nfuga"
# nil
/fuga\Z/ =~ "hoge\nfuga"
# 5

また、文字列の末尾に改行文字が存在する場合 は、下記のようになります。

## \Z は末尾に改行があった場合に直前にマッチするため、改行文字の前の fuga にマッチする
/fuga\Z/ =~ "hoge\nfuga\n"
# 5

## \z は常に末尾にマッチするため、改行文字そのものにマッチし、その直前の fuga にはマッチしない
/fuga\z/ =~ "hoge\nfuga\n"
# nil

先読みと後読み

「前後にパターンAがある場合」という条件のもとで特定のパターンBをマッチさせたいという場合に使われるのが 先読みと後読み です。

そこにさらに否定が加わることで、「〜がない場合」についても表現できるようになります。

記号 意味
(?=) 先読み
(?!) 否定の先読み
(?<!) 後読み
(?<!) 否定の後読み
(?#) コメントアウト

例を見てみましょう。
下記の例では、「数学1位 国語3位 英語5位」という文字列があった場合に、真ん中の国語の順位にマッチさせます。

str = "数学1位 国語3位 英語5位"

## 「国語」で後読み(抜き出す対象の手前のパターンを指定する)
%r{(?<=国語)(\d+)}.match(str)
# #<MatchData "3位" 1:"3">

## 「 英語」で先読み(抜き出す対象の後ろのパターンを指定する)
%r{(\d+)(?= 英語)}.match(str)
# #<MatchData "3位" 1:"3">

最後に否定の例を見ておきます。
都市名と天気の書かれた文字列から、天気が 雨 ではない場合のみマッチさせます。

%r{(.+):(?!)}.match("京都:雨")
# nil
%r{(.+):(?!)}.match("東京:晴れ")
# #<MatchData "東京:" 1:"東京">
%r{(.+):(?!)}.match("名古屋:曇り")
# #<MatchData "名古屋:" 1:"名古屋">