世の中の1%の人の為になるかもしれないRails tips

# はじめに
本記事は[Zeals Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zeals)の24日目です。メリークリスマスイブ！

本記事では僕が日々開発しているなかで、どうしてもなんとかしなければいけない…という時に活用したtipsをまとめたものです。
タイトルにある通り、エッジケースすぎるのでもし同じような状況で困っている人の為になれたら幸いです。

# 環境
Rails 5.2.3
Ruby 2.5.5

# tips
といっても今回紹介するのは2つです

- カラムにaliasをかける
- caller

## カラムにaliasをかける
Railsではcolumnに対してaliasを貼ることができます。

| モデル名 | カラム名 |
|:-----------------:|:------------------:|
| Button | name |
| Choice | label |

このようなモデルが2つあるとします。
カラム名はそれぞれ違いますが同じものとして扱いたい時があると思います。
そんな時は[alias_attribute](https://apidock.com/rails/Module/alias_attribute) が使えます。

```ruby
class Choice < ActiveRecord::Base
  alias_attribute :name, :label
```

`alias_attribute`を使うことでChoiceの`label`を`name`として扱うことができるようになります。
ただ`name`として扱うことができるようになるだけではなく

```ruby
Choice.first.name?
=> true
```

といったActiveRecordの述語メソッドやゲッターやセッターを生成してくれます。
ただ同名として扱いたいだけで、述語メソッドを生成するまでもない場合は以下のようにシンプルに定義するだけで良さそうです。

```ruby
class Choice < ApplicationRecord
  def name
    label
  end
```

カラム名を変更したい。変更したほうが良い。でも`rename_column`してる時間ないよぉ…
という時に使いました、便利ですねー

## caller

| モデル名 | カラム名 |
|:-----------------:|:------------------:|
| User | cliend_id |

| モデル名 | カラム名 |
|:-----------------:|:------------------:|
| Answer | client_id |

| モデル名 | カラム名 | カラム名 |
|:-----------------:|:------------------:|:------------------:|
| UserAnswer | user_id | answer_id | 　　　　　　　

このような中間テーブルがあるとします。
以下はmodel

```ruby
class UserAnswer < ApplicationRecord
  belongs_to :user
  belongs_to :answer

  validates :should_be_same_client

  def self.import_associations!(users, answer)
    associations = users.map do |user|
      UserAnswer.new(user_id: user.id, answer_id: answer.id)
    end
    UserAnswer.bulk_import! associations, validate: false
  end


  def should_be_same_client
    return user.client == answer.client
    
    errors[:base] << ' client_id of user and client_id of answer are different'
  end
end
```

定義されている
`import_associations!`は引数としてuserの配列と指定のanswer(回答)を受け取ります。

カスタムのvalidationメソッドが定義されており、`User`も`Answer`も`Client`に対して`belongs_to`の関係で、`client_id`が違うものが作成されないようにしています。

`should_be_same_client`はUserAnswer単体を生成する時は動いてほしいものですが、`bulk import`で作成したい時はvalidationをskipしたい。カスタムvalidationも動いてほしくないとします。

Railsのversionは5.2系なので(早く6に上げたい…)bulk importは[activerecord-import](https://github.com/zdennis/activerecord-import)を利用します。

bulk_importはoptionで`validate: false`を渡すことでmodelのvalidationをskipさせることができます。
しかし今回定義したようなカスタムvalidationはskipしてはくれません。
bulk importする件数が数件であれば都度カスタムvalidationが走ったところで問題はないのですが、件数が多くなればなるほどN+1で都度カスタムvalidationが走るにより実行時間がどんどん長くなるので避けなければなりません。

つまり`bulk_import`を呼び出すメソッドのときのみカスタムvalidationをskipする必要があります。

そこで[caller](https://docs.ruby-lang.org/ja/latest/method/Kernel/m/caller.html)を使いました。
callerはバックトレースの情報を確認することができるので、**どのメソッドから呼び出されたのか,どのメソッドを経由してきたのかを確認することができます**

callerで呼び出されたバックトレースを正規表現を使って、メソッド名だけを見れるようにします

```ruby
caller.map { |c| c[/`([^']*)'/, 1] }
```

```sh
=> ["eval",
 "evaluate_ruby",
 "handle_line",
 "block (2 levels) in eval",
 "catch",
 "block in eval",
 "catch",
 "eval",
 "block in repl",
 "loop",
 "repl",
 "block in start",
 "__with_ownership",
 "with_ownership",
.......
```

これでどのメソッドを経由してきたのかがわかります。
取得したバックトレースないにvalidateをskipしたいメソッドが含まれているかを確認するメソッドを追加し、

```ruby
def call_from_import_associations?
  caller.map { |c| c[/`([^']*)'/, 1] }.include?('import_associations!')
end
```

```ruby
validates :should_be_same_client, unless: call_from_import_associations?
```

カスタムvalidationの発火条件として追加し、`import_associations!`から呼び出されるときのみカスタムvalidationが発火しないようにすることができます。


# まとめ

実際にこのユーザーに対してtagを一括でつける機能は使用頻度の高い機能だったので、早急に修正する必要があり、本当になんとかするためにひねり出した苦肉の策ではあります…w

早急に対応しなければいけない。なんとかしなければいけない。という同じような問題に詰まっている人たち、もしくは同じようなケースで困っているの助けに少しでもなっていれば幸いです！

明日はついにAdvent Calendarの最終日です！
最終日は[@pannpers](https://qiita.com/pannpers)です！
それでは良いクリスマスイブを！