読者です 読者をやめる 読者になる 読者になる

アトラシエの開発ブログ

株式会社アトラシエのブログです

gem使わなくても簡単にできるrailsのtips

それgem使わなくてもできるよまとめ

gemは便利なのですが闇雲に入れるべきものでもありません。とくに用途が限られていて、わざわざgemを使うまでもないことや使用目的以上にgemが巨大な場合は簡単に自分で代替物を作ったほうがメンテナンスやコントロールが容易な場合もあります。

Devise

Deviseのような認証機構はシンプルなものであればこういったsessionにuser_idを格納するだけで十分です。

user.rb

class User < AR::Base
  has_secure_password
end

session_controller.rb

if user.authenticate(params[:password])
  sign_in(user)
end

application_controller.rb

def sign_in(user)
  session[:user_id] = user.id
end

def sign_out
  session[:user_id] = nil
end

def current_user
  User.find_by(id: session[:user_id])
end

def user_sign_in?
  !!current_user
end

Railsのsessionはデフォルトではcookieに暗号化された値として保存されます。基本的にuser_idのような固有値を入れてもクライアント側では復号化できないはずです。(secrets.ymlの値があれば復号できると思います)

セキュリティ上の優先順位としては SSL通信にする、cookieにsecure属性を付与する ことが重要で、理想的にはcookie storeではなくmemcacheなどをサーバ側に用意することでより安全になります ^1

仮にDeviseの主要機能であるメール認証、パスワードリセット、ロックなどをすべて実装するにしても自分で作ってもさほど難しくない上保守性が高いのでオススメです。(というよりDeviseを使っても結局Deviseのコントローラをオーバーライドする羽目になる気がします)

friendly_id

user.rb

class User < AR::Base
  before_create :set_uuid

  private

  def set_uuid
    self.uuid = SecureRandom.hex(10)
  end
end

routes.rb

get 'users/:uuid' => 'users#show', as: :user

users_controller.rb

def show
  @user = User.find_by(uuid: params[:uuid])
end

view.html.slim

= link_to user.name, user_path(user.uuid)

Railsのデフォルトである連番idが嫌な場合にfriendly idを使うケースがあると思いますが、単にルーティングをいじればいいのでこっちのほうが簡単です。 とりあえず適当な値が欲しいならSecureRandomを使えばまず大丈夫です。

secure randomで作った値を使うほうが権限チェックのミスがあっても漏洩リスクを多少緩和できると思います。

連番idのとき

/secure_photos/3

# 本当はcurrent_userから引っ張らないとダメ
# current_user.secure_photos.find(params[:id])
# 
SecurePhoto.find(params[:id])

/secure_photos/7997a08d65b56628

SecurePhoto.find_by!(uuid: params[:uuid])

といっても本気でアタックする人にとっては意味がないのでしっかり権限チェックしてください。

Draper

Draperのすべての仕組みを理解していませんが、SimpleDelegatorを使うだけで十分な気がしています。

user_decorator.rb

class UserDecorator < SimpleDelegator
  def h1_title
    "#{name}のページ"
  end
end

user.rb

include Decoratable

app/models/concerns/decoratable.rb

module Decoratable
  def decorate
    klass = begin
              "#{self.class.to_s}Decorator".constantize
            rescue NameError
              "#{self.class.base_class.to_s}Decorator".constantize
            end
    klass.new(self)
  end
end

これだけで使う時は@user.decorateとするだけでデコレータのクラスに変換されます。 この方法がいいのは自分でデコレータのクラスと生のモデルクラスをコントロールできる点、軽い点、適宜デコレータを増やしてdecorateメソッドを変えればモデルとデコレータを1対多対応させられる点にあります。

各viewやAPIごとにdecoratorを分割し、デコレータが必要な文脈ごとに変換をかけるのは少し手間ですが、暗黙に変換されると挙動がよくわからなくなることがあります。

画像アップロード

これは上記とは仕組みが違いますが、carrierwaveやminimagickを使わなくても画像をアップロードする仕組みは作れます。

簡単に言えば画像自体を受け取らず、アップロード先を別に持ってURLや識別子だけ受け取って保存すればいいのです。

Cloudinary

cloudinary.com

このようなサービスを使えば画像のストレージとサイズ変換などの面倒をすべて見てくれるので、gemでいえばcarrierwave, minimagick, fogあたりを省略できます。

まとめ

以上、少しやってみればgemを使わなくても労を少なく、下手をするとgemを使うより簡単に必要なことができることがあります。 gemを入れ過ぎるとgem同士がバッティングしたりバージョンアップに苦労します。もちろんgemのようにメンテナンスが繰り返されるソフトウェアには一定のメリットがあるので必要な局面では使うべきでしょう。例を上げればStateMachineは一見自分で実装可能なようで、ライブラリに依存したほうが確実に見通しがいいケースが多かったです(あくまで私の経験です)。