Railsの地雷メソッド TOP3  #omotesandorb

表参道.rb #25 ~ Rails アンチパターン ~

https://omotesandorb.connpass.com/event/62936/

---

<img width="637" alt="ito.png" src="https://qiita-image-store.s3.amazonaws.com/0/30459/30981dd2-1791-0f57-f0d1-b94059e0ece5.png">

---

<img width="826" alt="comment.png" src="https://qiita-image-store.s3.amazonaws.com/0/30459/49819a46-d7e7-c780-4068-0c4ec4ff1fbe.png">

---

## ダメ例

```rb
class AddBirthdayToUser < ActiveRecord::Migration[5.0]
  def change
    add_column :users, :birthday, :datetime

    User.find_each do |user|
      # 後から `age` が無くなったらエラーになる
      user.update!(birthday: user.age.years.ago)
    end
  end
end
```

---

## 自己紹介

名前: sinsoku
会社: 株式会社grooves
github: [sinsoku](https://github.com/sinsoku)
twitter: [@sinsoku_listy](https://twitter.com/sinsoku_listy)

---

ポートフォリオサービス [Forkwell Portfolio](https://portfolio.forkwell.com/)

<img width="701" alt="top.png" src="https://qiita-image-store.s3.amazonaws.com/0/30459/5abbbd23-816a-abd6-fda3-e60dbd1ff8e4.png">

---

## 地雷メソッド

![fedcfb5d583f9c58a74ba1c9cdd3fc45_s.jpg](https://qiita-image-store.s3.amazonaws.com/0/30459/0b0cd7e1-9c2d-8a09-c600-02c3e43739d6.jpeg)

---

- TOP 3
  1. default_scope
  2. accepts_nested_attributes_for
  3. Active Record Callbacks
- おまけ
  - belongs_to
  - validates_uniqueness_of

---

## default_scope

```rb
class Article
  default_scope { where(published: true) }
end

Article.new.published    # => true
Article.create.published # => true

Article.all
# => SELECT * FROM articles WHERE published = true
```

---

## has_many

```rb
class User
  # せめて :published_articles だったら...
  has_many :articles, -> { where(published: true) }
end
```

---

## 問題点

- admin 画面など後で外すケースが意外と多い
- 一般的な名前を使われると直すのがツラい
  - `has_many :_articles` が生まれる危険性
- joins, merge にも当然だけど影響する
  - （昔は order にも影響した気がする）

```rb
User.joins(:articles).group(:xxx).count
Aritcle.merge(user.articles).count
```

---

## accepts_nested_attributes_for

```rb
class Member < ActiveRecord::Base
  has_one :avatar
  accepts_nested_attributes_for :avatar
end

params = {
  member: {
    name: 'Jack',
    avatar_attributes: { icon: 'smiling' }
  }
}
member = Member.create(params[:member])
member.avatar.id # => 2
member.avatar.icon # => 'smiling'
```

---

## Strong Parameters

```rb
def member_params
  params.require(:member)
    .permit(:name, avatar_attributes: [:icon, :_destroy])
end
```

---

## fields_for

```erb
<%= form_for(@member) do |f| %>
  <%= f.text_field :name %>
  <%= f.fields_for :avatar do |avatar_fields| %>
    <%= avatar_fields.text_field :icon %>
  <% end %>
<% end %>
```

---

## 問題点

- だいたい複雑になってツラくなる
- Callback やバリデーションの順序が複雑
- 解決方法は難しい...
  - サービスクラスにする？

---

## そのうち消える運命

<img alt="dhh.png" src="https://qiita-image-store.s3.amazonaws.com/0/30459/005d728f-5173-c858-d5d8-31c0f7dba754.png">

https://github.com/rails/rails/pull/26976#discussion_r87855694

---

## Active Record Callbacks

```rb
class User
  before_validation :set_age_from_birthday
  before_create :build_user_profile
  after_create :notify_signup_to_admins
end
```

---

## 問題点

- 1つの save で連鎖的に Callback が発火する
  - factory_girl がたまに死ぬ
- テストで無駄にレコードが増える
- テストで無駄にメールが（ｒｙ
- callback が増えてきたら注意
  - サービスクラスなど別クラスにした方が良い

---

## 解決例

```rb
def create
  @user = User.new(user_params)

  if @user.save
    UserCreatedJob.perform_later(@user)
    redirect_to user_path(@user)
  else
    render :new
  end
end
```

```rb
class UserCreatedJob < ApplicationJob
  def perform
    # do something
  end
end
```

---

## おまけ: 暗黙的にSQLが使われるメソッド

- belongs_to
  - belongs_to_required_by_default = true
- validates_uniqueness_of

大量の INSERT があるレコードの場合は問題になることもある。

---

## 技術書典３

日時: 10/22(日) 11:00〜17:00
場所: アキバ・スクエア

<img width="633" alt="twitter.png" src="https://qiita-image-store.s3.amazonaws.com/0/30459/83719111-d884-815f-5370-4be9ff343844.png">