Quantcast
Channel: Rubyの記事一覧|TechRacho by BPS株式会社
Viewing all 1080 articles
Browse latest View live

[Railsガイド] Rails 5.1リリースノートを翻訳・公開いたしました

$
0
0

こんにちは、hachi8833です。
先週リリースされたRuby on Rails 5.1のリリースノートとアップグレードガイドを早速翻訳し、公式ドキュメントであるRailsガイド日本語版で公開いたしました。

Rails5.1リリースノート


https://railsguides.jp/より

Rails アップグレードガイドも近日中に5.1の部分などをアップデートいたします(まだ原文の分量が少ないので途中かもしれません)。

追加: アップグレードガイド

以下の5.0->5.1アップグレードガイドですが、英語版が作業中の可能性があるため、仮のものである点にご注意ください。

補足: Railsガイドの翻訳について

Railsチュートリアルのときと同様、最初は自分用にと思って始めたRailsガイドの翻訳ですが、いつしか公式ドキュメントとなり今日のように継続していることをうれしく思います。

今見直してみるとどちらも途方もない量なのですが、やっている間はそんなことを考えるゆとりもなく、可能な限り自分でも動かしながら翻訳を進めてきました。Railsガイドのコミット数もいつの間にか1,100件を超えていますね。

共同発起人の安川さんのおかげで私は翻訳に集中でき、またGitHubのyasslab/railsguides.jpで訳の訂正・原文更新対応などを多くの方に助力いただいています。
いつもありがとうございます。

関連記事


[Rails 5.1] ‘form_with’ APIドキュメント完全翻訳

$
0
0

こんにちは、hachi8833です。先週リリースされたRails 5.1の目玉機能のひとつである#form_withのAPIドキュメントを翻訳いたしました。

概要

原文の更新や訳文の誤りにお気づきの方は、ぜひフォームまたは@techrachoまでお知らせください。

参考

Rails 5.1 #form_with APIドキュメント(翻訳)

# API呼び出し
form_with(model: nil, scope: nil, url: nil, format: nil, **options)

URL、スコープ、モデルの組み合わせを元にformタグを作成します。

URLのみを指定する

<%= form_with url: posts_path do |form| %>
  <%= form.text_field :title %>
<% end %>

# =>
<form action="/posts" method="post" data-remote="true">
  <input type="text" name="title">
</form>

inputフィールド名にスコープのプレフィックスを追加する

<%= form_with scope: :post, url: posts_path do |form| %>
  <%= form.text_field :title %>
<% end %>

# =>
<form action="/posts" method="post" data-remote="true">
  <input type="text" name="post[title]">
</form>

渡されたモデルからURLとスコープを自動推測する

<%= form_with model: Post.new do |form| %>
  <%= form.text_field :title %>
<% end %>

# =>
<form action="/posts" method="post" data-remote="true">
  <input type="text" name="post[title]">
</form>

既存のモデルを更新するフォームで、モデルの値をフィールドに表示する

<%= form_with model: Post.first do |form| %>
  <%= form.text_field :title %>
<% end %>

# =>
<form action="/posts/1" method="post" data-remote="true">
  <input type="hidden" name="_method" value="patch">
  <input type="text" name="post[title]" value="<postのtitle>">
</form>

フォームのフィールドは、必ずしもモデルの属性と対応してなくてもよい

<%= form_with model: Cat.new do |form| %>
  <%= form.text_field :cats_dont_have_gills %>
  <%= form.text_field :but_in_forms_they_can %>
<% end %>

# =>
<form action="/cats" method="post" data-remote="true">
  <input type="text" name="cat[cats_dont_have_gills]">
  <input type="text" name="cat[but_in_forms_they_can]">
</form>

フォームのパラメータは、コントローラでパラメータのネストに沿ってアクセスできます。つまり、inputフィールドにtitlepost[title]というフィールド名がある場合、コントローラではそれぞれparams[:title]params[:post][:title]としてアクセスできます。

フォームのinputフィールド名 コントローラのparams
title params[:title]
post[title] params[:post][:title]

#form_withにはデフォルトでdata-remote属性が追加され、rails-ujsなどのunobtrusive JavaScript(控えめなJavaScript)ドライバが使われている場合はバックグラウンドでXMLHTTPRequest経由でフォームを送信します。詳しくは:localオプションをご覧ください。

なお、上の例では比較しやすさのため送信ボタンを省略しています。また、UTF-8サポート用に自動生成される隠しフィールドや、CSRF(クロスサイト・スクリプト・フォージェリ)攻撃からの保護のために自動追加される認証トークンも省略しています。

#form_withで利用できるオプション

:url
フォームの送信先URLを指定します。
渡せる値は、url_forlink_toで渡せる値と似ています。たとえば、名前付きルートを直接渡すこともできますし、:urlなしで:scopeを渡すと、現在のURLにフォームを送信することもできます。
:method
フォーム送信時のHTTPメソッド(verb)を指定します。
通常は:get:postを指定します。
:patch:put:deleteを指定すると、隠しinput名の後ろに_methodが追加され、POST verb上でこれらのHTTP verbをシミュレートします。
:format
フォーム送信先であるルーティングのフォーマットを指定します。
:jsonなど通常と異なるリソースタイプを送信するのに便利です。
:urlがオプションに渡されている場合、このオプションはスキップされます。
:scope
inputフィールド名のプレフィックスにスコープを追加します。これにより、送信されたパラメータをコントローラでグループ化できます。
:model
:url:scopeの自動推測に使うモデルオブジェクトを指定し、inputフィールドにモデルの値を表示します。
たとえば、title属性の値が"Ahoy!"ならtitleの入力フィールドの値に"Ahoy"と表示されます。
モデルが新しいレコードの場合は作成用フォームが生成され、モデルが既存のレコードの場合は更新用フォームが生成されます。
デフォルトの動作を上書きするには、:scope:urlを渡します(params[:post]params[:article]に変更するなど)。
:authenticity_token
フォームで使う認証トークンを指定します。
カスタムの認証トークンを指定して上書きすることも、falseを渡して認証トークンのフィールドをスキップすることもできます。
有効なフィールドのみに制限されている支払用ゲートウェイへのような外部リソースにフォームを送信する場合に便利です。
config.action_view.embed_authenticity_token_in_remote_forms = falseを指定すると、埋め込み認証トークンがリモートフォームで省略されることがあります。この指定はフォームでフラグメントキャッシュを使う場合に便利です(リモートフォームがmetaタグから認証トークンを取得するようになるので、JavaScriptがオフになっているブラウザをサポートする場合を除けば認証トークンをフォームに埋め込む必要がなくなります)。
:local
local: trueを指定するとフォームのリモート + unobtrusive XHR送信が無効になります(デフォルトのフォームではリモート + unobtrusive XHRが有効になります)。
:skip_enforcing_utf8
trueを指定すると、送信時にutf8という隠しフィールドがスキップされます(デフォルトの送信ではutf8フィールドが出力されてエンコードがUTF-8になります)。
:builder
フォームのビルドに使うオブジェクトをオーバーライドします。
:id
HTMLのid属性を指定します(オプション)。
:class
HTMLのclass属性を指定します(オプション)。
:data
HTMLのdata属性を指定します(オプション)。
:html
上以外のHTML属性を使う場合に指定します(オプション)。

#form_withにブロックを渡さない場合は、開始formタグを生成します。

# 名前付きパスを指定する場合
<%= form_with(model: @post, url: super_posts_path) %>

# スコープを追加する場合
<%= form_with(model: @post, scope: :article) %>

# フォーマットを指定する場合
<%= form_with(model: @post, format: :json) %>

# トークンを無効にする場合
<%= form_with(model: @post, authenticity_token: false) %>

ルーティングをadmin_post_urlのような名前空間化する場合は以下のようにします。

<%= form_with(model: [ :admin, @post ]) do |form| %>
  ...
<% end %>

たとえばリソースに関連付けが定義されているとします。ルーティングが正しく設定されているdocumentにcommentを追加したい場合は次のようにします。

<%= form_with(model: [ @document, Comment.new ]) do |form| %>
  ...
<% end %>

上のdocumentには@document = Document.find(params[:id])が既に与えられているとします。

#form_withでラベルを表示する場合、フィールドのid:にラベルを設定する必要があります。

<%= form_with(model: @post) do |form| %>
  <%= form.label :title %>
  <%= form.text_field :title, id: :post_title %>
<% end %>

for属性の導出方法についてはlabelを参照してください。

他のフォームヘルパーと組み合わせる

#form_withではFormBuilderオブジェクトが使われていますが、単独のFormHelperのメソッドやFormTagHelperのメソッドと共存させることもできます。

<%= form_with scope: :person do |form| %>
  <%= form.text_field :first_name %>
  <%= form.text_field :last_name %>

  <%= text_area :person, :biography %>
  <%= check_box_tag "person[admin]", "1", @person.company.admin? %>

  <%= form.submit %>
<% end %>

同様に、FormOptionHelperのメソッド(FormOptionHelper#collection_selectなど)と共存させたり、DateHelperのメソッド(ActionView::Helpers::DateHelper#datetime_selectなど)と共存させることもできます。

HTTPメソッド(verb)の指定方法

以下のHTTP verbの完全な配列をoptionsハッシュに渡すことができます。

method: (:get|:post|:patch|:put|:delete)

verbがGETPOST以外の場合(この2つはHTMLフォームでネイティブでサポートされます)、フォームそのものにはPOST verbが設定され、_methodという名前の隠しinputフィールドには指定の verbが設定され、後者がサーバーで解釈されます。

HTMLオプションの設定方法

HTMLのdata-*属性はdata:ハッシュで直接渡せますが、id:class:を含む他のすべてのHTMLオプションについては次のようにhtml:ハッシュの中に置く必要があります。

<%= form_with(model: @post,
              data: { behavior: "autosave" },
              html: { name: "go" }) do |form| %>
  ...
<% end %>

上のコードから以下のHTMLが生成されます。

<form action="/posts/123" method="post" data-behavior="autosave" name="go">
  <input name="_method" type="hidden" value="patch" />
  ...
</form>

非表示のモデルidを出力しないようにする

#form_forメソッドを使うと、自動的にモデルidが隠しフィールドとしてフォームに含まれます。このモデルidは、フォームデータとそれに関連付けられているモデルとの関連を保つために使われます。

ORMシステムによってはネストしたモデルでこうしたidを使わないものもあるので、その場合は次のようにinclude_id: falseを指定することで隠しフィールドのモデルidを出力しないようにできます。

<%= form_with(model: @post) do |form| %>
  <%= form.fields(:comments, skip_id: true) do |fields| %>
    ...
  <% end %>
<% end %>

上の例では、NoSQLデータベースにPostというモデルがひとつと、それに一対多で関連付けられるCommentというモデルが保存されています。:commentsには主キーはありません。

フォームビルダをカスタマイズする

FormBuilderクラスをカスタマイズしてフォームをビルドすることもできます。カスタマイズするには、FormBuilderを継承してサブクラスを作り、必要なヘルパーメソッドを定義またはオーバーライドします。

次のコード例では、フォームのinputにラベルを自動追加するヘルパーを作成済みであることが前提です。

<%= form_with model: @person, url: { action: "create" }, builder: LabellingFormBuilder do |form| %>
  <%= form.text_field :first_name %>
  <%= form.text_field :last_name %>
  <%= form.text_area :biography %>
  <%= form.check_box :admin %>
  <%= form.submit %>
<% end %>

上のようにコードを書いてから、次のコードを書きます。

<%= render form %>

これにより、people/_labelling_formというテンプレートを使ってレンダリング(=HTML生成)され、フォームビルダを参照するローカル変数の名前はlabelling_formになります。

特に指定しない限り、カスタムのFormBuilderクラスは、ネストした#fields_for呼び出しのオプションと自動的にマージされます。

上のようなコードを別のヘルパーにも含めておきたい場合は、以下のように書くこともできます。

def labelled_form_with(**options, &block)
  form_with(**options.merge(builder: LabellingFormBuilder), &block)
end

関連記事

テストを不安定にする5つの残念な書き方(翻訳)

$
0
0

こんにちは、hachi8833です。今回はテストを正しく書く方法を解説する記事の翻訳をお送りします。

概要

原著者の許諾を得て翻訳・公開いたします。

テストを不安定にする5つの残念な書き方(翻訳)

不安定なテストは、毎日の苦労を台無しにしてくれる技術上の負債の一部となります。テストが不安定だとCIが赤信号になってしまい、それだけのために新しいコードのリリースを中断してビルドをやりなおすはめになります。実際のコードはどこもおかしくないのに、どこかがおかしいのではないかという疑念が湧くと、ストレスの元になります。

数百人の開発者と5万件のテスト項目があるような大規模案件では、不安定なテストが混入する可能性がさらに高まります。

本記事で扱うデモの中には、テストの実行順序に関連するものもありますが、そうでないものもあります。テストの実行順序とは何か、それがテストにどう関連するのか。それを確認する一番の方法は、テストの実行順序をランダムにしてみることでしょう。そうすれば、あるテストが他のテストに紐付けられていないことと、テストが実行順序に依存していないことがわかります。

本記事ではMiniTestを用いますが、どの問題もフレームワークに依存しないものばかりなので、MiniTestであるかRSpecであるかを問わず、一般的に応用できます。

1. ランダムなファクトリー

# emailフィールドにはunique制約がかかっているとする
10.times do
  Customer.create!(email: Faker::Internet.safe_email)
end

どこに問題があるかおわかりでしょうか。

このテストはほとんどの場合パスしますが、ごくたまに、既に使ったメールアドレスがFakerからもう一度返されることがあります。そしてunique制約エラーに引っかかってテストがクラッシュするというわけです。

正しい書き方はこうです。

10.times do |n|
  Customer.create!(email: Faker::Internet.safe_email(n.to_s))
end

Fakerに渡す引数を、(デフォルトの)ランダムなメールではなく、n番目のメールを指定するのがポイントです。

2. データベースのレコード順

assert_equal([1, 2, 3], @products.pluck(:quantity))

このテストもほぼすべてのケースでパスしますが、ORDERなしのSELECTクエリではレコードの順序が保証されません。こうしたランダムな要素が原因のエラーを回避するには、次のように明示的に順序を指定します。

assert_equal([1, 2, 3], @products.pluck(:quantity).sort)
# または
assert_equal([1, 2, 3], @products.order(:quantity).pluck(:quantity))

3. グローバル環境の汚染

BulkEditor.register(User) do
  attributes(:email, :password)
end
assert_equal [:email, :password], BulkEditor.attributes_for(@user)

私の経験した例では、登録されたモデルのリストをBulkEditorがグローバルな環境に保存していたことがありました。これでテストを実行するとレジストリが汚されてしまい、その後に実行される他のテストが影響されてしまいます。順序依存の一丁上がりです。

解決方法は次のとおりです。

setup to
  BulkEditor.register(User) do
    attributes(:email, :password)
  end
end

teardown do
  BulkEditor.unregister(User)
end

私の経験したグローバル環境汚染からもうひとつご紹介しましょう。

test "something" do
  SomeGem::VERSION = '9999.99.11'
  assert_not @provider.supported?
end

このテストコードの後に実行されるテストでは、SomeGem::VERSIONから返る値がおかしくなってしまいます。さらに、Rubyレベルでのワーニング「warning: already initialized constant SomeGem::VERSION」が表示されます。

解決方法は次のとおりです。

test "something" do
  # 変更された定数値をブロックだけが受け取るようにする
  stub_constant(SomeGem, :VERSION, '9999.99.99') do
    assert_not @provider.supported?
  end
end

4. 時間に依存するテスト

post = publish_delayed_post
assert_equal 1.hour.from_now, post.published_at

このテストは通常であればパスしますが、ごくたまに、postの公開に要する時間が1msecを超え、かつ#published_atの所要時間が1.hour.from_nowを超えることがあります。

こういう場合に便利なのは、#assert_in_deltaという特殊なヘルパーメソッドです。

post = publish_delayed_post
assert_in_delta 1.hour.from_now, post.published_at, 1.second

他にも、Timecopなどのライブラリで時間を止める方法があります。

5. requireに依存するテスト

テスト用のクラスが2種類あり、1つはリモートHTTP呼び出しを許可するがもう1つは許可しないとします。以下のような感じのコードになるでしょう。

# test/unit/remote_api_test.rb
require 'remote_test_helper'

class RemoteServiceTest < ActiveSupport::TestCase
  test "something" do
    # ...
  end
end

# test/unit/simple_test.rb
require 'test_helper'

class SimpleTest < ActiveSupport::TestCase
  test "something" do
    # ...
  end
end

外部へのHTTP呼び出し用に#remote_test_helperを使うテストが大量にあるとします。私見では、こうしたテストは単独であればおそらく完璧に動作します。しかしCIですべてのテストを通しで実行すると、テストの順序によってはリモート呼び出しの後のあらゆるテストで外部呼び出しができてしまう可能性があります😱

requireグローバルであり、グローバルな状態を変更します。このことを肝に銘じておいてください。

よりよい解決方法は、特定のテストのコンテキストのみを変更するマクロを使うことです。

# test/unit/remote_api_test.rb
require 'test_helper'

class RemoteServiceTest < ActiveSupport::TestCase
  allow_remote_calls!

  test "something" do
    # ...
  end
end

# test/unit/simple_test.rb
require 'test_helper'

class SimpleTest < ActiveSupport::TestCase
  test "something" do
    # ...
  end
end

まとめ

実行結果の不安定なテストの修正はたいてい面倒なものであり、それだけでブログ記事が1本書けるほどです。ひとまず、こうした不安定な要素をテストに持ち込まないよう注意することをおすすめします。

不安定なテストについてご興味がおありの場合は、以下の英語記事をご覧ください。

関連記事

[Devise How-To] sign_inとsign_outのデフォルトルーティングを変更する(翻訳)

$
0
0

こんにちは、hachi8833です。
Devise gemのWiki How-To翻訳、第2弾です。DeviseもRails 5.1対応で忙しそうですね。

概要

保存版 [Rails] Devise Wiki How-To翻訳: 総もくじ

原文が更新されていることにお気づきの場合は、ぜひ@techrachoまでお知らせください。更新いたします。

[How-To] sign_inとsign_outのデフォルトルーティングを変更する

Deviseで使うロール(役割)が1つしかない状態で、ログイン(sign in)とログアウト用(sign out)のルーティングをデフォルトの /users/sign_in/users/sign_outから/loginlogoutに変更したいことがあります。

これはデフォルトのままのDeviseではできません。Deviseは、URLをチェックすることによってユーザーのアクセスするスコープを決定するからです。URLが/users/loginであればスコープはuserであることがDevise側でわかりますが、/loginにアクセスすると、使われるべきスコープをDevise側で決定できません。幸いなことに、Deviseにはデフォルトスコープを指定するしくみがあるので、これによってURLを短くできます。

手順(Rails 3.0以降)

この設定は、アクセスに使うURLをルーティングのdevise_scopeに記述するだけで完了します。

devise_scope :user do
  get 'login', to: 'devise/sessions#new'
end

以下のように、devise_scopeのエイリアスであるasも使えます。

as :user do
  get 'login', to: 'devise/sessions#new'
end

sign_outの場合は次のようにします。

devise_scope :user do
  delete 'logout', to: 'devise/sessions#destroy'
end

次のようにskip:オプションですべてのセッションルーティングをスキップし、必要なルーティングだけを定義することもできます。

devise_for :users, skip: [:sessions]
as :user do
  get 'signin', to: 'devise/sessions#new', as: :new_user_session
  post 'signin', to: 'devise/sessions#create', as: :user_session
  delete 'signout', to: 'devise/sessions#destroy', as: :destroy_user_session
end

このようにすると、#authenticate_user!などのヘルパーを使ったときに、定義済みの正しいカスタムページにユーザーをリダイレクトできます。

ただし、:sign_out_viaという設定オプションを使っていると上のsignoutアクションがエラーになることがあります。その場合は、デフォルトの動作を複製して次のように:sign_out_viaによってdeletegetに変更します。

devise_for :users, skip: [:sessions]
as :user do
  get 'signin', to: 'devise/sessions#new', as: :new_user_session
  post 'signin', to: 'devise/sessions#create', as: :user_session
  match 'signout', to: 'devise/sessions#destroy', as: :destroy_user_session, via: Devise.mappings[:user].sign_out_via
end

別のもっとシンプルな方法

#devise_forメソッドにはこういうときに便利なオプションパラメータが多数用意されています。

たとえば、usersという名前空間をすべてのルーティングから取り除き、sign_insign_outをそれぞれloginlogoutに置き換えるには、次のようにします。

devise_for :users, path: '', path_names: { sign_in: 'login', sign_out: 'logout'}

ただし、Deviseで複数のコントローラ(パスワード用など)を使いたい場合は、最初の方法を使う必要があります。

morimorihogeより追記

上記の sign_out_via に関連してすこし補足です。

Deviseではログインセッションをsessionという概念で管理しており、RESTful的には「新規sessionを作る(Devise::SessionsController#create)=ログインする」といった抽象化がされています。
そのため「ログアウトする」はsessionを破棄する、すなわちsessionの削除(Devise::SessionsController#destroy)というインターフェースになっています。

このとき、確かにsession削除はDELETEメソッドで呼ばれるのがRESTの概念上あるべき姿なのですが「ログアウトする」という機能自体はRailsの外から呼びたかったり、リダイレクト処理の中で強制ログアウトさせたいといったケースが考えられるため、DELETEメソッドよりもGETメソッドでログアウトできた方が便利です( _method=delete とか付ければGETでもDELETEメソッドにbypassできますが、いちいち考えるのが面倒)。

というわけで、以下の様に sign_out_via を設定してやることで、ログアウトについてはGETリクエストで処理でき、 link_to ヘルパでも単なる destroy_user_session_path へのリンクとして記述できるようになります。

Devise.config.sign_out_via = :get
= link_to 'ログアウト', destroy_user_path

関連記事(Devise)

Rails開発者のためのPostgreSQLの便利技(翻訳)

$
0
0

こんにちは、hachi8833です。
今回は、PostgreSQLのスケーリングソリューションで知られるCitus Data社のブログ記事の翻訳をお送りいたします。

概要

Rails開発者のためのPostgreSQLの便利技(翻訳)

今週のRailsConfで、私たちはRailsでPostgreSQLを使ううえでの多くの知見を共有しました。有用な情報なので、その多くを広く公開したいと思います。Railsアプリでのデバッグやデータベースのパフォーマンス改善にお役立ていただければと思います。

また、フェニックスで開催されたRailsConfに惜しくも出席できなかったRailsコミュニティメンバーの皆さまにも、私たちがRailsConfで得た有用な情報を提供したいと思います。元記事末尾のフォームにてニュースレターをお申込みいただければ、Citusのこの他のノウハウをメール配信いたしますので、ぜひご応募ください。

それでは本編にまいりましょう。

実行時間の長いクエリをstatement_timeoutで管理する

クエリ実行に時間がかかると、データベースでさまざまな問題を引き起こす可能性があります。数時間単位のクエリはもちろん、秒単位のクエリですらデータベースのロックやログ先行書き込み(WAL)でのキュー待ちが発生したり、そこまでいかなくてもシステムリソースを大量に消費したりすることがあります。

PostgreSQLでは、デフォルトのstatement timeout値を変更することでこの問題をもう少し安全に扱えるようになります。この方法のよい点は、たとえばデフォルト値を5秒に設定すると、それより時間のかかるクエリがすべて無効になることです。

production:
   url: <%= DATABASE_URL %>
   variables:
     statement_timeout: 5000

データベースセッション内で実行時間をもっと長く取りたい場合は、次のように現在の接続でのみ有効なstatement timeout値を別途指定することもできます。

class MyAnalyticsJob < ActiveJob::Base
  queue_as :analytics
  def perform
    ActiveRecord::Base.connection.execute "SET statement_timeout = 600000" # 10分
    # ...
  ensure
    ActiveRecord::Base.connection.execute "SET statement_timeout = 5000"   # 5秒
  end
end

不正なクエリを検出する

Railsでは、データベースとのやりとりの多くが抽象化されています。抽象化にはメリットもありますが、デメリットもあります。PostgreSQLは実行に時間のかかるクエリを表示する機能がありますが、Railsアプリが成長して複雑になるにつれ、PostgreSQLの通常ログ出力だけでは問題解決に必要な情報が足りないことがあります。

クエリの発生元を突き止めるmarginaliaという小さな専用gemを使うと、クエリの正確な発生元をログ出力できます。クエリに問題がある場合や異様に遅い場合は、この情報を元に問題をピンポイントで修正できます。

Account Load (0.3ms)  SELECT `accounts`.* FROM `accounts`
WHERE `accounts`.`queenbee_id` = 1234567890
LIMIT 1
/*application:BCX,controller:project_imports,action:show*/

データベースクエリの概要を詳しく表示する

データベースで起こっていることをもう少し詳しく知りたいと思うことはよくあります。pg_stat_statementsは、Citus Cloudなどのクラウド環境でPostgreSQLにプレインストールされる拡張機能であり、統計情報を最後にリセットしてから実行されたクエリや、クエリの実行状況を詳しく表示できます。

たとえば、実行時間の長い上位10件のクエリをリストアップして平均実行時間を表示するには、以下のようにします。

SELECT query, total_time / calls AS avg_time
 FROM pg_stat_statements
ORDER BY total_time DESC
LIMIT 10;

PostgreSQLデータベースでtrack_io_timingをオンにしておけば、パフォーマンスのボトルネックがCPUやI/Oで生じているかどうかも確認できるようになります。詳しくはpg_stat_statementsについての記事をご覧ください。

PostgreSQLの高度な機能を利用する

Railsでは、データベーススキーマのコピーをデフォルトでschema.rbというファイルに保存します。このスキーマファイルは、テストの実行前などにデータベースを初期化するのによく使われます。

残念なことに、関数インデックス部分インデックス、複合プライマリキーといったPostgreSQLの高度な機能は、スキーマファイルのDSLでは記述できません。

そういう場合は、Railsでdb/structure.sqlというファイルをgenerateし、config/application.rbで以下のように指定するとstructure.sqlをスキーマファイルにできます。

    # データベース作成時にActive Recordのスキーマダンプの代わりにSQLを使う
    # スキーマダンプからの出力が完全ではない場合に必要
    # (例: データベースの制約や固有のカラム型を使う場合)
    config.active_record.schema_format = :sql

この場合、内部的にはPostgreSQLのpg_dumpフォーマットが使われます。このフォーマットはやや記述が増える代わりに、データベース構造を完全にリストアできるようになります。このフォーマットにするとstructure.sqlが更新されるたびに大量のdiffが発生するので、これがつらい場合はactiverecord-clean-db-structure gemをご検討ください。

相互ロックする複雑なトランザクションを監視する

Railsではすべてをトランザクションに入れることが好まれています。特に、before_saveフックや、モデル間の多段リレーションシップにおいて顕著です。トランザクションを監視するうえで1つ注意したい点があります。この問題は、スケールアウトしたときに発生する可能性があります。

以下のようなトランザクションがあるとします。

BEGIN;
UPDATE organizations SET updated_at = ‘2017-04-27 11:31:03 -0700’ WHERE id = 123;
SELECT * FROM products WHERE store_id = 456;
--- 省略
COMMIT;

最初のUPDATEステートメントを発行すると、organizationsid= 123で即座に行レベルロックが発生します。そしてこのロックは、COMMITが実行されるまで継続します。

同じorganizationに異なるユーザーから別のリクエストが発生し、これと同じようなトランザクションが発生したとしましょう。通常、別のリクエストは最初のトランザクションがコミットするまで待機しないと自分のトランザクションを進められないので、レスポンスタイムが低下します。

この問題を修正するには、トランザクションの見直しが有用です。UPDATEを最後の方に移動し、さらにtimestampフィールドの変更処理をトランザクションの外に出して、メインの処理が完了してから行われるようにします。

こうした問題を検出するには、PostgreSQLでlog_lock_waits = onを設定するのが便利です。

データベース接続を管理する

Railsのデータベース接続は、デフォルトでコネクションプールに保持されます。新規リクエストを受け付けると、Railsはコネクションプールから接続を1つ取り出してアプリに渡します。Railsアプリがスケールすると数百ものデータベース接続がオープンされますが、実際に動作しているのはほんの一握りです。

動作しているデータベース接続数を削減するには、pgBounceなどのコネクションプール管理ソフトウェアが便利です。こうしたソフトウェアは、トランザクションが有効なときにコネクションをオープンし、処理の終わったアイドリング状態のコネクションにはトランザクションを渡さないようにします。

もっと知りたい方へ

RailsでPostgreSQLを管理するうえでお役に立てば幸いです。他にも便利な情報をお持ちの方は、@citusdataまでお知らせください。

こうした記事にご興味がおありの方は、元記事下部のフォームにメールアドレスを登録いただければ毎月ニュースレターをお届けいたします。内容には自信がありますので、どうかスパムとお間違えにならないよう。

関連記事

[Devise How-To]デフォルトのSign_up登録パスをカスタムパスに変更する(翻訳)

$
0
0

こんにちは、hachi8833です。一昨日に続き、Devise How-Toシリーズを公開いたします。

概要

原文の更新や誤りにお気づきの場合は、ぜひ@techrachoまでお知らせください。更新いたします。

[How-To] デフォルトのSign_up登録パスをカスタムパスに変更する(翻訳)

Deviseのsign_pathでは、デフォルトで/users/sign_upというパスを使います。何らかの理由でこのデフォルトパスをdomain.com/sign_upに変更したい場合は、次のようにします(registration/sign_upというカスタムコントローラを生成してないことが前提です)。

# routes.rbのトップレベル
Rails.application.routes.draw do
  devise_scope :user do
    get "/sign_in" => "devise/sessions#new" # login/sign_inへのカスタムパス
    get "/sign_up" => "devise/registrations#new", as: "new_user_registration" # sign_up/registrationへのカスタムパス
  end

  # その他のすべてのルーティング
  devise_for :users

  ...
end

これにより、ビューで以下のように登録パスを指定できるようになります。

<%= link_to "Sign up", new_user_registration_path %>

その他に必要な変更はありません。サーバーを再起動すれば反映完了です。

好みに応じてsign_upregisterに変更することもできます。

関連記事(Devise)

[Devise How-To]ユーザー登録ページへのルーティングをカスタマイズする(翻訳)

$
0
0

こんにちは、hachi8833です。Devise How-Toシリーズ、本日2本めです。

概要

原文の更新や誤りにお気づきの場合は、ぜひ@techrachoまでお知らせください。更新いたします。

[How-To] ユーザー登録ページへのルーティングをカスタマイズする(翻訳)

Customer::PrivateCustomer::Publicという2つのDeviseユーザーモデルがあるとします。

models/customer/private.rb

models/customer/public.rb

コントローラの設定

それぞれのユーザーのアクションについてスコープを設定するために、以下のコントローラを作成します。

# app/controllers/customer/private/registrations_controller.rb
class Customer
  class Private
    class RegistrationsController < Devise::RegistrationsController
    end
  end
end

ビューの設定

以下のような2つのビューを作成する必要があります。

views/customer/private/registrations/new.html.haml

views/customer/public/registrations/new.html.haml

You will likely want to have a _form.html.haml partial for each.

それぞれのビューには_form.html.hamlというパーシャル(部分テンプレート)があるとします。

Deviseの登録ルーティングを設定する

ルーティングファイル(conf/routes.rb)に以下を記述します。

  devise_for :private_customers, :class_name => 'Customer::Private', :controllers => {:registrations => "customer/
private/registrations", :sessions => 'main' } do
    get   "private_customer/sign_up" => "customer/private/registrations#new", :as => :private_customer_signup
    get   "private_customer/sign_in" => "main#index", :as => :private_customer_signin
  end

上はprivate_customers用です。public_customersについても同じように設定します。

あとはビューにlink_to 'register', private_customer_signup_pathと記述すればビューで使えるようになります。

関連記事(Devise)

週刊Railsウォッチ(20170512)Rubyの不思議な挙動「シャドウイング」、コードレビュー作法を定めるDanger gemほか

$
0
0

こんにちは、hachi8833です。Rails 5.1でrails newすると忘れた頃にspringに邪魔されます。

GWをはさんだ2週間ぶりのRailsウォッチ、いってみましょう。

Rails: aggregated_resultsで任意の種類の引数を取れるよう修正

y-yagiさんという方の「なるようになるブログ」は、Railsのコミットログをひたすら読む記事を毎日のようにアップするというすごいブログなのですが、そのy-yagiさんがRailsにちょくちょくプルリクを送っていることに昨日初めて気づきました。

コミットログでお名前を少しだけ追いかけてみたところ、少なくとも今年3月ぐらいから続々淡々とプルリクしていて、kamipoさんに迫りそうな勢いです。上は現時点で最新のPRです。
いつもありがとうございます。

Rails: secretsに関する記述を修正

-building on top of the [sekrets](https://github.com/ahoward/sekrets) gem.
+inspired by the [sekrets](https://github.com/ahoward/sekrets) gem.

これ、私も記事を書いていて引っかかってしまいました。Sekrets gemがRailsに組み込まれたのではなく、Secretsにインスパイアを受けて独自に実装したというのが正解でした。

BPS WebチームのakioさんがRails 5.1でrails newしてみて「Sekrets gemが入ってませんねー」と知らせてくれたことでわかりました。ありがとうございます。

Rails: #map#flat_mapに変更

-        chain.map(&:scopes).flatten
+        chain.flat_map(&:scopes)

Rubyスタイルガイドでも推奨されている#flat_mapへの置き換えです。

参考: Rubyスタイルガイドを読む: #mapと#flattenではなく#flat_mapを使うこと

Rails: ActionMailer::Baseからのrequire "active_support"の脱落を修正

# actionmailer/lib/action_mailer.rb
+require "active_support"
 require "active_support/rails"

どこで抜けたのかと思って履歴を追ってみたところ、3f8409でaction_mailer/base.rbからrequire行が引越したのを見つけました。

+require 'active_support/core_ext/class'
+require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/array/uniq_by'
+require 'active_support/core_ext/module/attr_internal'
+require 'active_support/core_ext/module/delegation'
+require 'active_support/core_ext/string/inflections'

そしてその前からrequire "active_support"はなかったようです。

-require 'active_support/core_ext/class'
-require 'active_support/core_ext/object/blank'
-require 'active_support/core_ext/array/uniq_by'
-require 'active_support/core_ext/module/delegation'
-require 'active_support/core_ext/string/inflections'

Rails: RuntimeReflection#alias_nameに型変換を追加

以下が改修点です。

-        Arel::Table.new(table_name)
+        Arel::Table.new(table_name, type_caster: klass.type_caster)

そのままArel::Tableも見てみました。

def initialize(name, as: nil, type_caster: nil)
      @name    = name.to_s
      @type_caster = type_caster
...
end

type_casterは以下にありました。

...
    class Connection # :nodoc:
      def initialize(klass, table_name)
        @klass = klass
        @table_name = table_name
      end
...

Rails: bind_parambind_attributeActiveRecord::TestCaseに切り出す

kamipoさんによるテストコードの修正です。

-        result = @connection.select_all("SELECT * FROM posts WHERE id = #{Arel::Nodes::BindParam.new.to_sql}", nil, [[nil, post.id]])
+        result = @connection.select_all("SELECT * FROM posts WHERE id = #{bind_param.to_sql}", nil, [[nil, post.id]])
-        binds = [Relation::QueryAttribute.new("id", "10", Type::Integer.new)]
+        binds = [bind_attribute("id", "10", Type::Integer.new)]

テストコードで上のように重複している多数のnewをリファクタリングして、以下のようにtest_case.rbに集約しています。

# activerecord/test/cases/test_case.rb
+    def bind_param
+      Arel::Nodes::BindParam.new
+    end
+
+    def bind_attribute(name, value, type = ActiveRecord::Type.default_value)
+      ActiveRecord::Relation::QueryAttribute.new(name, value, type)
+    end

一同でチェックしながら「たぶんテストコードのファイルの複製を繰り返しているうちにnewがかぶってきたんだろうねー」という声がありました。
kamipoさんの地道な修正に頭が下がります。

mini_racer: 軽快なJS V8エンジン gem

mini_racerは名前からうかがえるように、GoogleのJavaScriptエンジンであるV8をRailsで使うときの定番gemであるtherubyracerの縮小版です。

試しにmini_racerをインストールしてみると、libV8のバージョンがtherubyracerより随分進んでます。

READMEのベンチマークを見る限りはかなり軽快そうです。

# https://github.com/discourse/mini_racer より
$ bundle exec ruby bench.rb mini_racer
Benching with mini_racer
mini_racer minify discourse_app.js 9292.72063ms
mini_racer minify discourse_app_minified.js 11799.850171ms
mini_racer minify discourse_app.js twice (2 threads) 10269.570797ms

sam@ubuntu exec_js_uglify % bundle exec ruby bench.rb node
Benching with node
node minify discourse_app.js 13302.715484ms
node minify discourse_app_minified.js 18100.761243ms
node minify discourse_app.js twice (2 threads) 14383.600207000001ms

sam@ubuntu exec_js_uglify % bundle exec ruby bench.rb therubyracer
Benching with therubyracer
therubyracer minify discourse_app.js 171683.01867700001ms
therubyracer minify discourse_app_minified.js 143138.88492ms
therubyracer minify discourse_app.js twice (2 threads) NEVER FINISH

Killed: 9

ところで、therubyracerといえば以下の記事で知られているように昔のRailsでひどい目にあった方が結構いました。RailsのV8周りについては、mini_racerに置き換えるにしても一応注意する方がよさそうです。

私もGemfileのtherubyracerのコメントアウトをむやみに解除しないようにします。

sassc-ruby: 高速Sassコンパイラ gem

sassc-rubyはSassのコンパイルをC言語ライブラリで行えるgemです。

# https://github.com/sass/sassc-rub より
[1] pry(main)> Benchmark.bm { |bm| bm.report { Rails.application.assets["application.css"] } }
       user     system      total        real
   1.720000   0.170000   1.890000 (  1.936867)

# Using sass-rails

 [1] pry(main)> Benchmark.bm { |bm| bm.report { Rails.application.assets["application.css"] } }
       user     system      total        real
  7.820000   0.250000   8.070000 (  8.106347)

sassc-rubyのリポジトリでは★50個程度で「ありゃ」と思ったのですが、READMEに書いてあったsassc-railsの方は★400個達成しています。こちらはまだ2年ほどの比較的若いgemです。

急いでインストールする必要はないと思いますが、Sassの性能改善が不可避になったときに検討してみてもよいかもしれません。

Oj(Optimzed JSON) gem 3.0.0リリース(RubyWeeklyより)

高速を謳っているJSONパーサーgemです。akioさんがその場で早速動かしてみました。

require 'json'
v = {:a => {:b => :c}}
s = JSON.dump(v)                        # => "{\"a\":{\"b\":\"c\"}}"
JSON.parse(s, :symbolize_names => true) # => {:a=>{:b=>"c"}}

require 'oj'
v = {:a => {:b => :c}}
# s = Oj.dump(v, :mode => :compat)      # => "{\"a\":{\"b\":\"c\"}}"
s = Oj.dump(v)                          # => "{\":a\":{\":b\":\":c\"}}"
Oj.load(s)                              # => {:a=>{:b=>:c}}

# oj はいったん文字列にしてしまったキーはシンボルにはできないと思いきや :symbol_keys オプションでシンボルに戻せる
# ただ値は文字列のまま
s = Oj.dump(v, :mode => :compat)        # => "{\"a\":{\"b\":\"c\"}}"
Oj.load(s, :symbol_keys => true)        # => {:a=>{:b=>"c"}}

それにしても、JSONパーサーってどうして言語ごとにあんなにたくさんあるんでしょうか。せっかくJSONの仕様が定まっているのに実装がこんなに多いのは「とにかく今この問題を切り抜けたい」「既存のJSONパーサーの使い勝手が気に入らない」といった理由なのでしょうか。

なお私はbashスクリプトでjqをときどき使っています。癖は強いですが速いです。

bootsnap gem: Railsに新たなキャッシュを追加して高速化(RubyWeeklyより)

大規模Railsアプリをキャッシュで高速化するgemだそうです。
READMEで「Beta-quality」と書かれていることもあり、まだ発展途上のようですが、READMEはかなりみっちり書かれています。

やっていることは「$LOAD_PATHなどのスキャンを止める」「RubyのバイトコードコンパイルやYAMLをキャッシュする」など、きわどい感じです。一同から「えぇー、#requireにパッチ?!」と声が上がりました。

# https://github.com/Shopify/bootsnap/blob/master/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb#L48 より
class << Kernel
  alias_method :require_without_cache, :require
  def require(path)
    if resolved = Bootsnap::LoadPathCache.load_path_cache.find(path)
      require_without_cache(resolved)
    else
      raise Bootsnap::LoadPathCache::CoreExt.make_load_error(path)
    end
  rescue Bootsnap::LoadPathCache::ReturnFalse
    return false
  rescue Bootsnap::LoadPathCache::FallbackScan
    require_without_cache(path)
  end
...

READMEで参考として挙げられていた中にRubyコミッターであるko1さんのライブラリがありました。

コマンドがkakidasuだったりと「こういう名前って英語圏ではどうなんでしょ?」と聞かれてしまいました。私は英語村出身ではありませんが、類推が効かなくてつらそうな感じですね。

ところで、READMEに書いてあるyomidasugemspeck以外のどこにも見当たりませんでした。もしかして修正漏れでしょうか?

Rubyのシャドウイングで生じたバグ(RubyWeeklyより)

この挙動には一同かなり驚きました。Rubyがこんなふうに振る舞うなんて。akioさんも早速ぶん回してみました。

# https://thomasleecopeland.com/2017/04/20/shadowing-bug-in-the-wild.html
class Foo
  def buz
    42
  end
  def bar
    unless buz
      buz = 21
    end
    buz
  end
end

p Foo.new.bar # => さて何が返るでしょうか?

Ruby 2.4.1での実行結果は以下でした。

はい、ご覧のとおりnilが返ります。

元記事で「シャドウイング」と呼んでいるこの動作は、Rubyの公式ドキュメントにもしっかり書かれています。知らずに踏みそうで怖いですね。ドキュメントをざっと翻訳してみました。

Ruby 公式ドキュメントより

ローカル変数は、代入の発生時ではなくパーサーが代入を検出したときに作成される。

a = 0 if false    # ここではaに何も代入されないことに注意

p local_variables # => [:a]

p a               # => nil

メソッド名とローカル変数が類似していると、次の例のようにコードで混乱が生じることがある。

def big_calculation
  42 # なにしろ「42」なので計算にものすごく時間がかかる
end

big_calculation = big_calculation()

ここではbig_calculationへの参照はすべてローカル変数と解釈され、キャッシュされる。メソッドを呼ぶにはself.big_calculationを使う。

上のように空の丸かっこ()をつけるかself.などのレシーバを明示的に使うことで、メソッドを呼び出せる。ただしレシーバを明示すると、メソッドがpublicでない場合にNameErrorが生じることがある。

もうひとつの紛らわしい例は、以下のように後置のifを使う場合である。

p a if a = 0.zero?

上を実行するとtrueではなく「undefined local variable or method ‘a’」が出力される。Rubyはifの左にある単独のaをまず解析する。解析の時点では代入が行われていないため、Rubyはaをメソッドであると仮定する。その後aへの代入が行われると、Rubyはこれをローカルメソッドへの参照と仮定する。

この紛らわしい動作は、式が見た目とは違う順序で実行されていることによる。最初にローカル変数への代入が行われ、続いて存在しないメソッドを呼び出そうとしている。

Bundlerの新しいオプション

bundle update --conservativeというオプションを指定すると、gemのバージョンの進み方を極力少なくできるそうです。いいことを知りました。

見つけにくいスペルミスが原因でエラーになったお(RubyWeeklyより)

gaugesという名前を誤ってguagesにしてしまい、長年そのまま動いていたが、コード改修後のデプロイでビルドできなくなったことで発覚したというストーリーです。

レビュアーは何人もいたのに誰一人このスペルミスに気づかなかったという事実に著者も呆然としたそうです。

StringIO: ファイルのように振る舞うオブジェクト

StringIOを使って、ファイルのように振る舞うオブジェクトを使う方法を解説しています。StringIOはRubyの公式ライブラリです。

テストコードで「ファイル入出力をテストしたいが実際にファイルは作って欲しくない」場合などに便利そうです。

途中で「StringScannerは名前は似てるけど違いますよー」と書いてあります。

Friendly_id: URLの無味乾燥な数字をActiveRecordの値に置き換えるgem(RubyFlowより)

以下のように、URLのパラメータをActiveRecordの値に置き換えてくれるそうです。日本人には関係なさそうですね。

http://example.com/states/4323454
↓
http://example.com/states/washington

一同で見ながら「ブログタイトルなんかをこれで処理したら、タイトルが変わったときにURLがユニークじゃなくなっちゃうんじゃ?」というツッコミがありました。

RailsをCapybaraでリダイレクトさせる方法(RubyFlowより)

拍子抜けするほど短い記事です。英語の記事を読み慣れていない方はぜひチャレンジしてみてください。

Rails製CMSアプリのベストはどれだ(RubyFlowより)

Rails製CMSアプリっていったいいくつあるのでしょうか。こんなに嫌になるほど種類があって分散してしまうと「だったらWordPressでいいよ」となってしまいそうです。

Rubyで学ぶSOLIDオブジェクト指向(RubyFlowより)

みっちり書かれていてよさそうな感じの記事です。SOLIDについては以下をどうぞ。

我、明示的でないプログラミングを愛する(RubyWeeklyより)

DHHのエッセイです。

ざっとしか読んでませんが、「コードで常に何もかも明示的に書かなきゃいけないんだとしたらそんなのゴメンだ」「ActiveRecordのモデルみたいに簡潔な方がいいに決まってる」「コードを明示的に書くのが好きというヤツは、似たような定型コードを何度も何度も書かされるのが好きってことなんだろ、そのことを認識しないでそう言ってるんだとしたら不実じゃないのか」といったDHH節炸裂です。

原文のvalueは技術用語の「値」ではなく、本来の「値打ち」「価値」の方の意味ですね。

Danger: プルリクの書式や手順を揃えるgem(GitHub Trendingより)


http://danger.systems/より

CIでのコードレビュー作法を定めるgemだそうです。以下のような感じでコメントを付けます。


http://danger.systems/より

GitHub/GitLab/BitBucketなどのメジャーなリポジトリに対応しています。設定ファイルはそのまんまDangerfileです。以下のようなオプションがあります。

tableにコメント
message("アプリにgemを3つ以上追加したな(゚Д゚)ゴルァ!!")
CIワーニングの宣言
warn("CHANGELOGに何も書いてないぞ(゚Д゚)ゴルァ!!")
CIブロッキングエラーの宣言
fail("linterは大層お怒りのご様子です")
tableの下にmarkdownを出力
markdown("## ")
diff行にmarkdownを出力
warn("自分の名前書けや", file: "CHANGELOG.md", line: 4)

どなたか試してみたい勇者はいらっしゃいませんか。

Rooby->Goby: 100% Go言語で書かれたRuby実装

この間ウォッチで取り上げたgorubyはイマイチでしたが、このGobyはわたしもつい「もしや」と思ってしまいました。3か月ほどで早くも★が1100越えで、毎日のようにコミットが増えています。MacならHomebrewのcaskでもインストールできます。

ご多分に漏れず多くの機能が未実装で(まだirbもない)、最適化もこれからという状態ですが、ちょっと触ってみた限りでは動作が非常に安定しており、しかも「Ruby Under Microscope」やVMコードへのトランスパイルなどを下敷きにしたまっとうなつくりのようです。動作の猿真似ではなくちゃんと「すべてがクラス」になっています。

たとえば以下はRubyとGobyのどちらでも動きます。goby -cでバイトコードを出力できます(バイナリではなくテキスト)。

module Foo
  def ten
    10
  end
end

class Baz
  def ten
    1
  end

  def five
    5
  end
end

class Bar < Baz
  include(Foo)
end

b = Bar.new

puts(b.ten * b.five)

Gobyの立ち上がりはRubyに比べてかなり速いですが、現時点の機能の少なさと、外部ライブラリを読み込んでないオールインワンバイナリという点を差し引いておく方がよさそうです。個別のメソッドにはまだまだ遅いものもあるようです。

元々Roobyという名前でしたが、おととい 「Roobyという名前は米国人にとってRubyと発音がまったく同じなので紛らわしいんだよね: もちっと違う名前にしない?」というIssueが上がり、その翌日本当に名前をGobyに変えてしまいました


今週は以上です。

関連記事

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやRSSなど)です。

Ruby Weekly

RubyFlow

160928_1638_XvIP4h

Github Trending

160928_1701_Q9dJIU


【保存版】Rubyスタイルガイド(日本語・解説付き)総もくじ

$
0
0

こんにちは、hachi8833です。

「Rubyスタイルガイドを読む」シリーズのバックナンバーを改定し、「Rubyスタイルガイド(解説付き)」と題して総もくじ記事といたしました。

本スタイルガイドの元になっているbbatsov/ruby-style-guideは、同じ著者によるRuboCop gemで使われているスタイルです。

誤りや原文の追加・更新にお気づきの場合は、末尾のフォームまたは@techrachoでお知らせください。

1. ソースコードレイアウト

(1)エンコード、クラス定義、スペースなど

通し番号 カテゴリ スタイル
1-01 【統一】 ファイルエンコーディングはUTF-8にする
1-02 【統一】 インデントはスペース2文字で表現する
1-03 【統一】 改行文字はUnixスタイル(LFのみ)とする
1-04 【統一】 セミコロン;は文の区切りとしては使わない
1-05 【統一】 本文のない、空のクラス定義は1行で書くのが好ましい
1-06 【統一】 1行に多数の式を書くメソッド(シングルラインメソッド)は避ける
1-07 【統一】 演算子の前後にはスペースを置き、カンマ・コロン・セミコロンの後ろにもスペースを置く
1-07a 【例外】 累乗演算子**の場合はスペースを前後に置かない
1-08 【統一】 ()[]のすぐ内側にはスペースを置かず、{}のすぐ内側にはスペースを置く
1-08a 【例外】 式展開(interpolation)では波かっこ{}のすぐ内側にスペースを置かない

(2)インデント、記号など

通し番号 カテゴリ スタイル
1-09 【統一】 感嘆符!の後ろにはスペースを置かない
1-10 【統一】 範囲演算子.....の前後にはスペースを置かない
1-11 【統一】 case文内部のwhenのインデントの深さはcaseと同じにする
1-12 【統一】 変数に代入する条件式の後続文は、条件式と同じ位置にインデントを揃える
1-12a 【選択】 1-12のインデントスタイルは「良好1」「良好2」から選べる
1-13 【統一】 メソッド定義とメソッド定義の間には空行を置き、論理上のパラグラフ区切りにも空行を置く
1-14 【統一】 メソッド呼び出しの末尾パラメーターの後ろにはカンマを置かない(特にメソッド呼び出しが1行の場合)
1-15 【統一】 メソッドのパラメータでデフォルト値を与える場合、=の前後にはスペースを置く)
1-16 【統一】 \で行を継続するのは避ける(文字列の結合での利用のみ認める)
1-17 【統一】【選択】 ドット.によるメソッドチェーンの行分割はOption A(1-17a)とOption B(1-17b)のいずれかに揃える
1-18 【統一】 メソッド呼び出しを2行以上に分割する場合はパラメータを揃える
1-19 【統一】 配列リテラルの要素を複数行に分割する場合は縦を揃える
1-19a 【選択】 配列リテラルのブラケット[も次の行に送ってよい
1-20 【統一】 桁数の多い数値はアンダースコア_で区切る
1-21 【統一】 数値のprefixは小文字で書く(0x0o0bなど)
1-22 【統一】 APIドキュメントにはRDocを使い、RDocコメント行とdefの間に空行を置かない
1-23 【統一】 1行は80文字以内とする
1-24 【統一】 行末にスペース文字(ホワイトスペース)を置かない
1-25 【統一】 ファイルの末尾には空行を1つ置く
1-26 【統一】 =begin=endによるブロックコメントは使わない

2. 文法

(1)メソッド定義、引数、多重代入など

通し番号 カテゴリ スタイル
2-01 【統一】 ::は、定数(クラスやモジュールも含む)、コンストラクタ(Array()Nokogiri::HTML()など)の参照にのみ使う
2-02 【統一】 メソッド定義にパラメータがない場合は、def行のメソッドにかっこ()をつけない
2-03 【統一】 メソッド呼び出しのかっこ()は省略しない(特に第1引数がかっこ()を使った式の場合)
2-03a 【例外】 次の場合のみかっこ()を省略する:「引数のないメソッド呼び出し」「内部DSLの一部となっているメソッド呼び出し」「ステータスをキーワードで指定するメソッド」
2-04 【統一】 オプション引数は引数リストの末尾に置く
2-05 【統一】 変数定義での多重代入は避ける

(2)アンダースコア、多重代入、三項演算子、if/unless

通し番号 カテゴリ スタイル
2-06 【統一】 多重代入の左辺で単独のアンダースコア_変数を末尾に置くことはなるべく避ける
2-07 【統一】 forは原則使わない
2-08 【統一】 thenは、ifunlessが複数行の場合は使わない
2-09 【統一】 ifunlessと条件は常に同じ行に書く
2-10 【統一】 三項演算子(?:)の利用を推奨する
2-11 【統一】 三項演算子はネストしないこと
2-12 【統一】 if x; ...は三項演算子にする
2-13 【統一】 ifunlessが値を返す機能を積極的に使う
2-14 【統一】 case文で、1行で終わるwhen節ではthenを使う(when x; ...は使わない)

(3)演算子とif/unless

通し番号 カテゴリ スタイル
2-15 【統一】 否定は!演算子で表すこと(notは使わない)
2-16 【統一】 !!は避ける
2-17 【統一】 論理演算には常に演算子&&||を使う(andorは使わない)
2-18 【統一】 三項演算子(?:)は複数行では使わない
2-19 【統一】 実行行が1つの場合は後置のif/unlessを推奨する(&&||による制御フローでもよい)
2-20 【統一】 複雑な複数行ブロックでの後置if/unlessは避ける
2-21 【統一】 後置のif/unless/while/untilはネストしないこと(必要なら&&/||で)
2-22 【統一】 否定条件はなるべくunlessや制御フロー||を使って読みやすくする
2-23 【統一】 unlesselseを併用しないこと
2-24 【統一】 制御式の条件はかっこ( )で囲まない

(4)ループ

通し番号 カテゴリ スタイル
2-25 【統一】 複数行のwhileuntilでは条件の後にdoを置かない
2-26 【統一】 whileuntilの本文が単文の場合は後置にするのが望ましい
2-27 【統一】 否定条件をwhileで表すよりも、条件を肯定にしてuntilにする方が望ましい
2-28 【統一】 無限ループはKernel#loopで書くこと
2-29 【統一】 begin/end/untilbegin/end/whileではなく、Kernel#loopbreakを使う
2-30 【統一】 暗黙のハッシュを引数として渡す場合、外側の波かっこ{ }は省略する
2-31 【統一】 内部DSLの一部であるメソッドでは、引数の波かっこ{ }と丸かっこ( )を両方省略する

(5)ブロック、proc

通し番号 カテゴリ スタイル
2-32 【統一】 ブロックの各要素でメソッドを1つだけ呼び出す場合は、proc呼び出しのショートハンド「&:」を使う
2-33 【統一】 ブロック記法: 1行に収まる場合は原則{...}を使い、制御フローやメソッド定義では常にdo...endを使う
2-34 【統一】 引数を別のブロックに渡すだけのブロックは、明示的なブロック引数で書き直す
2-35 【統一】 不必要なreturnは避ける
2-36 【統一】 不必要なselfは避ける
2-37 【統一】 ローカル変数をメソッドでシャドウイングすることは避ける

(6)演算子など

通し番号 カテゴリ スタイル
2-38 【統一】 分岐の条件部分で=による代入の結果を値として使う場合は、条件全体をかっこ( )で囲むこと
2-39 【統一】 できるだけ自己代入演算子(+=など)を使う
2-40 【統一】【ヒント】 「変数が初期化されていない場合のみ初期化する」場合は、||=を使う
2-41 【統一】 論理値の初期化には||=を使わないこと
2-42 【統一】【ヒント】 「その変数が存在する場合のみ値を代入する」 処理には&&=を使う
2-43 【統一】 ===はなるべく避ける
2-44 【統一】 eql?は必要ない限り使わない: 極力==を使う
2-45 【統一】 Perl由来の特殊変数($:$;など)は極力避けること
2-46 【統一】 メソッド名と開きかっこ( の間にはスペースを置かない

(7)lambda、標準入出力など

通し番号 カテゴリ スタイル
2-47 【統一】 Rubyインタプリタは常に-wオプションをつけて実行すること
2-48 【統一】 メソッド定義はネストしないこと(ネスト定義が必要ならlambdaを使う)
2-49 【統一】 ブロックが1行のみなら、lambdaの略記->()を使い、ブロックが複数行なら略記でないlambdaメソッドを使う
2-50 【統一】 lambdaの略記->() { }でパラメータがある場合は丸かっこ( )を省略しないこと
2-51 【統一】 lambdaの略記->() { }でパラメータがない場合は丸かっこ( )を省略すること
2-52 【統一】 Proc.newは避け、procを使う
2-53 【統一】 lambdaやprocの実行にはproc.call()を使う: proc[]proc.()は避ける
2-54 【統一】 使わないブロックパラメータ名やローカル変数名の冒頭には_を付ける(_のみでもよい)
2-55 【統一】 標準入出力は$stdout/$stderr/$stdinに統一する
2-56 【統一】 $stderr.putsは避け、#warnを使うこと
2-57 【統一】 String#%は避け、sprintfまたはformatを使うこと

(8)配列や論理値など

通し番号 カテゴリ スタイル
2-58 【統一】 文字列を引数に取るArray#*はわかりにくいので避け、Array#joinを使うこと
2-59 【統一】【ヒント】 「変数が配列でない場合は配列に変換する」処理はArray()で書くこと
2-60 【統一】 範囲演算子やComparable#between?を使って比較をできるだけ簡潔に書く
2-61 【統一】 比較条件ではeven?zero?などの述語メソッドが望ましい(==の直接使用は避ける)
2-62 【統一】 nilでないことのチェック(nilチェック)は、論理値を扱っていることが確実でない限り行わないこと
2-63 【統一】 BEGINブロックは避ける
2-64 【統一】 ENDブロックは避け、Kernel#at_exitにする
2-65 【統一】 フリップフロップは避ける
2-66 【統一】 制御で条件をネストすることは避ける
2-67 【統一】 ループではnextの利用が望ましい
2-68 【目安】 #collectより#map#detectより#find#find_allよりselect#injectより#reduce#lengthより#sizeを優先
2-69 【統一】 #count#sizeの意味で使わないこと
2-70 【統一】 #map#flattenではなく#flat_mapを使うこと
2-71 【統一】 #reverse.eachよりも#reverse_eachが望ましい

3. 命名

通し番号 カテゴリ スタイル
3-01 【統一】 識別子の命名には英語を使う
3-02 【統一】 シンボル名、メソッド名、変数名はスネークケースにする
3-03 【統一】 シンボル名、メソッド名、変数名に付ける数字は文字と接すること
3-04 【統一】 クラス名とモジュール名はキャメルケースにする
3-05 【統一】 ファイル名とディレクトリ名はスネークケースにする
3-06 【統一】 ソースファイル名にはクラス名やモジュール名のキャメルケースをスネークケースに変えたものを使う
3-07 【統一】 その他の定数名は「スクリーミングスネークケース」にする
3-08 【統一】 述語メソッド名の末尾には疑問符?を置くこと
3-09 【統一】 述語メソッドの冒頭にisやdoesやcanといった助動詞はなるべく置かないようにする
3-10 【統一】 破壊的でないメソッドと破壊的なメソッドがある場合、破壊的メソッド名の末尾には感嘆符!を置くことで区別する
3-11 【統一】 可能な場合は、!付きの破壊的メソッドに対応する!なしの「破壊的でない」メソッドも定義すること
3-12 【統一】 2項演算子の定義では引数をotherに揃える

4. コメント、アノテーション、マジックコメント

コメント

通し番号 カテゴリ スタイル
4-01 【統一】 コード自身に語らせろ(コメントは最小限に)
4-02 【統一】 コメントは英語(≒自国語)で書くこと
4-03 【統一】 コメント記号#とコメント記号の間は1スペース開けること
4-04 【統一】 1語より長いコメント文は冒頭を大文字にし、句読点を省略せず、ピリオドの後にスペースをひとつ置く
4-05 【統一】 無駄なコメントは削ぎ落とすこと
4-06 【統一】 コメントを古いままにしないこと
4-07 【統一】 ダメなコードをカバーしようとするコメントを書くな

アノテーション

通し番号 カテゴリ スタイル
4-08 【統一】 アノテーションは、説明したいコードのすぐ上に書くこと
4-09 【統一】 アノテーションは、最初にキーワード、次にコロンとスペースを置き、それから問題を記述すること
4-10 【統一】 1行に収まりきれない場合は、2行目以降の#以降のスペースを3文字増やす
4-10a 【例外】 問題点が自明なら、該当箇所の後ろにキーワードだけのアノテーションをつけてもよい
4-11 【統一】 アノテーションのキーワードには以下を使う
TODO
後日追加が必要な、不足の機能
FIXME
修正の必要な箇所
OPTIMIZE
パフォーマンス低下の原因となる箇所
HACK
リファクタリングの必要なマズいコード
REVIEW
意図したとおりに動くかどうかチェックが必要
その他
必要ならキーワードを足してもよいが、READMEでキーワードを説明すること

マジックコメント

通し番号 カテゴリ スタイル
4-12 【統一】 マジックコメントはすべて最上部に置くこと
4-13 【統一】 1行に複数のマジックコメントを書かない
4-14 【統一】 マジックコメントと他の部分との間には空行を1つ置く

5. クラスとモジュール

(1)構造

通し番号 カテゴリ スタイル
5-01 【統一】 クラス定義は以下の構造で統一する
5-02 【統一】 mixinが複数ある場合は別々に書く
5-03 【統一】 1つのファイル内で、複数行の大きなクラスを別のクラス内で直接ネストしない
5-04 【統一】 クラスメソッドしか持たないクラスは、モジュールに書き換えるのが望ましい
5-05 【統一】 extend selfmodule_functionに書き換えるのが望ましい

(2)クラス設計・アクセサ・ダックタイピングなど

通し番号 カテゴリ スタイル
5-06 【統一】 クラス設計の階層はLiskovの置換原則に従う
5-07 【統一】 クラスはできるだけSOLIDに設計すること
5-08 【統一】 ドメインオブジェクトを表現するクラスには常に適切なto_sメソッドを実装すること
5-09 【統一】 重要度の低いアクセサやミューテータはattr_*で定義する
5-10 【統一】 アクセサやミューテータの名前にget_set_を使うことは避ける
5-11 【統一】 attrは原則使わない
5-12 【ヒント】 Struct#newを積極的に使う
5-13 【禁止】 Struct#newで初期化したインスタンスを継承しないこと
5-14 【ヒント】 クラスによってはファクトリーメソッドの追加を検討する
5-15 【ヒント】 継承よりもダックタイピングを積極的に使うこと

(3)クラスメソッド、スコープ、エイリアスなど

通し番号 カテゴリ スタイル
5-16 【統一】 クラス変数(@@で始まる変数)はできるだけ避ける
5-17 【統一】 メソッドの公開範囲を用途に応じて適切に設定する
5-18 【統一】 privateprotectedは対象となるメソッド定義とインデントを揃える
5-19 【統一】 クラスメソッドはdef self.methodで定義する
5-20 【統一】 メソッドをレキシカルクラススコープでエイリアスする場合はaliasを使うのが望ましい
5-21 【統一】 モジュールやクラスやシングルトンクラスのメソッドを実行時にエイリアスする場合は常にalias_methodを使う
5-22 【統一】 クラスやモジュールの中で同じクラスやモジュール内の他のメソッドを呼ぶ場合は、呼び出しのself.を省略する

6. 例外処理

通し番号 カテゴリ スタイル
6-01 【統一】 例外処理ではraiseを使う方がfailより望ましい
6-02 【統一】 raiseで引数を2つ与える場合、RuntimeErrorは明示的に指定しない
6-03 【統一】 raiseでは、例外クラスのインスタンスを引数に渡すのではなく、例外クラスとメッセージを2つの引数として与えるのが望ましい
6-04 【統一】 ensureブロックでは結果を返さないこと
6-05 【統一】 可能であればbeginブロックは省略する
6-06 【統一】 「contingency method」でbeginブロックを減らす
6-07 【統一】 例外を握りつぶさないこと
6-08 【統一】 後置のrescueは避ける
6-09 【統一】 例外処理を制御フローに使わないこと
6-10 【統一】 Exceptionクラスをrescueすることは避ける
6-11 【必須】 詳細な例外処理はrescueチェーンの上位に置き、大域的な例外処理は下位に置くこと
6-12 【統一】 プログラムで取得した外部リソースは必ずensureブロックで解放すること
6-13 【統一】 リソース取得メソッドは、不要になったリソースを自動でクリーンアップするタイプのものを使う
6-14 【統一】 例外処理には標準ライブラリを使うのが望ましい

7. コレクション(Array、Hash、Setなど)

通し番号 カテゴリ スタイル
7-01 【統一】 配列やハッシュの作成は[]{}によるリテラル表記が望ましい
7-02 【統一】 配列のリテラル表記で語の配列が必要な場合は%wが望ましい
7-03 【統一】 配列のリテラル表記でシンボルの配列が必要な場合は%iが望ましい
7-04 【統一】 配列のリテラルやハッシュのリテラルで最後の項目直後のカンマは避ける
7-05 【統一】 大きな配列を途中から作成することは避ける
7-06 【統一】 配列の最初の要素には#first、最後の要素には#lastでアクセスするのが望ましい
7-07 【統一】 要素を重複させたくない場合はArrayではなくSetを利用する
7-08 【統一】 ハッシュのキーは文字列よりもシンボルが望ましい
7-09 【統一】 ミュータブルなオブジェクトをハッシュのキーにすることは避ける
7-10 【統一】 ハッシュのキーがシンボルならシンボル:で表記する
7-11 【統一】 Ruby 1.9記法とロケット演算子記法を同一ハッシュリテラル内で混ぜないこと
7-12 【統一】 ハッシュではHash#key?Hash#value?を使うこと
7-13 【統一】 ハッシュではHash#each_keyHash#values_eachを使うこと
7-14 【統一】 ハッシュのキーの存在を前提にする場合はHash#fetchを使うこと
7-15 【統一】 ハッシュ値のデフォルト値が欲しい場合はHash#fetchでデフォルト値を与えること
7-16 【ヒント】 Hash#fetchで取る値を評価するコードに副作用がある場合やコストが高い場合は、コードをデフォルト値で与えるよりもコードをブロックで与える方が望ましい
7-17 【統一】 ハッシュから複数の値を一度に取り出す場合はHash#values_atを使うこと
7-18 【統一】 ハッシュの要素の順序が保持されることを前提としてコードを書くこと
7-19 【統一】 コレクションの全要素をスキャン中に要素を変更しないこと
7-20 【統一】 コレクションにはできるだけ[n]以外の読み出しメソッドでアクセスすること
7-21 【統一】 コレクションにアクセサを実装する場合はnilチェックすること

8. 数値、文字列、日時(日付・時刻・時間)

数値

通し番号 カテゴリ スタイル
8-01 【統一】 整数値のチェックにはIntegerを使うこと
8-02 【統一】 乱数生成では範囲演算子...の利用が望ましい

文字列

通し番号 カテゴリ スタイル
8-03 【統一】 文字列の結合は+ではなく、式展開と文字列フォーマットが望ましい
8-04 【統一】 一重引用符(シングルクォート: ')と二重引用符(ダブルクォート: ")の用法をいずれかに統一すること
8-04a 【選択】 A: 一重引用符'を優先するスタイル
8-04b 【選択】 B: 二重引用符"を優先するスタイル
8-05 【統一】 ?xのような文字列リテラル構文は使わないこと
8-06 【統一】 文字列にインスタンス変数やグローバル変数を式展開で挿入する場合に{}を省略しないこと
8-07 【ヒント】 式展開するオブジェクトでObject#to_sを呼ばないこと
8-08 【ヒント】 大きなデータの作成にはString#+ではなく、String#<<を使うこと
8-09 【統一】【ヒント】 文字列置き換えや削除は可能な限りString#gsub以外のメソッドを使うこと
8-10 【ヒント】 複数行に渡る文字列をヒアドキュメントで扱う場合は、各行冒頭のスペース文字も読み込まれていることに注意する
8-11 【統一】 ヒアドキュメントで複数行のインデントを保つにはRuby 2.3の「<<~終了文字列」記法を使うこと

日付・時間

通し番号 カテゴリ スタイル
8-12 【統一】 システム時間の取得はTime.nowが望ましい
8-13 【統一】 DateTimeはカレンダーを過去に遡って再編成する必要がない限り使わないこと

9. 正規表現、%リテラル、メタプログラミングなど

正規表現

通し番号 カテゴリ スタイル
9-01 【統一】 文字列変数['テキスト']」で単純な文字列を検索するのであれば正規表現にしないこと
9-02 【選択】 コードをシンプルにするために、文字列のインデックスに正規表現を直接書いてもよい
9-03 【統一】【ヒント】 正規表現でキャプチャの結果が不要な場合はキャプチャなしグループ(?:)を使うこと
9-04 【統一】 最後にマッチしたグループの取り出しにPerl由来の$記法($1$2など)を使わないこと
9-05 【統一】 キャプチャグループは番号での指定ではなく名前付きグループでの指定が望ましい
9-06 【統一】 文字クラス[ ]の中ではドット.やかっこの類をエスケープしないこと
9-07 【必須】 重要: パターンの冒頭と末尾は^$ではなく、\A\zで表すこと
9-08 【統一】 複雑な正規表現はx修飾子で読みやすくすること
9-09 【ヒント】 複雑な置換では、#sub#gsubにブロックやハッシュを与えてもよい

リテラル

通し番号 カテゴリ スタイル
9-10 【統一】 二重引用符を含む1行の文字列を式展開する場合は%()%Qの省略形)を使う: 複数行ならヒアドキュメントを使う
9-11 【統一】 %()(または同等の%q())は、一重引用符と二重引用符を両方含む文字列以外では使わないこと
9-12 【統一】 正規表現リテラル%rは、正規表現に/文字が含まれていなければ使わないこと
9-13 【統一】 コマンドリテラル%xは避ける(コマンド自体にバッククォートがある場合を除く)
9-14 【統一】 シンボルリテラル%sの利用は避けること
9-15 【統一】 %リテラルで使うかっこは、リテラルの種類に応じて使い分けること(文字列: ()、配列: []、正規表現: {}

メタプログラミング

通し番号 カテゴリ スタイル
9-16 【統一】 必要のないメタプログラミングは避けること
9-17 【統一】 コアクラスにモンキーパッチを当てないこと
9-18 【統一】 class_evalは文字列の式展開形式ではなくブロック形式が望ましい
9-19 【統一】 文字列の式展開でclass_evalなどのevalメソッドを使う場合は、式展開を具体的に示すコメントを追加すること
9-20 【統一】 method_missingは避け、代わりに委譲、プロキシ、define_methodの利用を検討すること
9-21 【統一】 sendは避け、public_sendを使うこと
9-22 【統一】 sendよりもアンダースコア付きの__send__が望ましい

その他

通し番号 カテゴリ スタイル
9-23 【統一】 ruby -wオプションを使って安全なコードを書くこと
9-24 【統一】 オプションパラメータにハッシュを使うことは避ける
9-25 【目安】 1つのメソッドのコードは10行以内に収めること
9-26 【統一】 パラメータリストは最大5個までにする
9-27 【統一】 「グローバルな」メソッドがどうしても必要な場合は、Kernelクラスのprivateメソッドにすること
9-28 【統一】 グローバル変数は使わないこと: 必要な場合はモジュールのインスタンス変数を使う
9-29 【統一】 コマンドラインオプションが複雑になったらOptionParserを使い、細かなオプションではruby -sを使う
9-30 【統一】 理由がない限り、状態の保持を避けて関数的に書くこと
9-31 【統一】 受け取ったパラメータを変更しないこと(破壊的なメソッドを除く)
9-32 【統一】 ブロックのネストは3階層までにする
9-33 【統一】 コーディングスタイルを一貫させること
9-34 【統一】 常識を働かせること

凡例

本スタイルガイドのコード例で使われている英数字と記号はすべてASCII(半角文字)です。本文中のみあえて全角丸かっこ()を使うことがあります。

原文にはありませんが、各スタイルや解説の冒頭に以下のカテゴリを追加します。
カテゴリの前には「章番号-項番号」形式の通し番号とパーマネントリンクを追加します。

  • 【統一】: Rubyで利用できる複数の記法を統一する指示です(狭い意味でのスタイルガイド)
  • 【選択】: 案件や現状に応じて選んでよいスタイルを示します
  • 【必須】: Rubyの特定の機能などで必須の記法を示します(スタイルよりも意味が強い)
  • 【禁止】: Rubyでどんな場合にも使ってはならない禁止事項を示します(スタイルよりも意味が強い)
  • 【例外】: スタイルや禁止事項などの例外を示します
  • 【目安】: スタイルよりも制約の少ない目安を示します
  • 【ヒント】: スタイルに限定されない、知っておくと有用な知識であることを示します

お気づきの点やご意見・ご感想はこちらでどうぞ

[Devise How-To] OmniAuth: 概要(翻訳)

$
0
0

こんにちは、hachi8833です。Devise How-TO翻訳シリーズは、需要の多いOmniAuth関連を当面優先してお送りいたします。どうぞよろしくお願いします。

DeviseでOmniAuthを使うことで、Facebook認証、Twitter認証、Googleアカウント認証、GitHubアカウント認証などを導入できます。本翻訳記事の第一弾は、その基礎にあたる部分の解説です。

概要

読みやすさのため、config.omniauthのコードを途中で改行しています。

原文の更新や誤りにお気づきの場合は、ぜひ@techrachoまでお知らせください。更新いたします。

OmniAuth: 概要(翻訳)

Deviseはバージョン1.2からOmniAuthとの統合をサポートしています。このWikiページでは「とあるOAuthプロバイダ」を例に使って連携の基本を解説します。

Deviseバージョン1.5はOmniAuth 1.0以降をサポートするため、本チュートリアルではこれを用います。

始める前に

アプリケーションにはconfig.omniauthによってomniauth providerミドルウェアが追加されることを理解しておいてください。つまり、このproviderミドルウェアをconfig/initializers/omniauth.rbに再度追加してはいけないということです。もし再追加すると両方ともクラッシュし、認証が常に失敗してしまいます。

Facebookの例

最初にアプリにOmniAuth gemを追加します。この場合はGemfileに以下のように記述します。

gem 'omniauth-facebook'

ここではFacebookを例として使いますが、他のどんなOmniAuth gemを使ってもかまいません。gem名は一般に「omniauth-プロバイダ名」の形式になっています。プロバイダ名はfacebookだったりtwitterだったりします。完全なプロバイダリストについてはList of Strategiesをご覧ください。

次に、Userモデルにproviderカラム(文字列)とuidカラム(文字列)を追加します。

rails g migration AddOmniauthToUsers provider:string uid:string
rake db:migrate

次に、使うプロバイダをconfig/initializers/devise.rbで宣言する必要があります。

config.omniauth :facebook,
                "APP_ID",
                "APP_SECRET"

注意: v2.0.1にはコールバックURLエラーの問題があります。回避するには、configにcallback_urlを追加する必要があります。

config.omniauth :facebook,
                "APP_ID",
                "APP_SECRET",
                callback_url: "CALLBACK_URL"

上のAPP_IDAPP_SECRETは、アプリのidとsecret(秘密情報)に置き換えます。CALLBACK_URLhttps://myapp.com/users/auth/facebook/callbackのような形式にする必要があります。

パーミッションやリクエストされたスコープを変更するには、the omniauth-facebook gemのREADMEをご覧ください。

strategyの設定が終わったら、モデル(app/models/user.rbなど)に以下を記述してOmniAuthを有効にします。

devise :omniauthable, :omniauth_providers => [:facebook]

注意: Railsサーバーを実行している場合は、Deviseイニシャライザに:omniauthableを追記するなどして変更した後にRailsを再起動しないとエラーになります。

現時点では、Deviseでomniauthableにできるモデルは1つだけです。OmniAuthで複数のモデルを使いたい場合は、OmniAuth with multiple modelsをご覧ください。

Userモデルをomniauthableにすると、config/routes.rbにdevise_for :usersを記述することで以下のURLメソッドがDeviseによって作成されます。

  • user_omniauth_authorize_path(provider)
  • user_omniauth_callback_path(provider)

Deviseは*_urlというメソッドは作成しません(そのディレクトリより上でコールバックヘルパーを使うことはありえないため)。

Facebookによる認証を提供するには、レイアウトに上の1つ目のメソッドを以下のように追加します。

<%= link_to "Sign in with Facebook", user_facebook_omniauth_authorize_path %>

注意: user_omniauth_authorize_pathメソッドに渡されるシンボルは、Deviseのconfigブロックに渡されるプロバイダのシンボルと一致します。どのシンボルを渡せばよいかについては、omniauth-*のREADMEをご覧ください。

上のリンクをクリックすると、ユーザーはFacebookにリダイレクトされます(リンクが表示されていない場合は、サーバーを再起動してください)。

Facebook側で認証情報が挿入されると、再度リダイレクトしてアプリケーションのコールバックメソッドに戻ります。コールバックを実装する場合に最初に必要なのは、config/routes.rbを開いて、Omniauthコールバックを実装するコールバックをDeviseに伝えます。

devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }

次は、以下のコードのみを含むapp/controllers/users/omniauth_callbacks_controller.rbを追加します。

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
end

実装するコールバックのアクションは、プロバイダと同じ名前にする必要があります。facebookプロバイダのアクションの場合、たとえば以下のようなコードをコントローラに追加できます。

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def facebook
    # 以下のメソッドをモデル(app/models/user.rbなど)で実装する必要がある
    @user = User.from_omniauth(request.env["omniauth.auth"])

    if @user.persisted?
      sign_in_and_redirect @user, :event => :authentication #@userが無効だと例外がスローされる
      set_flash_message(:notice, :success, :kind => "Facebook") if is_navigational_format?
    else
      session["devise.facebook_data"] = request.env["omniauth.auth"]
      redirect_to new_user_registration_url
    end
  end

  def failure
    redirect_to root_path
  end
end

このアクションについていくつか補足します。

  1. FacebookからOmniAuthで取得したすべての情報はrequest.env["omniauth.auth"]のハッシュでアクセスできます。取得できる情報の種類については、OmniAuthのドキュメントとと各gem(Facebookの場合はomniauth-facebook)のREADMEをご覧ください。
  2. 有効なユーザーが見つかると、Deviseのsign_inメソッドまたはsign_in_and_redirectメソッドでサインインできます。:event => :authenticationはオプションであり、Wardenのコールバックを使う場合にのみ渡す必要があります。

  3. Deviseのデフォルトのメッセージの中からflashメッセージに流用することも、使わないこともできます。

  4. ユーザーがサイトから離れたら、OmniAuthのデータをセッションに保存します。データの保存では、devise.が名前空間のキーとして使われます。これにより、ユーザーがサインインに使ったセッションからdevise.で始まるすべてのデータがDeviseによって削除され、セッションが自動的にクリーンアップされます。最後に、ユーザーを元の登録フォームページにリダイレクトします。

コントローラを定義したら、モデル(app/models/user.rbなど)で以下のようにfrom_omniauthメソッドを実装します。

def self.from_omniauth(auth)
  where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
    user.email = auth.info.email
    user.password = Devise.friendly_token[0,20]
    user.name = auth.info.name   # Userモデルにnameカラムがあるとする
    user.image = auth.info.image # Userモデルにimageカラムがあるとする
    # Deviseのconfirmableを使い、かつプロバイダがメールのバリデーションを行っている場合は、
    # 以下の行のコメントを解除して確認メールをスキップする
    # user.skip_confirmation!
  end
end

このメソッドは、providerフィールドとuidフィールドを指定して既存のユーザーを検索します。ユーザーが見つからない場合は、ランダムなパスワードなどの情報を追加して新しいユーザーを作成します。

なお、first_or_createメソッドを使うと、ユーザー作成時にproviderフィールドとuidフィールドが自動的に作成されることにご注意ください。first_or_create!メソッドも基本的に同じですが、ユーザーレコードのバリデーションに失敗するとExceptionがraiseされる点が異なります。

訳注: first_or_createメソッドやfirst_or_create!メソッドは以下のようにActiveRecord::Queryingdelegateで実装されていますが、APIドキュメントはありません。

# https://github.com/rails/rails/blob/5-1-stable/activerecord/lib/active_record/querying.rb#L5 より
module ActiveRecord
  module Querying
    ...
    delegate :first_or_create, :first_or_create!, :first_or_initialize, to: :all

また、DeviseのRegistrationsControllerはリソースをビルドする前にデフォルトでUser.new_with_sessionを呼び出します。つまり、ユーザー登録(サインアップ)前にユーザーが初期化された場合にセッションのデータをコピーする必要がある場合は、モデルでnew_with_sessionのみを実装すればよいということです。

以下のコード例では、Facebookのメールが取得できる場合にメールをコピーします。

class User < ApplicationRecord
  def self.new_with_session(params, session)
    super.tap do |user|
      if data = session["devise.facebook_data"] && session["devise.facebook_data"]["extra"]["raw_info"]
        user.email = data["email"] if user.email.blank?
      end
    end
  end
end

最後に、ユーザーがFacebookでのユーザー登録(サインアップ)をキャンセルできるようにするには、ユーザーをcancel_user_registration_pathにリダイレクトするようにします。これにより、Deviseが開始したセッションデータはすべて削除され、上のnew_with_sessionフックがその後呼ばれることはなくなります。

ログアウト用リンク

# config/routes.rb
devise_scope :user do
  delete 'sign_out', :to => 'devise/sessions#destroy', :as => :destroy_user_session
end

上のコードだけでできます。動作がうまく統合されるようになったら、以下を参考に統合テストを書きましょう。

OmniAuthだけで認証する

他の認証方法を使わずOmniAuthだけで認証する場合、new_user_sessionという名前のルーティングを定義する必要があります(未定義の場合はrootが使われます)。

この場合のルーティング例を以下に示します。OmniAuthでデータベースや他の認証方法を併用している場合、以下は不要です。

devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }

devise_scope :user do
  get 'sign_in', :to => 'devise/sessions#new', :as => :new_user_session
  get 'sign_out', :to => 'devise/sessions#destroy', :as => :destroy_user_session
end

上のルーティングを使う場合、sessionsコントローラには何も処理を追加しません。たとえばプロバイダの認証リンクを表示できればよいのであれば、これで十分です。

注意: Deviseでsign_out_via = :deleteを設定している場合は、sign_outルーティングがDELETEリクエストにマッチするようコードの修正が必要になることがあります。

また、:database_authenticatableを使っていない場合は、以下のようにnew_session_path(scope)ヘルパーメソッドを定義して、失敗した場合に正しくリダイレクトする必要があります。

class ApplicationController < ActionController::Base
# ...
  def new_session_path(scope)
    new_user_session_path
  end
end

トラブルシューティング

ユーザーがメールアドレスの利用を許可していなかった場合

Facebookの新しいログインダイアログでは、ユーザーはメールアドレスの入力を拒否できるようになっています。通常、Deviseの登録ではメールが必要になります。こういう場合の手っ取り早い解決方法は、メールアドレスがない場合は再度リクエストするというものです。

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def facebook
    if request.env["omniauth.auth"].info.email.blank?
      redirect_to "/users/auth/facebook?auth_type=rerequest&scope=email"
    end
  end
end

Facebookから返されたメールアドレスが空になっている場合

2015年7月8日以降、FacebookはAPIをv2.4に更新しました。このため、emailフィールドにアクセスするにはinfo_fieldsの追加が必要です。

config.omniauth :facebook,
                "APP_ID",
                "APP_SECRET",
                scope: 'email',
                info_fields: 'email,name'

参考: @techmonsterによる解決方法

OpenSSLエラー

以下のようなOpenSSLエラーが発生することがあります。

OpenSSL::SSL::SSLError (SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed):

この場合、CA証明書ファイルの保存場所をOmniAuthに対して明示的に指定する必要があります。これには、certified gemを使う方法と、以下の方法があります。後者は実行しているOS環境によって異なります。

config.omniauth :facebook,
                "APP_ID",
                "APP_SECRET",
                :client_options => {:ssl => {:ca_path => '/etc/ssl/certs'}}

Herokuの場合、CAファイルは/usr/lib/ssl/certs/ca-certificates.crtに保存されます。

Engine Yard Cloud serversの場合、CAファイルは/etc/ssl/certs/ca-certificates.crtに保存されます。

証明書の設定には、:ca_fileキーを使います。

config.omniauth :facebook,
                "APP_ID",
                "APP_SECRET",
                :client_options => {:ssl => {:ca_file => '/usr/lib/ssl/certs/ca-certificates.crt'}}

OAuth gemを使うstrategy(omniauth-oauthなど)を利用する場合、次の方法で証明書ファイルを指定します。

config.omniauth :facebook,
                "APP_ID",
                "APP_SECRET",
                :client_options => {:ca_file => '/usr/lib/ssl/certs/ca-certificates.crt'}

Mac OS Xでは証明書はファイルシステムではなくMacのキーチェーンアプリに保存されるので、Mac OS上での開発中に限れば、以下のように単に証明書の検証を無効にするのが最も簡単です。

require "omniauth-facebook"
config.omniauth :facebook,
                "APP_ID",
                "APP_SECRET",
                :client_options => { :ssl => { :verify => !Rails.env.development? } }

このエラーの詳しい議論については、#260をご覧ください。

strategyクラスを読み込めない場合

Deviseが何らかの理由でアプリのstrategyクラスを読み込めない場合、以下のように:strategy_classオプションで明示的に指定できます。

config.omniauth :facebook,
                "APP_ID",
                "APP_SECRET",
                :strategy_class => OmniAuth::Strategies::Facebook

関連記事(Devise)

[Rails 5.1]thread_mattr_accessorの変数はサブクラスと共有されないようになった(翻訳)

$
0
0

こんにちは、hachi8833です。BigBinaryシリーズは、Rails 5.1記事と、それに関連するRails 5時代の記事をお送りいたします。

[Rails5]モジュールやクラスレベルの変数をスレッドベースで作成する機能(翻訳)

概要

以下の環境で確認しました。

  • Rails 5.1.1 + Ruby 2.4.1
  • Rails 5.0.2 + Ruby 2.4.1
  • Rails 5.0.0 + Ruby 2.3.3

thread_mattr_accessorの変数はサブクラスと共有されないようになった(翻訳)

Rails 5.0から、クラス変数をスレッドベースで定義するmattr_accessorが提供されています。

しかし、定義した変数は子クラスとも共有されるという問題がありました。つまり、子クラスで変数を変更すると以下のように親クラスの変数も変更されてしまいます。

class Player
  thread_mattr_accessor :alias
end

class PowerPlayer < Player
end

Player.alias = 'Gunner'
PowerPlayer.alias = 'Bomber'

> PowerPlayer.alias
#=> "Bomber"

> Player.alias
#=> "Bomber"

これはオブジェクト指向的に望ましい動作ではありません。

この問題はRails 5.1で修正されましたthread_mattr_accessorの値を子クラスで変更しても親クラスの値には影響しません。

class Player
  thread_mattr_accessor :alias
end

class PowerPlayer < Player
end

Player.alias = 'Gunner'
PowerPlayer.alias = 'Bomber'

> PowerPlayer.alias
#=> "Bomber"

> Player.alias
#=> "Gunner"

訳注: この修正は5.0-stableにもバックポートされています。

関連記事

[Rails5]モジュールやクラスレベルの変数をスレッドベースで作成する機能(翻訳)

$
0
0

こんにちは、hachi8833です。BigBinaryシリーズの本日2本目をお送りします。

本機能のRails 5.1での修正点については「[Rails 5.1]thread_mattr_accessorの変数はサブクラスと共有されないようになった(翻訳)」をご覧ください。

概要

モジュールやクラスレベルの変数をスレッドベースで作成する機能(翻訳)

Railsでは以前から、cattr_readercattr_writer、cattr_accessor`という一連のメソッドを使ってクラスレベルの変数やモジュールレベルの変数を作成する機能が提供されていました。

Rails 5ではこれをさらに押し進め、クラスレベルの変数やモジュールレベルの変数をスレッド固有の形で作成できるようになりました。
以下の例をご覧ください。

module CurrentScope
  thread_mattr_accessor :user_permissions
end

class ApplicationController < ActionController::Base

  before_action :set_permissions

  def set_permissions
    user = User.find(params[:user_id])
    CurrentScope.user_permissions = user.permissions
  end

end

このCurrentScope.user_permissionsは、実行中のスレッドが完了するまでアクセスできるので、このコード以降で変数にアクセスできます。

これを利用して、たとえばコントローラで明示的にcurrent_userを渡さなくてもすべてのモデルで変数にアクセスできるようになります。

class BookingsController < ApplicationController
  def create
    Booking.create(booking_params)
  end
end

class Booking < ApplicationRecord
  validate :check_permissions

  private

  def check_permissions
    unless CurrentScope.user_permissions.include?(:create_booking)
      self.errors.add(:base, "Not permitted to allow creation of booking")
    end
  end
end

内部で使われているThread.current#[]=メソッドによって、指定したすべての変数のスコープが現在実行中のスレッドに対して設定されます。

作成したクラスやモジュールの変数では名前空間が有効になるため、衝突の心配なしにCurrentScope.user_permissionsRequestScope.user_permissionsでアクセスできます。

従来PerThreadRegistryでグローバル変数を管理していた場合は、Rails 5からthread_mattr_*thread_cattr_*に置き換えられます。

グローバル変数は一般に悪影響をもたらすため、今後はこのような改良APIを利用しましょう。

訳注: PerThreadRegistryは現在非推奨になっています。

関連記事

[Devise How-To] OmniAuth認証を複数のモデルで共用する方法(翻訳)

$
0
0

こんにちは、hachi8833です。引き続きDevise How-ToのOmniAuthシリーズをお送りします。

概要

読みやすさのため、横に長いコードを途中で改行しています。

原文の更新や誤りにお気づきの場合は、ぜひ@techrachoまでお知らせください。更新いたします。

OmniAuth: 概要(翻訳)

現時点では、DeviseのOmniauthableモジュールはそのままでは1つのモデルでしか利用できません。しかしご安心ください。OmniauthableはOmniAuthの単純なラッパーにすぎないので、方法はあります。

OmniAuthを複数モデルで認証に使えるようにするには、次の手順を進めます。

1. モデルから:omniauthable引数を削除する

2. Deviseの設定ファイルからOmniAuthの設定を削除して新しいファイルに移動する

設定を切り出す前のdevise.rbが以下のようになっているとします。

Devise.setup do |config|
  config.omniauth :twitter, ENV['TWITTER_KEY'], ENV['TWITTER_SECRET']
end

上を削除して、以下のように新しいomniauth.rbファイルに保存します。

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :twitter, ENV['TWITTER_KEY'], ENV['TWITTER_SECRET']
end

3. OmniAuth独自のルーティングを作成する

authentications_controller.rbというコントローラがある場合、次のようなルーティングを使います。

  • Rails 3、Rails 4の場合
get "/auth/:action/callback", :to => "authentications",
                              :constraints => { :action => /twitter|google/ }
  • Rails 5の場合
get "/auth/:action/callback", :controller => "authentications",
                              :constraints => { :action => /twitter|google/ }

OmniAuthによるすべての認証を同じcreateアクションにルーティングしたい場合は、次のようにします。この方法はプロバイダの種類に依存しません。

get "/auth/:provider/callback" => "authentications#create"

マッピングエラーが発生する場合は、以下のようにルーティングをdevise_scopeでラップします。

devise_scope :user do
  get "/auth/:provider/callback" => "authentications#create"
end

4. OmniAuthのon_failureフックを設定する

この設定は、以下のようにOmniAuthの設定用ブロックの中で行えます。

Rails.application.config.middleware.use OmniAuth::Builder do
  on_failure { |env| AuthenticationsController.action(:failure).call(env) }
end

あるいは、以下のように設定用ブロックの外でも行えます。

OmniAuth.config.on_failure = Proc.new { |env| AuthenticationsController.action(:failure).call(env) }

Deviseにはこの2番目のオプションが実装されていますが、omniauth.rbファイルが設定済みなので、1番目のオプションの方がシンプルに書けます。

この設定では、認証の失敗(ユーザーのログインのキャンセルなど)はすべてauthentications_controller.rbコントローラのfailureアクションにリダイレクトされます。

5. OmniAuthでの認証エラー処理

このライブラリのエラーは残念ながらあまり標準化されていないため、Devise自身で行っているエラー処理と同様に多くの条件分岐を処理するしかありません。ここから使えそうなコードをコピペして何とかすることになります。

RailsCastsの「Simple OmniAuth」も設定の参考になります。

関連記事(Devise)

OminiAuth

その他

週刊Railsウォッチ(20170519)Rails 5.1.1/5.0.3リリース、RailsでAPIサーバーを作る、HackerNewsでトップになるチャンス?ほか

$
0
0

こんにちは、hachi8833です。fishをそおっとインストールしてみましたがとりあえず元に戻しました。

番外

  • サイエンスライターの森山和道さんの「あれこれ新聞」にTechRacho記事を掲載いただきました。ありがとうございます!
  • パーフェクトRuby(改訂2版)が発売されました。Ruby 2.4対応だそうです。おめでとうございます。早速ポチりました。

早くもRails 5.1.1と5.0.3がリリース(Rails公式ニュースより)

バグフィックスが中心なので急いでインストールする必要はないとのことです。

5.0.2 -> 5.2.35.0.3のコミット
5.1.0 -> 5.1.1のコミット

今回は5.1.1の修正点を中心に追いました。Railsウォッチで取り上げたもの、バージョン変更、スペルミスなどのコミットは省略しました。

#29029: rake -Tでtest環境ではなくdevelopment環境をデフォルトで読み込むよう修正

# railties/lib/rails/test_unit/railtie.rb
 require "rails/test_unit/line_filtering"

  if defined?(Rake.application) && Rake.application.top_level_tasks.grep(/^(default$|test(:|$))/).any?
-   ENV["RAILS_ENV"] ||= "test"
+   ENV["RAILS_ENV"] ||= Rake.application.options.show_tasks ? "development" : "test"
  end

rake -Tそのものは、定義されているrakeタスク一覧を出力します。テスト環境のrakeタスクと知らなかったらはまりそうです。

その場でmorimorihogeさんから、bundle exec rake middlewareでRailsのミドルウェアをリストアップできることを教わりました。「このミドルウェアの読み込み順序が変わると問題が発生することあるんですよ」とのことです。

#29034: Rescuable#rescue_with_handlerで例外の原因チェーン(cause chain)を扱えるよう修正

Ruby 2.4.1だとこの現象を再現できなかったそうです。cause chainという言葉をどう扱おうかと思いましたが、ひとまず「原因チェーン」にしてみました。

# activesupport/lib/active_support/rescuable.rb
-     def rescue_with_handler(exception, object: self)
+      def rescue_with_handler(exception, object: self, visited_exceptions: [])
+        visited_exceptions << exception
+
          if handler = handler_for_rescue(exception, object: object)
            handler.call exception
            exception
          elsif exception
-          rescue_with_handler(exception.cause, object: object)
+          if visited_exceptions.include?(exception.cause)
+            nil
+          else
+            rescue_with_handler(exception.cause, object: object, visited_exceptions: visited_exceptions)
+          end
         end

#29040: ActionController::Parameters#deleteにブロックを渡せるよう再修正

# actionpack/lib/action_controller/metal/strong_parameters.rb
-    def delete(key)
-      convert_value_to_parameters(@parameters.delete(key))
+    def delete(key, &block)
+      convert_value_to_parameters(@parameters.delete(key, &block))
     end

もともと#20868Hash#deleteを継承してブロックを渡せるようになっていたのが、どこかで継承されなくなっていたとのことです。

#29043: FinderMethods#exists?でemptyの場合のeager loadingを抑制

kamipoさんによる#29025の修正です。

# activerecord/lib/active_record/relation/finder_methods.rb
-      return false if !conditions
+      return false if !conditions || limit_value == 0
+
+      relation = self unless eager_loading?
+      relation ||= apply_join_dependency(self, construct_join_dependency(eager_loading: false))

-      relation = apply_join_dependency(self, construct_join_dependency(eager_loading: false))
       return false if ActiveRecord::NullRelation === relation

#28995: Capybaraがマイナーバージョンアップした場合にも追従できるよう変更

2.13.0でロックされてたんですね。

-gem "capybara", "~> 2.13.0"
+gem "capybara", "~> 2.13"

つっつきボイス「これ悩ましい問題だよね」「semantic versioningに対して挙動の違うgemがあるとこうやってハマる」

#29002: テストコードの引数に丸かっこを追加

# activerecord/test/cases/quoting_test.rb
      def test_quote_with_quoted_id
-       assert_deprecated /defined on \S+::QuotedOne at .*quoting_test\.rb:[0-9]/ do
+       assert_deprecated(/defined on \S+::QuotedOne at .*quoting_test\.rb:[0-9]/) do
        assert_equal 1, @quoter.quote(QuotedOne.new)
      end

-       assert_deprecated /defined on \S+::SubQuotedOne\(\S+::QuotedOne\) at .*quoting_test\.rb:[0-9]/ do
+       assert_deprecated(/defined on \S+::SubQuotedOne\(\S+::QuotedOne\) at .*quoting_test\.rb:[0-9]/) do
        assert_equal 1, @quoter.quote(SubQuotedOne.new)
      end
    end

テストのワーニング解消です。その場では「正規表現の開始終了文字/でワーニングが出ていたのかな?」「それともブロックの有無で出るのかな?」という話題どまりでしたが、BPS Webチームのakioさんが翌日再確認したところ、前者の引数の/正規表現/を丸かっこで囲まないことに対してのワーニングでした。

  • //の場合: 「ambiguous first argument; put parentheses or a space even after `/’ operator」
  • (//)の場合: warningなし
  • %r//の場合: warningなし

akioさんいわく「Railsの修正も%r//を使った方がスマートだったかなー」とのことでした。ありがとうございます!

5a6091: テストコードでrubygems 2.6.12を一時的に差し止め

rubygemsのバグですが、原因不明なのでとりあえず2.6.11を使うようにしました。「rubygemsで起きるとはつらいねー」と思わず声が上がりました。

-  - "gem update --system"
+  - "gem update --system 2.6.11"

#28978#28337: Railsガイドのディレクトリパスがおかしいのを修正

ロケールが重複していたのを修正しています。韓国の開発者のようです。

Before:

source_dir: "source/ko/ko"
output_dir: "output"

After:

source_dir: "source/ko"
output_dir: "output/ko"

#28943: rails new-pオプションより環境変数が優先されていたのを修正

         def port
-          ENV.fetch("PORT", options[:port]).to_i
+          options[:port] || ENV.fetch("PORT", DEFAULT_PORT).to_i
         end

ポート番号を環境変数で指定すると-pオプションが効かなくなっていた問題の修正です。

つっつきボイス-pは臨時の指定だからやっぱりこちらが効いてくれないとねー」

#28939:マイグレーションを忘れたときのメッセージを見やすく変更


https://github.com/rails/rails/pull/28939より

マイグレーションを忘れてRailsを起動したりすると表示されていた大量のバックトレースを抑制しました。

つっつきボイス「これにびびった初心者多いだろうねw」

#28941: package.jsonが誤って削除されることがあるのを修正

# railties/lib/rails/generators/rails/app/app_generator.rb
      def create_vendor_files
         build(:vendor)
-
-        if options[:skip_yarn]
-          remove_file "package.json"
-        end
       end

つっつきボイス「Issueタイトルが「Remove unnecessary package.json deletion」なのでdeletionのremoveとかややこしいなー」「コードを見れば一目瞭然だけどね」

#28920: remove_possible_methodrequire漏れだったのを修正

# activesupport/lib/active_support/core_ext/date_time/compatibility.rb
require "active_support/core_ext/date_and_time/compatibility"
+require "active_support/core_ext/module/remove_method"

先週の#28835に続くrequire漏れ修正です。ActiveSupportが巨大なのでなかなか目が届かなさそうです。

Rails: action_controller_baseaction_controller_apiを追加(Rails公式ニュースより)

ここからはいつもの進行です。

ActiveSupportには#run_load_hooksというメソッドがあり、起動時の読み込みを絞り込むのに使われていることを知りました。

このメソッドで指定できるライブラリの中にaction_controllerもありますが、もう少し細かく指定できるようaction_controller_baseaction_controller_apiを追加したということです。

# actionpack/lib/action_controller/api.rb
ActiveSupport.run_load_hooks(:action_controller_api, self)

# actionpack/lib/action_controller/base.rb
ActiveSupport.run_load_hooks(:action_controller_base, self)

Rails: フィクスチャのアクセサメソッドに引数を渡さない場合にすべてのフィクスチャを返すよう変更(#railsnews)より)

# activerecord/lib/active_record/fixtures.rb
...
-              instances.size == 1 ? instances.first : instances
+              return_single_record ? instances.first : instances
...

従来は空の配列[]が返されていました。

Rails: HashWithIndifferentAccessfetch_valuesを実装(Rails公式ニュースより)

# activesupport/lib/active_support/hash_with_indifferent_access.rb
+    def fetch_values(*indices, &block)
+      indices.collect { |key| fetch(key, &block) }
+    end if Hash.method_defined?(:fetch_values)

Issueによると、Ruby 2.3.0という比較的最近の時期にHash#fetch_valuesが実装された(#10017)ので、それに合わせてActiveRecordのHashWithIndifferentAccessクラスでも使えるようにしたそうです。

このクラスは以下のようにハッシュのキーに文字列でもシンボルでも与えられるようにするものです。indifferentは人間に対して使うと「無関心」「無頓着」というニュアンスですが、ここでは「寛容」のニュアンスですね。

rgb['white'] = '#FFFFFF'
rgb[:white]  # => '#FFFFFF'
rgb['white'] # => '#FFFFFF'

Rails: 有効なコネクションを初期化後にクリアするよう修正(Rails公式ニュースより)

# activerecord/lib/active_record/railtie.rb
+    initializer "active_record.clear_active_connections" do
+      config.after_initialize do
+        ActiveSupport.on_load(:active_record) do
+          clear_active_connections!
+        end
+      end
+    end

スレッドが変わってもコネクションを使いまわせるようにするためだそうです。

Rails: JSレスポンスパーサーのバグを修正(Rails公式ニュースより)

今度はCoffeeScriptのAjax周りです。document.bodyではなくdocument.headに追加するように修正されています。

# actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee
   if typeof response is 'string' and typeof type is 'string'
      if type.match(/\bjson\b/)
        try response = JSON.parse(response)
-    else if type.match(/\bjavascript\b/)
+    else if type.match(/\b(?:java|ecma)script\b/)
        script = document.createElement('script')
-      script.innerHTML = response
-      document.body.appendChild(script)
+      script.text = response
+      document.head.appendChild(script).parentNode.removeChild(script)
      else if type.match(/\b(xml|html|svg)\b/)
        parser = new DOMParser()
        type = type.replace(/;.+/, '') # remove something like ';charset=utf-8'

Rails: dependent: :destroyを指定した場合のbefore_destroyの挙動についてドキュメントを修正(Rails公式ニュースより)

以下が追加されました。Railsガイド「Active Record コールバック」の3-3. オブジェクトのdestroyに該当します。

注: before_destroyコールバックは、dependent: :destroyにともなってレコードが削除される前に実行されなければならないので、dependent: :destroy関連付けより前に実行するか、prepend: trueオプションを指定すること。

参考: Rails API Active Record Callbacks

Rails: input文字列のfreezeを修正(Rails公式ニュースより)


https://github.com/rails/rails/pull/28729/filesより

コメントから:

  • 「条件節やfreezeのtrue/falseまでdupしないといけないのはちとイケてない」
  • 「同意、でもブランチでの変更量は割りと少なくてすみそう」

k0kubunさんがRubyコミッターに

前からコミッターだったような気がしていましたが、今回からだったんですね。おめでとうございます!

Google Summer of Code 2017に参加する学生が決定(Rails公式ニュースより)

以下の2名に決まったそうです。おめでとうございます。

Marko Bogdanović
Assain

Sinatra 2.0がリリース(RubyWeeklyより)

Changelogがどこにあるかややわかりにくいのですが、https://github.com/sinatra/sinatra/blob/v2.0.0/CHANGELOG.mdにありました。

普段Sinatraを追っていませんが、Changelogの最後にIndifferentHashというものを見つけました。
上でも取り上げたActiveSupport::HashWithIndifferentAccessがちょうどBPSの社内勉強会でも一瞬話題になったので気になりました。

SinatraではSinatra::IndifferentHashなんですね。APIドキュメントの冒頭が泣かせます。

  # A poor man's ActiveSupport::HashWithIndifferentAccess, with all the Rails-y
  # stuff removed.

つっつきボイス「ActiveSupportでかいからね」「Sinatra的には使ったら負け、なのかな?」「SinatraはRailsのマウンタブルエンジンにもできるよ」

RailsPanel: Rails開発を支援するChrome拡張機能(RubyWeeklyより)


https://github.com/dejan/rails_panelより

RailsPanelは以前から人気の高いツールです。SQL文を見やすく整形してくれるあたりがうれしいですね。私も以前インストールしたことがある気がするのですが、クリーンインストールのときに消してしまったようです。

Railsアプリにmeta_request gemを追加して[Chromeウェブストア]からインストールするだけで使えるようになります。

group :development do
  gem 'meta_request'
end

ActiveAdminが1.0でRails 5.1に対応(RubyWeeklyより)


https://activeadmin.info/より

つっつきボイス「管理画面系gemはカスタマイズしようとすればするほどハマる傾向があるなー」「カスタマイズなしで使える案件ってたぶんないでしょw」

Rackが404で返すヘッダにX-Runtimeがあるのヤバくね?(RubyWeeklyより)

HTTP/1.1 404 Not Found
Cache-Control: private, no-cache
Content-Type: text/html;charset=utf-8
Date: Mon, 29 Aug 2016 16:49:52 GMT
Request-Id: b3ca4a2c-1adf-4244-4cb0-17fbbc9deb06
Server: nginx/1.8.1
Strict-Transport-Security: max-age=31536000; includeSubDomains
Vary: Accept-Encoding
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-Runtime: 0.018023806                  <-コレ
X-Xss-Protection: 1; mode=block
Content-Length: 0
Connection: keep-alive

このヘッダを生成しているのが、Railsを縁の下から支えているRackです。

https://github.com/rack/rack/blob/master/lib/rack/runtime.rb#L12 より
  class Runtime
    FORMAT_STRING = "%0.6f".freeze # :nodoc:
    HEADER_NAME = "X-Runtime".freeze # :nodoc:
...

ユーザーが存在する場合としない場合でX-Runtimeが1桁近く違うので、これを使ってタイミング攻撃(この場合は辞書を食わせるなどしてユーザー名を特定)ができるのではないかという主張です。

# ユーザーが存在する場合
X-Runtime: 0.093990008
X-Runtime: 0.088857339
X-Runtime: 0.09251876
X-Runtime: 0.089843447
X-Runtime: 0.091845765
X-Runtime: 0.101260549

# ユーザーが存在しない場合
X-Runtime: 0.027787351
X-Runtime: 0.018023806
X-Runtime: 0.020997733
X-Runtime: 0.021645124
X-Runtime: 0.016645836
X-Runtime: 0.021828831
X-Runtime: 0.022211143

著者はこの問題をHerokuで見つけましたが、Heroku、BugCrowdともにあまり取り合ってもらっていない感じです。

つっつきボイス「影響の大きさ次第かな」「productionでX-Runtimeを表示しないぐらいはしてもいいかも」

引数にtrue/falseフラグを与えるのはコードが臭う前兆(RubyWeeklyより)

記事はそのまんまですが、そこからRubyMineの最近の機能であるparameter name hinting↓の話題に発展しました。

つっつきボイス「この機能、嫌いじゃない」「見た目のインデントが崩れるのいやだからオフにしてる」「デフォルトではパラメータ名と引数名が一致していれば表示されない」「挿入時にカーソルが置きにくい」

参考: Parameter Names Hinting

チュートリアル: RailsアプリをElastic Beanstalkにデプロイする(RubyWeeklyより)

元記事:

よくある記事ですが一応。

Active Model SerializerでRails APIサーバーを作る(RubyFlowより)

rails new--apiしてからカバレッジまでをひととおりカバーしています。

つっつきボイス「これ結構役に立ちそう」「Active Model Serializerについてあまり書かれてないけどねw」

Ralyxa: Amazon Alexaにアクセスするフレームワーク

Sinatraベースだそうです。AlexaはAmazon Echoのバックエンドにも使われているクラウド音声認識サービスです。
日本語版がいつ使い物になるかですね。

Rubyでバイナリデータを扱う(RubyFlowより)

Rubyによるビット操作、エンコードの検出、ビッグエンディアン・リトルエンディアンなどを解説しています。

そこから、Rubyはバイナリデータのハンドリングがイケているという話題になりました。私はテキスト処理がほとんどだったのでこのあたりは知りませんでした。

morimorihogeさんが「RubyでShiftJISのファイルを扱う(1.9.3, 2.0系対応版)」という記事を書いたことを思い出しながら、もっと前にJavaでやったときには「素のJavaにunsignedがないのでビット操作が不自由だったなー」「それ用のライブラリを使えば違ったかもしれないけど」と当時を回想していました。

他の記事もよい感じです。

Railsのテンプレートレンダリングのしくみ(RubyFlowより)

Railsで必ずといっていいほどお世話になっているビューテンプレートですが、実装はかなり生い茂っていることでも有名です。このあたりの解説はあまり見ないのでありがたいです。

スライドを見ていて「これ、Crafting Rails 4 Applicationsと構成が似ている気がするw」とmorimorihogeさんが気づきました。私も一応読んだはずなのに元ネタに気づけませんでした。そういえばこの本、未だに日本語化されていませんね。

著者のStan Loは台湾の人ですが、もしやと思ったら先週のウォッチで紹介したGobyの人でした。スライドを最後まで見るとわかります。

先のk0kubunさんもそうですが、RubyとGoを両方やっている人って意外にいますね。Goのpppecoなどもk0kubunさん作です。

JRubyのバグを見つけた話(RubyFlowより)

なにしろJRubyなのでJavaコードがほとんどです。

インテルが不揮発性メインメモリアーキテクチャのデモを公開

今朝morimorihogeさんがこのニュースを社内のSlackに流して一気に盛り上がりました。そのときの記事は日本語でしたが、意地で英語記事のリンクを貼りました。どんなふうに使おうかと夢は広がる一方です。

同記事には、まさにそのときのためにあるとしか思えないPersistent Memory Programmingというサイトも紹介されています。


http://pmem.io/2014/08/27/crawl-walk-run.htmlより

そして意外にも、このニュースが現時点でHacker Newsにまったく上がっていません。珍しいですね。

今から数時間以内ぐらいにHacker Newsに英語でタレこめばトップ取れるかもしれません。チャレンジャーは誰だ。

番外: 言語には人間性が反映される(RubyFlowより)

主にJRuby方面でご活躍のAlex Coles氏が、Sheffield Ruby User Groupの4月のカンファレンスでRubyや言語一般について自由に話している、雑学的に楽しい内容です。40分近くありますが、字幕も文字起こしもあり、日本人には聞き取りやすい英語だと思います。

個人的に、左のほっぺたのところのタトゥーが何の絵なのかが気になってしまいました。描いてる途中?

Wikipedia-ja: シェフィールド


今週は以上です。

関連記事

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやRSSなど)です。

Rails公式ニュース

Ruby Weekly

OpenRuby

RubyFlow

160928_1638_XvIP4h

[Devise How-To] OmniAuth: 結合テスト(翻訳)

$
0
0

こんにちは、hachi8833です。引き続きDevise How-ToのOmniAuthシリーズをお送りします。

概要

読みやすさのため、横に長いコードを途中で改行しています。

原文の更新や誤りにお気づきの場合は、ぜひ@techrachoまでお知らせください。更新いたします。

OmniAuth: 結合テスト(翻訳)

OmniAuthには、結合テスト(integration test: 統合テストとも)で認証フローをモックに差し替える方法がいくつか用意されています。

OmniAuth.config.test_mode

OmniAuthのテストモードを有効にするには、以下のように設定します。

OmniAuth.config.test_mode = true

訳注: 設定の記述場所はspec/support/omniauth.rbspec/support/omniauth.rbなどです。

テストモードが有効になると、後述の認証ハッシュのモックを用いるためにOmniAuthへのリクエストはすべてスキップされます。たとえば/auth/providerへのリクエストは即座に/auth/provider/callbackにリダイレクトされます。

OmniAuth.config.mock_auth

#mock_authを使うと、結合テスト中に認証プロバイダごとの認証ハッシュやデフォルトの認証ハッシュを返すことができます。以下は設定例です。

OmniAuth.config.mock_auth[:twitter] = OmniAuth::AuthHash.new({
  :provider => 'twitter',
  :uid => '123545'
  # (省略)
})

:defaultをキーを設定すると、認証プロバイダが指定されていない場合にデフォルトの認証プロバイダを返します。モック認証ハッシュが設定されてテストモードになると、OmniAuthへのすべてのリクエストに対してモックの認証ハッシュが返されるようになります。

OmniAuth.config.add_mock

#add_mockメソッドは、以下のように認証プロバイダの新しいモックをその場で追加するときにも使えます。設定した情報はデフォルトの情報と自動的にマージされるので、正しいレスポンスが返されます。

OmniAuth.config.add_mock(:twitter, {:uid => '12345'})

モックが失敗する場合

認証プロバイダのモックを以下のようにハッシュではなくシンボルで設定すると、モックが失敗してエラーメッセージが表示されます。

OmniAuth.config.mock_auth[:twitter] = :invalid_credentials

失敗の場合、 /auth/failure?message=invalid_credentialsにリダイレクトされます。

デフォルトのOmniAuthは、developmentモードとtestモードで無効な認証情報に対して例外をraiseします。developmentモードとtestモードで失敗したときに/auth/failureエンドポイントにリダイレクトしたい場合は、以下のコードを追加します。

OmniAuth.config.on_failure = Proc.new { |env|
  OmniAuth::FailureEndpoint.new(env).redirect_to_failure
}

クリーンアップ

テストとテストの合間にOmniAuthを特定の状態にリセットするには、テストスイートレベルの設定(setupbefore(:each)など)で以下を実行します。

OmniAuth.config.mock_auth[:twitter] = nil

コントローラの設定

OmniAuthのテストでは、コントローラで以下の2つの環境変数を設定する必要があります。以下はTwitter OmniAuthをRSpecでテストする場合のサンプルです。

before do
  request.env["devise.mapping"] = Devise.mappings[:user] # Deviseを使う場合
  request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:twitter]
end

新しいバージョンのRSpecではrequestオブジェクトにアクセスできません。request specを使う場合は以下のようにします。

before do
  Rails.application.env_config["devise.mapping"] = Devise.mappings[:user] # Deviseを使う場合
  Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[:twitter]
end

上のようにすることで、以下のサンプルのようなルーティングエラーを防止できます。

Failure/Error: get :twitter
AbstractController::ActionNotFound:
  Could not find devise mapping for path "/users/auth/twitter/callback".
  Maybe you forgot to wrap your route inside the scope block? For example:
    devise_scope :user do
      match "/some/route" => "some_devise_controller"
    end

関連記事(Devise)

OminiAuth

その他


[Rails 5] cache_keyによる結果セットとコレクションのキャッシュ機能(翻訳)

$
0
0

こんにちは、hachi8833です。BigBinaryシリーズは、Rails 5のキャッシュに関する翻訳記事をお送りいたします。元記事はRails 5リリース直前の頃のものなので、Rails 5.1で変わっている部分についても反映しました。

概要

訳文ではバージョンやリンクなどを現時点の内容に更新しています。

結果セットとコレクションのキャッシュ機能(翻訳)

Railsアプリを開発中に、Railsガイドに記載されているRails のキャッシュを使ってパフォーマンスを向上させたくなることがあります。Rails 5からは次のメソッドでレコードのコレクションをキャッシュできるようになります。

ActiveRecord::Relation.cache_key

コレクションのキャッシュについて

次の例で考えてみましょう。Miami cityにあるusersのすべてのコレクションを取り出しています。

@users = User.where(city: 'miami')

@usersはレコードのコレクションであり、ActiveRecord::Relationクラスのオブジェクトです。

上のクエリの結果は、以下の条件に応じて同じになることがあります。

  • クエリのステートメントが変わらない場合は同じになります。たとえば市の名前をMiamiからBostonに変えると結果は変わります。
  • レコードが削除されていない場合: コレクションのレコード数は変わりません。
  • レコードが追加されていない場合: コレクションのレコード数は変わりません。

レコードのコレクションのキャッシュ機能がRailsコミュニティによって実装されました。ActiveRecord::Relationに追加された#cache_keyメソッドは、クエリステートメント、updated_atカラム、コレクションのレコード数といったさまざまな要素を考慮してキャッシュを行います。

ActiveRecord::Relation#cache_keyを理解する

ActiveRecord::Relationクラスの@usersオブジェクトに対して#cache_keyメソッドを実行してみましょう。

 @users.cache_key
# => "users/query-67ed32b36805c4b1ec1948b4eef8d58f-3-20160116111659084027"

出力の内訳は次のとおりです。

  • usersは現在保持しているレコードの種類を表します。上の例ではUserクラスのレコードのコレクションを指しますので、usersレコードを保持していることがわかります。
  • query-の部分は固定です。

  • その後の67ed32b36805c4b1ec1948b4eef8d58fは、実行するクエリステートメントのダイジェストです。上の例ではMD5( "SELECT "users".* FROM "users" WHERE "users"."city" = 'Miami'")の結果と等価です。

  • 3はコレクションのサイズです。

  • 20160116111659084027の部分はコレクションで最後に更新されたレコードのタイムスタンプです。デフォルトではupdated_atがタイムスタンプカラムとして使われるので、コレクションで最も最近更新されたupdated_atの値になります。

ActiveRecord::Relation#cache_keyを使ってみる

#cache_keyで実際にキャッシュされるところを観察してみましょう。

次のRailsアプリのコードで、cityがMiamiであるusersのレコードをキャッシュします。

# app/controllers/users_controller.rb

class UsersController < ApplicationController

  def index
    @users = User.where(city: 'Miami')
  end
end
# users/index.html.erb

<% cache(@users) do %>
  <% @users.each do |user| %>
    <p> <%= user.city %> </p>
  <% end %>
<% end %>

1回目

Processing by UsersController#index as HTML
Rendering users/index.html.erb within layouts/application
(0.2ms) SELECT COUNT() AS “size”, MAX(“users”.”updated_at”) AS timestamp FROM “users” WHERE “users”.”city” = ? [[“city”, “Miami”]]
Read fragment views/users/query-37a3d8c65b3f0f9ece7f66edcdcb10ab-4-20160704131424063322/30033e62b28c83f26351dc4ccd6c8451 (0.0ms)
User Load (0.1ms) SELECT “users”.
FROM “users” WHERE “users”.”city” = ? [[“city”, “Miami”]]
Write fragment views/users/query-37a3d8c65b3f0f9ece7f66edcdcb10ab-4-20160704131424063322/30033e62b28c83f26351dc4ccd6c8451 (0.0ms)
Rendered users/index.html.erb within layouts/application (3.7ms)

2回目

Processing by UsersController#index as HTML
Rendering users/index.html.erb within layouts/application
(0.2ms) SELECT COUNT(*) AS “size”, MAX(“users”.”updated_at”) AS timestamp FROM “users” WHERE “users”.”city” = ? [[“city”, “Miami”]]
Read fragment views/users/query-37a3d8c65b3f0f9ece7f66edcdcb10ab-4-20160704131424063322/30033e62b28c83f26351dc4ccd6c8451 (0.0ms)
Rendered users/index.html.erb within layouts/application (3.0ms)

1回目はcountクエリを実行して最新のupdated_atsizeusersコレクションから取得しています。

Railsの#cache_keyはこのcountクエリからキャッシュを生成し、新しいキャッシュエントリを書き込みます。

2回目はcountクエリを再度実行し、このクエリのcache_keyがあるかどうかをチェックします。cache_keyがある場合は、SQLクエリを実行せずにデータを読み込みます。

テーブルにupdated_atカラムがない場合は?

前述のとおり、#cache_keyメソッドではテーブルのupdated_atカラムを使っています。#cache_keyには独自のカラムをパラメータとして渡せるオプションもあるので、その場合はコレクションにあるレコードの中でそのカラムの最も大きな値が使われます。

たとえば、productsテーブルのlast_bought_atというカラムがビジネスロジックで(いわゆる)キャッシング購入の日時として使われている場合は、以下のようにカラムを指定できます。

 products = Product.where(category: 'cars')
 products.cache_key(:last_bought_at)
# => "products/query-211ae6b96ec456b8d7a24ad5fa2f8ad4-4-20160118080134697603"

気をつけたいエッジケース

#cache_keyを実際に使う前に、キャッシュの動作における若干のエッジケースについて知っておきましょう。

アプリのusersテーブルに、cityがMiamiのエントリが5件あるとします。

#limitを使うとコレクションが読み込まれない場合のサイズが実際と異なってしまう

次のクエリを実行すると、cityがMiamiのユーザーが3人読み込まれます。

 users = User.where(city: 'Miami').limit(3)
 users.cache_key
# => "users/query-67ed32b36805c4b1ec1948b4eef8d58f-3-20160116144936949365"

usersにはレコードが3つしかないので、#cache_keyではコレクションのサイズが3件と認識されています。

続いて、レコードを事前にフェッチせずに同じクエリを実行してみましょう。

 User.where(name: 'Sam').limit(3).cache_key
# => "users/query-8dc512b1408302d7a51cf1177e478463-5-20160116144936949365"

limitで3を指定しているにもかかわらず、今度はキャッシュのカウントが5になっています。これは、ActiveRecord::Base#collection_cache_keyは実行時にコレクションのサイズをフェッチしていないためです。


訳注: BPS WebチームのakioさんがRails 5.1.1で挙動を確認したところ、以下のように先に#limitを呼んだ場合はなぜか正しいサイズを取得できました。

# Userに5件のデータがあるとする
users = User.all
users.cache_key          # => "users/query-211ae6b96ec456b8d7a24ad5fa2f8ad4-5-20170517093650195306"
users.limit(3).cache_key # => "users/query-211ae6b96ec456b8d7a24ad5fa2f8ad4-5-20170517093650195306"

# limitを先に実行すると正常に動作する
users = User.all
users.limit(3).cache_key # => "users/query-e5d164039a35b778a6f43694a9940c43-3-20170517093650194483"
users.cache_key          # => "users/query-211ae6b96ec456b8d7a24ad5fa2f8ad4-5-20170517093650195306"

コレクションの既存のレコードが置き換わってもキャッシュのキーが変わらない

以下のコードでは、idを降順にしてユーザーを3人取得しています。

 users1 = User.where(city: 'Miami').order('id desc').limit(3)
 users1.cache_key
# => "users/query-57ee9977bb0b04c84711702600aaa24b-3-20160116144936949365"

ユーザーのidは[5, 4, 3]になっています。

ここでid = 3のユーザーを削除します。

 User.find(3).destroy

 users2 = User.where(first_name: 'Sam').order('id desc').limit(3)
 users2.cache_key
# => "users/query-57ee9977bb0b04c84711702600aaa24b-3-20160116144936949365"

users1users2cache_keyが完全に一致しています。レコード数、クエリステートメント、最新レコードのタイムスタンプは、いずれもcache_keyに影響しないパラメータです。

cache_keyの一部にコレクションレコードのidを加えてはどうかという議論が#21503で行われていますので、この問題を解決するうえで参考になるかもしれません。


訳注: Rails 5.1.1 + Ruby 2.4.1環境で試してみたところ、5.1.1でも#destroy後にキャッシュは変化しませんでした。原文のコード例ではなぜかusers1とusers2のクエリが異なっていますので、以下のように同じクエリでも試してみましたが、やはりキャッシュは変化しませんでした。

users1 = User.where(city: 'Miami').order('id desc').limit(3)
users1.cache_key
# => "users/query-265ede4b5b3a003407f836b43a2d28d9-3-20170523082102806399"

User.find(3).destroy

users2 = User.where(city: 'Miami').order('id desc').limit(3)
users2.cache_key
# => "users/query-265ede4b5b3a003407f836b43a2d28d9-3-20170523082102806399"

groupクエリを使うとcache_keyのサイズが正しくならない

前述のlimitの問題と少し似ていますが、cache_keyの動作は、データがメモリ上に読み込まれている場合と読み込まれていない場合とで挙動が異なります。

first_nameが’Sam’であるユーザーが2人いる場合を考えてみます。

コレクションがメモリに読み込まれない場合は次のようになります。

 User.select(:first_name).group(:first_name).cache_key
# => "users/query-92270644d1ec90f5962523ed8dd7a795-1-20160118080134697603"

cache_keyのサイズは1になっています。キャッシュ機能の動作に従えば、サイズは1か5になります。この2つのサイズは、グループのサイズです。

続いて、コレクションを事前にメモリに読み込んだ場合を見てみましょう。

 users = User.select(:first_name).group(:first_name)
 users.cache_key
# => "users/query-92270644d1ec90f5962523ed8dd7a795-2-20160118080134697603"

#cache_keyのサイズは2になっています。両者のクエリの結果は完全に同じですが、サイズはメモリに読み込む前の値と異なっています。

コレクションがメモリに読み込まれると、サイズは(該当するグループのサイズではなく)グループの個数に等しくなります。このため、各グループ内にあるレコードが異なっていても#cache_keyの値が同じになる可能性があります。


訳注: Rails 5.1.1 + Ruby 2.4.1環境ではどちらのクエリもサイズが同じになりました↓。

User.select(:first_name).group(:first_name).cache_key
# => "users/query-b38de3cd3957533edc1278d89aef1deb-1-20170523040909796341"

users = User.select(:first_name).group(:first_name)
users.cache_key
# => "users/query-b38de3cd3957533edc1278d89aef1deb-1-20170523040909796341"

これまたakioさんが、このusersに対して#to_aを実行してから#cache_keyを呼ぶと、「ActiveModel::MissingAttributeError: missing attribute: updated_at」エラーになることを見つけました。私がpryで試しているとusers.cache_keyの部分でこのエラーが表示されましたが、irbで試すと正常に動作しました。「もしかするとpryでは#to_aが暗黙に実行されているのかもしれない」とのことでした。


関連記事

Docker入門日誌-その3- Rails開発編

$
0
0

ebiです。前回の内容で終わってもよかったんですけど、せっかくなのでRails編まで書いてみることにしました。

とにかく rails new を目指す

まずは難しい内容を抜きにして最速で rails new を決めていきます。

rails用のイメージを利用する

前回に引き続きKitematicの画面です。何も考えずに rails と検索してみたら、公式イメージがヒットしました。これに決めます。

作成されたコンテナの設定を見ると、RubyとRailsのバージョンが指定してありました。

いや、こんなところ見ずともこのイメージの公式ドキュメントを読めばいいんですけどね。

Kitematicから作った場合は忘れずにポート指定をしておきましょう。

それでは docker exec でコンテナ内に乗り込みます。

あとは rails new するだけ

それっぽいディレクトリに移動して……。はい。rails new

無事完了したので、作ったディレクトリに移動して rails s

ブラウザにURLを打ち込んで……アクセスできました。ね、簡単でしょ?

省略してしまいましたが、実際やるなら前回の手順みたいにちゃんと共有フォルダを指定して、そこで rails new しましょう。なんだか普通にWindowsでRails開発できそうな気がしてきましたね……!!

もうちょっと真面目に環境作り

今までは、ただ用意されていたイメージを引っ張ってきてコンテナ作りをしていたのですが、Dockerでは配布されているイメージを元にオリジナルのイメージを作ったり、コンテナ同士の連携をしたりするのも簡単にできます。

公式のRails環境作りのサンプルにもその内容が含まれていたので、これを参考(そのままやりますけどね)に試していきます。

Dockerfile等を用意する

まずは適当な作業用のディレクトリを用意して、そこに Dockerfile と言う名前でファイルを作ります。拡張子は要りません。

これをエディタで編集して、以下の内容にします。

FROM ruby:2.3.3
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs
RUN mkdir /myapp
WORKDIR /myapp
ADD Gemfile /myapp/Gemfile
ADD Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
ADD . /myapp

FROMで元になるイメージを指定しています。その他の大文字で始まる記述も、初期設定として実行するものを定義しているようだと言うのはなんとなく分かると思います。
ちゃんとDockerfile用の命令一覧をどこかで参照して比較してみるともっと分かるはずです。

同様にGemfileを作成して、

source 'https://rubygems.org'
gem 'rails', '5.0.0.1'

Gemfile.lockは空のファイルとして作成します。
最後に docker-compose.yml を作成します。

version: '2'
services:
  db:
    image: postgres
  web:
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db

今回はdbとwebの2つのコンテナがあって、dbの方はpostgresイメージを、webの方はDockerfileからbuildしたコンテナを使うと書かれているようです。

buildしていく

Windowsのコマンドプロンプトを起動して、Dockerfile等を用意した作業用のディレクトリに移動したら、以下を実行します。

docker-compose run web rails new . --force --database=postgresql --skip-bundle

webの方のコンテナを動かして、rails new しちゃいます。実行完了すると、作業ディレクトリがRails仕様になりました。

続いて rails new で作成された config/database.yml を開いて、developmentの部分を以下に書き換えます。

development: &default
 adapter: postgresql
 encoding: unicode
 database: myapp_development
 pool: 5
 username: postgres
 password:
 host: db

終わったら、いよいよ立ち上げです。

docker-compose up -d

失敗しました。

web_1 | bundler: failed to load command: rails (/usr/local/bundle/bin/rails)
web_1 | Bundler::GemNotFound: Could not find gem ‘pg (~< 0.18)’ in any of the gem sources listed in your Gemfile.

Gemfileが悪そうです。流し読みして必要なさそうだと思って飛ばしてしまった build をしてみます。

docker-compose build

リベンジします。今度は上手くいったのでアクセスしてみます。DBがまだ作られていないので、

はい。db:createをしましょう。

docker-compose run web rake db:create

これでオッケーでしょう。と思ったのですが、上手くいかず。restartしてみるも

C:\Users\ebi\Desktop\rails_docker>docker-compose restart
Restarting railsdocker_web_1 ... done
Restarting railsdocker_db_1 ... done

C:\Users\ebi\Desktop\rails_docker>docker-compose ps
Name Command State Ports
----------------------------------------------------------------------
railsdocker_db_1 docker-entrypoint.sh postgres Up 5432/tcp
railsdocker_web_1 bundle exec rails s -p 300 ... Exit 1

な、なんでだー。お、落ち着いてdocker-compose logsでログを出してみよう……。

web_1 | A server is already running. Check /myapp/tmp/pids/server.pid.
web_1 | =< Booting Puma
web_1 | =< Rails 5.0.0.1 application starting in development on http://0.0.0.0:3000
web_1 | =< Run `rails server -h` for more startup options
web_1 | Exiting

怪しい。pidを消すといいらしいです。こんな感じかな(もちろん直接フォルダからデータ消せばいい気もする)。

docker-compose run web rm tmp/pids/server.pid

リベンジします。

できました。

さてさて、ここまで docker-compose web run を繰り返してたので、そのたびにコンテナが増えていました……。rails new とかのコマンドを実行したいだけだったら--rmオプションを付ける癖を身に付けるべきなのでしょう。

まとめ

Dockerを使ってRails開発をする想定で環境構築をしてみました。
すぐ使える便利なイメージを拾ってきたリ、Dockerfileを使って新しいイメージを作ってみたり、Docker Composeを利用して複数コンテナの連携をしてみたり、と基本的なことは一通り試せました。

Dockerを今すぐ実務に生かせるか、と言うとまだ不安だらけですが最初の一歩を踏み出すいいきっかけにはなりました。
公式のチュートリアルやDockerで~~してみましたみたいな記事も世の中にはたくさんあるようなので、もう少し色々試してみようと思います。

関連記事(Docker)


週刊Railsウォッチ(20170526)増えすぎたマイグレーションを圧縮するsquasher gem、書籍「Complete Guide to Rails Performance」ほか

$
0
0

こんにちは、hachi8833です。いつの間にかもう5月が終わろうとしています。

それでは今週のRailsウォッチいってみましょう。

Rails: 今週の改修(Rails公式より)

今回はRailsコミット系を1トピックにまとめてみました。タイトルは一応韻を踏んでいます。

リサイクル可能なキャッシュキーを追加

DHH提案の新しい内部機能です。コミットを見るとけっこう大きな改修になっています。

# actionpack/lib/abstract_controller/caching/fragments.rb#L86
+      def combined_fragment_cache_key(key)
+        head = self.class.fragment_cache_keys.map { |k| instance_exec(&k) }
+        tail = key.is_a?(Hash) ? url_for(key).split("://").last : key
+        [ :views, (ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]), *head, *tail ].compact
+      end

DHHのPRメッセージの中ほどをざっくり日本語にしてみました。

この問題は、明確なバージョンを分離してキーを安定化することで解決できる。従来だと”projects/1-20170202145500″のようにキーを組み合わせていたが、”products/1″や関連するバージョン(”20170202145500″など)といった組み合わせてないキーを安定して利用できるようになる。”projects/1″にどれほど頻繁にアクセスしようとも、単に同じキャッシュキーに書き込まれる。つまりこれがリサイクル部分になる。

つっつきボイス: 「#combined_fragment_cache_key自体はそもそもかなりraw levelなメソッドだし、キャッシュにはCacheHelper経由でアクセスするのが普通だから、普通なら直接触ることはないよ、ということか」「less-frequently-accessed-but-still-valid keysって書いてあるのがポイントかな。アクセス頻度は低くてもcache outしたくないようなデータをどうにかしたいと」「fetchにコストや時間がかかるようなデータなら永続化してもいいくらいのことってありますね」「cache versionとcache keyの管理を分離したい、という感じ」

rails consoleコマンドからirbにオプションを渡せるよう再修正

Rails 5からの機能だったのがいつの間にか消えていたので修正したそうです。

つっつきボイス:「↓これかー」「誰だっARGV.clearなんか足したのはw」

# railties/lib/rails/commands/console/console_command.rb#L82
-        ARGV.clear # Clear ARGV so IRB doesn't freak.

データベースのダンプで’SchemaDumper.ignore_tables’が効くように修正

# activerecord/lib/active_record/tasks/mysql_database_tasks.rb#L60
         args.concat(["--routines"])
         args.concat(["--skip-comments"])
         args.concat(Array(extra_flags)) if extra_flags
+
+        ignore_tables = ActiveRecord::SchemaDumper.ignore_tables
         if ignore_tables.any?
+          args += ignore_tables.map { |table| "--ignore-table=#{configuration['database']}.#{table}" }
+        end
+
         args.concat(["#{configuration['database']}"])

         run_cmd("mysqldump", args, "dumping")

MySQLのほか、PostgreSQLやSQLiteも同様に修正されています。

つっつきボイス:「この機能、あれば使うかも」

キャッシュ機能にunless_existオプションを追加

# activesupport/lib/active_support/cache/strategy/local_cache.rb#L117
          def write_entry(key, entry, options)
-            local_cache.write_entry(key, entry, options) if local_cache
+            if options[:unless_exist]
+              local_cache.delete_entry(key, options) if local_cache
+            else
+              local_cache.write_entry(key, entry, options) if local_cache
+            end
+
             super
           end

unless_exist、つまりキャッシュが既にあれば上書きしないという処理です。

つっつきボイス: 「この修正ってキャッシュのatomicityがちゃんと保証されているのかな?」「できるかどうかはキャッシュエンジンに依存しそうだし」「unless_existチェックが終わってから書き込むまで間に別のプロセスが書き込むとかありえる」

Rubyのバックエンドパフォーマンス改善(RubyWeelkyより)

RailsConf 2017で行われたパネルディスカッションです。40分ほどの動画と概要が紹介されていて、パフォーマンスについていろいろ参考になります。

途中にこんなことも書いてあります。

Always

Be

Benchmarking

元記事では最後にThe Complete Guide to Rails Performanceをおすすめしています。3月17日のRailsウォッチでもこの書籍のことを簡単に取り上げました。

morimorihogeさんが「お、これはよさそう。『FASTとは何か』の定義から始まってて、ちゃんとしてそうに見える」と早速購入方法を調べ始めました。


http://schneems.com/2017/05/17/ruby-backend-performance-getting-started-guide/より

みっちり読む甲斐がありそうです。まともに買うと99ドルですが、頑張ってクーポンを探すともっと安く買えそうです。

補足

ご多分に漏れず、RailsConf 2017の動画がごっそりYouTubeにアップされていますね。後でチェックしてみようっと。


https://www.youtube.com/results?search_query=RailsConf+2017より

ハイパーメディアクライアントをRubyで書く(RubyWeelkyより)


https://robots.thoughtbot.com/writing-a-hypermedia-api-client-in-rubyより

「ハイパーメディアって何ぞや?」という点が気になりました。http://steveklabnik.github.com/hypermedia-presentation/のスライドがポイントのようですが、おっきな字ばっかりでよくわかりません。


http://www.steveklabnik.com/hypermedia-presentation/#1より

つっつきボイス: 「高橋メソッドはこうやって読むには向いてないなー」

akioさんが同記事の中から見つけた「HAL – Hypertext Application Language」の方がどうやらずっとよくまとまっていますね。HAL、覚えておこう。

Polyfill: 古いRubyで新しい機能を使えるようにするgem(RubyWeelkyより)

JavaScriptの方のPolyfillsを思い出してしまいました。Ruby 2.4、2.3、2.2をターゲットにしています。

つっつきボイス:「ここまでして古いRuby使わなきゃいけないんだろうかw」「Kernelにパッチ当てて新しいRubyのメソッドを使うよりはましなのかな?」

morimorihogeさんが「コード側でRubyバージョンをRUBY_VERSIONで調べて切り替えていたら、おそらくこのgemは効かないのではないか」と指摘しました。「Rubyバージョンをrespond_to?でチェックしているコードならこのgemとやっていけるだろうけど」「いったんconflictしたら根が深そう」とも。

Railsは最新のRubyが推奨されるので、よほど古いRailsアプリでなければ普通にRubyをアップグレードする方がよさそうですね。

RubyでUnicodeを修正する


https://blog.daftcode.pl/fixing-unicode-for-ruby-developers-60d7f6377388より

元記事の著者は名前を見るだけで一発でポーランド人とわかります。Lの小文字に斜めの線が入っているような文字が特徴です。

Unicode絵文字が普及したおかげで、日本を含むアジア方面ではおなじみの文字コードの苦労がやっと海外でも共有され始めているようです。私も珍しく「ふふ、ふふふ、もっと、もっと苦しむがよい」という気持ちが湧き上がってしまいました。

lltsv: LTSVパーサー

morimorihogeさんが見つけました。JSONよりお手軽そうです。

私も、個人的にCSVよりもタブ区切りテキストを好んでいる(=表計算にそのまま貼り付けられるから)ので、LTSVに期待しちゃいます。

Squasher: 増えすぎたマイグレーションを圧縮するgem(RubyFlowより)

年月を経てずらりと増えたマイグレーションを1つのコマンドに集約するgemです。★600越えと人気のほどがうかがえます。
うまく使うとマイグレーションファイルの整理に役立ちそうです。
「実行前にはspringとかzeusは止めておきましょう」と注意書きがあります。

つっつきながら、「これとよく似たgemがあったはずだけどなー」という話になり、調べてみたところridgepoleというgemでした。こちらはRails標準のマイグレーションの代わりに使う、より本格的なスキーマ管理向けだそうです。

参考: ridgepole {名} : 《建築》棟木

つっつきボイス:「ridgepole、そういえば某プロジェクトで知らないうちに入ってたことありましたよ」

Napsack: CIでテストをノードに分割して比較可能にするgem

Pro版もあるそうです。CIや監視がらみはPro版あり多いですね。類似のgemにrrrspecがあることを教わりましたが、また違うアプローチのようです。

Rubyで学ぶグラフ理論の初歩(RubyFlowより)

あきれるほど短い記事なので、グラフ理論のとっかかりによいと思います。心なしか字も大きめなので読みやすいと思います。あくまでとっかかりですが。

つっつきボイス:「Practical Graph Theoryと名乗るほどの内容じゃないかなー」

Electrino: Chromiumの代わりにChromeブラウザを使うJSアプリフレームワーク

なぜ今までなかったんだと思ってしまいました。20日で★2000越えの快挙です。「まだ機能がろくにないけど」と謙遜気味のREADMEです。

名前からElectronの縮小版ということがわかります。Chromiumエンジンを丸呑みしているElectronだと100MB越えなのに、Electrinoだと100kBクラスですよ、閣下。

つっつきボイス:「cookieやプロファイルはブラウザと共用するのかな」「それはないな、プロファイルはちゃんと仕切られるはず」

私はnativefierというElectronでWebページをアプリ化するのをよくやりますが、Electrinoに乗り換えてほしいものです。

Go言語1.8.2、1.8.3セキュリティアップデート

1.8.2では暗号化の楕円関数あたりが修正されましたが、直後に1.8.3がリリースされました。めまぐるしいです。

HPEが160テラバイトのメモリ空間を持つ「The Machine」のプロトタイプを発表

テラバイト、ペタバイト、エクサバイト、ゼタバイトの次はヨタバイトなんですね。


今週は以上です。

関連記事

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやRSSなど)です。

Ruby 公式ニュース

Rails公式ニュース

Ruby Weekly

RubyFlow

160928_1638_XvIP4h

[Rails] Devise Wiki:「ワークフローのカスタマイズ」もくじ(概要・用途付き)

$
0
0

こんにちは、hachi8833です。
Railsの認証用gemの定番中の定番であるDevise gemHow-To Wiki目次のうち、「ワークフローのカスタマイズ」の章見出しを日本語化し、概要や用途を付けました。今後も随時更新します。

Devise How-To原文は走り書きの傾向があり、見出しが内容を適切に表していなかったり、メモ程度の記述しかないことがあります。しかも記事を開いてみないと更新日がわからないので、こういうのが自分でも欲しかったのでした。

ワークフローのカスタマイズ

※以下は新しい順に並べ替えてあります。

ゲストユーザーを作成する

17 May 2017 · 32 revisions
How To: Create a guest user

ゲストのUserオブジェクトを使って、ログインしていない一般ユーザーでもセッションを管理できるようにする方法です。ここではゲストのオブジェクトを作成してidをsession[:guest_user_id]でデータベースに保存し、current_or_guest_userでゲストかどうかを判定します。

パスワード変更をユーザーにメール通知する

12 Apr 2017 · 7 revisions
Notify users via email when their passwords change

この機能はDevise 3.5.3からありますが、デフォルトではオフになっています。devise.rbで設定を変更するだけで機能を有効にできます。

サインイン・サインアウト・サインアップ・プロファイル更新後に元のページにリダイレクトする

27 Mar 2017 · 35 revisions
How To: Redirect back to current page after sign in, sign out, sign up, update

ユーザーがアカウント操作を行った後、ユーザーが元いたページにリダイレクトされるようにします。

Deviseでカスタムのメールバリデータを使う

24 Feb 2017 · 6 revisions
How to: Use a custom email validator with Devise

メールバリデータのコード例が掲載されています。カスタムメールバリデータはDeviseを改変しなくても使えます。

Devise自身のメールバリデータはあえてメールアドレスを極力リジェクトしない方針なので、入力されたメールアドレスが誤っているとユーザーに配信されない可能性もあります(メール配信側の問題による可能性もあります)。

セッション終了(サインアウト)後のリダイレクト先をカスタマイズする

17 Feb 2017 · 8 revisions
How To: Change the redirect path after destroying a session i.e. signing out

ユーザーがログアウトした後に特定のページにリダイレクトする方法です。モデルのインスタンスが複数ある場合の方法も紹介されています。

ユーザーが自分のパスワードを変更できるようにする

10 Jan 2017 · 48 revisions
How To: Allow users to edit their password

ビューだけで行う方法(新しいDeviseとStrong ParametersではApplicationControllerの変更も必要)、既存ユーザーにのみパスワード変更を許可してユーザー登録機能を無効にする方法、Deviseに手を加えずにパスワードの変更方法を独自実装する方法が紹介されています。


サインアップ(登録)に成功したら特定のページにリダイレクトする

13 Dec 2016 · 13 revisions
How To: Redirect to a specific page on successful sign up (registration)

「Rails 5 + Devise 4.2ではこの方法で動かなかったのでこのエントリは削除する方がよいのではないか」という指摘があります。最終的に「サインイン・サインアウト・サインアップ後に元のページにリダイレクトする」(←注意: How-Toの目次にはエントリがありません)でできたそうです。

sign_inとsign_outのデフォルトルーティングを変更する

5 Nov 2016 · 7 revisions
sign_inとsign_outのデフォルトルーティングを変更する原文

Deviseのデフォルトルーティング(/users/sign_in/users/sign_out)を/login/logoutなどに変更する方法です。

サインイン/アウトに成功したら特定のページにリダイレクトする

2 Sep 2016 · 14 revisions
How To: Redirect to a specific page on successful sign in out

ApplicationControllerでafter_sign_in_path_forヘルパーやafter_sign_out_path_forヘルパーを使ってリダイレクトします。サインイン後にユーザーを元のページにリダイレクトするには、request.fullpathを使うのが簡単です。サインアウト後のリダイレクト先は自由に設定できますが、サインイン後のリダイレクト先はそうではありません。

サインイン/サインアウト時に特定のページにリダイレクトする

2 Sep 2016 · 14 revisions
How To: Redirect to a specific page on successful sign in out

ログイン後やログアウト後のリダイレクト先を特定のページに変更する方法です。

デフォルトのSign_up登録パスをカスタムパスに変更する

20 Aug 2016 · 5 revisions
デフォルトのSign_up登録パスをカスタムパスに変更する原文

新規ユーザーの登録(=サインアップ)URLをDeviseデフォルトの/users/sign_upから/sign_upなどに変更する方法です。

管理者がアカウントを有効にするまでsign_inできないようにする

18 Aug 2016 · 17 revisions
How To: Require admin to activate account before sign_in”

confirmableモジュールを使わず、管理者(モデレーター: moderator)が承認するまでユーザーのアカウントを有効にしないようにする方法です。

Deviseで単一ユーザーシステムを構築する

10 Aug 2016 · 10 revisions
How To: Set up devise as a single user system

個人ブログアプリなど、ユーザーが1人に限定されたプライベートアプリをDeviseで構築する際に、新規ユーザー登録ページを非公開にする方法です。ユーザーが1人も登録されていない状態で一般ユーザーが登録ページにアクセスしようとするとトップにリダイレクトされ、ユーザーが1人登録済みの場合はsign_inページにリダイレクトされます。

確認をオーバーライドして、確認中にユーザーが独自のパスワードを選べるようにする

1 Jul 2016 · 73 revisions
How To: Override confirmations so users can pick their own passwords as part of confirmation activation

ユーザー名かメールアドレスだけを入力すればアカウントを作れるアプリで、ユーザーが自分に送信された登録確認メールのリンクをクリックすると、アプリの登録確認画面でユーザーが自分の使いたいパスワードを選択できるようにする方法です。以下の場合について詳細な手順が記載されています。

  • Rails 3/4、Devise 3.1〜3.5.1
  • Rails 3/4、Devise 2.x〜3.0、Devise 3.5.2以降
  • Rails 2.3、Devise 1.0.9

サインインに成功したら特定のページにリダイレクトする

15 Jun 2016 · 21 revisions
How To: redirect to a specific page on successful sign in

ログイン後に直前のページに戻る方法、OAuthでログイン後に直前のページに戻る方法、OAuthでログインできなかった場合に直前のページに戻る方法、リダイレクトのループを防ぐ方法が紹介されています。

2段階認証

11 Jun 2016 · 2 revisions
How To: Two step confirmation

以下の2つの記事への内部リンクのみです。具体的な手法は記載されていません。

アカウント削除後のUserデータを保存する(論理削除)

11 Feb 2016 · 6 revisions
How to: Soft delete a user when user deletes account

ユーザーがアカウントを削除してもユーザーのデータを実際には消さずに保存しておき、単にアカウントを無効にする方法です。

全ページで認証を必須にする

16 Jan 2016 · 6 revisions
How To: Require authentication for all pages

全ページで認証を必須にし、かつアプリのルートで「サインインが必要です」メッセージを表示しないようにします。database_authenticatableなしでomniauthableを使う場合は方法が少し異なります。


ユーザープロファイル編集後のリダイレクトをカスタマイズする

5 Apr 2015 · 11 revisions
How To: Customize the redirect after a user edits their profile

ユーザーが自分のプロファイルを変更した後に特定のページにリダイレクトする方法です。Devise::RegistrationsControllerのサブクラスを作る方法と、ルーティングだけで行う方法の2つが紹介されています。

ローカライズされたスコープでOmniAuthを使う

18 Mar 2015 · 4 revisions
How To: OmniAuth inside localized scope

セッションで設定されているロケールのスコープに沿ってOmniAuthで認証する方法です。

ユーザーを認証できなかった場合に特定のページにリダイレクトする

16 Mar 2015 · 17 revisions
How To: Redirect to a specific page when the user can not be authenticated

特定のサブドメインでフルパスのURLを使った例が紹介されています。DeviseはデフォルトではフルパスのURLを使わないため、Devise::FailureAppを継承したカスタムfailure appでフルパスのURLに対応しています。Rails 3と4、Rails 2のサンプルコードがあります。

認証失敗後のリダイレクトにロケールを適用する

18 Feb 2015 · 5 revisions
How To: Redirect with locale after authentication failure

認証失敗後のリダイレクト先ページをロケールに応じて変える方法です。Railsのi18n APIに記載されている方法とは異なります。


パスワードの最小文字数をカスタマイズする

1 Oct 2014 · 1 revision
Customize minimum password length

アプリ全体での設定方法と、モデルごとに最小文字数を設定する方法が紹介されています。

セッションタイムアウト後にログインページにリダイレクトしないようにする

23 Jul 2014 · 8 revisions
How To: Do not redirect to login page after session timeout

デフォルトのDeviseではセッションタイムアウト後にユーザーをログインページにリダイレクトするので、Devise::FailureAppを継承したカスタムfailure appで動作を変更します。

ユーザーのパスワードを自動生成する(シンプルな登録方法)

28 Jan 2014 · 3 revisions
ユーザーのパスワードを自動生成する(シンプルな登録方法)原文

新規ユーザーにメールアドレスだけを入力させ、パスワードを自動生成してメール送信する方法です。


サインインおよびサインアウトに成功したら特定のページにリダイレクトする

15 Jun 2013 · 2 revisions
How To: Redirect to a specific page on successful sign in and sign out

ApplicationControllerに#after_sign_in_path_for#after_sign_out_path_forを定義してリダイレクトする方法と、ログアウト後にリダイレクトしないようにする方法です。

サインイン成功時、またはサインアウト成功時に特定のページにリダイレクトする

15 Jun 2013 · 2 revisions
How To: Redirect to a specific page on successful sign in and sign out

Deviseのデフォルトであるroot_pathへのリダイレクトをモデル名_root_pathで変更できます。ルートURL /にアクセスした一般ユーザーを単にログインページにリダイレクトしたい場合は、#authenticatedというルーティングヘルパーメソッドを使うのが簡単です。
Rails 4以後は名前の同じルーティングが2つあるとアプリ読み込み時に例外が発生しますので注意が必要です。authenticated_root_pathroot_pathは異なるルーティング名ですが同じURLに応答します。


ユーザー登録ページへのルーティングをカスタマイズする

Aug 2012 · 2 revisions
ユーザー登録ページへのルーティングをカスタマイズする原文

Deviseのユーザーモデルが2つある場合にそれぞれについてsign_upsign_inを設定する方法です。

サインアウトに成功したらHTTPSからHTTPにリダイレクトする

13 Jun 2012 · 1 revision
How To: redirect from HTTPS to HTTP on successful sign out

ログイン中はアプリへのアクセスをHTTPSにし、ログアウトしたらHTTPに戻す方法です。

補足

  • 原文ではsign inとlog in、sign outとlog outがそれぞれ同じ意味で使われています。訳文では、紛らわしくなる場合を除き、原則として原文に沿ったカタカナを使用します。
  • 同じ手順が複数のカテゴリに含まれていることもあります(原文に従っています)
  • 原文が更新されていることにお気づきの場合は、ぜひ@techrachoまでお知らせください。更新いたします。

関連記事(Devise)

正規表現: 文字クラス [ ] 内でエスケープしなくてもよい記号

$
0
0

こんにちは、hachi8833です。久しぶりに正規表現についての記事を書いてみました。

一応Ruby(Onigmo)を対象にしましたが、なるべく一般的になるようにしています。エッジケースを扱っているためにシンタックスハイライトがついていけてない部分がありますのでご了承ください。

文字クラス[ ]内のエスケープ

正規表現でのエスケープ、特に記号のエスケープは何かと面倒になりがちです。記号が出てくるたびに片っ端からバックスラッシュ\でエスケープしてばかりだと疲れてしまいます。

しかしつい忘れがちですが、正規表現の中でも文字クラス[ ]の中だけは別世界になっていて、文字クラスの外よりもエスケープが少なくてすみます。

あくまで原則としてですが、文字クラス[ ]の中に限り、以下の4つの記号だけがメタキャラクタ(=機能を持つ記号)として扱われます。

マイナスハイフン
-
山形記号
^
閉じ角かっこ
]
バックスラッシュ
\

ということは、それ以外の記号はエスケープなしで楽々と文字クラス[ ]内に置けることになります。本当に?本当に?

文字クラス内メタキャラクタにも種類がある

ただし、上の中でバックスラッシュ\と他の3つのメタキャラクタは挙動が異なります。

バックスラッシュ\だけは絶対的なメタキャラクタであり、自分自身を含む直後の文字は何でもエスケープします。

逆に、マイナスハイフン: -、山形記号: ^、閉じ角かっこ: ]の3つは、文字クラス[ ]内では相対的なメタキャラクタです。

具体的には、文字クラス[ ]の中の先頭に置くかどうかで挙動が異なります。

文字クラス[ ]内の山形記号^

文字クラス[ ]内の先頭が山形記号^だと、文字クラスの否定を表します(Rubularの実行例)。

[^0-9a-zA-Z]

上のようにすると、「英数字以外のあらゆる文字」を表します。これはよく使われるのでご存じの方も多いと思います。

実は、^は文字クラスの先頭以外の場所に置くのであればエスケープ不要です(Rubularの実行例)。

[0-9a-z^A-Z]

正確に言うと、先頭以外の場所であればエスケープしてもしなくて同じです。

文字クラス[ ]内のマイナスハイフン -

文字クラス[ ]内のマイナスハイフン-は、範囲を表すメタキャラクタです。

[a-zA-Z]

[a-zA-Z]とすると、小文字のaからzと、大文字のAからZの文字を表します。これもよく使われるのでご存じの方が多いと思います。

文字クラス[ ]内でマイナスハイフン自体を表すには、\^のようにバックスラッシュでエスケープする方法のほかに、以下のように文字クラスの先頭に置くことでもできます(Rubularの実行例)。

[a-zA-Z\-]
[-a-zA-Z]

上の文字クラスは、どちらもマイナスハイフンと、小文字のaからzと、大文字のAからZの文字を表します。ここでも文字クラスの先頭が特別扱いされています。

文字クラス[ ]内の閉じ角かっこ ]

次がやや気色悪いです。

文字クラス[ ]内の先頭が閉じ角かっこ ]だと、エスケープなしで通常の文字として扱われます。つまり、]は文字クラスの先頭においた場合に限り機能しなくなります。

[a-zA-Z&*()\]]
[]a-zA-Z&*()]

たとえば上はいずれも、]a-zA-Z&*()のいずれかにマッチします。

とはいうものの、[]なんちゃら]などと書くと、間違って2回閉じてしまったように見えてしまうので、いくらバックスラッシュでエスケープしなくてよくなるからといっても使いたくないですね。少なくとも私は。

普通に[a-zA-Z&*()\]]]\でエスケープする方がなんぼかましです。

さらに、JavaScriptでは[]空の文字クラスと認識する(regex101.comの実行例)など、実装によって変わる可能性が大なので、この挙動をあてにするとはまると思います。

文字クラス[ ]内で「現実に」エスケープが必要なその他の文字

いよいよ本題です。

さきの原則どおりであれば、文字クラス内に記号を書くときは^-]\の4つの文字だけをエスケープすればよいことになります。

!"#$%&'()*,-./:;<>?@[\]^_`{|}~

上はASCIIの記号たちです(UTF-8でも共通です)。これを文字クラスの中に書いてエスケープするとしましょう。例の4つのメタキャラクタ「^-]\」をエスケープすると以下のようになります。

!"#$%&'()*,\-./:;<>?@[\\\]\^_`{|}~

しかし実際にやってみると怒られます(Rubularの実行例)。

うすうす見当の付いた方もいらっしゃると思いますが、現実には4つのメタキャラクタの他に、少なくとも以下のエスケープも必要になることがあります。実装によってはこの他にもエスケープが必要になる記号があるかもしれません。

正規表現の開始/終了記号
スラッシュ/が一般的ですが、少なくともRubyでは開始/終了記号を他の文字に変えることもできます。
開始かっこ[
これは実装に依存する可能性があります。少なくともRubyのOnigmoでは文字クラスの中で開始かっこ[もエスケープが必要です。

というわけで、/[もエスケープしてみました(Rubularの実行例)。

!"#$%&'()*,\-.\/:;<>?@\[\\\]\^_`{|}~

はい、今度はきれいにすべての記号にマッチしました。

まとめ

まとめると、少なくともRubyの正規表現の文字クラス内であれば、メタキャラクタである/^-]、そのほかに/[だけをエスケープすればよいことになります。さらに、^は先頭に置かないように注意すればエスケープ不要です。

それ以外の記号はエスケープなんかしなくたってよいのです(エスケープしても動きますが)。

ここまでわかれば、文字クラスの中をエスケープだらけにせずに安心して記号を書けるようになります。

以下の文字クラスではエスケープをひとつも使っていませんが、ちゃんと機能します(Rubularの実行例)。

[#$@%&*._^(){}]

これでエスケープの強迫観念がだいぶ軽くなった気がします。

おまけ

ついでながら、文字クラス[ ]の中には順序の概念がありません(例の4つのメタキャラクタの振る舞いはもちろん除きます)。

したがって、[bar]でも[abr]でも同じです。

間違えられやすいのですが、[^bar]は「”bar”でない文字」ではなく、「”b”でも”a”でも”r”でもない文字」です。

おまけ 2

参考までに、RubyやPerlや.NET Frameworkなどにバンドルされているリッチな正規表現ライブラリで、かつ対象がASCII限定であれば、以下のようにUnicodeのカテゴリを指定してASCIIの全記号にマッチさせることもできます(Rubularの実行例)。JavaScriptやsed/awkなどでは残念ながら標準ではサポートされていません。

[\p{P}\p{S}]

ただしこれは全角記号や句読点や絵文字を含むありとあらゆる記号にマッチする、やばいぐらい大ざっぱな正規表現です。対象がソースコードのようなものならともかく、一般的な文書だといらん記号にまでがんがんマッチするので大変なことになります。

\p{P}はあらゆる約物(punctuation)、\p{S}はあらゆるシンボル(symbol)を表します。詳しくはUnicode Character Categoriesをご覧ください。

参考

関連記事(正規表現)

Viewing all 1080 articles
Browse latest View live