RubyやRailsで使用頻度は高くないが知っていたら便利そうな機能

使用頻度は高くないので忘れちゃいそうだけど、ここぞという時に知っていたら便利そうなメソッドを備忘のためにまとめておきます。
(便利かつ高頻度で使うものは忘れないと思うのでここには書きません）

今後も便利そうだと思うメソッドがあったら随時追加していく予定です。

# clamp
数値の上限・下限を制限する

```ruby
523.clamp(0, 100)
# => 100
```

# 配列の共通の値を見つける

```ruby
irb(main):001:0> a = [1, 2, 3]
=> [1, 2, 3]
irb(main):002:0> b = [2, 4]
=> [2, 4]
irb(main):006:0> a.intersection b
=> [2]
irb(main):007:0> b.intersection a
=> [2]
```

# email_address_with_name
action_mailerのtoに<>や"が含まれていた場合、エスケープしてくれる

```ruby
# <>や"がnameやemailに入っていてもエスケープしてくれる。
mail(to: email_address_with_name(@user.email, @user.name))
```
# 改行コードを置換(\R)

```ruby
# \Rを使うと\r\n, \r, \n全てを置換対象にしてくれる
"あい\r\nう\rえ\nお".gsub(/\R/, '')
```

https://docs.ruby-lang.org/ja/latest/doc/spec=2fregexp.html#str

# 全角スペースも含めてトリム

```
> '　あうい  　  '.strip
=> "　あうい  　"

> '　あうい  　  '.gsub(/\A[[:space:]]*|[[:space:]]*\Z/, '')
=> "あうい"
```

# squish
スペースやタブ、改行をいい感じに除去してくれる

```ruby
" \n  foo\n\r \t bar \n".squish
# => "foo bar"
```

# キャメルケース、スネークケース

```ruby
'ham_method'.camelize
=> "HamMethod"

'ham_method'.camelize(:lower)
=> "hamMethod"

'HamMethod'.underscore
=> "ham_method"
```

# 文字列からClassに変換

```ruby
'Admin'.constantize
=> Admin(id: integer, name: string, email: string,created_at: datetime, updated_at: datetime)
```

# tally (>= ruby2.7)
配列の値をグルーピングしてカウントしてくれる

```ruby
['a', 'b', 'b', 'c', 'b'].tally
# => { 'a'=>1, 'b'=>3, 'c'=>1 }
```

# cover?: 時刻がRange内に入っているか確認する


```ruby
((Time.now - 1.day)..Time.now).cover?(Time.now - 1.5.day)
=> false
((Time.now - 1.day)..Time.now).cover?(Time.now - 0.5.day)
=> true
```

# 対象日の1日〜月末のrange

```ruby
Time.now.all_month
=> 2022-08-01 00:00:00 +0900..2022-08-31 23:59:59.999999999 +0900

# all_day, all_week, all_year, all_quarterもあり
```


# 2つの日付の日数を取得する

```ruby
(today..tomorrow).count
=> 2
```

# transform_{keys|values}
hashのkey or valueだけ変換する

```ruby
h = { a: 1, b: 2, c: 3 }
h.transform_keys {|k| k == :a ? :A : k }  #=> {:A=>1, :b=>2, :c=>3}

h.transform_values {|v| v * v + 1 }  #=> { a: 2, b: 5, c: 10 }
```

Ruby3.0からtransform_keysにhashが渡せるようになったようです。

```ruby
h.transform_keys { { |k| "#{k}#{k}" }  #=> {"aa"=>1, "bb"=>2, "cc"=>3}
h.transform_keys({ a: :A })  #=> {:A=>1, :b=>2, :c=>3}
```

# ファイルの文字コードを判定

https://docs.ruby-lang.org/ja/latest/class/NKF.html

```irb
irb(main):006:0> require 'nkf'
=> true
irb(main):007:0> NKF.guess("にほんご")
=> #<Encoding:UTF-8>
irb(main):008:0> NKF.guess("にほんご").name
=> "UTF-8"
irb(main):009:0> NKF.guess(File.read('utf8.txt')).name
=> "UTF-8"
irb(main):010:0> NKF.guess(File.read('sjis.txt')).name
=> "Shift_JIS"
```

# delegate_missing_to

https://railsguides.jp/active_support_core_extensions.html#delegate-missing-to

> UserオブジェクトにないものをProfileにあるものにすべて委譲したいとしましょう。
delegate_missing_toマクロを使えばこれを簡単に実装できます。

```ruby
class User < ApplicationRecord
  has_one :profile

  delegate_missing_to :profile
end
```

# Delayed Job

```ruby
# 任意のジョブを実行する。削除されないので不要になったら手動で削除すること
Delayed::Job.find(1).invoke_job

# 削除
Delayed::Job.find(1).destroy

# オブジェクト取得
job = Delayed::Job.find(1)
job.payload_object
```

# Enumerable
Enumerableは痒いところに手が届くメソッドがたくさん
https://docs.ruby-lang.org/ja/latest/class/Enumerable.html

# ヒアドキュメント
記号忘れがち
https://docs.ruby-lang.org/ja/latest/doc/spec=2fliteral.html#here

# Active Support
Railsガイド
https://railsguides.jp/active_support_core_extensions.html

# missing, associated(>=Rails 6.1)
関連先が存在しないレコードを取得したいとき

```ruby
Post.where.missing(:author)
# 下記と同じ意味
Post.left_joins(:author).where(authors: { id: nil })
```

```ruby
has_many :users, -> { where.associated(:contact) }
# 下記と同じ意味
has_many :users, -> { joins(:contact).where.not(contact_id: nil) }
```

# ActiveRecord::Relation.none
データが存在しないけどActiveRecord::Relationは返却したいとき

```ruby
Post.none
```

https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-none


# SQL構築でSQLインジェクションの警告が出た場合

Arel.sqlを通すと警告を回避できる。
ただし、Arel.sqlで囲むとエスケープされるだけではなく、危険な文字列が混入したらSQLインジェクションになる可能性がある。
下記のように確実に危険な文字列が入らない場合のみ使用する。

```
Arel.sql('max(`index`)')
```

# SQLのサニタイズメソッド色々

https://api.rubyonrails.org/classes/ActiveRecord/Sanitization/ClassMethods.html

# joinsで条件を指定する方法

```ruby
# sanitize_sqlを使う必要がある
joins(sanitize_sql(['LEFT OUTER JOIN hoges ON hoges.id = fugas.hoge_id AND hoges.status = ?', status])
```

# mergeでorが使える

1つのmergeで生成したorは()でくくられ、1つのグループとして扱われる。

```ruby
# users.activated = true OR users.confirmed = true
merge(User.activated.or(User.confirmed))
```

# ActiveRecordで更新前の値を取る方法

save前

```ruby
hoge.will_save_change_to_attribute?
hoge.attribute_in_database
```

save後

```ruby
hoge.saved_change_to_attribute?
hoge.attribute_before_last_save
```

# sort, sort_by

```ruby
# col1の降順, col2の昇順で並び替え

# sortの場合はaとbの順序で制御する
array.sort do |a, b|
  (b[:col1] <=> a[:col1]).nonzero? || (a[:col2] <=> b[:col2])
end

# sort_byの場合はマイナスにするといいらしいけど、数値じゃないとできない
# 日付などでもto_iすれば使える
array.sort_by do |a|
  [-a[:col1], a[:col2]]
end
```

ドキュメントにソートが一位にならないときのやり方が書いてあった。
https://docs.ruby-lang.org/ja/latest/method/Enumerable/i/sort_by.html

> Enumerable#sort_by は安定ではありません (unstable sort)。ただし、sort_by を以下のように使うと安定なソートを実装できます。

```ruby
i = 0
ary.sort_by {|v| [v, i += 1] }
```

# in_order_of

配列やActiveRecordで使える

```ruby
# idが2, 3, 1の順番に並び替えられる
[Post.find(1), Post.find(2), Post.find(3)].in_order_of(:id, [2, 3, 1]) 

# MySQLだとFIELDが使われる。
> Post.in_order_of(:id, [3, 5, 1])
SELECT "posts".* FROM "posts" ORDER BY FIELD("posts"."id", 1, 5, 3) DESC
```

# spec

## mock
https://qiita.com/ham0215/items/f0564c9933e573834eae

## 時間を止める
`travel_to`を使う

```ruby
travel_to(Time.now.prev_month) do
  # 時間が止まる
end
```

https://api.rubyonrails.org/v5.1.7/classes/ActiveSupport/Testing/TimeHelpers.html

テスト中はずっと止める場合は`travel 1.minute`のようにブロックにしなくても良い。テストが終わったら解除されるようだ。