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

【Ruby】【Rails】RAILS ANTIPATTERNS、chapter3 viewのまとめ

目的

RAILS ANTIPATTERNSをのchapter3(view)の箇所を読んだのでまとめました。

Rails AntiPatterns: Best Practice Ruby on Rails Refactoring (Addison-Wesley Professional Ruby Series)

Rails AntiPatterns: Best Practice Ruby on Rails Refactoring (Addison-Wesley Professional Ruby Series)

PHPitis(PHPっぽくなりがち)

ドメインロジックとか複雑な表示ロジックとか、全部ビュー(app/viewsの下にあるファイル)に書かれがちだよねという話です。

解決策:View Helper勉強しよ!

詳しくは書かないけど、form_for, render, content_for, yield(:symbol) || 'default', content_for?について書かれていました。
form_forは本当にいろいろ使えてすごいのだけど、まだまだ使い切れていない感がある。
content_forの並びで言うと、provideもぜひ使いたいですね。

http://api.rubyonrails.org/classes/ActionView/Helpers/CaptureHelper.html#method-i-provide

解決策:いい感じにモデルにメソッド追加しよ!

以下のようなコードがあるとする。

<% if current_user &&
      (current_user == @post.user ||
      @post.editors.include?(current_user)) && @post.editable? &&
      @post.user.active? %>
  <%= link_to 'Edit this post', edit_post_url(@post) %>
<% end %>

これはモデルにメソッドを定義してあげて、view側は以下のような形にする。

<% if @post.editable_by?(current_user) %>
  <%= link_to 'Edit this post', edit_post_url(@post) %>
<% end %>

すっきり!

メソッドを定義する場所としては、まあまあ当たり前だけどコントローラで使う場合はモデル、そうじゃなければhelperに置くと書いてあった。

そのif文、helperに移動しよ!

app/views/alerts/index.html.erbが以下のようになっているとする。

<div class="feed">
  <% if @project %>
    <%= link_to "Subscribe to #{@project.name} alerts.", project_alerts_url(@project, :format => :rss), :class => "feed_link" %>
  <% else %>
    <%= link_to "Subscribe to these alerts.", alerts_url(format => :rss), :class => "feed_link" %>
  <% end %>
</div>

この場合はapp/helpers/alerts_helper.rbを作って以下のようにメソッドを追加する。(若干仕様変わってる?)

module AlertsHelper
  def rss_link(project = nil)
    link_to("Subscribe to these #{project.name if project} alerts.",
            alerts_rss_url(project),
            class: 'feed_link')
  end

  def alerts_rss_url(project = nil)
    if project
      project_alerts_url(project, format: :rss)
    else
      alerts_url(:rss)
    end
  end
end

view側のapp/views/alerts/index.html.erbは以下。

<div class="feed">
  <%= rss_link(@project) %>
</div>

いつも<div class="feed">が必要な場合は、content_tagを使ってhelperにうつしてしまってもいいとありました。
その場合はlink_toじゃなくてcontent_tag :aをあえて使ってもいいと書いてました。

ちょっとした感想

最後の、「helperに移動しよ!」的な話はいつも悩むところなのと、helperメソッドでcontent_tagをもりもり書くのなら、パーシャルでいいのでは?と思ったりします。
でも今回の= rss_link(@project)が、= render rss_link, project: @projectになるのはイマイチ感がありますね…なんでだろ。

viewにかかわらず、Railsはやりたいことを色んな方法で実装することができるので、一人で作っているときでさえ統一感がなくなることが有ります。rubocopで潰せるところはいいけど、自分が統一感を持って実装出来ているかどうかたまに振り返りながらすすめていきたいですね。

【Rails】【RSpec】【shoulda-matchers】validate_uniqueness_ofのscoped_toではまったときのまとめ

目的

shoulda-matchersでモデルのバリデーションのテストを書いていたときに、Railsアトリビュートメソッドのキャッシュの動きで若干はまったため、簡単にまとめる。

github.com

shoulda-matchersの簡単な導入

shoulda-matchersはgemで、RSpecやMinitestにvalidate_presence_ofなどのワンライナーのテストのシンタックスを追加するもの。
詳しい話、やその他のテストメソッドに関しては、本家のreadme参照。(知らない機能がたくさんある!)

Railsアトリビュートメソッドのキャッシュ機能

メタプログラミングRubyを読んでいただいた方はわかるかもしれないが、Rails4系・5系のコードはメソッドの定義をキャッシュすることでパフォーマンスの向上を図っている。
詳しい実装には触れないが、具体的には以下のような動作になる(Rails 5.0.0.1, Ruby 2.3.1)

2.3.1 (main):0 > User.method_defined?(:name)
=> false
2.3.1 (main):0 > User.new
=> #<User id: nil, name: nil, role_id: nil, created_at: nil, updated_at: nil, email: "">
2.3.1 (main):0 > User.method_defined?(:name)
=> true

shoulda-matchersのvalidate_uniqueness_ofメソッドのscoped_toチェーン

モデルでのバリデーションを以下のように書く

class Phone < ActiveRecord::Base
  validates :phone, uniqueness: true
end

shoulda-matchersでテストを書くと以下のように書ける

require 'rails_helper'

describe Phone do
  it { is_expected.to validate_uniqueness_of(:phone) }
end

さらに、複数カラムでユニークになるようにバリデーションをすることもでき

class Phone < ActiveRecord::Base
  validates :phone, uniqueness: { scope: :contact_id }
end

shoulda-matchersで書くとこうなる

require 'rails_helper'

describe Phone do
  it { is_expected.to validate_uniqueness_of(:phone).scoped_to(:contact_id) }
end

しかし、上記のテストは(他のテストにもよるが、)コケる。

なぜテストが失敗するのか

以下のようなメッセージが出る

:contact_id does not seem to be an attribute on Phone.

ん?カラムあるよ? これでしばらく進まなかった。 実際は、scoped_toで指定されたカラムは、以下のようにshoulda-matchersのなかでmethod_defined?を使ってチェックしている。

def attribute_present_on_model?
  model.method_defined?("#{attribute}=") ||
    model.columns_hash.key?(attribute.to_s)
end

試しにbinding.pryでとめ、contact_id=を実行してみてからテストを流すとうまくいった。 メソッドのキャッシュのタイミングはバージョンによって異なるので、最新版であれば大丈夫かもしれません。

まとめ

感想

  • gemのソースを読むのはなかなか楽しい。
  • 今回のがバグなのかわからないが、最新版を使ってバグを踏んで、PR送ろう!

【Rails】権限管理のgem「pundit」を使ってみたときのまとめ

目的

punditというgemを使う際に「Rubyist Magazine - 権限管理のgem、Punditの紹介」を参考に作ったときのまとめ。

railsapp/policiesディレクトリって何を入れるのかというところを前回調べたのですが、代表的なものとしてpunditというgemを使うと出て来るというのが代表的な例なようなので、使ってみました。
【Rails】policiesディレクトリの使い方 - ITの勉強をいろいろやってみたブログ

るびまを参考にして作ったときに考えていたこと等をまとめてみます。

まとめ

ApplicationPolicyのサブクラスの初期化時には、recordではなくclassを渡す

rails g pundit:installを実行すると、app/policies/application_policy.rbが作られるのだが、初期実装は以下のようになっている。

class ApplicationPolicy
  # ...
  def initialize(user, record)
    @user = user
    @record = record
  end
  # ...
end

これだと、レコードを渡すことで「自分のレコードは更新できる。他の人のレコードは閲覧だけ。」みたいなレコード単位の権限をつけられる。
それはそれで便利なのだけど、社内用の管理画面等はレコード単位ではなくクラス・モデル単位での権限がついていれば十分。なので、以下のようにクラスを受け付ける想定にしておいて、その他も適宜書き換える。(モンキーパッチを当てる必要はなし)

class ApplicationPolicy
  # ...
  def initialize(user, klass)
    @user = user
    @klass = klass
  end
  # ...
end

ApplicationControllerには以下のようなメソッドを準備しておく。

class ApplicationController < ActionController::Base
  # ...
  def pundit_auth
    authorize controller_name.classify.constantize
  end
end

UsersControllerbefore_actionを呼ぶ

class UsersController < ApplicationController
  before_action :pundit_auth
  # ...
end

すっきり。

cancancan vs punditはRSpec vs minitestと似ている気がする

cancancanはAbilityというクラスを定義して、その中にDSLをわーっと書いて権限設定を定義します。
それに対しpunditはピュアなObjectを継承したpureなRubyのクラスで定義し、コントローラで呼ぶためのメソッドを提供しているだけです。

このような観点の比較はRSpec vs minitestのときの議論と似ているかなと思っています。
MinitestとRSpec、FixturesとFactoryGirlの良いところ悪いところをコードを書いて比較してみた - give IT a try

RSpec vs minitestのときはテストコードの話なのですが、権限は実際のアプリケーションの設計に関わってくる所なので、妥協したりプラグインに引っ張られた設計になるのは避けたいところです。
複雑な仕様がある場合にはcancancanにモンッキーパッチをあてる等して使うより、自由に書けるpunditを使っておくのも悪くない選択かなと思いました。

TODO

  • cancancanで同じことをやる
  • 認証はdeviceを使ってやったが、自前で実装してみる

【Rails】policiesディレクトリの使い方

目的

railsapp/policiesディレクトリを作るときに、中身には何を入れるのかまとめる。

パーフェクトRuby on Railsの9章を読んでいて、コールバック・バリデーションをモデルから分離して、独立したクラスを作る方法を学びました。

パーフェクト Ruby on Rails

パーフェクト Ruby on Rails

それらはappディレクトリ以下に、callbacksvalidatorsという他にも、app配下に置いておくものとしてpoliciesというディレクトリが有るようです。
ただし、調べてみるとpoliciesディレクトリの使い方は、そこまで一般解があるように思えなかったので、一旦まとめておいて今後検討する材料にしようかと思いました。

policiesディレクトリの使い方概要

  1. punditを使う(わりと一般的?)
  2. cancancanのavility.rbを、modelに入れずにpoliciesに入れる(avilitiesでもいいと思う)
  3. 特にgem等は使わず自分でピュアRubyのオブジェクトを定義して入れる
  4. policyっていうgemを使う(3をちょっと便利にした感じのもの。メンテされてる??)

punditを使う

app/policiesは以下のようなクラスが入ります

# app/policies/post_policy.rb
class PostPolicy
  attr_reader :user, :post

  def initialize(user, post)
    @user = user
    @post = post
  end

  def update?
    user.admin? or not post.published?
  end
end

punditの使い方に関しては以下を
Rubyist Magazine - 権限管理のgem、Punditの紹介 参考
Railsを責務に応じてリファクタリングするための9つのパターン | Simplie Post

gemを自然に使うとこうなるため、サンプルには事欠かなさそう。

cancancanのavility.rbを、modelに入れずにpoliciesに入れる

app/models/avility.rbが肥大化してイヤだ!というときにapp/policiesに入れるという選択肢があるらしい。
中規模Web開発のためのMVC分割とレイヤアーキテクチャ - Qiita
Separating abilities in CanCan | The Frontier Group Journal
サンプルがあまり多くないので、上記ふたつをよく読んでから

特にgem等は使わず自分でピュアRubyのオブジェクトを定義して入れる

ちょっと古めの記事ですが、以下の記事で出て来るPolicy Objectのことです。
7 Patterns to Refactor Fat ActiveRecord Models - Code Climate Blog

典型例は以下でしょうか
Policy Object – Medium

punditとpolicy objectがおなじpolicyでややこしいから、名前を変えたよという話?
Authorizers, Extractors, and Policy objects

policyというgemを使う

メンテされてなさそうですが、自分で作ったpolicy objectとcontrollerの橋渡しをしてくれるようなgemのようです
Rails - The Missing Parts - Policies | Grouper Engineering Blog

まとめ

今後の方針としては以下のような形で使い分けていきたい

  • アクションベースのシンプルな権限管理にはpundit
  • ロールベースの権限管理はcancancanを使う。app/policiesは必要になったら作る
  • 条件が複雑なロジック(アドやレコメンドを出すかどうかみたいな?)がたくさんある場合にはpolicy objectを作る。たくさん無いときはserviceとかmodelに入れれば良さそう

gemの使い方はgemに合わせるわけですが、そうでない場合はcallback、validators同様、そのロジックが業務上重要で複雑な場合は、早めにクラスをわけてメンテし易いように保ちましょう、という話でした。

【パーフェクトRails】バリデーションをクラスに分離する

目的

パーフェクトRuby on Railsの9章の9−2「複雑なバリデーションとコールバックを整理する」を読んでいて 「バリデーションをクラスに分離する」について、知らないことがいくつかあったのでまとめます。

パーフェクト Ruby on Rails

パーフェクト Ruby on Rails

バリデーションをクラスに分離するのは必要?

メールとか氏名とか、プロジェクトで要件が共通になると思うので積極的にやって良いと思う。 Open Source Rails でもapp/validatorsは結構見かける。

どうやってやるのか?

例によってサンプルを見るのがわかりやすいと思います。

EachValidatorを使う

# app/validators/email_validator.rb
class EmailValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    record.errors.add(attribute, 'is not an email') unless value =~ /.+@.+/
  end
end

上記の通りActiveModel::EachValidatorを継承してvalidate_eachを定義すればいい。
呼ぶときは以下のように呼ぶ。

class Person
  include ActiveModel::Validations
  attr_accessor :email, :name

  validates :email, email: true
end

validates :email, email: trueは「emailというフィールドに対して、EmailValidator#validate_eachでチェックしてください。」というような解釈で良いと思う。EmailValidatorを見ると引数をがどのように渡ってくるかがわかると思う。

Validatorを使う

# app/validators/multi_presence_validator.rb
class MultiPresenceValidator < ActiveModel::Validator
  def validate(record)
    record.errors.add(:base, 'bust be presence') if options[:attributes].all? { |c| record.send(c).blank? }
  end
end

ActiveModel::Validatorを継承してvalidateメソッドを定義する。
呼び出し方は以下。

class Person
  include ActiveModel::Validations
  attr_accessor :email, :name

  validates_with MultiPresenceValidator, attributes: [:email, :name]
end

validates_with MultiPresenceValidatorは見たとおりMultiPresenceValidator#validateでバリデーションをする。

attributes: [:email, :name]という引数に関しては、ActiveModel::Validatorインスタンスからoptions[:attributes]とすればさわれるようになっている。

validates_with Validator1, Validator2と書くと両方のバリデーションを付け加えることができる http://apidock.com/rails/ActiveModel/Validations/ClassMethods/validates_with

EachValidatorとValidatorの違い

EachValidatorは、上のサンプルの呼び出し方にある通り、1つのフィールドをチェックするやりかた(もちろん複数のフィールドをチェックするように書くことも出来なくはない)になる。

Validatorは複数のフィールドをチェックするのに使う。(こちらも1つのフィールドだけをチェックチェックするのは問題なく出来る。)

EachValidatorValidatorのサブクラスになっている。

その他

これらの自作バリデーションや標準のバリデーションに関しては、モデルクラスごとにオブジェクトを持つ。インスタンス単位ではない。したがって、状態を持たないようにすること。

まとめ

  • app/valiadtorsディレクトリはわりと一般的になっているので、プロジェクトたちあげのときに追加してバリデーションのクラス化を促すのが良いと思う。

  • 1フィールドのときは、ActiveModel::EachValidatorを継承してvalidate_eachメソッド定義

  • 複数フィールドのときは、ActiveModel::Validatorを継承してvalidateメソッド定義

  • PresenceValidatorsの実装とか読むといいと思う

【パーフェクトRails】コールバックをクラスに分離する

目的

パーフェクトRuby on Railsの9章の9−2「複雑なバリデーションとコールバックを整理する」を読んでいて コールバックをクラスに分離する場面ややり方がよくわからなかったのでまとめる。

パーフェクト Ruby on Rails

パーフェクト Ruby on Rails

はじめにさらっと今回の主張だけ述べて、本編に突入します。

コールバックをクラスに分離するのは必要?

  1. あってもいいとは思うけど、作る機会は少ないかも。
    作る場合はapp/modelsapp/servicesに入れておけばいいかな

「コールバックをクラスに分離する」とは何か?

Railsのドキュメントにあったソースをそのまま載せてしまいます。

class BankAccount < ActiveRecord::Base
  before_save      EncryptionWrapper.new("credit_card_number")
  after_save       EncryptionWrapper.new("credit_card_number")
  after_initialize EncryptionWrapper.new("credit_card_number")
end

class EncryptionWrapper
  def initialize(attribute)
    @attribute = attribute
  end

  def before_save(record)
    record.send("#{@attribute}=", encrypt(record.send("#{@attribute}")))
  end

  def after_save(record)
    record.send("#{@attribute}=", decrypt(record.send("#{@attribute}")))
  end

  alias_method :after_initialize, :after_save

  private
    def encrypt(value)
      # Secrecy is committed
    end

    def decrypt(value)
      # Secrecy is unveiled
    end
end

before_save EncryptionWrapper.new("credit_card_number")って書くとbefore_saveのときにEncryptionWrapper.new("credit_card_number").before_save(self)が呼ばれます。
つまり、渡されたオブジェクトに対してbefore_saveを委譲できるという機能というかapiです。

みんなやっているのか?

ひとまず参考資料をさがしてみることに。
ActiveRecord::Callbacks
9.2 Callbacks | Advanced Active Record in Rails 4 | InformIT
てめえらのRailsはオブジェクト指向じゃねえ!まずはCallbackクラス、Validatorクラスを活用しろ! - Qiita
ruby - Rails: monkey-patching ActiveRecord::Base vs creating a Module - Stack Overflow

以下の、オープンソースrailsプロジェクトがまとまっているサイトがあるのですが、コールバック用クラスの実装にであえませんでした。
Open Source Rails

GitLabで似たようなことをやっているようなソースも有りましたが、細かいところまでは読めませんでした。

今後どうしていくか

コールバックを別クラスに書くことは悪くはないのだが、そもそもコールバックの処理が肥大化していきそうで、あんまり良くないかなと思っている(ユーザ側に関係ない処理であれば良い)。
加えて、既存のrailsプロジェクトであまり行われていなさそうということも今回の調査でわかった。
よって、app/callbacksみたいなディレクトリは作らず、もしコールバックの処理が複数のクラスで共通になったり、肥大化していった場合には、いったんapp/modelsに作る。

【Ruby】【キャッシュ】dalli(memcachedのクライアントgem)を使ってみました

目的

memcachedrubymemcachedクライアントであるgemのdalliを使ってみたのでまとめ

memcached.org github.com

少し前に、キャッシュについていろいろやらないといけない機会があったのですが
キャッシュを実現するミドルウェアを触ったことがなかったため、手も足も出ないような状況になってしまいました。

これを期にキャッシュに使われるミドルウェアを触っていこうと思い
手始めにmemcachedと、rubyクライアントの中で割りと広く使われているdalliを触ってみました。 基本的な使い方と、覚えておけばどこかで役立ちそうな使い方についてまとめてみます。

基本的な使い方(インストールから)

インストールはgemのreadmeに親切に書いてあるのでその通りにやりましょう

$ brew install memcached
$ gem install dalli

使いかたもreadmeと一緒ですが、irbで以下のように叩くと試せます。

irb(main)> require 'dalli'
irb(main)> options = { namespace: 'app_v1', compress: true }
irb(main)> dc = Dalli::Client.new('localhost:11211', options)
irb(main)> dc.set('abc', 123)
irb(main)> dc.get('abc') # => 123

deleteしたり、有効期間を指定してsetするとかが出来ます

irb(main)> dc.set('abc', 123)
irb(main)> dc.delete('abc')
irb(main)> dc.get('abc') # => nil
irb(main)>
irb(main)> dc.set('abc', 123, 5)
irb(main)> dc.get('abc') # => 123
irb(main)> sleep 6
irb(main)> dc.get('abc') # => nil

覚えていて役立ちそうな使い方1(get_multi)

1つのキーに対するget、set、deleteはできるけど、複数キーはどうなの?という話。 memcachedのコマンドだと同じgetでできるみたいです。

github.com

dalliの場合はDalli::Client#get_multiを使います。

irb(main)> dc.set('abc', 123)
irb(main)> dc.set('def', 456)
dc.get_multi('abc', 'def') # => {"abc"=>123, "def"=>456}

DBと組み合わせて使うときに、N+1対策で使えそうです。

覚えていて役立ちそうな使い方2(cas)

Check And Set (or Compare And Swap)の略で、memcachedで楽観的ロックを実現するための機能です

irb(main)> dc.set('a', 'value1')
irb(main)> dc.cas('a') { 'value2' }
irb(main)> dc.get('a') # => value2
irb(main)> dc.cas('a') { |v| sleep 10 "#{v}+1" } # sleepの間に別コンソールでdc.set('a', 'value4')を実行する
=> false
irb(main)> dc.get('a') # => value4

casメソッドのブロックの中で行う処理に関しては、他者がそのキーの値を変更していない場合のみ書き込みでき
変更している場合にはfalseが返され書き込みに失敗します。
配列みたいなデータ構造があると、ロックと組み合わせてキューみたいな使い方ができるかなと思いました。

casメソッドにまかせている色々なことを、自力でやることもできます。詳細は述べませんが、以下のようにやるといろいろメソッドが追加されます。

irb(main)> require 'dalli/cas/client'

詳細は以下

dalli/client.rb at master · petergoldstein/dalli · GitHub