Open50

RubyとかRailsの小技をまとめるスクラップ

ダン@HyperFormダン@HyperForm

data: {confirm: "削除しますか?"}

link_toのオプションにdata: {confirm: "メッセージ"}をつけると、リンク先に移動する前にjsのダイアログで確認ができる

<%= link_to '削除', task_path(task), method: :delete, data: {confirm: "削除しますか?"} %>

たしかこれはrails-ujsのおかげだった。

ダン@HyperFormダン@HyperForm

each_with_indexとeach.with_index

each_with_index

indexの初期値は0

array = %w(foo bar baz)
array.each_with_index do |x, index|
  puts "#{index}番目#{x}"
end

=>
0番目foo
1番目bar
2番目baz

each.with_index

引数にindexの初期値をいれる

array = %w(foo bar baz)
array.each.with_index(1) do |x, index|
  puts "#{index}番目#{x}"
end

=>
1番目foo
2番目bar
3番目baz

https://mikamisan.hatenablog.com/entry/2017/06/10/103402

ダン@HyperFormダン@HyperForm

redirect_toとflashメッセージ

redirect_to hoge_url, notice: '成功しました'

という書き方で、リダイレクトとflashメッセージの代入を同時にできる。
これが使えるのはnoticealertだけ。

ダン@HyperFormダン@HyperForm

.to_s(:delimited)

3桁以上の数値にカンマをつけて出力したい時に使える。

10000000.to_s(:delimited)
=> "10,000,000"

1000.to_s(:delimited)
=> "1,000"

100.to_s(:delimited)
=> "100"
ダン@HyperFormダン@HyperForm

CSRF対策を一部解除

class ApplicationController < ActionController::Base
  protect_from_forgery except: [:action_name]
end
ダン@HyperFormダン@HyperForm

truncate(文字数)

引数に指定した文字数で丸めてくれる。

'abcdefghtjklmn'.truncate(5)
=> "ab..."

'abcdefghtjklmn'.truncate(10)
=> "abcdefg..."

'abcdefghtjklmn'.truncate(15)
=> "abcdefghtjklmn"
ダン@HyperFormダン@HyperForm

gsubで文字列を置換

'abcdefg'.gsub(/abc/, 'ABC')
=> "ABCdefg"

'abcdefg'.gsub(/abc/, '')
=> "defg"

'1234567890'.gsub(/([0-9]{2})([0-9]+)/, '※※\2') 
=> "※※34567890"
ダン@HyperFormダン@HyperForm

beginとendでコメントアウト

html.erbファイルのコメントアウトに
beginとendが使える

<%
=begin
%>
<p>普通のHTMLの記述もコメントアウト</p>
<%=  'Rubyの記述もコメントアウト' %>
<%
=end
%>
ダン@HyperFormダン@HyperForm

リダイレクトして処理を終了する

return redirect_to hoge_url
# or
redirect_to hoge_url && return
ダン@HyperFormダン@HyperForm

日付や時刻のデータを生成する

Date.new(1993, 2, 24)
Date.parse('1993-02-24')
DateTime.new(2021, 2, 24, 12, 30, 45)
DateTime.parse('1993-02-24T12:30:45')
ダン@HyperFormダン@HyperForm

number_to_currency

number_to_currency(123456789)
# $123,456,789.00

number_to_currency(123456789, unit: "¥")
# ¥123,456,789.00

number_to_currency(123456789, format: "%u%n", unit: "¥")
# 123,456,789.00円
# config/locales/jp/yml
jp:
  number:
    currency:
      format:
        unit: "円"
        format: "%n%u"
        negative_format: "-%n%u"
        precision: 0

number_to_currency(123456789, locale: 'jp')
# 123,456,789円

https://railsdoc.com/page/number_to_currency

ダン@HyperFormダン@HyperForm

time_ago_in_words

現在時刻と引数に渡した時刻の差分を出力

time_ago_in_words(Time.current)
#=> less than a minute

time_ago_in_words(Time.current + 3.days)
#=> 3 days
ダン@HyperFormダン@HyperForm

キーワード引数

メソッドの初期値を指定したり、呼び出し側で何のために引数を代入してるかわかりやすくできる。

デフォルト値あり
def hello(from: 'Me', to: 'World')
  puts "#{from}: Hello, #{to}!"
end

hello(to: 'My World') #=> Me: Hello, My World!
hello(from: 'Tom') #=> Tom: Hello, World!
デフォルト値なし
def hoge(a: )
  p a
end

hoge(a: 'aaa')
#=> "aaa"
hoge('aaa')
#=> ArgumentError: missing keyword: a
ダン@HyperFormダン@HyperForm

Layoutファイルを指定する

https://www.javadrive.jp/rails/template/index3.html

コントローラー単位で指定

class HogesController < ApplicationController
  layout 'hoge'
end

アクション単位で指定

class FugasController < ApplicationController
  def show
    render layout:  'fugashow'
  end
end

レイアウトを指定しない

class BooksController < ApplicationController
  layout false

  def show
  end
end
class MusicsController < ApplicationController
  def show
    render layout: false
  end
end
ダン@HyperFormダン@HyperForm

数値を0埋めする

"%02d" %を使う

pry(main)> "%02d" % 5
=> "05"
pry(main)> "%02d" % 9
=> "09"
pry(main)> "%02d" % 10
=> "10"
pry(main)> "%02d" % 100
=> "100"
pry(main)> "%04d" % 5
=> "0005"
ダン@HyperFormダン@HyperForm

binディレクトリ直下に以下のようなファイルを作ると

bin/hogehoge
#!/bin/sh

rubocop -a
yarn stylelint:fix
$ bin/hogehoge

だけで、ファイル内に記述したコマンドをまとめて叩ける。

ダン@HyperFormダン@HyperForm

collection_select

プルダウンメニューをDBの値から生成できるフォームヘルパー

<%= f.collection_select :anime_id, Anime.all, :id, :title %>
  • 第1引数[:anime_id]
    • 値を登録するDB上のカラム名
  • 第2引数[Anime.all]
    • プルダウンを構成するデータを取得するためのActiveRecordの取得メソッド
  • 第3引数[:id]
    • DBに登録するカラム
  • 第4引数[:title]
    • プルダウンに表示するカラム

https://iriya-0624.hatenadiary.org/entry/20130429/1367243032

ダン@HyperFormダン@HyperForm

rspecの-eオプション

# 指定したディレクトリの'自動更新'という文字列が名前に入っているexample(it)だけを実行する
$ rspec spec/models -e '自動更新'
ダン@HyperFormダン@HyperForm

ENV[]ENV.fetch()の違い

.env
HOGE='ハローハロー'
環境変数がセットされている時は同じ
ENV['HOGE']
=> "ハローハロー"

ENV.fetch('HOGE')
=> "ハローハロー"
環境変数がセットされていない時
ENV['FUGA']
=> nil

ENV.fetch('FUGA')
=> KeyError: key not found: "FUGA"

ENV.fetch('FUGA'){'うううう'}
=> "うううう"

https://qiita.com/sukebeeeeei/items/576f109d57218c8397d5

ダン@HyperFormダン@HyperForm

to_sql

Blog.joins(:articles).where(articles: { name: "hoge" }).to_sql
# => SELECT "blogs".* FROM "blogs" INNER JOIN "articles" ON "articles"."blog_id" = "blogs"."id" WHERE "articles"."name" = 'hoge'

クエリを確認できる。

includes使って関連テーブルのデータを取ってこようとしてる場合、1つ目のSQL文しか表示されないので注意。

Answer.where(question_id: 181).includes(:user)
  Answer Load (0.8ms)  SELECT `answers`.* FROM `answers` WHERE `answers`.`question_id` = 181
  User Load (0.9ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` IN (34, 28, 50, 93)
=> [#<Answer:0x00007f5ee608f588

Answer.where(question_id: 181).includes(:user).to_sql
=> "SELECT `answers`.* FROM `answers` WHERE `answers`.`question_id` = 181"
ダン@HyperFormダン@HyperForm

33. db:seed:replant

Rails6からの新機能。
dbのレコードを全削除した後に、seedを実行することができる。
ステージングやレビュー用アプリで、データベースをDROPせずにseedを再実行したい場合に特に便利。

ダン@HyperFormダン@HyperForm

35. bodyタグのクラスに、コントローラー名とアクション名を動的にセット

app/views/layouts/application.html.erb
<body class="<%= "#{controller_name} #{action_name}" %>">

application.html.erbbodyタグにコントローラー名とアクション名のクラスを付与するようにしておくと、CSSの名前空間ができる。

こうすることで、itemlistみたいな単純なクラス名を、重複を気にすることなく使うことができる。BEMから解放されてCSS設計が楽になる。またSCSS内のクラス名の記述が&__elementみたいにならないので、ファイル検索も楽になる。

app/assets/stylesheets/application.scss
.users {
  //
  &.new {
    //
  }
  &.index {
    //
  }
}
.articles {
  //
  &.edit {
    //
  }
  &.show {
    //
  }
}
ダン@HyperFormダン@HyperForm

36. strip_heredoc

ヒアドキュメント冒頭のインデントを消す

puts <<-'EOS'
  おじいさんは、山へ芝刈りに。
  おばあさんは、川へ洗濯物に。
EOS
    おじいさんは、山へ芝刈りに。
    おばあさんは、川へ洗濯物に。

puts <<-'EOS'.strip_heredoc
  おじいさんは、山へ芝刈りに。
  おばあさんは、川へ洗濯物に。
  めでたし、めでたし。
EOS
おじいさんは、山へ芝刈りに。
おばあさんは、川へ洗濯物に。
めでたし、めでたし。

https://www.techscore.com/blog/2013/03/04/activesupport-の-strip_heredoc-でヒアドキュメントを綺麗に書く/

ダン@HyperFormダン@HyperForm

38. 自己代入

hoge || = 'hogeがnilならこれを代入'
hoge = foo || bar || baz || '全部nilならこれを代入'
ダン@HyperFormダン@HyperForm

39. find_or_initialize_by

#太郎という名前のユーザーが存在すればfind、しなければnew
User.find_or_initialize_by(first_name: '太郎') 
ダン@HyperFormダン@HyperForm

40. Numbered parameter(ナンパラ)

# ナンパラを使わない場合
%w(goro, toshiya, gibson).map{ |i| i.upcase }
%w(goro, toshiya, gibson).map(&:upcase)

# ナンパラを使う場合
%w(goro, toshiya, gibson).map{ _1.upcase }
ダン@HyperFormダン@HyperForm

42. RubyでHTTPリクエストを投げる方法

GET
require 'uri'
require 'net/http'

uri = URI('https://hogehoge.com/fuga')
params = {
  token: 'aaabbbccc',
  user_id: 12345
}
uri.query = URI.encode_www_form(params)

res = Net::HTTP.get_response(uri)
puts res.body if res.is_a?(Net::HTTPSuccess)
POST
require 'uri'
require 'net/http'

uri = URI('https://hogehoge.com/fuga')
params = {
  token: 'aaabbbccc',
  user_id: 12345
}

res = Net::HTTP.post_form(uri, params)
puts res.body  if res.is_a?(Net::HTTPSuccess)
ダン@HyperFormダン@HyperForm

44. =====eqlequal?の違い

===は、左辺の正規表現や範囲などを解釈する
/foo/ == "foo"  #=> false
/foo/ === "foo" #=> true

(0..10) == 1  #=> false
(0..10) === 1 #=> true
eql?は、型までチェックする
1 == 1.0      #=> true
1.eql?(1.0)   #=> false
equal?は、object_idまでチェックする
a = b = "foo"
a.equal?(b) #=> true

c = "bar"
d = "bar"
c.equal?(d) #=> false

ポイント

  • 基本的に==を使っていれば問題なさそう
  • case式の条件の比較は、内部的に===が使われている
  • hashのキーは、eql?がtrueのときに同一のキーだとみなされる
    • hash[1]hash[1.0]は異なるキーだとみなされる

https://mickey24.hatenablog.com/entry/20100910/1284052782
https://techracho.bpsinc.jp/hachi8833/2018_07_04/58707

ダン@HyperFormダン@HyperForm

45. ルーティングで/:idの代わりに/:hogehogeを使う

routes.rb
resources :users, param: :hogehoge
$ rails routes
Prefix Verb   URI Pattern                                                                                       Controller#Action
edit_user GET    /users/:hogehoge/edit(.:format)                                                                   users#edit
     user GET    /users/:hogehoge(.:format)                                                                        users#show
          PATCH  /users/:hogehoge(.:format)                                                                        users#update
          PUT    /users/:hogehoge(.:format)                                                                        users#update
          DELETE /users/:hogehoge(.:format)                                                                        users#destroy
users_controller.rb
class UsersController < ApplicationController
  def show
     # URL上の値をparams[:hogehoge]で値を取得できる
    @user = User.find_by(username: params[:hogehoge])
  end
end

https://blog.takady.net/blog/2015/11/29/rails-routing-with-username-instead-of-id/

ダン@HyperFormダン@HyperForm

47 長いコードを改行する方法

式で長くなるとき

+=などを行末に置けば、式が終わっていないものとみなされるので改行を入れることができる

total = english_score +
        math_score +
        sciense_score

文字列で長くなるとき

\を使う。Ruby Style Guideでは「良い例(ただし、それでも極めて醜い)」とされている書き方

改行しない場合
long_string = "hogehogehogehoge fugafugafugafuga"
改行する場合
long_string = "hogehogehogehoge "\
              "fugafugafugafuga"

https://masuyama13.hatenablog.com/entry/2020/05/15/200125

ダン@HyperFormダン@HyperForm

48 transactionを使うときの注意点

transaction中にrescueするとロールバックしない

例外の捕捉をトリガーにロールバックが行われるので、自分で例外をキャッチして処理すると、ロールバックが行われなくなってしまう。

class User < ApplicationRecord
  validates :name, uniqueness: true
end
🙆‍♂️ ロールバックする例
def exec_transaction
  ApplicationRecord.transaction do
    User.create!(name: '山田太郎')
    User.create!(name: '山田太郎')
  end
end
🙅 ロールバックしない例
def exec_transaction
  ApplicationRecord.transaction do
    User.create!(name: '山田太郎')
    User.create!(name: '山田太郎')
  rescue
    #エラー発生時に行いたい処理
  end
end
🙆‍♂️ 自分で例外をキャッチしつつ、ロールバックも行う例1 (明示的に例外を発生させる)
def exec_transaction
  ApplicationRecord.transaction do
    result1 = User.create(name: '山田太郎')
    result2 = User.create(name: '山田太郎')
    unless (result1 && result2)
      raise ActiveRecord::Rollback #明示的に例外を発生させる
    end
  end
end
🙆‍♂️ 自分で例外をキャッチしつつ、ロールバックも行う例2 (transactionの外側で例外をキャッチ)
# ロールバックが発生したら例外は再度送出される、という性質を利用する
def exec_transaction
  ApplicationRecord.transaction do
    User.create!(name: '山田太郎')
    User.create!(name: '山田太郎')
  end
rescue
  #エラー発生時に行いたい処理
end

https://qiita.com/ytnk531/items/a0db31ee4311425a3933
https://qiita.com/jnchito/items/3393c5c1a744199e128a#参考2-destroy_allを使う場合の注意点
https://api.rubyonrails.org/classes/ActiveRecord/Rollback.html

ダン@HyperFormダン@HyperForm

49 配列の末尾に値を追加

# 以下2つは同じ意味
array.push('hoge')
array << 'hoge'
ダン@HyperFormダン@HyperForm

50 意図的に例外を発生させる方法

raiseメソッドを使う

# エラークラスを指定しないと、RuntimeErrorが発生する
raise

# エラーメッセージをセットするとログに出力される。またrescue文の中でe.messageとして参照できる
raise 'ooでエラーが発生しました'

# 発生させるエラークラスを指定することもできる
raise ArgumentError, 'ooでエラーが発生しました'
raise ArgumentError.new('ooでエラーが発生しました')

https://mgmmy.hatenablog.com/entry/2019/12/01/232019