Help us understand the problem. What is going on with this article?

Railsの地雷メソッド TOP3 #omotesandorb

More than 3 years have passed since last update.

Railsの地雷メソッド TOP3 #omotesandorb

by sinsoku
1 / 22

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

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


ito.png


comment.png


ダメ例

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
twitter: @sinsoku_listy


ポートフォリオサービス Forkwell Portfolio

top.png


地雷メソッド

fedcfb5d583f9c58a74ba1c9cdd3fc45_s.jpg


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

default_scope

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

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

問題点

  • admin 画面など後で外すケースが意外と多い
  • 一般的な名前を使われると直すのがツラい
    • has_many :_articles が生まれる危険性
  • joins, merge にも当然だけど影響する
    • (昔は order にも影響した気がする)
User.joins(:articles).group(:xxx).count
Aritcle.merge(user.articles).count

accepts_nested_attributes_for

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

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

fields_for

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

問題点

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

そのうち消える運命

dhh.png

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


Active Record Callbacks

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 がたまに死ぬ
  • テストで無駄にレコードが増える
  • テストで無駄にメールが(ry
  • callback が増えてきたら注意
    • サービスクラスなど別クラスにした方が良い

解決例

def create
  @user = User.new(user_params)

  if @user.save
    UserCreatedJob.perform_later(@user)
    redirect_to user_path(@user)
  else
    render :new
  end
end
class UserCreatedJob < ApplicationJob
  def perform
    # do something
  end
end

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

  • belongs_to
    • belongs_to_required_by_default = true
  • validates_uniqueness_of

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


技術書典3

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

twitter.png

sinsoku
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away