Ruby
Rails
RubyOnRails

意外と知らないかもしれないRuby, Railsのメソッドとか

はじめに

Railsを使ってコードを書いていると、「どんなメソッドが使えるんだっけ?」 「もっと便利なメソッドないのか?」と思うことがあると思います。

そんな時に、私がよく使うのがObject.methodsや、特定のメソッドを探すObject.methods.grep()になるのですが、この記事ではそこで見つけた意外と知らないかもしれないメソッドなどを紹介していきたいと思います。

Ruby編

itself

selfを返します。

'test'.itself # 'test'

配列自身でgroup_byする時などに綺麗に書けます。

array = [4, 7, 3, 7, 7, 4, 1, 1]
array.group_by(&:itself) # {4=>[4, 4], 7=>[7, 7, 7], 3=>[3], 1=>[1, 1]}

参考URL: https://goo.gl/E4mv1b

allocate

クラスのインスタンスの作成を行いますが、initializeは呼ばれません。

class Cat
  attr_accessor :name
  def initialize
    @name = "NoName"
  end
end

cat1 = Cat.new
p cat1.name # "NoName"
cat2 = Cat.allocate
p cat2.name # nil

参考URL: http://ref.xaio.jp/ruby/classes/class/allocate

tap

ブロックにレシーバを入れて実行し、戻り値はレシーバ自身というメソッドです。

irb(main):001:0> array = [1, 2, 3, 4, 5]
=> [1, 2, 3, 4, 5]
irb(main):002:0> array.tap{ |a| p a }.collect{ |a| a.to_s  }
[1, 2, 3, 4, 5]
=> ["1", "2", "3", "4", "5"]

Rails編

increment, decrement

特定のレコードの値を増やしたり、減らしたりできます。

[1] pry(main)> user = User.find(1)
  User Load (5.6ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
=> #<User id: 1, email: "hoge@hoge.com", created_at: "2016-10-28 15:40:00", updated_at: "2016-10-28 15:40:00", point: 0>

[2] pry(main)> user.point
=> 0

[3] pry(main)> user.increment(:point, 1000)
=> #<User id: 1, email: "hoge@hoge.com", created_at: "2016-10-28 15:40:00", updated_at: "2016-10-28 15:40:00", point: 1000>

[4] pry(main)> user.save
   (0.2ms)  BEGIN
  SQL (5.6ms)  UPDATE `users` SET `point` = 1000, `updated_at` = '2016-10-29 00:22:49' WHERE `users`.`id` = 1
   (10.9ms)  COMMIT
=> true

[5] pry(main)> user.decrement(:point, 500)
=> #<User id: 1, email: "hoge@hoge.com", created_at: "2016-10-28 15:40:00", updated_at: "2016-10-29 00:22:49", point: 500>

increment!, decrement!を使うとsaveはいらなくなります。

[6] pry(main)> user.increment!(:point, 2000)
   (0.2ms)  BEGIN
  SQL (0.3ms)  UPDATE `users` SET `point` = 2500, `updated_at` = '2016-10-29 00:27:20' WHERE `users`.`id` = 1
   (7.1ms)  COMMIT
=> true

[7] pry(main)> user.decrement!(:point, 1000)
   (0.2ms)  BEGIN
  SQL (0.3ms)  UPDATE `users` SET `point` = 1500, `updated_at` = '2016-10-29 00:28:12' WHERE `users`.`id` = 1
   (6.3ms)  COMMIT
=> true

※ increment!ですが、rails5から挙動が少し変わっています。
参考URL: http://qiita.com/kano-e/items/f186a24aaa6db8d19903

update_column

updated_atの値を更新せず、値を上書きしたい時に使います。
バリデーションやコールバックはスキップされます。

[1] pry(main)> article = Article.find(1)
  Article Load (19.2ms)  SELECT  `articles`.* FROM `articles` WHERE `articles`.`id` = 1 LIMIT 1
=> #<Article:0x007f92156caf58
 id: 1,
 user_id: 1,
 text: "サンプルテキスト1",
 created_at: Fri, 28 Oct 2016 15:40:00 UTC +00:00,
 updated_at: Fri, 28 Oct 2016 15:40:00 UTC +00:00>

[2] pry(main)> article.update_column(:text, "updated_atは更新しないよ")
  SQL (28.3ms)  UPDATE `articles` SET `articles`.`text` = 'updated_atは更新しないよ' WHERE `articles`.`id` = 1
=> true

[3] pry(main)> article
=> #<Article:0x007f92156caf58
 id: 1,
 user_id: 1,
 text: "updated_atは更新しないよ",
 created_at: Fri, 28 Oct 2016 15:40:00 UTC +00:00,
 updated_at: Fri, 28 Oct 2016 15:40:00 UTC +00:00>

[4] pry(main)> article.update_attribute(:text, "updated_atを更新するよ")
   (0.2ms)  BEGIN
  SQL (6.0ms)  UPDATE `articles` SET `text` = 'updated_atを更新するよ', `updated_at` = '2016-10-29 01:08:05' WHERE `articles`.`id` = 1
   (11.0ms)  COMMIT
=> true

[5] pry(main)> article
=> #<Article:0x007f92156caf58
 id: 1,
 user_id: 1,
 text: "updated_atを更新するよ",
 created_at: Fri, 28 Oct 2016 15:40:00 UTC +00:00,
 updated_at: Sat, 29 Oct 2016 01:08:05 UTC +00:00>

touch

updated_atを現在時刻でアップデートできます。

[1] pry(main)> article = Article.find(1)
  Article Load (0.4ms)  SELECT  `articles`.* FROM `articles` WHERE `articles`.`id` = 1 LIMIT 1
=> #<Article:0x007f92142ed440
 id: 1,
 user_id: 1,
 text: "サンプルテキスト1",
 created_at: Sat, 29 Oct 2016 02:32:16 UTC +00:00,
 updated_at: Sat, 29 Oct 2016 02:32:16 UTC +00:00>

[2] pry(main)> article.touch
   (0.2ms)  BEGIN
  SQL (0.3ms)  UPDATE `articles` SET `articles`.`updated_at` = '2016-10-29 02:36:13' WHERE `articles`.`id` = 1
   (2.7ms)  COMMIT
=> true

[3] pry(main)> article.updated_at
=> Sat, 29 Oct 2016 02:36:13 UTC +00:00

in?

引数で与えられた要素の中にレシーバが含まれているか判定するメソッドになります。
下の例は配列ですが、文字列に対しても使えます。

[1] pry(main)> array = ["Tanaka", "Sato", "Suzuki"]
=> ["Tanaka", "Sato", "Suzuki"]
[2] pry(main)> "Tanaka".in?(array)
=> true

in?はinclude?のレシーバと引数を逆にしたもののようです。
定義部分のソースコードを見てみると分かります。

def in?(another_object)
  another_object.include?(self)
rescue NoMethodError
  raise ArgumentError.new("The parameter passed to #in? must respond to #include?")
end

presence_in

in?メソッドはtrue, falseを返すのに対して、presence_inはtrueの場合、自身を返して、falseの場合nilを返します。

[1] pry(main)> array = ["Tanaka", "Sato", "Suzuki"]
=> ["Tanaka", "Sato", "Suzuki"]
[2] pry(main)> "Tanaka".presence_in(array)
=> "Tanaka"
[3] pry(main)> "Takahashi".presence_in(array)
=> nil

こちらもソースコードを読んでみるとよくわかります。

def presence_in(another_object)
  in?(another_object) ? self : nil
end

take

引数で指定した件数のレコードを取得します。

[1] pry(main)> Article.take(3)
  Article Load (0.3ms)  SELECT  `articles`.* FROM `articles` LIMIT 3
=> [#<Article:0x007f9219993f38
  id: 1,
  user_id: 1,
  text: "サンプルテキスト1",
  created_at: Sat, 29 Oct 2016 01:12:54 UTC +00:00,
  updated_at: Sat, 29 Oct 2016 01:12:54 UTC +00:00>,
 #<Article:0x007f92199911e8
  id: 2,
  user_id: 1,
  text: "サンプルテキスト2",
  created_at: Sat, 29 Oct 2016 01:12:54 UTC +00:00,
  updated_at: Sat, 29 Oct 2016 01:12:54 UTC +00:00>,
 #<Article:0x007f9219991058
  id: 3,
  user_id: 1,
  text: "サンプルテキスト3",
  created_at: Sat, 29 Oct 2016 01:12:54 UTC +00:00,
  updated_at: Sat, 29 Oct 2016 01:12:54 UTC +00:00>]

many?

条件を満たす要素が2つ以上ある場合にtrueが返ります。

[1] pry(main)> article = Article.where(id: 1)
  Article Load (0.3ms)  SELECT `articles`.* FROM `articles` WHERE `articles`.`id` = 1
=> [#<Article:0x007f9215621750
  id: 1,
  user_id: 1,
  text: "サンプルテキスト1",
  created_at: Sat, 29 Oct 2016 01:12:54 UTC +00:00,
  updated_at: Sat, 29 Oct 2016 01:12:54 UTC +00:00>]

[2] pry(main)> article.many?
=> false

[3] pry(main)> article = Article.where(id: [1, 2])
  Article Load (0.3ms)  SELECT `articles`.* FROM `articles` WHERE `articles`.`id` IN (1, 2)
=> [#<Article:0x007f9215c8e070
  id: 1,
  user_id: 1,
  text: "サンプルテキスト1",
  created_at: Sat, 29 Oct 2016 01:12:54 UTC +00:00,
  updated_at: Sat, 29 Oct 2016 01:12:54 UTC +00:00>,
 #<Article:0x007f9215c8dcb0
  id: 2,
  user_id: 1,
  text: "サンプルテキスト2",
  created_at: Sat, 29 Oct 2016 01:12:54 UTC +00:00,
  updated_at: Sat, 29 Oct 2016 01:12:54 UTC +00:00>]

[4] pry(main)> article.many?
=> true

似たようなメソッドで、all?,one?,any?none?などもありますね。

カラム名?

カラムの値がnil, ""の時はfalseを返します。

[1] pry(main)> article = Article.find(1)
  Article Load (1.0ms)  SELECT  `articles`.* FROM `articles` WHERE `articles`.`id` = 1 LIMIT 1
=> #<Article:0x007f9219a28d90 id: 1, user_id: 1, text: nil, created_at: Sat, 29 Oct 2016 01:12:54 UTC +00:00, updated_at: Sat, 29 Oct 2016 01:33:22 UTC +00:00>
[2] pry(main)> article.text
=> nil
[3] pry(main)> article.text?
=> false
[4] pry(main)> article.text = ""
=> ""
[5] pry(main)> article.text?
=> false
[6] pry(main)> article.text = []
=> []
[7] pry(main)> article.text?
=> true
[8] pry(main)> article.text = false
=> false
[9] pry(main)> article.text?
=> true

changed?

インスタンスが変わったかどうか調べることができます。
changesを用いるとどのカラムの値が変わったのかわかります。

[1] pry(main)> article = Article.find(3)
  Article Load (0.3ms)  SELECT  `articles`.* FROM `articles` WHERE `articles`.`id` = 3 LIMIT 1
=> #<Article:0x007f9219268358 id: 3, user_id: 1, text: "サンプルテキスト3", created_at: Sat, 29 Oct 2016 01:12:54 UTC +00:00, updated_at: Sat, 29 Oct 2016 01:12:54 UTC +00:00>
[2] pry(main)> article.changed?
=> false
[3] pry(main)> article.text = "変わるかな"
=> "変わるかな"
[4] pry(main)> article.changed?
=> true
[5] pry(main)> article.changes
=> {"text"=>["サンプルテキスト3", "変わるかな"]}

※ rails5.1からは非推奨になっています。

カラム名_changed?

カラムの値が変わったか調べることができます。
カラム名_changeを用いると変更前と変更後の値を取得できます。

[1] pry(main)> article = Article.find(2)
  Article Load (0.3ms)  SELECT  `articles`.* FROM `articles` WHERE `articles`.`id` = 2 LIMIT 1
=> #<Article:0x007f9214277b50 id: 2, user_id: 1, text: "サンプルテキスト2", created_at: Sat, 29 Oct 2016 01:12:54 UTC +00:00, updated_at: Sat, 29 Oct 2016 01:12:54 UTC +00:00>
[2] pry(main)> article.text
=> "サンプルテキスト2"
[3] pry(main)> article.text_changed?
=> false
[4] pry(main)> article.text = "上書き"
=> "上書き"
[5] pry(main)> article.text_changed?
=> true
[6] pry(main)> article.text_change
=> ["サンプルテキスト2", "上書き"]

カラム名_wasを用いると上書き前のテキストが表示されます。

[6] pry(main)> article.text_was
=> "サンプルテキスト2"

※ こちらもrails5.1からは非推奨になっています。

カラム名_will_change!

そのカラムの値がすでに変更されたものとして扱われることになります。
それに対して、restore_カラム名!をすると変更されていないものとして扱えます。

[1] pry(main)> article = Article.find(3)
  Article Load (0.3ms)  SELECT  `articles`.* FROM `articles` WHERE `articles`.`id` = 3 LIMIT 1
=> #<Article:0x007f92155f30a8 id: 3, user_id: 1, text: "サンプルテキスト3", created_at: Sat, 29 Oct 2016 01:12:54 UTC +00:00, updated_at: Sat, 29 Oct 2016 01:12:54 UTC +00:00>
[2] pry(main)> article.changed?
=> false
[3] pry(main)> article.text_changed?
=> false
[4] pry(main)> article.text_will_change!
=> "サンプルテキスト3"
[5] pry(main)> article.changed?
=> true
[6] pry(main)> article.text_changed?
=> true

collection_singular_ids

リレーションがhas_manyの場合、例えば、user.article_idsとするとuser.articles.pluck(:id)user.articles.map(&:id)と同じ結果が返ってきます。

[1] pry(main)> user = User.find(1)
  User Load (0.5ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
=> #<User id: 1, email: "hoge@hoge.com", created_at: "2016-10-29 01:12:54", updated_at: "2016-10-29 01:12:54", point: 0>
[2] pry(main)> user.article_ids
   (0.4ms)  SELECT `articles`.id FROM `articles` WHERE `articles`.`user_id` = 1
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[3] pry(main)> user.articles.pluck(:id)
   (0.3ms)  SELECT `articles`.`id` FROM `articles` WHERE `articles`.`user_id` = 1
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[4] pry(main)> user.articles.map(&:id)
  Article Load (0.3ms)  SELECT `articles`.* FROM `articles` WHERE `articles`.`user_id` = 1
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

before_add_for_collection

リレーションがhas_manyの場合、コールバックをProcとして取得できます。
after_add, before_remove, after_removeに関しても同様です。

class User < ActiveRecord::Base
  has_many :articles, before_add: :output_message

  def output_message(article)
    puts "#{article}を作ります。"
  end
end
[1] pry(main)> user = User.find(1)
  User Load (0.3ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
=> #<User id: 1, email: "hoge@hoge.com", created_at: "2016-10-29 02:32:16", updated_at: "2016-10-29 02:32:16", point: 0>
[2] pry(main)> user.before_add_for_articles
=> [#<Proc:0x007f921578d1c0@/Users/hiroki/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/activerecord-4.2.6/lib/active_record/associations/builder/collection_association.rb:50 (lambda)>]

to_param

URLのidの部分にid以外のものを指定できるようになります。

class Article < ActiveRecord::Base
  def to_param
    text
  end
end

article = Article.find_by(text: 'sample')
article_path(article)  # => "/articles/sample"

まとめ

生命、宇宙、そして万物についての究極の疑問の答えは「42」である。

[1] pry(main)> Article.forty_two
  Article Load (0.7ms)  SELECT  `articles`.* FROM `articles`  ORDER BY `articles`.`id` ASC LIMIT 1 OFFSET 41
=> #<Article:0x007f9215fc6170 id: 42, user_id: 1, text: "サンプルテキスト42", created_at: Sat, 29 Oct 2016 02:32:16 UTC +00:00, updated_at: Sat, 29 Oct 2016 02:32:16 UTC +00:00>