読者です 読者をやめる 読者になる 読者になる

Rails Webook

自社のECを開発している会社で働いています。Rails情報やサービスを成長させる方法を書いていきます

Ruby 2.1 / Rails4.1 の意外に忘れられている便利なメソッド

Ruby Rails中級 まとめ リファレンス

Ruby 2.1 / Rails4.1 での意外に忘れらている便利なメソッドを集めました。

RubyやRailsでは有用なメソッドがたくさん実装されており、車輪の再発明にならないように、それらを覚えて適切な箇所で使うだけで生産性はあがります。

(大雑把に書いたので、まだまとまってないので、随時修正します)

動作確認

  • Ruby 2.1.2
  • Rails 4.1
  • ActiveSupport 4.1.7

目次

代入、範囲、正規表現、%記法

文字列からクラスを作成 constantize
動的にメソッドを定義 class_eval

nilや空を判定する(present?, blank?)、nilや空以外の値を取得(present)、nilを扱う(try)

複数行の文字列(ヒアドキュメント)、文字列を含む(include?, index)、パターンにマッチする(match, =~)
パターンで置換する(gsub)、文字列で分割し配列にする(split)
1行ずつ取り出す(each_line)、1文字ずつ取り出す(each_char)、部分文字列を取り出す(slice)
文字列を削除(delete)、空白を除く(strip)、改行文字除く(chomp)

長さ(length, size), 空かどうか(empty?), 値が含まれるか(include?)
繰り返し(each, each_with_index, reverse_each)
連結(+)、追加(<<)
1要素の検索(find), 複数要素の検索(select, reject), 最大値、最小値(max, min)、各要素を処理(collect, map)
すべて◯◯か(all?), どれか◯◯か(any?)
各要素を結合(join)、合計を計算(sum)
ソート(sort)、重複除去(uniq)、ランダムに要素を取得(sample)
その他のArrayクラスのメソッド

要素の取り出しと設定(, =)、キーを配列で取得(keys)、値を配列で取得(values)、値の数(length)
空かどうか(empty?)、キーが含まれるか(has_key?)、値が含まれるか(has_value?)
繰り返し(each)、値の取得(select, collect, map)
ハッシュを統合(merge)、差を取得する(diff)
その他のHashクラスのメソッド

指定した要素を配列で取得(pluck)
変更を確認する(changes, changed?)
カラム値の存在有無を確認(カラム名+?)
DBに保存されていないか(new_record?)、DBに保存されているか(persisted?)
DBに存在しない場合作成(find_or_create_by)

コントローラーのメソッドをビューで使う(helper_method)




Rubyの基本

  • 代入
# 多重代入
a, b = 1, 2
a # => 1
b # => 2

a, b = [1, 2, 3]
a # => 1
b # => 2

a, *b = [1, 2, 3]
a # => 1
b # => [2, 3]

a, b, c = [1, 2]
a # => 1
b # => 2
c # => nil

# 入れ替え
a, b = b, a

# 自己代入
a += 1   # a = a + 1
b ||= 2  # b = b || 2
  • 範囲

>ruby|
(1..5).include?(5) # => true (5も含む)
(1...5).include?(5) # => false (5は含まない)

  • %記法
%w(Alice Bob Chrlie) # => ["Alice", "Bob", "Chrlie"]


Rubyの基本

  • 文字列からクラスを作成(constantize)
"User".constantize #=> Userクラスが作成される
"User".constantize.new
#=> #<User id: nil, name: nil, created_at: nil, updated_at: nil>

# 応用: URLから動的にモデルのレコードを読み出す
#
# URL例(request.path): /events/:id/comments, /articles/:id/comments
resource, id = request.path.split('/')[1, 2] # resource = events か articles
@commentable = resource.singularize.classify.constantize.find(id)


メタプログラミング

  • メソッドを動的に定義する(class_eval, module_eval)
# Cクラスを定義
class C; end

# class_evalメソッドで、Cクラスにmメソッドを定義
C.class_eval %Q{
  def m
    puts "hello"
  end
}

# mメソッドを実行
C.new.m
# => hello


Object

nilや空文字など空でないか確認(present?)
'', ' ', nil, [], {}など空っぽいもの以外の場合、trueを返す

['', ' ', "", " ", nil, [], {}].each { |a| p a.present? }
# => false
# ... (全て false)

nilや空文字など空であるか確認(blank?)
'', ' ', "", " ", nil, [], {}などの空ぽいものの場合、trueを返す

['', ' ', nil, [], {}].each { |a| p a.blank? }
# => true
# ... (全て true)

nilや空文字など空でない値を取得(present)
'', ' ', nil, [], {}など空っぽいもの以外の場合、値を返す。

'a'.presence #=> "a"
''.presence  #=> nil

tryメソッド(nilの場合、nilを返す)

@person ? @pserson.name : nil
# をtryで次のように書き換えれる
@poerson.try(:name)

# 引数やブロックも渡せる
"abc".try(:includes?, "a")


String

複数行に渡る文字列(ヒアドキュメント)

str = <<TEXT
1行目
2行目
3行目
TEXT

str #=> "1行目\n2行目\n3行目\n"


指定した文字列を含んでいるか(include?)

"abcde".include?("bc") #=> true
"abcde".include?("zy") #=> false


指定した文字列を含んでいれば、その開始位置を整数で返す(index)

"abcdefg".index("cd") # => 2
"abcdefg".index("zy") # => nil


パターンにマッチする(=~, match)

html = "<p></p>"
html.match(/<(\w)>/) do |m|
  p match[0]
  p match[1]
end
# => "<p>"
# => "p"


パターンで置換(gsub)

html = "hello,\nworld\r\n"

# 改行コードを<br />に置き換える
html.gsub(/(\r\n|\r|\n)/, "<br />") # => "hello,<br />world<br />"


指定した文字列で分割し、配列にする(split)

"Alice,Bob,Charlie".split(",") # => ["Alice", "Bob", "Charlie"]


1行ずつ取り出す(each_line)

"1行目\n2行目\n3行目\n".each_line { |line| p line }
# => "1行目\n"
# => "2行目\n"
# => "3行目\n"


1文字ずつ取り出す(each_char)

"abcde".each_char { |ch| p ch }
# => "a"
# => "b"
# ...


部分文字列を取り出す(slice)

"abcdefg".slice(1, 3) # 2番目から3文字分
# => "bcd"
"abcdefg".slice(1..3) # 1文字目から3文字目まで
# => "bcd"
"abcdefg".slice(/\w+/) # 正規表現
# => "abcdefg"


指定した文字列を削除(delete)

"abcde".delete("cd") # => "abe"
"abcde".delete("xy") # => "abcde"


先頭と末尾の空白を取り除く(strip)

" hi \t “.strip # => “hi”


文字列の末尾の改行文字を除く(chomp)

"hi\r\n".chomp  # => “hi”
"hi\n".chomp    # => "hi"
"hi\r".chomp    # => "hi"

"1行目\n2行目\n3行目\n".chomp #=> "1行目\n2行目\n3行目"


Array

長さ(length, size)

[].length #=> 0
[1, 2, 3].length #=> 3
[1, 2, 3].size   #=> 3


空かどうか(empty?)

[].empty?  #=> true

# Railsの場合、ActiveSupportで拡張されたblank?の方が都合が良い場合が多い
[].blank?  #=> true


値が含まれるか(include?)

[1, 2, 3].include?(1)  #=> true
[1, 2, 3].include?(10) #=> false


繰り返し(each, each_with_index, reverse_each)

[1, 2, 3].each { |a| p a }
# => 1
# => 2
# => 3

[1, 2, 3].each_with_index { |a, i| p "#{i}番目:#{a}" }
# => "0番目:1"
# => "1番目:2"
# => "2番目:3"

[1, 2, 3].reverse_each { |a| p a }
# => 3
# => 2
# => 1


配列の連結(+)

[1, 2, 3] + [4, 5, 6] #=> [1, 2, 3, 4, 5, 6]
[1, 2, 3] + []        #=> [1, 2, 3]


要素の追加(<<)

[1, 2, 3] << 4      #=> [1, 2, 3, 4]
[1, 2, 3] << [4, 5] #=> [1, 2, 3, [4, 5]]


1要素の検索(find)、複数要素の検索(select, reject),

[1, 2, 3, 4, 5, 6].find { |n| n % 3 == 0 }
# => 3
[1, 2, 3, 4, 5, 6].find { |n| n % 3 == 100 }
# => nil (条件に当てはまらないとnilを返す)

# selectはブロック内の条件に当てはまる要素を返す
[1, 2, 3, 4, 5, 6].select { |n| n % 3 == 0 }
=> [3, 6]
[1, 2, 3, 4, 5, 6].select { |n| n % 3 == 100 }
=> [] (条件に当てはまらないと[]を返す)

# rejectはブロック内の条件に当てはまらない要素を返す
[1, 2, 3, 4, 5, 6].reject { |n| n % 3 == 0 }
=> [1, 2, 4, 5]


最大値、最小値(max, min)

[2, 3, 1, 10, 8].min  #=> 1
[2, 3, 1, 10, 8].max #=> 10


各要素を処理する(collect, map)
collectもmapも別名で同じ処理をする。ブロックに与えた処理を行ったあとに、それらを配列で返す

['ruby', 'rails'].map { |str| str.capitalize } # => ["Ruby", "Rails"]

# 別の書き方もできる
['ruby', 'rails'].map(&:capitalize)            # => ["Ruby", "Rails"]


すべて◯◯か(all?), どれか◯◯か(any?)

[true, true, true].all?      # => true
[true, true, false].all?     # => false

[true, true, false].any?     # => true
[false, false, false].any?   # => false

[true, false, false].one?    # => true
[true, true, false].one?     # => false

[false, false, false].none?  # => true

# ブロックを渡して条件判定ができる
["abc tomato", "xyz tomato", "123 tomato"].all? { |a| a.include?("tomato") }
# => true


各要素を指定した文字列で結合して文字列で返す(join)

["ABC", "DEF", "GHI"].join(",")
=> "ABC,DEF,GHI"
[].join(",")
=> ""


合計を計算(sum)

[1, 2, 3].sum             # => 6
["abc", "def", "ghi"].sum #=> "abcdefghi"


ソート(sort)

[8, 3, 2, 9, 1].sort #=> [1, 2, 3, 8, 9]


重複する要素を除去する(uniq)

[3, 1, 2, 3, 1, 4, 2, 1].uniq # => [3, 1, 2, 4]


ランダムに要素を取得する(sample)

[1, 2, 3, 4, 5].sample # => 5
[1, 2, 3, 4, 5].sample # => 2

Hash

要素の取り出しと設定(, =)

hash = { "1" => { "username" => "taro", "age" => 20 }, "2" => { "username" => "chika", "age" => 18 } }

# 要素の取り出し
hash["1"]
# => {"username"=>"taro", "age"=>20 }

# 要素の設定
hash["3"] = { "username" => "tom", "age" => 25 }
hash
# => {"1"=>{"username"=>"taro", "age"=>20}, "2"=>{"username"=>"chika", "age"=>18}, "3"=>{"username"=>"tom", "age"=>25}}

キーを配列で取得(keys)、値を配列で取得(values)、値の数(length)

hash = { "1" => { "username" => "taro", "age" => 20 }, "2" => { "username" => "chika", "age" => 18 } }

hash.keys   # => ["1", "2"]
hash.values # => [{"username"=>"taro", "age"=>20}, {"username"=>"chika", "age"=>18}]
hash.length # => 2

空かどうか(empty?)、キーが含まれるか(has_key?)、値が含まれるか(has_value?)

hash = { name: :tom }
hash.empty?           # => false
hash.has_key?(:name)  # => true
hash.has_value?(:tom) # => true

hash = {}
hash.empty?           # => true
hash.has_key?(:name)  # => false
hash.has_value?(:tom) # => false


繰り返し(each)

hash = { "1" => { "username" => "taro", "age" => 20 }, "2" => { "username" => "chika", "age" => 18 } }

hash.each { |k, v| p "#{k}:#{v}" }
# => "1:{\"username\"=>\"taro\", \"age\"=>20}"
# => "2:{\"username\"=>\"chika\", \"age\"=>18}"

値の取得(select, collect, map)

  • select ... ブロックの条件に合致するハッシュを返す
  • collect(map) ... 各要素に対して、処理を行った結果を配列で返す
hash = { "1" => { "username" => "taro", "age" => 20 }, "2" => { "username" => "chika", "age" => 18 } }

# ageが20以上のハッシュを取得する
hash.select { |_, v| v["age"].to_i >= 20 }
# => {"1"=>{"username"=>"taro", "age"=>20}}

# ageを配列で取得する
hash.collect { |_, v| v["age"].to_i }
# => [20, 18]
# mapとcollectのエイリアスであるため挙動は同じ
hash.map { |_, v| v["age"].to_i }
# => [20, 18]


ハッシュを統合(merge)

h1 = { price: 100 }
h2 = { published: false }
h1.merge(h2) # => {:price=>100, :published=>false}

# 同じ値の場合は、後のハッシュが優先になる
h1 = { published: true  }
h2 = { published: false }
h1.merge(h2) # => {:published=>false}


ActiveRecord

  • 指定した要素を配列で取得(pluck)
User.all.pluck(:id, :name)
#(0.2ms)  SELECT "users"."id", "users"."name" FROM "users"
# => [[1, nil], [2, "田中"]]
  • 変更を確認する(changes, changed?)
u = User.first

# 変更された要素を取得する
u.changes       # => {}
# すべての要素が変更されたかbooleanで取得
u.changed?      # => false
# nameカラムが変更されたかbooleanで取得
u.name_changed? #=> false

u.name = "test"

u.changes       #=> {"name"=>[nil, "test"]}
u.changed?      #=> true
u.name_changed? #=> true
  • カラムの存在有無を確認する(カラム名+?)

boolean値のカラム

u = User.first

# nil, "", {}, [], falseなどの場合、falseを返す
u.name  = nil
u.name? # => false
u.name  = ""
u.name? # => false
u.name = {}
u.name? # => false
u.name = []
u.name? # => false
u.name = false
u.name? # => false

u.name = "test"
u.name? # => true
  • DBに保存されていないか(new_record?)、DBに保存されているか(persisted?)
# DBに保存されていない値
u = User.new
u.new_record? # => true
u.persisted?  # => false

# DBに保存されている値
u = User.first
u.new_record? #=> false
u.persisted?  #=> true
  • DBに存在しない場合作成(find_or_create_by)
# DBに存在する場合、レコードを取得
User.find_or_create_by(name: "test")
# => #<User id: 1, name: "test", created_at: "2014-11-29 09:02:15", updated_at: "2014-11-30 10:49:04", active: nil>

# DBに存在しない場合、レコードを作成
User.find_or_create_by(name: "test3") do |u|
  # 引数で指定した値は自動的に設定される
  # ブロック内でその他の値を設定する
  u.active = false
end
# => #<User id: 4, name: "test3", created_at: "2014-11-30 10:51:45", updated_at: "2014-11-30 10:51:45", active: false>


コントローラー

  • コントローラーのメソッドをビューで使う(helper_method)
# Controller

  # ビューでcurrent_userメソッドが利用できる
  helper_method :current_user

  private
  def current_user
    @_current_user ||= User.find(session[:user_id])
  end
end

# View
ユーザー名: <%= current_user.name %>

参考文献