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

Active Support core_extのHash#diffはRails 4で非推奨化・廃止された

$
0
0

こんにちは、hachi8833です。新しい情報ではありませんが、Active Support Core ExtensionsのHash#diffは、Rails 4.0で非推奨化され、4.1で廃止されていたことを確認したのでメモします。

apidock.comではRails 3以降についての表示がありませんが(下図)、実際にはRails 3にもありました。

Rails 3から4にかけてのHash#diff

GitHubでは、以下のとおりRails 3.0〜3.1までHash#diffのコードがありました。

def diff(h2)
  dup.delete_if { |k, v| h2[k] == v }.merge!(h2.dup.delete_if { |k, v| has_key?(k) })
end

そして以下のようにRails 4.0で非推奨化(deprecated)され、4.1で廃止されました。

Hash#diff is no longer used inside of Rails, and is being deprecated with no replacement. If you’re using it to compare hashes for the purpose of testing, please use MiniTest’s assert_equal instead.
(大意)Hash#diffはRails内部で使われていないので非推奨化中。代替メソッドはなし。テストでハッシュの比較が必要であれば、MiniTestのassert_equalを使うこと。
diff.rbのメッセージより

MiniTestのassert_equal

Hash#diffの代わりに使って欲しいとあるassert_equalは、RailsではなくRubyのMiniTestのメソッドです。

# File lib/minitest/assertions.rb, line 171
def assert_equal exp, act, msg = nil
  msg = message(msg, E) { diff exp, act }
  assert exp == act, msg
end

追伸

http://apidock.com/の情報を訂正したいのですが、なぜかアカウント登録しようとするとエラーになって先に進めませんでした。後日再挑戦してみます。

参考

関連記事


[翻訳]RailsチュートリアルがRails 5.0に完全対応しました!(第4版)🌟

$
0
0

こんにちは、hachi8833です。

これまで私とともにRailsチュートリアルの翻訳に携わってきたYassLab代表の安川さん@yasulabのご尽力により、Ruby on RailsチュートリアルがRails 5に完全対応しましたので、ここに発表いたします。

注: 日本語版は「Ruby on Railsチュートリアル」または「Railsチュートリアル」とカタカナ表記し、原著の英語版は「Ruby on Rails Tutorial」または「Rails Tutorial」と英語表記します。

Ruby on Rails Tutorialは以前からRails 5に対応しておりましたが、今回差分翻訳およびフォーマットの整形などが完了し、このたび無事にリリースされました。安川さん、ありがとうございます&お疲れさまでした!

「第4版」について

少々紛らわしいのですが、Ruby on Rails Tutorialでは、Rails 5対応バージョンは第4版(4th Edition)と呼ばれています。第5版ではありませんのでご注意ください。

今回リリースされたRails 5対応のRailsチュートリアルは、Rails Tutorialの第4版をベースにしています。

第3版から第4版への変更点について、詳しくはRailsチュートリアルの歩き方(第4版)(SlideShareスライドp40より)が便利です。

「第3版」との切り替えについて

現在、Railsチュートリアルのトップページでは、旧版である第3版が表示されます。上の画像のお知らせ(ハイライト部分)で示されているとおり、2016年12月15日よりトップページにデフォルトで第4版が表示されるようになります

現在チュートリアルをご利用の方は、版の切り替わりにご注意ください。

こぼれ話: 翻訳について

今回のRails 5対応翻訳作業については私がまとまった時間を取れず、第1章の差分翻訳のみを担当いたしました。その他の章の差分翻訳や製版作業は、安川さんを中心として行われたことをお断りしておきます。

今回の版はRails 4対応の旧版の多くを継承していますが、翻訳自体のボリュームに加え、構成の変更などによる翻訳以外のリンクタグやスタイルタグの修正といったポストプロダクション・製版作業も予想外に多く、相当難航したそうです。本当にお疲れさまでした。

従来のRailsチュートリアルでは、HTMLと電子書籍の生成にRe:VIEW gemを使っていましたが、原著がsoftcoverで原稿作成されていたので、原著の環境に近づけるためsoftcoverへの移行も行ったのだそうです。

なお、softcoverはRuby on Rails Tutorial原著者であるMichael Hartl作のgemです。

翻訳手法の変化

Railsチュートリアルの翻訳方法は、今後変更されます。

従来はGoogle翻訳者ツールキット(GTT)を利用して大量翻訳を実現しました。
しかしGTTでは更新翻訳の作業が難しく、チュートリアルを更新するたびに大きな負担がかかっていました。

そのため、GTTを使わずに継続的に更新翻訳を行う体制の構築を安川さんたちがすすめ、完全に載せ替えが完了しました。継続的翻訳については、主にRailsガイドを題材としていますが、
How We Continuously Translate Tech Docs
(SpeakerDeckスライド)が参考になると思います。

安川さんからの発表

Railsチュートリアル の第4版 (Rails 5.0対応) をリリースしました! 📕

http://railstutorial.jp/?version=5.0
Rails 5.0対応に注目されがちですが、個人的に一番の目玉は「演習」の大幅更新です🔧 すべての章において演習の数が5倍〜10倍ほど増えていて (例えば第4章なら3個→36個)、また、演習も「章末にまとめて」ではなく「セクションごとに細かく」設置されるようになりました 📝

🔧 第3版→第4版の改善点
http://www.slideshare.net/yasulab/railstutorialjp-4e#slides-40
また、今回もいつものようにhachi8833さんに第1章の翻訳を手伝っていただきました! 😸 ただ都合がうまく合わせられず、残りの第2章〜第14章は僕の方で翻訳しています (なのでここ2ヶ月ほど1人でずっとデスマーチしてました😭)
 
内部の技術的には、ついに Google Translator Toolkit を卒業し、継続的翻訳システムに完全に載せ換えました! ♻️
♻️ 継続的翻訳システム
https://speakerdeck.com/yasulab/how-we-continuously-translate-tech-docs
GitHub 上の更新を自動的に追従翻訳できるようになったので、原著と訳文との開きをさらに短くすることができます (チューニング次第ですが、最短で1〜2時間ぐらいまで短縮可能) 💨
 
最後に、第4版に合わせて、解説セミナーの動画販売も開始しました! 📹✨ Ruby/Railsもできて収録/編集もできるハイスペックな FranLiber Inc. さんにご協力していただき、「計34時間」の解説動画が完成しました! 😆

📹 ライブ収録動画 (第4版、Rails5.0対応)
http://railstutorial.jp/seminars#record
今後もRails解説セミナーは Coworking space kayabacho, co-edo tokyo で開催されていく予定ですが、セミナーだと日程が合わない場合や、何度も見返して理解したい場合、また、地理的に参加が難しい場合はぜひ購入をご検討ください! 💰
 
以上、まとめての報告となりましたが、弊社 (YassLab – ヤスラボ) では大型書籍や大型ドキュメントの継続的翻訳を通して、関わるすべてのコミュニティに弊社が受けた恩恵を還元していけたらなと考えています🏃💨
今後とも引き続きよろしくお願いします! :D
Facebookでの発表より、リンクなどを整形して引用

関連記事

Ruby/Rails界隈ウォッチ(2016/11/25)Railsのデータベース・ベストプラクティス、SQLインジェクション解説ほか

$
0
0

こんにちは、hachi8833です。昨日のTechRachoでお知らせしたとおり、RailsチュートリアルがRails 5に完全対応しました。それでは今週の界隈ウォッチです。

臨時ニュース

Ruby 2.3.3リリース

11月21日に2.3.3がリリースされました。

このリリースには Refinements と Module#prepend に関する不具合の修正が含まれています。 同じ Class に対して Module#refine と Module#prepend を併用すると意図しない NoMethodError が発生することがありました。 これは先日の Ruby 2.3.2 のリリースで入り込んだ不具合です。 詳しくは [Bug #12920] を参照してください。
ruby-lang.orgニュースより

Rails公式ニュース

CapybaraがRails 5.1での統合準備すすむ

統合後はRailsのテストフレームワークがActionSystemTestと呼ばれるようになるそうですが、また変わるかもしれませんね。最初Capybaraの名前が変わるのかと空目してしまいましたが、違いました。

なおBona fideはラテン語で「本物の」「真正の」といった意味です。

Ruby Weekly

RubyのC言語拡張機能を書く―2016年版

morimorihoge: サウンド関係など、リアルタイム処理やりたいときや特殊なドライバをRubyからつつきたいときは必要になるかもしれないですね。

KubernetesでRailsをデプロイする

Kubernetesは自前でクラウドを構築するレベルのソフトウェアなので、強烈に難しそうです。morimorihogeさんの解説では、live migrationひとつとっても膨大な問題をクリアする必要があるそうです。

morimorihoge: 今ならDocker SwarmがDocker Engineに統合されたし、AWS ECSやGCPなどにも類似コンテナサービスがあるはずなので、そっちを使いたいですね。

Kubernetesはギリシャ語由来だそうで、発音は「クーベルネイテス」が近そうですね。きぐしねいです。

WebPackとYarnでAsset Pipelineを置き換える

多くの人がSprocketsやAsset Pipelineを何とかしようとして頑張っていますね。早く決定打が登場して欲しいです。

Railsのデータベース・ベストプラクティス

  1. データベースに本来の仕事をさせよう
  2. 有効かつチェイン可能な(chainable)スコープを使おう
    1. スコープはActiveRecord::Relationを返そう
    2. フィルタはRubyではなくデータベース側で行おう
    3. ソートはRubyではなくデータベース側で行おう
    4. スコープで不必要なORDERを指定しないようにしよう
  3. データベース呼び出しの回数を減らそう
  4. インデックスを使おう
  5. クエリが複雑になったらQueryオブジェクトを使おう
  6. ScopeやQueryオブジェクトの外でその場限りのクエリを行わないようにしよう
  7. 型を正しく指定しよう
  8. データベースの全文検索機能の利用も検討してみよう
  9. ストアドプロシージャは最後の手段にとっておこう

morimorihoge: 7.について、PostgreSQLは型についてかなり厳格だけどMySQLは逆にユルいので、Active RecordでMySQLを使っている人ほど型を正しく指定することに気をつけることをおすすめします。

RubyistのためのO(n)記法

計算量のO(n)記法をRubyのコードで解説してくれているのがありがたいですね。

RubyFlow

160928_1638_XvIP4h

Rails 5でのSQLインジェクション問題解説サイト: rails-sqli.org

現時点では以下のSQLインジェクション例が紹介されています。再現用のGitHubリポジトリもあります。

  • Calculations#calculateメソッド
  • delete_allメソッド
  • destroy_allメソッド
  • exists?メソッド
  • find_byfind_by!メソッド
  • fromメソッド
  • groupメソッド
  • havingメソッド
  • joinsメソッド
  • lockメソッド、find:lockオプション
  • orderメソッド
  • pluckメソッド
  • reorderメソッド
  • selectメソッド
  • whereメソッド
  • update_allメソッド

提供元はBrakeman Proのスタッフです。すべてを網羅しているわけではないので、新しいパターンを見つけたらぜひ貢献して欲しいとのことです。

Hacker News

160928_1654_q6srdR

SpaceX社が低遅延のギガビット級衛星インターネットを計画

morimorihogeさんの解説によると、衛星経由のインターネット接続は広帯域ではあるが遅延(レイテンシ)が大きいという特性があるそうです。TCP 3-wayハンドシェイクとかいかにも苦しそうですね。

SpaceX社は、高度715〜823マイルの低軌道衛星を4,425機打ち上げ、現行のHugesNet衛星(高度22,000マイル)の600ms程度の遅延を25〜35msに短縮する計画であるとのことです。

なお715マイルは約1150km、22,000マイルは約35400kmなので、SpaceX社の衛星は低軌道とは言っても国際宇宙ステーションより高高度になりそうです。

Quick, Draw

Made with some friends from Google
Quick, Drawより(Quick, DrawはGoogleのサービスです)

指定の絵を20秒以内に描いてGoogleのニューラルネットワークに判定させるという遊びです。先週からBPSでも密かに流行っています。

これ本当に、子どもの英語学習教材として非常に強力なんじゃないでしょうか。

Googleが.NET Foundation加入を表明

Google公式ブログ(英語)でも、.NET FrameworkをGoogle Cloud Platform(GCP)で優先サポートすると発表されています。ついこの間のマイクロソフトのLinux Foundation加入に答えるかのようなタイミングです。

もしかするとGoogleは本気でJavaからC#に乗り換えるのかもしれません。ご存じのとおり、既に.NET FrameworkやC#などがオープンソース化され、Macでも開発・実行可能な環境が急速に整いつつありますが、それにしても動きが早くなってきました。

警告: Windowsのアプリやツールで(WSLの)Linuxファイルを絶対に変更しないでください

MSDNブログでの発表です。Windows側からLinuxファイルを変更するとデータが破損、最悪の場合ディストリの再インストールが必要になるかもしれないとのことです。

Github Trending

160928_1701_Q9dJIU

最近のGitHub Trendingは何とかAwesomeといった単なるまとめ情報が上位にあがってきて邪魔ですね。それも機械学習系のまとめ情報が目立っています。

devdocs

http://devdocs.io/は、複数のAPIドキュメントを串刺し検索できます。Dashのオンライン版のようなおもむきですね。私は一発で気に入ってしまいました。

使いたいドキュメントの[Enable]をクリックすることで、検索窓で検索できるようになります。

morimorihoge: GitHubでソースが公開されているのが良いですね。

Goの部屋

GDrive

Googleドライブのコマンドライン版クライアントです。ファイルリビジョンまで扱えます。これを使ってちょっとしたアプリケーションすら作れそうです。

無印枠

secureheaders gem

セキュリティ関連のヘッダーを集めたgemです。

今週は以上です。

関連記事

[Rails5] Active Support Core ExtensionsのStringクラス(2)html_safe

$
0
0

こんにちは、hachi8833です。今回はRails 5 Active SupportのString#html_safeを見てみます。

今回のメソッド

条件

ソースのコメント等は適宜省略します。

html_safeは「安全」であるとマーキングする

今回取り上げるString#html_safeについては、TechRachoの以前の記事『RailsビューのHTMLエスケープは#link_toなどのヘルパーメソッドで解除されることがある』も合わせてお読みいただくことをおすすめします。

html_safeメソッドは対象が安全であることをマーキングし、以後エスケープを行わないようにするメソッドなので、安全であることが間違いなく確認できてから呼び出す必要があります。安全でないオブジェクトを安全にするものではありません

また、ソースのコメントでも「このメソッドよりもsanitizeメソッドをおすすめする」と記載されています。

output_safety.rbの構成

output_safety.rbでは以下をrequireしています。

require 'erb'
require 'active_support/core_ext/kernel/singleton_class'

output_safety.rbは大きく分けて以下のような構成になっています。html_safeは最後のStringクラスで定義されています。

  • ERBクラス内でUtilモジュールを定義
  • Objectクラス(html_safe?を定義)
  • Numericクラス(html_safe?を定義)
  • ActiveSupportモジュールでSafeBufferクラスを定義
  • Stringクラス(html_safeを定義)
class ERB
  module Util
    ...
  end
end
class Object
  def html_safe?
    false
  end
end
class Numeric
  def html_safe?
    true
  end
end
module ActiveSupport #:nodoc:
  class SafeBuffer < String
    class SafeConcatError < StandardError
      ...
    end
    ...
  end
end
class String
  def html_safe
    ActiveSupport::SafeBuffer.new(self)
  end
end

html_safe

html_safeのコードそのものは以下の1行だけであり、ActiveSupport::SafeBufferオブジェクトを作成して返しています。ActiveSupport::SafeBufferクラスは同じファイルにあります。

class String
  def html_safe
    ActiveSupport::SafeBuffer.new(self)
  end
end

ActiveSupport::SafeBufferは同じoutput_safety.rbファイルにあり、以下のメソッドがあります。

クラスメソッド

  • initializeメソッド

インスタンスメソッド

ここがhtml_safeの中心となるメソッド群です。

  • []メソッド
  • safe_concatメソッド
  • initialize_copyメソッド
  • clone_emptyメソッド
  • concatメソッド
  • prependメソッド
  • +メソッド
  • %メソッド
  • html_safe?メソッド
  • to_sメソッド
  • to_paramメソッド
  • encode_withメソッド

たとえばconcatメソッドは以下のように式展開のみエスケープして親クラスに渡しています。

def concat(value)
  super(html_escape_interpolated_argument(value))
end

プライベートメソッド

  • html_escape_interpolated_argumentメソッド

html_escape_interpolated_argumentは、html_safeでない式展開をエスケープします。

関連記事

[Ruby] each_with_objectもmapも使わずにto_hだけで配列をハッシュに変換する

$
0
0

こんにちは、hachi8833です。

Web開発チームのtsunekawaさんに教えていただいた方法をメモします。

to_hだけで配列をハッシュに変換できる

早速試してみましょう。

each_with_objectメソッドの場合

[["Alice", 50], ["Bob", 40], ["Charlie", 70]].each_with_object({}) do |(key, value), hash|
  hash[key] = value
end

map.to_hで書いた場合

[["Alice", 50], ["Bob", 40], ["Charlie", 70]].map.to_h

to_hだけで書いた場合

[["Alice", 50], ["Bob", 40], ["Charlie", 70]].to_h

実行結果

確かに3つとも同じ結果になってますね。

#to_hについて

せっかくなので、「to_hだけで書いた場合」で呼ばれているto_hがどこにあるのかを確認してみましょう(参考: RubyのIRBやpryでメソッドの定義元をすっと調べる方法)。

to_hだけで書いた場合

素のpryで実行していることからもおわかりのように、「to_hだけで書いた場合」で呼ばれているのはRuby標準ライブラリのArray#to_hでした。

なお、Array#to_hが導入されたのはRuby 2.1.0からなので、意外と新しいんですね(参考: Ruby 2.1.0リリース!注目の新機能を見てみましょう)。

map.to_hで書いた場合

順序が逆になりますが、map.to_hの場合もついでに調べてみました。

予想どおり、こちらはRuby標準ライブラリのEnumerable#to_hが呼ばれています。

今回のパターン「[["Alice", 50], ["Bob", 40], ["Charlie", 70]]」では、Array#to_hEnumerable#to_hは同じ結果になりましたが、レシーバーの内容によっては同じにならないかもしれません。

参考

たとえばRailsだとActive Support core_extにもXMLConverter#to_hがあり、実際にはXMLConverter#deep_to_hを呼んでいます。

    def to_h
      deep_to_h(@xml)
    end

ほかにもAction Controller(Action Pack内)のmetal/strong_parameters.rbにもParameters#to_hがあります。

    def to_h
      if permitted?
        convert_parameters_to_hashes(@parameters, :to_h)
      else
        slice(*self.class.always_permitted_parameters).permit!.to_h
      end
    end

Rails環境では他にもさまざまなライブラリにto_hがあります。

参考

関連記事

RubyMineからVimに乗り換えて2年経ちました【BPS Advent Calendar: 12/08】

$
0
0

RubyMineからVimに変えたときに記事を書こうと思っていたら、2年が過ぎてしまいました。
なので、今日は、RubyMineからVimに変えた当時を思い出しつつ以下のような流れでVimに変えた経緯やVimの良さを書いていこうと思います。

  • なぜVimにしたのか
  • Vimライフを支える厳選おすすめ設定、プラグイン
  • 現在のVimライフ
  • Vimの布教活動状況
  • 最後に

↓私が使ってるVimの設定
https://github.com/djkazuma/vim

なぜVimにしたのか

理由その1: Vim使う人ってなんだかすごそう

「エディタなに使ってるんですか?」
Vimです!
こういわれると
「あ、なんかすごそう」、「ガチっぽい気がする」って感じがしないですか?
(私だけかもしれないですが)
Vim部部長がすごかったからか、Vimで作業する人はものすごくガチな印象が私にはありました。
ちなみにVim使ってみようと思ったきっかけはこのVim部部長がやっていたVim部の活動です。

理由その2: エディタを変えるのが面倒

私の場合、並行して作業する案件が大体3、4で多いと7、8くらいの案件を同時にやっていた時がありました。
案件によって言語がまちまちなので、Vimにする前は、

こんな感じでやってました。
EclipseとRubyMineを同時起動して作業するとか面倒だし、Rails ConsoleやDBへのアクセス、SSHでの作業とかもやったりするので、Eclipse、RubyMine、コンソールも立ち上げて作業するとかよくわからない状況になってました。
しかもRubyMineが複数起動されていて、「あれ?、この案件のやつはどれだ?」とかもあり、大変苦労していました。

理由その3: いつでもどこでも同じエディタがすぐに準備できる

家などで同じ環境を作るのにEclipseとかRubyMineだと、Webサイトからダウンロードして、それから設定してってのがめんどくさいなと感じてました。
商用で使うとかになるとRubyMineは有料になっちゃうので、ちょっとそこも嫌でした。
その点、Vimは無料で、.vimrcさえ持ってくれば大体同じになるので楽だなと思ってます。

Vimライフを支える厳選おすすめ設定、プラグイン

ここでは、これのおかげでVimの見方が変わったという設定やプラグインを紹介しようと思います。
「これがなければVimは使っていない」というくらい私の中では便利なものです。

Vimのおすすめ設定

以下を.vimrcに追加します。

" jjで挿入モードから抜ける設定
inoremap <silent> jj <ESC>

Vim部部長直伝のこの設定がものすごく便利です。
毎回、ESCで抜けていて、めんどくさいと思っていましたが、この設定のおかげでコーディングの速度が上がりました。
(ただ、この設定を使っている影響で、たまに他のエディタを使うと間違ってjjとか打っちゃうということが発生していますがw)

Vimのおすすめプラグイン

NERDTree

" NERDTreeの画面を開閉する
map <C-n> :NERDTreeToggle<CR>

このプラグインを使うことでファイルを開くのがものすごく楽になりました。

現在のVimライフ

RubyMineに戻りたいとか全くなく快適に使えています。
byobu(うらtmux)と併用の環境ですが、「エディタが複数立ち上がって画面にいっぱいある」みたいな状況がなくなったのが一番よかったなと感じてます。
あと、コーディングするのにマウス使わなくていいのが楽です。

現在、問題なくコーディングできていますが、Vimに変えた当時、RubyMineにあったコードジャンプの機能はものすごく便利だったのでVimで同じようなことを実現できないかいろいろ調べてみてやってみましたが、これだけはうまくいっていない状態です。
(最近はRailsやGemのソースコードを読む勘所もわかってきたので、なくてもあまり今は困ってないですが。)

Vimの布教活動

小川さんのしたでやる人はエディタVim、仮想端末byobu(うしろはtmux推奨)とすることにしました。

こんな暴挙に出たのは去年の3月ごろ。
去年、今年と新人が一人ずつ私のしたに入ってきましたが、問答無用でVimを使わせました。
その後も中途採用で人が入ってきて、エディタにこだわりがないという人にはとりあえずVimを進めていますが、普及率は6割程度な感じがしています。
教えたりする際に、環境が違うとちょっと面倒だったりするので、スタンダートとしてVimを推していきたい気持ちはありますが、Vimで完全に縛りたいという気持ちはなく、使う本人の業務効率が下がるようであれば使う必要はないと思ってます。(というのを私のしたでやってる人には伝えました)
が、やはりこれからもVimは推していきます。

最後に

Vimに変えた理由の中には今後のエンジニア生活で絶対役に立つという思いがあったんですが、Vimに限らずこの先、何を身につけていくのかは常に考えていきたいと思っています。

[Rails] RSpecをやる前に知っておきたかったこと

$
0
0

こんにちは、hachi8833です。

今さらですが、RSpecをやる前に知っておきたかったことを記事にしました。少しさかのぼって、ソフトウェアのテストについても最初に簡単にまとめてみました。

ソフトウェアのテストとは

まず、広い意味でのソフトウェアテストは「人間による目視チェック」のようなものも含まれる点にご注意ください。

ソフトウェアの動作確認をどのように行うかは、ソフトウェア開発において常に大きな課題です。

当然ながら、ソフトウェアの動作確認を思い付きでやっていると漏れが発生しやすく、確認のたびにチェック内容が変わってしまったりするので好ましくありません。

そこで、ソフトウェアのテスト方法を体系化し、

  • 誰がやっても同じようにテストできる方法
  • 誰でもテスト計画を策定できる手法

が模索されてきました。

個別のテスト実施はドラゴン桜の名言でいうところの「作業」であり、ひとつひとつはそれほど重たいタスクではありませんが、テスト項目の漏れがないようにし、再現可能・繰り返し可能・手分け可能にすることが重要です。

Wikipedi: ソフトウェアテストを見ると項目が多くてびっくりしますが、これはソフトウェアテストが長年研究の対象となっており、大型機などの大規模な開発や学術研究でのテストをも網羅できるようになっているためです。

テストの自動化

ソフトウェアテストの研究が進み、大規模なソフトウェアが開発されるようになるにつれ、繰り返し行われるテストそのものをソフトウェアで記述して自動化する、という発想が生まれました。これは自然な考えです。

こうしたいきさつから、ソフトウェア開発ではある時期から「テスト自体をソフトウェアで行う」ということがさかんに行われるようになり、現在では言語や規模、案件を問わず開発手順に組み込まれるのが普通になっています。

個別のテスト支援ツールや、テストの自動実行ツールなどはたくさんありますが、そのほとんどは上で紹介したWikipediaのソフトウェアテストの項目を踏まえています。

なお、人間による目視チェックもPhantomJSなどによる自動化である程度カバーできるようになりつつありますが、完全に置き換えるまでにはいたっていません。

テストをどこまでやるか

初めのうちはこの点に一番悩むと思います。

テストの範囲は案件によって大きく異なりますので、「必ずこうしなければならない」と一般化するのは簡単ではありません。

不必要なテストは時間とコストの浪費につながりますので、お客様からのリクエストを満たすことを前提に項目を絞り込むのが普通です。

そのためにも、ソフトウェアテストにはどのような項目があるのかを一度は専門書で勉強しておくのがよいでしょう。

アマゾンのサーバでエラーが起こっているかもしれません。一度ページを再読み込みしてみてください。

Railsのテストフレームワークによるアプリケーションの自動テスト

ここから先は、ソフトウェアテストを狭い意味で使います。テスト用のフレームワークを使ってテストコードを書き、実行することを指します。

先のWikipediaの項目のうち、Railsアプリケーションでテストコードを書く目的は、主に以下の2つです。

  • Unit test(単体テスト) — ソフトウェアを構成する小さな機能ごとのテスト
  • Integration test(結合テスト) — ソフトウェアの実際の動作の流れに沿った総合的なテスト

Integration testは「統合試験」などと訳されることもありますが、現実の開発現場で使われている「結合テスト」と呼ぶことにします。

Ruby on Railsにおける自動テスト

Ruby on Railsの場合、以下の2つのテストフレームワークがよく使われます。

RSpecはRails限定というわけではなく、Rails以外のRubyのテストにも使えます。

miniTestはRuby標準のテスト用ライブラリであり、Ruby on Railsでも標準とされています。

しかし現実には多くの開発会社ではRSpecが普及しています。RSpecはRailsには標準ではインストールされていないので、rspec gemを後からインストールして使います。

Railsの生みの親であるDHHはRSpecを好んでいないという事情もあるので、RailsでRSpecが標準セットに含まれることは当面なさそうです。

なお、BPSのRails開発ではRSpecが標準です。

テストをどこまで行うか – その2

RSpecをRailsにインストールすると、コントローラ・モデル・ビューなどにテスト用のファイルが自動で生成されます。テスト関連のgemをインストールすると他にもテスト用ファイルが作られたりします。

私も最初のうちは、これらをすべて埋めなければならないのかと思って考え込んでしまいました。しかし前述のとおり、テストは必要な部分をまず押さえるのがよいと考え直しました。

このあたりを一般化するのは難しいのですが、Railsアプリの場合たとえばコントローラのテストはそれほど重要でないことが多いと思われます。まずはモデルのテスト、続いてビューのテストが必要になることが多いのではないでしょうか。

なお、コントローラのコードはなるべくスリムに保ってモデルに書くようにするのが一般的です。モデルのコードが膨れ上がってきたらdecoratorなどを使ってコードを整理しましょう。

テストの動作について

私がRSpecを書き始めた当初わかっていなかったのは、「個別のテストコードが独立している」ということでした。

独立しているというのは、たとえばテストコードAとテストコードBがあった場合、AとBは関連しないということであり、またAとBを関連させるようなテストコードを書くべきではないということです。

より具体的には、テストコードAでなにがしかの値が設定されているということを、テストコードBであてにするべきではありません。初期値やデータは、テストコードごとに用意します。

RSpecをverboseモードで実行してみると、各テストコードを実行するたびにRailsのインスタンスが立ち上がり、終了してから次のテストコードでまたRailsのインスタンスが立ち上がり…といった具合になっています。

それでもテストコードが複雑になると、あるテストコードで設定した値や保存したファイルがその後のテストコードに影響してしまうことがあるかもしれません。その場合、テストコードAとテストコードBの実行順序を入れ替えると結果が変わってしまうでしょう。

またテストの内容によっては、毎回同じ結果を得るのが難しいものもあります。

テスト実行のランダム化

現在のRSpecは、各テストコードの実行順序はデフォルトでランダムになります。これは上述のようなテスト同士の無用な結合を検出するためでもあります。

以前のRSpecでは実行順序がデフォルトでランダムではなかったので、たまにランダムにしてみるとそれだけでテストがパスしなくなったりして焦ったことがあります。

追伸: テスト駆動開発(TDD)について

TDDは開発手法のひとつです。大ざっぱに言うと、先にテストを書き、続いてそのテストをパスするように本編のコードを書く、というものです。

TDDの是非についてはすぐ宗教論争になってしまうので深入りしませんが、個人的には「茶室に入るときに右足から入るか左足から入るか」という表千家と裏千家の違いのような、作法の話なのだと理解しています。

テストを書かなければいけないことはもう間違いないので、後はどちらの方法であってもコーディングのリズムに乗ることができればよいのかなという程度に考えています。

なおBPSの開発でTDDを採用するかどうかは開発速度と品質のトレードオフによります。Railsアプリの開発でTDDが必要になったことはこれまでありませんでしたが、モバイルアプリの開発では案件によっては行われることもあります。

baba's comment

参考

関連記事

[Rails4] bundlerのエラー「Your Gemfile.lock is corrupt.」

$
0
0

こんにちは、hachi8833です。rspecのエラーかと思ったらbundlerの方でした。

症状

  • Ruby: 2.1.1
  • Rails 4.1

gem install bundlerに続いてbundleを実行すると以下のエラーが発生することがあります。

Your Gemfile.lock is corrupt. The following gem is missing from the DEPENDENCIES section: ‘rspec’

なお今回のエラーは、『Everyday Rails – RSpecによるRailsテスト入門 – テスト駆動開発の習得に向けた実践的アプローチ』のサンプルコード: everydayrails/rails-4-1-rspec-3-0で発生しました。

参考までに、このときのGemfile.lockは以下のとおりでした。コミットは944f452です。

DEPENDENCIES
  bcrypt (~> 3.1.7)
  bootstrap-sass (~> 3.1.1)
  capybara (~> 2.4.3)
  coffee-rails (~> 4.0.0)
  database_cleaner (~> 1.3.0)
  factory_girl_rails (~> 4.4.1)
  faker (~> 1.4.3)
  guard-rspec (~> 4.3.1)
  jbuilder (~> 2.0)
  jquery-rails
  launchy (~> 2.4.2)
  rails (= 4.1.1)
  rspec-rails (~> 3.1.0)
  sass-rails (~> 4.0.3)
  sdoc (~> 0.4.0)
  selenium-webdriver (~> 2.43.0)
  shoulda-matchers (~> 2.6.2)
  spring
  spring-commands-rspec (~> 1.0.2)
  sqlite3
  turbolinks
  uglifier (>= 1.3.0)

回避方法

サンプルコードのブランチを順に試している間に、隣のアプリチームtjmさんがいち早く以下のissueを見つけてくれました。

I updated bundler to 1.11.0 and got an error running bundle install:

I found a way to solve the error on Windows:

gem uninstall bundler
gem install bundler -v 1.10.6
Then run in your project:
bundle install

bundler 1.11以降でこのエラーが起きることがあるようです。私の環境ではbundler 1.13.6でした。

Issueを参考に以下を実行し、Ruby 2.1.1のbundlerを1.10.0に下げてみたところ、正常にインストールできるようになりました。

gem uninstall bundler
gem install bundler -v 1.10.0
bundle

これはあくまで回避方法です。

追記: Fatal: No live threads leftエラーが発生する場合

bundleを実行したときに、まれにFatal: No live threads left. Deadlock?で始まるエラーが大量に出力されることがあります。bundlerのスレッドが何かのはずみで残っているときにこのエラーが発生するようです。

その場合は以下のようにbundlerに--jobs=1オプションを追加することで解消できます。

bundle --jobs=1

エラーが解消すれば、以後は--jobs=1を付けなくても実行できるようになります。

その後

GithubのリポジトリにIssueを送ったところ、レスをいただきました。近々修正されると思います。Thanks!

関連記事


[Rails 5] rbenvでRubyをインストールして新規Rails開発環境を準備する

$
0
0

こんにちは、hachi8833です。

今回はrbenvを使ってRails 5のrails newを実行できるまでの環境づくりについてメモします。

以前のTechRacho記事「Rails4でサイト構築をする – Rails環境構築編」ではrvmを使いましたが、今回はWebチームでも主流のrbenvにします。

1. rbenvとruby-buildをインストールする

rbenv
複数バージョンのRubyをインストールして簡単に切り替えるソフトウェアです。
ruby-build
rbenvのプラグインで、さまざまなバージョンのRubyの取得元が保存されています。新しいバージョンのRubyがリリースされるとruby-buildも更新されるので、これを取得することで新しいバージョンのRubyをrbenvで簡単にインストールできるようになります。

詳しくは本家Readmeにすべて書いてあります。

aptコマンドが使えるLinux環境の場合

yumDNFを使うLinuxディストリビューションの場合、rbenvはgit cloneでインストールします

apt-get installコマンドでインストールが完了します。

$ apt-get install rbenv ruby-build

新しいRubyがリリースされたら、以下を実行してruby-buildを更新できます。他のパッケージもついでに更新されます。

$ apt-get update
$ apt-get upgrade

注: 開発環境を念頭に置いているので、本番環境ではaptコマンドをむやみに実行しないようご注意ください。

Mac + homebrewの場合

homebrewを使ってインストールするのが楽です。

通常であれば、以下のようにrbenvとruby-buildをhomebrewでインストールします。

$ brew install rbenv ruby-build

私も当初上の方法でインストールしていましたが、homebrewのruby-buildはRubyの新バージョンリリース後すぐに更新されないことがよくあったので(1日〜2日ほどの遅れ)、以下の方法でホームディレクトリの.rbenv/pluginsの下に直接ruby-buildをインストールする方法に切り替えました。

$ brew install rbenv
$ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build

git cloneでruby-buildをインストールした場合、新しいRubyがリリースされたときに次の方法でruby-buildを更新できます。

$ cd ~/.rbenv/plugins/ruby-build
$ git pull

追記: rbenv rehashは現在は不要です

以前のrbenvでは、Rubyをインストール/アンインストールした後にrbenv rehashを実行する必要がありました。
これが面倒だということで、rbenv-gem-rehashというプラグインをインストールするのが流行ったことがあります。

しかしその後rbenvでrehashが不要になったため、rbenv-gem-rehashプラグインも非推奨(Deprecated)になりました。

追記: Macでpryに日本語を入力する

必須ではありませんが、Macのpryで日本語を入力できない場合、以下の方法で入力できるようになります(ついでにOpenSSLライブラリもインストールしています)。

Rubyをインストールする前に以下を実行します。

$ brew install readline openssl

通常ならこれでRubyをインストールすればpryで日本語を入力できるようになりますが、私のMac環境では以下の方法でRubyをインストールしないとpryで日本語を入力できませんでした。

$ RUBY_CONFIGURE_OPTS="--with-readline-dir=$(brew --prefix readline) --with-openssl-dir=$(brew --prefix openssl)" rbenv install <バージョン>

2. rbenvを設定する

2-1. パスの設定

以下を~/.bash_profileに追記し、シェルを再起動するか新しいシェルを起動するとパスが有効になります(.bash_profileが.bashrcで読み込まれるようになっているのが前提です)。

export PATH="$HOME/.rbenv/bin:$PATH"
if which rbenv > /dev/null; then eval "$(rbenv init -)"; fi

パスをすぐに有効にしたい場合は、以下を実行します。

$ source ~/.bash_profile

2-2. デフォルトgemの設定

rbenvをインストールすると、ホームディレクトリの.rbenvに以下のファイル/ディレクトリが作成されます。

rbenv dir

このうちdefault-gemsというファイルにgemの名前だけを記入しておくと、rbenvで新しいRubyをインストールするときにそのgemも自動でインストールしてくれます。

default-gemsにはどのgemを指定するのがよいか

default-gemsにどんなgemを記入しておくのがよいかは、Rubyの利用目的によってさまざまだと思います。

しかし、Rails開発であればdefault-gemsにはbundlerだけを記入するのがおすすめです。

bundler

default-gemsにたくさんのgemを追加するとRubyのインストールが遅くなるためです。

なお、私の場合はRailsと別にRubyを単独で使うことも多いので、pryとpry関連のgemだけ追加しています。

bundler
pry
pry-byebug
pry-stack_explorer
pry-doc

gemがインストールされる場所

  • rbenvを利用すると、default-gemsおよびgem installコマンドでインストールされるgemは~/.rbenv/versions/<バージョン>/lib/ruby/gems以下に置かれます。
  • Railsのproduction環境では、--deploymentオプションをつけることでプロジェクトフォルダのvendor/bundle以下にgemがインストールされます。これはrbenvとは無関係です。

参考: Railsのbundle install –deploymentとは何なのか

3. rbenvでRubyをインストールする

rbenvが準備できたので、最新バージョンのRubyをインストールしてみましょう。

3-1. ruby-buildのアップデート

以下の方法で最新のruby-buildを取得します。

  • aptが使えるLinux: apt-get update; apt-get upgrade
  • Mac: cd ~/.rbenv/plugins/ruby-build; git pull

3-2. Rubyバージョンの確認とインストール

  • rbenv install -lを実行し、ruby-buildで利用できるバージョンのリストを確認します。
  • インストールしたいバージョンを見つけたら、次を実行してRubyをインストールします。

# rbenv install <インストールするバージョン>

rbenvはRubyのソースコードを取得してビルドするので、それなりに時間がかかります。これは仕方がないですね。

  • rbenv versionsを実行し、Rubyがインストールされたことを確認します。*が付いているバージョンが現在アクティブなバージョンです。ディレクトリに.ruby-versionファイル(後述)があると、そこで指定しているバージョンが強制的にアクティブなバージョンになるのでご注意ください。

rbenv versions

3-3. Rubyバージョンを切り替える

  • rbenv global <バージョン>を実行すると、デフォルトのRubyバージョンを指定できます。
  • バージョン抜きでrbenv globalを実行すると、現在のグローバルなバージョンが表示されます。
  • Railsの特定プロジェクトのみ別のバージョンを指定するには、プロジェクトディレクトリでrbenv local <バージョン>と入力します。

rbenv localを実行すると、そのディレクトリに.ruby-versionという名前のファイルが作成されます。ファイルにはバージョン番号のみが指定されます。したがって、echo <バージョン> >.ruby-versionで作成しても同じ結果になります。

.ruby-versionファイルで指定されたバージョンは、そのディレクトリのサブディレクトリでも有効になります。

4. Railsプロジェクトを生成する

長くなりましたが、やっとRailsプロジェクトを生成できる環境が整いました。

以下の手順を使うことで、bundler以外のすべてのgemを新たにrubygems.orgから取り込んでrails newできます。

4-1. Gemfileを作成する

プロジェクトディレクトリを作成し、プロジェクトディレクトリに移動します。

$ mkdir プロジェクト名
$ cd プロジェクト名

以下の内容のGemfileを作成します。

source "http://rubygems.org"
gem "rails", "5.0.0" #利用するバージョンを指定する

プロジェクトでrubyのバージョンを指定したい場合は、例のrbenv local <バージョン>を実行して.ruby-versionファイルをプロジェクトディレクトリに作成します。

4-2. bundle install

Gemfileを置いたディレクトリで以下を実行します。--path vendor/bundleを指定しないと、gemがシステム側(この場合はrbenv)にインストールされてしまうのでご注意ください。

# --pathオプションを付けて実行
$ bundle install --path vendor/bundle

4-3. rails new

後はbundle exec rails new プロジェクト名を実行して好きに料理します。

上で作ったGemfile、Gemfile.lock、vendorディレクトリはインストールのためだけのものなので、削除しておきます。

参考

関連記事

[Rails5] Active Support Core ExtensionsのStringクラス(3)#to_time、#to_date、#to_datetime

$
0
0

こんにちは、hachi8833です。

Active Support探訪シリーズ第3回は、Core Extensionsのconversions_rbにある、Stringクラスの3つのメソッドです。

今回のメソッド

条件

conversions.rb

ソースのコメント等は適宜省略します。

require 'date'
require 'active_support/core_ext/time/calculations'

class String
  def to_time(form = :local)
    parts = Date._parse(self, false)
    used_keys = %i(year mon mday hour min sec sec_fraction offset)
    return if (parts.keys & used_keys).empty?

    now = Time.now
    time = Time.new(
      parts.fetch(:year, now.year),
      parts.fetch(:mon, now.month),
      parts.fetch(:mday, now.day),
      parts.fetch(:hour, 0),
      parts.fetch(:min, 0),
      parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0),
      parts.fetch(:offset, form == :utc ? 0 : nil)
    )

    form == :utc ? time.utc : time.to_time
  end

  def to_date
    ::Date.parse(self, false) unless blank?
  end

  def to_datetime
    ::DateTime.parse(self, false) unless blank?
  end
end

Active Supportらしい短いコードなのでほっとします。

追伸: RubyMineとpryはコードダイブにも便利

Railsのコードを追うときは、実際にはどのクラスのメソッドが呼び出されているのかを常に気にする必要があります。RubyMineの強力なコードジャンプ機能pryでのメソッド定義表示は、こういうときにもとても役に立ちますね。

コードの内容

String#to_timeString#to_dateString#to_datetimeはRubyにはないメソッドです。

たまにピュアRubyを使っていると#to_dateがないことに気付いて、いそいそとActive Supportをrequireしたりしますね。

最初に以下をrequireしています。

require 'date'
require 'active_support/core_ext/time/calculations'

参考までに、require元のactive_support/core_ext/time/calculationsではさらに以下をrequireしています。

require 'active_support/duration'
require 'active_support/core_ext/time/conversions'
require 'active_support/time_with_zone'
require 'active_support/core_ext/time/zones'
require 'active_support/core_ext/date_and_time/calculations'
require 'active_support/core_ext/date/calculations'

コード内で頻繁に使われている#parseメソッドは、RubyのDateクラス、Timeクラス、DateTimeクラスのメソッドです。以下ではDate#parseを明示的に呼んでいます。

::Date.parse(self, false) unless blank?

String#to_dateString#to_datetime

#to_date#to_datetimeunless blank?だけチェックして、素直にDate#parseDateTime#parseを呼んでいます。

  def to_date
    ::Date.parse(self, false) unless blank?
  end

  def to_datetime
    ::DateTime.parse(self, false) unless blank?
  end

簡単でよかった。

String#to_time

String#to_timeはもう少し凝ったコードです。デフォルト値は:localです。

  def to_time(form = :local)
    parts = Date._parse(self, false)
    used_keys = %i(year mon mday hour min sec sec_fraction offset)
    return if (parts.keys & used_keys).empty?

    now = Time.now
    time = Time.new(
      parts.fetch(:year, now.year),
      parts.fetch(:mon, now.month),
      parts.fetch(:mday, now.day),
      parts.fetch(:hour, 0),
      parts.fetch(:min, 0),
      parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0),
      parts.fetch(:offset, form == :utc ? 0 : nil)
    )

    form == :utc ? time.utc : time.to_time
  end

#_parseメソッド

上でアンダースコア付きの#_parseメソッドが呼ばれています。

parts = Date._parse(self, false)

Date#_parseはRubyのext/date/date_core.cにありました。リンク先はRubyの標準ライブラリなのでC言語のコードになっています。

アンダースコアなしのDate#parseは時間の文字列をDateオブジェクトに変換するメソッドですが、アンダースコアありのDate#_parseはハッシュを返す点が異なります。

parse and _parse

#fetchメソッド

続く#fetchメソッドはRubyのHashクラスで、hash.cにあります。hash.cファイルがRubyプロジェクトディレクトリの直下にあったのでちょっとびっくりしました。

parts.fetch(:year, now.year),

Hash#fetchはハッシュのキーを与えて値を返しますが、キーが空の場合のデフォルト値を与えられるので簡潔に書くことができます。

タイムゾーンがUTCの場合の処理

最後にformが:utcの場合の処理を加えています。それ以外の場合はformで指定したタイムゾーン(デフォルトは:local)が使われます。

form == :utc ? time.utc : time.to_time

こんなふうになっていたんですね。

関連記事

週刊Railsウォッチ(20161218)Ruby 2.4ではFloat#roundの動作が変わる、デフォルトのプライマリキーをBIGINTに変更ほか

$
0
0

こんにちは、hachi8833です。

ちょいと日曜日にずれこんでしまいましたが、Railsウォッチ今週もいってみましょう。

余談: Railsウォッチつっつき会について

なおBPSのWebチームでは、いつの頃からかRailsウォッチの公開前に有志でぶらっと集まり、ドラフト記事を読みながら気軽にコメントしたりプルリクを追ってみたり重要なポイントを発見したりする通称「つっつき会」が催されるようになっています。

もともとRailsウォッチの公開前チェックだったのですが、小一時間程度の気楽な催しなので、つっつきながら雑談が思わぬ方向に発展したり、普段聞こうと思って忘れていたことを記事で思い出して、思い切って質問してみたりと、今ではこれが毎週の楽しみになっています。

このつっつき会は私にとっても強烈に勉強になっていて、毎回たくさんの新発見があります。英文情報などを一人で黙々と集めたりRailsのソースコードを全部読んだりして技術力向上に努めるのもよいですが、こういう肩のこらない形で共有することで、自分一人だけで頑張る場合と比べて効果が数倍アップすると思います。

だいたい技術記事って、読むだけだとその場でふむふむと納得して終わって、その後スカッと忘れてしまったりしますよね。読んだ技術情報が血となり肉となるには何らかの形で出力するに限るのですが、コードを書いたり記事を書いたりプレゼンしたりする時間もなかなか取れない昨今、つっつき会のような場なら短時間で大きな効果を得られることに後から気が付きました。

つっつきながら主にmorimorihogeさんがSlackに要点を流し、しかも私がそれを記事にするので、「楽しく雑談して終わり」にならず、ちゃんと情報として残るのも大きなメリットです。

発展系として「オレもこんな記事見つけたぜ」とか「この本すっごくよかった」みたいなこともこういう場なら気楽に言えますね。

もくもく会やハッカソンは一定時間内に何らかの成果を出すという場という感じですが、つっつき会はまた違う効果がある場だと思います。

Railsウォッチをお読みいただいている皆様も、たまにはチームでRailsウォッチを肴にツンツンしてみてはいかがでしょうか。

有志の皆様いつもありがとうございます!

Rails公式ニュース

#27300 yarnをデフォルトにしてvendor/assetsを廃止することに

DHHがyarnを大歓迎しています。yarnは以前のRailsウォッチでもご紹介しましたが、まだ登場後半年も経っていないのにこの普及ぶりはすごいですね。

サムアップがめちゃ集まっています。反対らしい反対もなく、ストンとマージされています。

なおvendor/assetsには一部のgemがアセットを置くことがあるようなので、今後そうしたgemがアップデートされることになりそうです。

Issueで、package.jsonファイルはプロジェクトのルートに置くべきだという意見が出されており、つっつき会でも皆うなずいていました。

#27288 webpacker gemの改良始まる

yarnデフォが決まってまた動きが激しくなりました。機能がかぶるwebpackとwebpackerをどうするかという点ですが、webpackの機能をwebpackerにも移植して強化を図ります。

#26266 デフォルトのプライマリキーをBIGINTに変更

この変更はかなりでかそうです。「テーブルがロックされるーw」という小さな悲鳴も聞こえてきました。

こんなツイートも紹介されています。

#25451新機能: NOT NULL violation時にActiveRecord::NotNullViolationをraiseする

このプルリク@kamipoさんだったんですね。データベース関連では有名な方です。

morimorihogeコメント
今まではnullを渡してもActive Recordを通過してしまい、SQLでやっとエラーになってましたね。
エラーメッセージがわかりやすくなるので、初心者とかあんまりRailsに慣れていない人たちにはありがたい修正だと思います。地味に良いですね。

#27271 RSpecの結合テストが40%高速化

digest cacheをクリアしないようになったことでRSpecを高速化したとのことです。

#27248 after_commitコールバックを冪等化

after_commitってどんなときに使うんだ?」と皆で思ったりしましたが、要求のシビアな案件などで必要になるかもしれないというコメントもありました。

今さらですが「冪等」は「ばくとう」ではなく「べきとう」と読みます。英語ではidempotentですね。もともとはもろに数学用語で、冪等化行列は「方程式E2=Eを満たす行列」を指します。

#28303 テンプレートでblockという名前の変数を使えるようにした

変数名がblockってあまり筋がよくなさそうな気がします。

morimorihogeさんがソースから予約語を扱っている部分を掘り当てました。RailsやERBってこういう感じで予約語を管理しているのを初めて知りました。

RUBY_RESERVED_KEYWORDS = %w(alias and BEGIN begin break case class def defined? do
...
locals = @locals - Module::RUBY_RESERVED_KEYWORDS

Ruby Weekly

SidekiqのSinatraをRackに置き換える

Sidekiqはバックグラウンドジョブを扱うgemですが、今まではRailsにSidekiqをインストールするとSinatraまでインストールされていたということがわかり、思わず一同顔を見合わせてしまいました。

SinatraはRailsよりも軽量なRubyのWebフレームワークですが、RailsにSinatraがインストールされるのは明らかに冗長なのでこの修正は助かりますね。

つい「畸形嚢腫」という言葉を思い出してしまいました。私の年代ではピノコのことですね!

AWSの新サービスRekognitionをRubyから使う

こういう機械学習系のサービスがgemで導入できるようになったんですね。

Ruby 2.4の新機能

つっつき会でもっとも反響があったのがこの記事でした。

  • Float#roundのデフォルトの動作が変わる
# Ruby 2.3
(2.5).round
3

# Ruby 2.4
(2.5).round
2

四捨五入の変更はきついですね。「SQLでの四捨五入とRubyの四捨五入がずれたりするかもしれない」との声もありました。要注意です。

最近接偶数への丸め
最近接偶数への丸め (round to the nearest even; RN) は、端数が0.5より小さいなら切り捨て、端数が0.5より大きいならは切り上げ、端数がちょうど0.5なら切り捨てと切り上げのうち結果が偶数となる方へ丸める。JIS Z 8401で規則Aとして定められていて、規則B(四捨五入)より「望ましい」とされている。
四捨五入ではバイアスが発生する、端数0.5のデータが有限割合で存在する場合でも、バイアスがないのが特徴であり、多数足し合わせても丸め誤差が特定の側に偏って累積することがない(偶数+0.5は現れるが奇数+0.5は現れない、といったような特徴があるデータであれば、やはりバイアスはあらわれる)。
単に「偶数丸め」「最近接丸め」とも呼ばれる。JIS Z 8401で定められていることから「JIS丸め」、あるいは同様にISO 31-0で定められていることから「ISO丸め」ともいう。英語では、誤差の累積を嫌い銀行家が好んで使ったため「銀行家の丸め (bankers’ rounding)」、「銀行丸め」ともいう。5が切り捨てられたり切り上げられたりするので「五捨五入」と呼ばれたり、端数がちょうど0.5の場合に整数部分が偶数なら切り捨て奇数なら切り上げるので「偶捨奇入」と呼ばれたりもする。
Wikipedia: 端数処理より

その他の新機能は以下のとおりです。

  • IOのメソッドにChompフラグを追加
  • Pathname#empty?
  • compare_by_identity
  • Kernel#sendBasicObject#sendSymbol#to_procの改良
  • Hash#transform_values
  • Kernel#cloneでオプションのキーワード引数を利用可能に
  • Thread.report_on_exception
  • Binding#irb

最後のBinding#irbは、pryBinding#pryと同等のデバッグ機能をpryなしでも使えるので便利そうですね。

参考 from morimorihoge

Ansistrano

CapistranoをAnsibleに移植したことで、PHP/Python/RubyなどのアプリをCapistrano風にAnsibleでデプロイできるとのことです。

Railsのレガシーコントローラのリファクタリング

Rails-MVC

MVCのコントローラにターゲットを絞ったリファクタリング記事です。

コントローラのコードはできるだけ少なくするのが定番なので、コントローラが膨れ上がっていたり、コントローラのテストがやけに多かったりするのはそれだけでやばい臭いがしますね。

RubyFlow

160928_1638_XvIP4h

PolyBelongsTo gem

PolyBelongsTo gemはActive Recordのリレーションを拡張するメソッド群です。

MyObject.pbtなど、さすがにメソッド名の付け方はブーイングの嵐でした。

morimorihogeコメント
複雑に絡み合ったモデルの関連や階層を取り出すに使えそうなメソッドがいろいろあるので、調査用ツールを作るのに向いてそうですね。

Rails開発で役立つpry活用術

短くて拍子抜けしそうな記事ですが、その分すぐ読めるので英文慣れしてない方向け。

Pry#wtf?は直前に発生した例外を追うのに便利そうです。

openruby.com

openruby.com

openruby.comをニュースソースに加えてみました。ときどき日本語記事も流れてくることもあるようです。

gemとnpmのバージョン管理方法の違い

diff gem-npm

上の表はリンク先記事からのものですが、バージョン表記の解釈がgemとnpmで見事に違っているのがよくわかります。これが時に困った問題を引き起こしてしまいます。

つっつきでは「gemのバージョン指定方法ってなんであんなにわかりにくいんだ」との声もありました。

Hacker News

160928_1654_q6srdR

lanchaco.com

lanchao.com

画面をポチポチするだけで、ドメイン名取得も含めてレスポンシブなWebサイトを無料で作れるようです。お便利サービスもここまで来たかと。悪いことに使っちゃやーよ。

BOSEのHearphone

BOSEの新製品Hearphoneは、ノイズをカットして会話を聞き取りやすくしたり、大きな音を和らげたり、コンサートの音質を変えたりできるようです。

darkpatterns.org

ユーザーをだますためのUIをあれこれ集めた動画です。既に日本語ニュースサイトでも紹介されているようですね。

Goの間

Go 1.8ではhttpサーバーのgraceful shutdownが導入される

これ地味にありがたいです。群雄割拠するGo製Webフレームワークたちはgraceful shutdownがあったりなかったりするので、スペックだけ見てフレームワークを選ぶとこういうところで小さく困ってしまいます(&困ってます)。

あなたの知らないGo向けツール

この中では、interfaceの型を調べてくれるinterfacerが気になりました。Go言語のinterfaceではいつも迷ってましたが、自分だけではなかったと知ってちょっと安心しました。

無印枠

USBに挿すとLinuxマシンになるXtra-PC

「古いマシンを再生できる」が売りのようで、WinでもMacでもOK、本体HDは読み取りのみなのでデータも壊れず、HDがなくてもいい、だそうです。

案外トラブルシューティングに便利かもしれないと思ってしまいました。

時間は現実ではない: あらゆる事象は同時に生起している

日本語でも出回っているニュースですね。タイトル前半のおかげでSEO的にバズってますが、個人的にはタイトルの後半に力点があるように思えます。

英語圏のプログラミング言語擬人化

Webチームのtakanekoさん情報です。これはもう説明するだけ野暮というものでしょう。

takanekoさんによるともともとDHHが以下をリツィートしたのがバズったきっかけのようです。Ruby on Railsの擬人化だけ女子高生だからじゃないのかとのツッコミもありました。

今週は以上です。

[Rails5] Active Support Core ExtensionsのString#acts_like_string?

$
0
0

こんにちは、hachi8833です。Active Support探訪シリーズ、今回はString#acts_like_string?にお邪魔します。

今回のメソッド

条件

string/behavior.rb

class String
  # Enables more predictable duck-typing on String-like classes. See <tt>Object#acts_like?</tt>.
  def acts_like_string?
    true
  end
end

拍子抜けするような簡素なメソッドです。コメントによれば、Stringと似た振る舞いをするクラスで、duck-typingがより期待どおりに動作するためのメソッドであるとのことです。

Object#acts_like?を見てみましょう。devdocs.ioで探すのが早いですね。

object/acts_like.rb

class Object
  # A duck-type assistant method. For example, Active Support extends Date
  # to define an <tt>acts_like_date?</tt> method, and extends Time to define
  # <tt>acts_like_time?</tt>. As a result, we can do <tt>x.acts_like?(:time)</tt> and
  # <tt>x.acts_like?(:date)</tt> to do duck-type-safe comparisons, since classes that
  # we want to act like Time simply need to define an <tt>acts_like_time?</tt> method.
  def acts_like?(duck)
    respond_to? :"acts_like_#{duck}?"
  end
end

大意: ダックタイピングのアシスタントメソッドです。
例: Active SupportではDateをextendして#acts_like_date?メソッドを定義し、Timeをextendして#acts_like_time?を定義しています。これにより、x.acts_like?(:time)<tt>x.acts_like?(:date)などのようなダックタイプ安全かどうかの比較を行えます。Timeとして振る舞って欲しいクラスにはacts_like_time?メソッドが定義されていて欲しいからです。

String#acts_like_string?はダックタイピングのためだったんですね。開発者が直接使うというより、Rails内部で使われることが多いのではないかととりあえず推測しました。

String#acts_like_string?を使ったダックタイピングがRailsの中でどのように使われているのか、ユースケースを掘り出してみましょう。

Railsでの用例

GitHubで検索してみると、activerecord/lib/active_record/sanitization.rbに一箇所だけ用例がありました。

コメントや途中のコードは適宜...で省略しています。

module ActiveRecord
  module Sanitization
    extend ActiveSupport::Concern
    module ClassMethods
      protected
        # Accepts an array or string of SQL conditions and sanitizes
        # them into a valid SQL fragment for a WHERE clause.
        #
        #   sanitize_sql_for_conditions(["name=? and group_id=?", "foo'bar", 4])
        #   # => "name='foo''bar' and group_id=4"
        ...
        alias :sanitize_sql :sanitize_sql_for_conditions
        alias :sanitize_conditions :sanitize_sql
        ...
        def replace_bind_variable(value, c = connection) # :nodoc:
          if ActiveRecord::Relation === value
            value.to_sql
          else
            quote_bound_value(value, c)
          end
        end
        ...
        def quote_bound_value(value, c = connection) # :nodoc:
          if value.respond_to?(:map) && !value.acts_like?(:string)
            if value.respond_to?(:empty?) && value.empty?
              c.quote(nil)
            else
              value.map { |v| c.quote(v) }.join(",")
            end
          else
            c.quote(value)
          end
        end
        ...
    end
    ...
  end
end

いつの間にかActive Recordのprotectedなモジュールにまで足を踏み入れてしまいました。

大意:
# SQL条件の配列または文字列を受け取り、サニタイズして
# WHERE句で使える有効なSQLフラグメントにする
#
# sanitize_sql_for_conditions([“name=? and group_id=?”, “foo’bar”, 4])
# # => “name=’foo”bar’ and group_id=4″

#sanitize_sql_for_conditions->#sanitize_sql_array->#replace_bind_variable->#quote_bound_valueが呼び出され、その中で以下の文があります。

          if value.respond_to?(:map) && !value.acts_like?(:string)

文字列でないかどうかの判定に使っているようですが、正直これだけだとありがたみがピンときませんでした。

acts_like?の使い道

acts_like?はどんなふうに使うことが想定されているのでしょうか。

Object#acts_like?(ActiveSupport)という記事を見かけたので、以下に引用します。

  • 個人的には、respond_to?で単にあるメソッドがあるか調べるのに比べて、コードの意図が明確になると思います。
    Object#acts_like?(ActiveSupport)より

当初はRails内部での利用が主なのかなと推測しましたが、自分のクラスにacts_like?を独自に実装して、同じ流儀で判定できるようにすることが想定されているのかもしれないと思いました。

参考

morimorihogeさんのサジェスチョン

acts_like?の利用法について、BPS Webチーム部長のmorimorihogeの知恵をお借りしましたので自分なりに再構成しました。以下はコードからわかる範囲での見解に基づいていますのでご了承ください。


#acts_like?については、インターフェースのチェックという意味合いが考えられます。たとえば#respond_to?だと個別のメソッドの存在確認しかできないので、StringならStringとしてのインターフェース集合があるかどうかをチェックするのによさそうです。

メソッドの存在確認を#respond_to?で行おうとすると、たとえば#performのような具体的でないメソッド名はクラスによって動作が異なってしまう可能性があるので、振る舞いまでは#respond_to?などでは調べきれません。

ひとつの方法ですが、(Railsではなく)RubyのModule#include?を使ってモジュールの有無をチェックする方がより確実かもしれません。

module A
end
class B
  include A
end
class C < B
end
B.include?(A)   #=> true
C.include?(A)   #=> true
A.include?(A)   #=> false

ただし、モジュールという概念に収まりきれないような横断的な機能についてインターフェースをチェックするのであれば、#acts_like?のような方法が役に立つこともあるかもしれません。

なお、behavior.rbのblameは2008-2009年とだいぶ昔です。当時のプルリクbabbc15@ManfredFixを見ると、先ほどの#quote_bound_valueはもともとactiverecord/lib/active_record/base.rbにあったんですね。

quote_bound_value

おまけ

関連記事

Ruby 2.4.0がリリースされました!

$
0
0

こんにちは、hachi8833です。

おとといの週刊Railsウォッチのあと、Ruby 2.4.0が公開されました。新バージョンリリースをクリスマスに行うというRubyの習慣は今回も守られましたね。

Ruby 2.4.0 released

新機能

Ruby 2.4のいくつかの新機能は既に週刊Railsウォッチでお伝えしているので、未紹介の機能からいくつかリストアップします。

もう少し詳しい更新情報についてはruby/Newsをご覧ください。いずれ翻訳されるようです(追伸: その後翻訳されました)。

2.3から2.4の比較を見ると、コミットが多すぎて最初の250件しか表示されていませんね。

2.4のインストール

rbenvをお使いの方は、以下を参考に2.4をインストールしてください。

今回は以下のコマンドでインストールしました。

cd ~/.rbenv/plugins/ruby-build/
git pull            # ruby-buildを更新
rbenv install 2.4.0 # 2.4.0をインストール(バージョン確認は省略)
rbenv global 2.4.0  # デフォルトに設定(お好みで)

Rails 5.0.1 + Ruby 2.4で動かしてみる

Ruby 2.4にアップグレードすると、これまたリリース間もないRails 5.0.1rails newしたときにActive Support周りで若干deprecationの警告が表示されます。

deprecation warning

警告が出ている箇所のコードを見てみましょう。

  • ruby/2.4.0/gems/activesupport-5.0.1/lib/active_support/xml_mini.rb
      # No need to map these on Ruby 2.4+
      TYPE_NAMES["Fixnum"] = "integer" unless Fixnum == Integer
      TYPE_NAMES["Bignum"] = "integer" unless Bignum == Integer
  • ruby/2.4.0/gems/activesupport-5.0.1/lib/active_support/core_ext/numeric/conversions.rb
# Ruby 2.4+ unifies Fixnum & Bignum into Integer.
if Integer == Fixnum
  Integer.prepend ActiveSupport::NumericWithFormat
else
  Fixnum.prepend ActiveSupport::NumericWithFormat
  Bignum.prepend ActiveSupport::NumericWithFormat
end
Float.prepend ActiveSupport::NumericWithFormat
BigDecimal.prepend ActiveSupport::NumericWithFormat

Rails5.0.1側でも2.4の受け入れ準備ができていることがわかります。

関連記事

[Rails5] Active Support Core ExtensionsのString#pluralize

$
0
0

こんにちは、hachi8833です。2016年度最後の記事はActive Support探訪シリーズです。

今回はString#pluralizeにお邪魔します。

先日Railsが5.0.1になりましたので今後5.0.1を使うことにしました。

今回のメソッド

条件

  • Railsバージョン: 5-0-stable(執筆時点では5.0.1)
  • Rubyバージョン: 2.4.0

String#pluralizeについて

ご存じのとおり、RailsではMVCのクラス名やファイル名などで英語の単数形<=>複数形などの変換が多用されています。scaffold`コマンドを使ってモデル/ビュー/コントローラを生成すると、たとえば次のようになります。

要素 単複 クラス名の例(キャメルケース) ファイル名の例(スネークケース)
コントローラ 複数形 AdminUsers admin_users.rb
モデル 単数形 AdminUser admin_user.rb
ビュー 複数形 AdminUsers admin_users.erb

こうした変換に使われているのが、String#pluralizeなどの各種活用形メソッドです。

詳しくはRailsガイド: 活用形をご覧ください。

今回は、String#pluralizeで不規則な活用(octopus->octopiなど)が行われる部分に絞って追ってみたいと思います。きっとどこかに不規則活用表に相当するデータを持っているはずなので、それを見つけるところまでやってみます。

string/inflections.rb

例によってコメント行は省略しています。

require 'active_support/inflector/methods'
require 'active_support/inflector/transliterate'

class String
  def pluralize(count = nil, locale = :en)
    locale = count if count.is_a?(Symbol)
    if count == 1
      self.dup
    else
      ActiveSupport::Inflector.pluralize(self, locale)
    end
  end

  def singularize(locale = :en)
    ActiveSupport::Inflector.singularize(self, locale)
  end

  def constantize
    ActiveSupport::Inflector.constantize(self)
  end

  def safe_constantize
    ActiveSupport::Inflector.safe_constantize(self)
  end

  def camelize(first_letter = :upper)
    case first_letter
    when :upper
      ActiveSupport::Inflector.camelize(self, true)
    when :lower
      ActiveSupport::Inflector.camelize(self, false)
    end
  end
  alias_method :camelcase, :camelize

  def titleize
    ActiveSupport::Inflector.titleize(self)
  end
  alias_method :titlecase, :titleize

  def underscore
    ActiveSupport::Inflector.underscore(self)
  end

  def dasherize
    ActiveSupport::Inflector.dasherize(self)
  end

  def demodulize
    ActiveSupport::Inflector.demodulize(self)
  end

  def deconstantize
    ActiveSupport::Inflector.deconstantize(self)
  end


  def parameterize(sep = :unused, separator: '-', preserve_case: false)
    unless sep == :unused
      ActiveSupport::Deprecation.warn("Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '#{sep}'` instead.")
      separator = sep
    end
    ActiveSupport::Inflector.parameterize(self, separator: separator, preserve_case: preserve_case)
  end

  def tableize
    ActiveSupport::Inflector.tableize(self)
  end

  def classify
    ActiveSupport::Inflector.classify(self)
  end

  def humanize(options = {})
    ActiveSupport::Inflector.humanize(self, options)
  end

  def upcase_first
    ActiveSupport::Inflector.upcase_first(self)
  end

  def foreign_key(separate_class_name_and_id_with_underscore = true)
    ActiveSupport::Inflector.foreign_key(self, separate_class_name_and_id_with_underscore)
  end
end

一読してわかるのは、String#pluralizeを含むほとんどのメソッドがActiveSupport::Inflectorに丸投げされていることです。いわゆる委譲(delegate)ですね。

pluralizesingularizeはロケールによって変える必要があるので、ロケールを引数として渡せるようになっています。デフォルトのロケールはenです。

履歴をざっと見てみると、もともとはcore_ext/string/inflections.rbにあったコードが、7db0b0: Make ActiveSupport::Inflector locale aware and multilingualのマルチリンガル対応などの改修によってinflector/inflections.rbに順次移動したようです。

ActiveSupport::Inflector

というわけで、本体であるActiveSupport::Inflectorを見てみます。#pluralizeはどこでしょうか。

require 'concurrent/map'
require 'active_support/core_ext/array/prepend_and_append'
require 'active_support/i18n'

module ActiveSupport
  module Inflector
    extend self

    class Inflections
      @__instance__ = Concurrent::Map.new

      class Uncountables < Array
        def initialize
          @regex_array = []
          super
        end

        def delete(entry)
          super entry
          @regex_array.delete(to_regex(entry))
        end

        def <<(*word)
          add(word)
        end

        def add(words)
          self.concat(words.flatten.map(&:downcase))
          @regex_array += self.map {|word|  to_regex(word) }
          self
        end

        def uncountable?(str)
          @regex_array.any? { |regex| regex === str }
        end

        private
          def to_regex(string)
            /\b#{::Regexp.escape(string)}\Z/i
          end
      end

      def self.instance(locale = :en)
        @__instance__[locale] ||= new
      end

      attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex

      def initialize
        @plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], Uncountables.new, [], {}, /(?=a)b/
      end

      # Private, for the test suite.
      def initialize_dup(orig) # :nodoc:
        %w(plurals singulars uncountables humans acronyms acronym_regex).each do |scope|
          instance_variable_set("@#{scope}", orig.send(scope).dup)
        end
      end

      def acronym(word)
        @acronyms[word.downcase] = word
        @acronym_regex = /#{@acronyms.values.join("|")}/
      end

      def plural(rule, replacement)
        @uncountables.delete(rule) if rule.is_a?(String)
        @uncountables.delete(replacement)
        @plurals.prepend([rule, replacement])
      end

      def singular(rule, replacement)
        @uncountables.delete(rule) if rule.is_a?(String)
        @uncountables.delete(replacement)
        @singulars.prepend([rule, replacement])
      end

      def irregular(singular, plural)
        @uncountables.delete(singular)
        @uncountables.delete(plural)

        s0 = singular[0]
        srest = singular[1..-1]

        p0 = plural[0]
        prest = plural[1..-1]

        if s0.upcase == p0.upcase
          plural(/(#{s0})#{srest}$/i, '\1' + prest)
          plural(/(#{p0})#{prest}$/i, '\1' + prest)

          singular(/(#{s0})#{srest}$/i, '\1' + srest)
          singular(/(#{p0})#{prest}$/i, '\1' + srest)
        else
          plural(/#{s0.upcase}(?i)#{srest}$/,   p0.upcase   + prest)
          plural(/#{s0.downcase}(?i)#{srest}$/, p0.downcase + prest)
          plural(/#{p0.upcase}(?i)#{prest}$/,   p0.upcase   + prest)
          plural(/#{p0.downcase}(?i)#{prest}$/, p0.downcase + prest)

          singular(/#{s0.upcase}(?i)#{srest}$/,   s0.upcase   + srest)
          singular(/#{s0.downcase}(?i)#{srest}$/, s0.downcase + srest)
          singular(/#{p0.upcase}(?i)#{prest}$/,   s0.upcase   + srest)
          singular(/#{p0.downcase}(?i)#{prest}$/, s0.downcase + srest)
        end
      end

      def uncountable(*words)
        @uncountables.add(words)
      end

      def human(rule, replacement)
        @humans.prepend([rule, replacement])
      end

      def clear(scope = :all)
        case scope
          when :all
            @plurals, @singulars, @uncountables, @humans = [], [], Uncountables.new, []
          else
            instance_variable_set "@#{scope}", []
        end
      end
    end

    def inflections(locale = :en)
      if block_given?
        yield Inflections.instance(locale)
      else
        Inflections.instance(locale)
      end
    end
  end
end
# Specifies a new pluralization rule and its replacement.
# Specifies a new singularization rule and its replacement.

といったコメントが目につきます。

しかしinflector/inflections.rbにはInflectorモジュールはあるものの、意外にも#pluralizeメソッドが見当たりません。

inflector/methods.rbのInflectorモジュール

探してみたところ、#pluralizeはinflector/methods.rbのInflectorモジュールにありました。長くなるので今度はメソッド定義だけ取り出してみました。

def pluralize(word, locale = :en)
  apply_inflections(word, inflections(locale).plurals)
end

def singularize(word, locale = :en)
  apply_inflections(word, inflections(locale).singulars)
end

今度は#apply_inflectionsです。これは同じinflector/methods.rbにありました。#pluralize#apply_inflectionsもInflectorモジュールのメソッドです。

def apply_inflections(word, rules)
  result = word.to_s.dup

  if word.empty? || inflections.uncountables.uncountable?(result)
    result
  else
    rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
    result
  end
end

inflections(locale).pluralsinflections(locale).singularsを引数として渡すことで動作を変更するようになっています。

では引数のinflections(locale)はどこにあるのでしょうか。探してみるとinflector/inflections.rbのInflectorモジュールにありました。

def inflections(locale = :en)
  if block_given?
    yield Inflections.instance(locale)
  else
    Inflections.instance(locale)
  end
end

ブロックを渡すかどうかで若干動作が異なりますが、今度はInflections.instanceを呼び出しています。だんだん不安になってきました。Inflections.instanceはどこにあるのでしょうか。

inflections.rb

活用表に相当するデータは、結局active_support/inflections.rbのActiveSupportモジュールで見つかりました。

module ActiveSupport
  Inflector.inflections(:en) do |inflect|
    inflect.plural(/$/, "s")
    inflect.plural(/s$/i, "s")
    inflect.plural(/^(ax|test)is$/i, '\1es')
    inflect.plural(/(octop|vir)us$/i, '\1i')
    inflect.plural(/(octop|vir)i$/i, '\1i'
...
    inflect.singular(/s$/i, "")
    inflect.singular(/(ss)$/i, '\1')
    inflect.singular(/(n)ews$/i, '\1ews')
    inflect.singular(/([ti])a$/i, '\1um')
    inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i, '\1sis')
...
    inflect.irregular("person", "people")
    inflect.irregular("man", "men")
    inflect.irregular("child", "children")
...
    inflect.uncountable(%w(equipment information rice money species series fish sheep jeans police))
  end
end

英語の宿命である不規則活用は機械的な活用形を拒むので、こうした対応表がどこかで必要になります。

senpaiのような新語や外来語は、不規則活用表になければ当然規則活用が適用されることになります。

#pluralize#singularizeはRailsで必要な活用変化をカバーするのが目的と考えるのが自然なので、person<->peopleなど、Rails開発で使われる可能性のある語彙に絞っていると理解しました。英語のあらゆる不規則活用表をカバーするのは現実的ではなさそうですし、何より不規則活用表を進んでメンテする人がいなさそうです。

#pluralizeがこんなにたらい回しにされているあたりにRailsの歴史を感じてしまいました。

活用形のカスタマイズ方法

活用形周りをカスタマイズする方法については、以下の外部記事をご覧ください。

記事中にもあるとおり、Railsのconfig/initializers/inflections.rbに追記すればよいです。以下にRails 5のinflections.rbをざっくり訳して引用します。

# このファイルを変更後サーバーを必ず再起動すること
# 以下のフォーマットで新しい活用形ルールを追加できる
# 活用形はロケールに依存するので、好きなだけロケールごとの活用ルールを設定できる
# ちなみに以下の例はすべてデフォルトで有効になっている(のでここで足さなくてよい)
# ActiveSupport::Inflector.inflections(:en) do |inflect|
#   inflect.plural /^(ox)$/i, '\1en'
#   inflect.singular /^(ox)en/i, '\1'
#   inflect.irregular 'person', 'people'
#   inflect.uncountable %w( fish sheep )
# end

# 以下の活用ルールはサポートされているがデフォルトでは有効になっていない
# ActiveSupport::Inflector.inflections(:en) do |inflect|
#   inflect.acronym 'RESTful'
# end

サンプル部分をコードハイライトしたかったので取り出してみました。

ActiveSupport::Inflector.inflections(:en) do |inflect|
  inflect.plural /^(ox)$/i, '\1en'
  inflect.singular /^(ox)en/i, '\1'
  inflect.irregular 'person', 'people'
  inflect.uncountable %w( fish sheep )
end
ActiveSupport::Inflector.inflections(:en) do |inflect|
  inflect.acronym 'RESTful'
end

最後に

ActiveSupport::Inflectorには便利メソッドが多いので、次回は他のメソッドも概観してみようと思います。

関連記事

[Rails5] Active Support::Inflectorの便利な活用形メソッド群

$
0
0

こんにちは、hachi8833です。

Active Support探訪シリーズは、前回のString#pluralizeで扱ったActiveSupport::Inflectorの便利メソッド群を概観します。

今回のメソッド

今回は、初めてcore extension以外のメソッドにお邪魔することになります。

条件

  • Railsバージョン: 5-0-stable(執筆時点では5.0.1)
  • Rubyバージョン: 2.4.0

ActiveSupport::Inflector

Rails 5.0のActiveSupport::Inflectorメソッド一覧

Rails 5.0のActiveSupport::Inflectorには以下の活用形関連メソッドがあります。ActiveSupport::Inflectorのメソッド群はやや雑然としているので、対になっているメソッドなどをグループ化してみました。

  • 英単語の単数形⇔複数形変換
    • #pluralize
    • #singularize
  • 英語の序数
    • #ordinal
    • #ordinalize
  • アルファベットの大文字小文字変換
    • #upcase_first
    • #humanize
    • #titleize
  • 記号の変換
    • #dasherize
  • 定数名・モジュール名の変換
    • #constantize
    • #safe_constantize
    • #deconstantize
    • #demodulize
  • キャメルケース⇔スネークケース変換
    • #camelize
    • #underscore
  • クラス名/モジュール名/テーブル名の変換
    • #classify
    • #tableize
  • パラメータ化
    • #parameterize
  • 活用形のカスタマイズ
    • ActiveSupport::Inflector.inflections
  • 近い英文字への変換
    • ActiveSupport::Inflector.transliterate
  • 外部キー名への変換
    • #foreign_key

多くはStringクラスですが、数値に関連するメソッドもあります。

最初に、各ケースについて以下に簡単にまとめておきます。

スネークケース
active_record」のように小文字のみの単語をアンダースコアで結合(Railsではメソッド名などで使用)
キャメルケース(upper camel case)
ActiveRecord」のように大文字で始まる単語を結合(Railsではクラス名などで使用)
キャメルケース(lower camel case)
activeRecord」のように先頭のみ小文字、残りは大文字で始まる単語を結合

英単語の単数形⇔複数形変換

#pluralize
(Stringクラス) 英語の単数形を複数形に変換します
#singularize
(Stringクラス) 英語の複数形を単数形に変換します

活用形はすべてを網羅しているわけではないのでご注意ください。

#pluralize
'post'.pluralize             # => "posts"
'octopus'.pluralize          # => "octopi"
'sheep'.pluralize            # => "sheep"
'words'.pluralize            # => "words"
'the blue mailman'.pluralize # => "the blue mailmen"
'CamelOctopus'.pluralize     # => "CamelOctopi"
'apple'.pluralize(1)         # => "apple"
'apple'.pluralize(2)         # => "apples"
'ley'.pluralize(:es)         # => "leyes"
'ley'.pluralize(1, :es)      # => "ley"

# singularize
'posts'.singularize            # => "post"
'octopi'.singularize           # => "octopus"
'sheep'.singularize            # => "sheep"
'word'.singularize             # => "word"
'the blue mailmen'.singularize # => "the blue mailman"
'CamelOctopi'.singularize      # => "CamelOctopus"
'leyes'.singularize(:es)       # => "ley"

英語の序数

#ordinal
(Integerクラス)数字に対応する英語の序数(ordinal numbers)の接尾語を返す
#ordinalize
(Integerクラス)数字に英語の序数(ordinal numbers)を追加して返す
# ordinal
1.ordinal     # => "st"
2.ordinal     # => "nd"
1002.ordinal  # => "nd"
1003.ordinal  # => "rd"
-11.ordinal   # => "th"
-1001.ordinal # => "st"

# ordinalize
1.ordinalize     # => "1st"
2.ordinalize     # => "2nd"
1002.ordinalize  # => "1002nd"
1003.ordinalize  # => "1003rd"
-11.ordinalize   # => "-11th"
-1001.ordinalize # => "-1001st"

これらのみIntegerクラスを拡張しています。

アルファベットの大文字小文字変換

#upcase_first
(Stringクラス) 最初の文字のみを大文字に変換
#humanize
(Stringクラス) アンダースコアをスペースにして先頭を大文字にするなど、英文「らしく」整形する(末尾の_idは除去する)
#titleize
(Stringクラス) 小文字の英語フレーズを英語のタイトル「らしく」整形する(Rails内部では使っていない)
# upcase_first
'what a Lovely Day'.upcase_first # => "What a Lovely Day"
'w'.upcase_first                 # => "W"
''.upcase_first                  # => ""

# humanize
'employee_salary'.humanize              # => "Employee salary"
'author_id'.humanize                    # => "Author"
'author_id'.humanize(capitalize: false) # => "author"
'_id'.humanize                          # => "Id"

# titleiize
'man from the boondocks'.titleize # => "Man From The Boondocks"
'x-men: the last stand'.titleize  # => "X Men: The Last Stand"

そして#titleize内部で呼ばれている#humanizeソースを見ると、一律単語の先頭を大文字にしているようです。

追伸: titleizeは英語のタイトルスタイルそのものではない

現実の英文におけるタイトルのスタイルは、「inやtoなどの前置詞は大文字にしない」などルールが複雑で、かつ出版社や新聞社によって異なるので機械的な処理が困難です。

#titleizeに限らず、こうしたメソッドはコーディング支援のためのものであり、実用的な英文に変換するものではないと考えるほうがよいでしょう。

記号の変換

#dasherize
(Stringクラス) アンダースコア_をダッシュ-に変換
'puni_puni'.dasherize # => "puni-puni"

なお、ここでいうダッシュはhyphen-dash(U+002D)(要するにマイナス/ハイフン/ダッシュのどれにも使われている例のASCII文字)のことです。

定数名・モジュール名の変換

#constantize
(Stringクラス) 指定の文字列を定数に変換する(元がキャメルケースかつ該当の定数が存在する場合以外はエラー)
#safe_constantize
(Stringクラス) constantizeと同様だが、無効な文字列の場合にはnilを返す
#deconstantize
(Stringクラス) 定数名(文字列)の最も右の要素を除去する
#demodulize
(Stringクラス) モジュール名の最も右の要素を返す
# constantize
'Module'.constantize  # => Module
'Class'.constantize   # => Class
'blargle'.constantize # => NameError: wrong constant name blargle

# safe_constantize
'Module'.safe_constantize  # => Module
'Class'.safe_constantize   # => Class
'blargle'.safe_constantize # => nil

# deconstantize
'Net::HTTP'.deconstantize   # => "Net"
'::Net::HTTP'.deconstantize # => "::Net"
'String'.deconstantize      # => ""
'::String'.deconstantize    # => ""
''.deconstantize            # => ""

# demodulize
'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections"
'Inflections'.demodulize                                       # => "Inflections"
'::Inflections'.demodulize                                     # => "Inflections"
''.demodulize

#constantizeすると文字列ではなく定数オブジェクトになりますので、#deconstantizeで逆変換できません。

#deconstantize#constantizeの逆操作ではないのがなかなか紛らわしいですね。

さらに、#demodulizeはモジュール名ではなく名前空間の方を削除しています。

キャメルケース⇔スネークケース変換

#camelize
(Stringクラス) キャメルケースに変換(デフォルトは:lower)、かつスラッシュ/をコロン2つ::に置き換える
#underscore
(Stringクラス) #camelizeの逆操作
# camelize
'active_record'.camelize                # => "ActiveRecord"
'active_record'.camelize(:lower)        # => "activeRecord"
'active_record/errors'.camelize         # => "ActiveRecord::Errors"
'active_record/errors'.camelize(:lower) # => "activeRecord::Errors"

# underscore
'ActiveModel'.underscore         # => "active_model"
'ActiveModel::Errors'.underscore # => "active_model/errors"

#camelize#underscoreが互いに逆操作というのが名前からはちょっとわかりにくいですね。

クラス名/モジュール名/テーブル名の変換

テーブル名はデータベースのテーブル名を指します。

classify
(Stringクラス) テーブル名(小文字、ハイフン)をクラス名に変換する
tableize
(Stringクラス) キャメルケースのクラス名(や単数形のスネークケース)をテーブル名に変換する
# classify
'ham_and_eggs'.classify # => "HamAndEgg"
'posts'.classify        # => "Post"

# tableize
'RawScaledScorer'.tableize # => "raw_scaled_scorers"
'ham_and_egg'.tableize     # => "ham_and_eggs"
'fancyCategory'.tableize   # => "fancy_categories"

#tableize#classifyの逆操作にもなっています。この手のメソッド名はスペルを間違えそうで冷や冷やします。

パラメータ化

#parameterize
(Stringクラス) 文字列をURLらしく変換する(記号の除去やスペース->ハイフン置き換え)

ただし全角文字は記号と同様除去されてしまうので、Base64#encode64などで別途変換する必要があります。

活用形のカスタマイズ

Inflector.inflections
(ActiveSupport::Inflector) 登録されていない活用形を追加する

rails generateでコントローラやモデルなどを生成するときの単語が惜しくも活用形に対応していない場合などに、config/initializers/inflections.rbに以下のように単語の活用を追加します。カスタマイズはロケールごとに行えます。

ActiveSupport::Inflector.inflections(:es) do |inflect|
  inflect.irregular 'ley', 'leyes'
end

なお、このメソッドはActiveSupport::Inflectorクラスのシングルインスタンスを生成します。

# File activesupport/lib/active_support/inflector/inflections.rb, line 234
def inflections(locale = :en)
  if block_given?
    yield Inflections.instance(locale)
  else
    Inflections.instance(locale)
  end
end

近い英文字への変換

Inflector.transliterate
(ActiveSupport::Inflector)ø、ñ、é、ß、лなどの英語にないアルファベットを近い英語に変換する

これはStringクラスではなく、ActiveSupport::Inflectorモジュールです。

ActiveSupport::Inflector.transliterate('Ærøskøbing') # => "AEroskobing"

この変換は以下のようにロケールごとにカスタマイズもできます。

# locales/de.ymlで指定する場合
i18n:
  transliterate:
    rule:
      ü: "ue"
      ö: "oe"
# Rubyコードで設定する場合
I18n.backend.store_translations(:de, i18n: {
  transliterate: {
    rule: {
      'ü' => 'ue',
      'ö' => 'oe'
    }
  }
})

外部キー名への変換

#foreign_key
(Stringクラス) クラス名を外部キー名に変換する(小文字化、アンダースコア除去、”_id” の追加)
'Message'.foreign_key        # => "message_id"
'Message'.foreign_key(false) # => "messageid"
'Admin::Post'.foreign_key    # => "post_id"

String#foreign_keyのソースをちょっとのぞいてみると、先に紹介したString#underscoreString#demodulizeを組み合わせて実現しています。

# File activesupport/lib/active_support/inflector/methods.rb, line 235
def foreign_key(class_name, separate_class_name_and_id_with_underscore = true)
  underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id")
end

参考: Rubyの活用形関連メソッド

以下はRailsではなくRubyのメソッドですが、上のActiveSupport::Inflectorの内部で呼ばれているなど、Railsとも密接に関連しています。それぞれ、!が末尾につく破壊的メソッドもあります。

これらのメソッドはRuby 2.4で大きく拡張され、トルコ語など多くのアルファベット系言語でUnicode仕様に準じた変換に対応したのは記憶に新しいところです。

なお、String#casecmpはまだUnicode仕様の変換に対応していません。

関連記事


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

$
0
0

こんにちは、hachi8833です。

Rails 3のform_forメソッドのAPIドキュメントをすべて翻訳いたしました。
Rails 4および5のform_forメソッドのAPIドキュメント翻訳については以下をご覧ください。

概要

Rails 3、4、5のform_forメソッドを最新の公式APIドキュメントでチェックしてみたところ、現時点でのRails 4とRails 5向けform_forメソッドのAPIドキュメントは完全に一致していました。また、Rails 3とRails 4/5のドキュメントの差分もさほどありませんでした。

なお、form_forform_tagは今後form_withで一元的に利用できるようになるとのことです。

form_for (Rails 3)

#API呼び出し(Rails 3、4、5共通)
form_for(record, options = {}, &block)

フィールドの値をユーザーに問い合わせるための特定のモデルオブジェクトを元に、フォームとスコープを作成します。

Railsのform_forを以下のように使って、簡潔な「リソース指向フォーム」を生成できます。

<%= form_for @offer do |f| %>
  <%= f.label :version, 'Version' %>:
  <%= f.text_field :version %><br />
  <%= f.label :author, 'Author' %>:
  <%= f.text_field :author %><br />
  <%= f.submit %>
<% end %>

form_forは、レコードのイントロスペクションに基づいたRESTfulなフォームパラメータを生成できます。しかし、この動作を理解するにはform_forの基本となる別の一般的な用法についてまず知っておく必要があります。

一般的な#form_for

form_forを一般的な方法で呼び出すと、モデルについてのフォームビルダが生成されます。

<%= form_for :person do |f| %>
  First name: <%= f.text_field :first_name %><br />
  Last name : <%= f.text_field :last_name %><br />
  Biography : <%= f.text_area :biography %><br />
  Admin?    : <%= f.check_box :admin %><br />
  <%= f.submit %>
<% end %>

引数には、フォームで使うオブジェクトの名前をシンボルか文字列で指定します。

フォームビルダは、モデルを何らかの形で実行する通常のフォームヘルパとして動作します。

<%= f.text_field :first_name %>

上のコードは以下のように展開されます。

<%= text_field :person, :first_name %>

form_forの一番右(ブロックを除く)の引数はオプションハッシュであり、以下のオプションを利用できます。

:url
フォームの送信先URLです。ここにはurl_forlink_toに渡すのと同じフィールドを渡せます。名前付きルートを直接渡すこともできます。デフォルトは現在のアクションです。
:namespace
フォーム要素のid属性を一意にするための名前空間を指定します。名前空間の属性の前には、生成されたHTML idの後ろにアンダースコア_を付けたものが追加されます。
:html
フォームのタグにHTML属性を追加するのに使います。

form_forは排他的なスコープを作成しないことにご注意ください。排他的なスコープの作成は、単独のFormHelperのメソッドとFormTagHelperのメソッドを両方使えば可能です。次の例をご覧ください。

<%= form_for @person do |f| %>
  First name: <%= f.text_field :first_name %>
  Last name : <%= f.text_field :last_name %>
  Biography : <%= text_area :person, :biography %>
  Admin?    : <%= check_box_tag "person[admin]", @person.company.admin? %>
  <%= f.submit %>
<% end %>

上の方法は、オブジェクトをベースとする設計になっているFormOptionHelperやDateHelperのメソッドでも使えます(FormOptionHelper#collection_selecやActionView::Helpers::DateHelper#datetime_select)。

リソース指向のスタイル

前述のとおり、#form_for呼び出しは手動で設定できるほか、命名規則や名前付きルーティングに基いてリソースを自動識別することもできます。現在の#form_forは、手動よりも自動化された方法で使うことが推奨されています。

たとえば、編集したい既存のレコード@postがあるとします。

<%= form_for @post do |f| %>
  ...
<% end %>

上のコード例は、以下ようなコードと同等です。

<%= form_for @post,
             :as => :post,
             :url => post_path(@post),
             :method => :put,
             :html => { :class => "edit_post", :id => "edit_post_45" } do |f| %>
  ...
<% end %>

新規レコードの場合は以下のように書けます。

<%= form_for(Post.new) do |f| %>
  ...
<% end %>

上のコード例は、以下ようなコードと同等です。

<%= form_for @post,
             :as => :post,
             :url => posts_path,
             :html => { :class => "new_post", :id => "new_post" } do |f| %>
  ...
<% end %>

自動生成されるコードは、次のように上書きもできます。

<%= form_for(@post, :url => super_posts_path) do |f| %>
  ...
<% end %>

送信フォーマットも次のように指定できます。

<%= form_for(@post, :format => :json) do |f| %>
  ...
<% end %>

オブジェクトのパラメータを別の方法で表す必要がある場合は次のようにします。ここではPersonClientとして扱っています。

<%= form_for(@person, :as => :client) do |f| %>
  ...
<% end %>

admin_post_url:のような名前空間化されたルーティングの場合は次のように書けます。

<%= form_for([:admin, @post]) do |f| %>
 ...
<% end %>

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

<%= form_for([@document, @comment]) do |f| %>
 ...
<% end %>

なお、上のコードでは@document = Document.find(params[:id])および@comment = Comment.newが設定されていることが前提です。

メソッドを設定する

オプションハッシュ内に次のように書くと、HTTP verbの完全な配列をフォームに渡して強制的に使うように設定できます。

:method => (:get|:post|:put|:delete)

こうすると、GETPOST(これらはHTMLフォームでネイティブでサポートされます)でないverbが使われた場合に、フォームでPOSTが設定され、_methodと呼ばれる非表示のinputによって、サーバーが解釈できる期待どおりのverbが実行されます。

「控えめなJavaScript」によるAjax

オプションハッシュで以下を指定すると、いわゆる「控えめな(unobtrusive)JavaScript」のドライバでフォームの挙動を変更できます。

:remote => true

このオプションを指定した場合のデフォルトの挙動では、通常のPOSTではなくXMLHttpRequestがバックグラウンドで動作することが期待されますが、最終的な動作はJavaScriptドライバの実装によって決定されます。

仮にフォームのさまざまな要素をJavaScriptによってシリアライズした場合でも、フォームを受信する側(つまりサーバー)から見ると通常の送信と同じ挙動になり、すべての要素をparamsで受け取れます。

次のコード例をご覧ください。

<%= form_for(@post, :remote => true) do |f| %>
  ...
<% end %>

上のERBによって次のHTMLが生成されます。

<form action='http://www.example.com' method='post' data-remote='true'>
  <div style='margin:0;padding:0;display:inline'>
    <input name='_method' type='hidden' value='put' />
  </div>
  ...
</form>

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

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

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

<%= form_for(@post) do |f| %>
  <% f.fields_for(:comments, :include_id => false) do |cf| %>
    ...
  <% end %>
<% end %>

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

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

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

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

<%= render f %>

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

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

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

def labelled_form_for(record_or_name_or_array, *args, &block)
  options = args.extract_options!
  form_for(record_or_name_or_array, *(args << options.merge(:builder => LabellingFormBuilder)), &block)
end

モデルのインスタンスにフォームをアタッチする必要がない場合は、ActionView::Helpers::FormTagHelper#form_tagを参照してください。

外部リソースを使うフォーム

外部リソース(認証トークンの設定が必要な場合や、認証なしでフォームだけ表示したい場合などを含む)を扱うフォームをビルドする場合を考えてみましょう。何らかの支払い用ゲートウェイ番号にデータを送信しなければならず、フィールドの種類にも制限があるとします。

必要な認証トークンを渡すには、:authenticity_tokenオプションを使います。

<%= form_for @invoice,
             :url => external_url,
             :authenticity_token => 'external_token' do |f| %>
  ...
<% end %>

認証トークンを一切出力したくない場合は、単にfalseを渡します

<%= form_for @invoice,
             :url => external_url,
             :authenticity_token => false do |f| %>
  ...
<% end %>

関連記事

週刊Railsウォッチ(20170421)RailsConfが来週アリゾナで開催、コントローラを宣言的に書けるdecent_exposure gemほか

$
0
0

こんにちは、hachi8833です。

Google Cloud Platform(GCP)+Rubyの現在のステータス(RubyWeeklyより)

元記事はGoogle公式の技術ブログです。現時点では日本語版記事はありません。

Google翻訳APIもgem経由で使えるようになるということですね。

たとえばロガーであれば、以下のような感じで利用できます。

# https://cloudplatform.googleblog.com/2017/04/the-state-of-Ruby-on-Google-Cloud-Platform.html より
require "google/cloud/logging"

logging = Google::Cloud::Logging.new
logger = logging.logger "my_app_log", resource, env: :production

logger.info "Job started"
logger.info { "Job started" }
logger.debug?

GCPのRubyではGemfileとbundlerが使えるので、RailsアプリSinatraアプリをデプロイした人も増え始めているようです。

インストール時にコンパイラが走るnative extension系のgemがどうなるのかが気になってましたが、以下の記事を見るとnokogiriなども使えていますね。

一同で元記事をチェックしながら、「当初GAE+RubyはAWS Lambdaに似ているかなと思ったけど、AWSのECSElastic Beanstalkの方が近いかもね」「Lambdaに近いのはGoogle Cloud Functionかも」といった話題になりました。

GCPも1年間使える$300の無料クレジットでお試しできるようにするなど差別化を図っています。インスタンスやストレージをうまく絞り込めば無料で使い続けることもできるようです。当分目が離せませんね。

RailsConfについて

上の記事でも触れられていますが、来週4月25日〜27日にかけて米国アリゾナ州で開催予定のRailsConfでもGoogleがGold Sponsorに名を連ねています。


https://railsconf.com/より

しかしチケット$900か…プロで固めた高級保育所サービスとかもこみこみだそうですが。

RailsConfのスケジュールより

見たところ、日本のRubyKaigi的な開発者向けの要素と、RubyBusiness Conference的なビジネス向けの要素と、コミュニティの親睦を深めるカンファレンス的な要素を全部投入したような趣ですね。


https://railsconf.com/scheduleより

これだけ同時開催されるイベントが多いと、全部を見て回るのは無理ですね。

目についたものだけざっとピックアップしてみました。

どうでしょうか?行ってみたくなりませんか?

dockrails: DockerでRails環境を自動構築するCLI

asciicast

なにしろ13 commits 0 issueという初々しすぎるソフトウェアなので、「Dockerの勉強用にならいいかも」「自分で一からDockerfileやdocker-compose.ymlを作る代わりに、これに作らせてみて設定を調べてみるとか」といった意見がありました。

そっとコードを開いてみると、f.writeの嵐でした。

# https://github.com/gmontard/dockrails/blob/master/lib/dockrails/generate.rb より
...
        if @config[:redis]
          f.write "\n redis:\n"
          f.write "   image: redis\n"
          f.write "   volumes:\n"
          f.write "     - ./data/redis:/data\n"
          f.write "   ports:\n"
          f.write "     - \"6379:6379\"\n"
        end

さすがにこれはヒアドキュメントにする方がよいと思います。

decent_exposure: コントローラを宣言的に書けるgem

「同じインスタンス変数を何度も書かなくて済む」のが売りです。コントローラが何だかRSpecっぽく見えてきました。

# https://github.com/hashrocket/decent_exposure より
class ThingsController < ApplicationController
  expose :things, ->{ Thing.all }
  expose :thing

  def create
    if thing.save
      redirect_to thing_path(thing)
    else
      render :new
    end
  end
...

「わざわざgemインストールするのもどうかなー」「でも新規プロジェクトなら最初に記法を統一するのにいいかも」といった声がありました。使い所によっては役に立ちそうです。

zhong: Radisベースの分散cron(RubyWeeklyより)

「Useful, reliable distributed cron」と名乗っています。

一同でチェックしてみましたが、「wheneverしたいなら普通にcrondでいいので、worker processをわざわざ起動する監視するソフトをインストールするのはちょっとねー」「また『監視ソフトを監視するソフトを監視…』の無限後退?」という意見が出たり、cronとanacronの違いについて話題になったりしました。

さらに「これならsidekiq-cronの方がよくない?」「監視する要素も少なくて済むし、sidekiqでログ集約できるし」

ということで、試すならsidekiq-cronをどうぞ。

goruby: goで実装されたRuby(RubyWeeklyより)

やっぱりこういう名前にするしかないんでしょうね。以前からこの手の実装をずっと探してたのですが、どうにも中途半端なものしか見つかりません。

これもチェックリストはまだまだ埋まっていませんね。特にループ系が未実装なのが痛いです。

それでもgirbというirbはかろうじて試せますが、動かしてみたところ、ちょっと油断するとガンガン落ちます。

先はかなり長そうですが、C言語でRubyをメンテナンスするコミッターの苦労を考えれば、ずっとよさそうな気はしています。むしろjRubyをGoに移植する方がよいのではないかと思いました。

GitHub::DS: GitHub内部のActiveRecord+SQLライブラリ(RubyWeeklyより)

Key-Valueストア(KVS)やMySQLなどへのインターフェースをwrapするライブラリです。GitHubで広く使われており、実績を積んでいるとのことです。

# Create new instance using ActiveRecord's default connection.
kv = GitHub::KV.new { ActiveRecord::Base.connection }

# Get a key.
pp kv.get("foo")
#<GitHub::Result:0x3fd88cd3ea9c value: nil>
# Select, insert, update, delete or whatever you need...
GitHub::SQL.results <<-SQL
  SELECT * FROM example_key_values
SQL

GitHub::SQL.run <<-SQL, key: "foo", value: "bar"
  INSERT INTO example_key_values (`key`, `value`)
  VALUES (:key, :value)
SQL
...
p sql.results

RubyでKVSしたいときに検討してみるとよいかもしれません。

Redditで自分のパスワードを自分でクラックするはめになったお(RubyWeeklyより)

涙ぐましい努力を払って、失われたパスワードを復元するまでの過程を記事にしています。ちゃんと復元できた模様。

「これ今日のLGTMにしよっと」と一同の注目を浴びた画像↓

via GIPHY

Railsフォームオブジェクトのバリデーションを再利用する(RubyFlowより)

著者は名前から明らかにポーランド系ですね。

class ValidateVoucher
  include ActiveModel::Model

  attr_accessor :voucher, :product_id

  validate :voucher_presence

  private

  def voucher_presence
    errors.add(:voucher, :invalid) unless Voucher.where(code: voucher, product_id: product_id).exists?
  end
end

一読して「何でActiveModel::Modelincludeするかなー?」「普通にActiveModel::Validator継承すればいいんじゃね?」「Railsガイドにもちゃんと載ってるでよ」と一斉にツッコミを喰らいました。

というわけで、Railsガイドからまともなコードを引用いたします↓。

class MyValidator < ActiveModel::Validator
  def validate(record)
    unless record.name.starts_with? 'X'
      record.errors[:name] << '名前はXで始まる必要があります'
    end
  end
end

class Person
  include ActiveModel::Validations
  validates_with MyValidator
end

テストの修正にどこから手を付けたらいいか(RubyFlowより)

記事はいたって普通な感じでしたが、上の図をきっかけに、BPS社内で使っているGitLabのCI機能が最近どんどんよくなっているという話になりました。

忙しい業務の合間を縫ってせっせとCIを設定してくれている方がいるおかげで、GitLabのコミット一覧にこんな感じで表示してくれます↓

GitLab CITravis CIを下敷きにしていることを初めて知りました。GitLab CIは良い。

ネーミングはつらいよ(RubyFlowより)

ベタベタな日本語見出しですみません。軽い読み物です。
「Naming Things is Hard」という言い方はよく使われているんだそうですが、それ自体の言い回しが英語的によくないというのが皮肉である、だそうです。

voiceless: ライブラリのincludeでtryできるgem(RubyFlowより)

RubyFlowの古いニュースからです。星3つしかありません。

こわごわソースを開けてみると、えらく短いのはまだしも、rescue Exception => eに「こらー!それRubyスタイルガイドでも禁止されてるだろ」と


require 'colorize' def voiceless begin yield rescue Exception => e # らめー! puts (">"*50).red puts "VOICELESS ALERT: #{ e.message.to_s }".red line = caller.each_with_index.map do |item, index| caller[index].split(':')[0..1].join(':') end.compact.select do |item| item unless item =~ /voiceless/mix end.first.to_s puts line.yellow if line.present? puts ("<"*50).red end end

というわけで、真似したらあかんやつでした。

Exceptionクラスをrescueすることは避ける
Rubyスタイルガイドを読む: 例外処理」より

routes.rbを整理する方法(RubyWeeklyより)

これも普通の感じの記事でしたが、以下の記法に「こういうのはありかも」「統一できるんならだけど」といった声がありました。

match '/products/:id' => redirect('/')

他にTracePoint and Array sizeなんてのもありましたが、「自分でもまだ解決できてないけど、興味ある人いるかもしれないので書いとく」とあり、走り書き感にあふれていますね。

動画: webpacker gemでWebPackを使ってみる(RubyWeeklyより)

21分です。WebPackerを急いで知りたい人向けです。

動画: Rails+interact.jsでドラッグ&ドロップを実装(RubyWeeklyより)

15分です。「interact.jsとか使わなくても、jQueryでやればいいんじゃね?」から始まって、CoffeeScriptの話題になりました。

CoffeeScriptは長らくRailsでデファクトに近い感じで使われていますが、「コードが複雑になるとインデントの深さがひと目でわからなくなってつらくなったりするよね」といった声が上がりました。

RailsのJavaScript環境も激しく変わりつつありますが、CoffeeScriptは果たしてどうなるでしょうか。

今週は以上です。

関連記事

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

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

Rails公式ニュース

Ruby Weekly

OpenRuby

RubyFlow

160928_1638_XvIP4h

Hacker News

160928_1654_q6srdR

Github Trending

160928_1701_Q9dJIU

リファクタリングRuby: サブクラスをレジストリに置き換える(翻訳)

$
0
0

こんにちは、hachi8833です。今回はRailsのリファクタリングについての翻訳記事をお送りいたします。

概要

原著者より許諾をいただいて翻訳・公開いたします。

リファクタリングRuby: サブクラスをレジストリに置き換える(翻訳)

あるとき私のチームに、レガシーなRailsアプリ全体にレスポンシブな画像を実装するという面倒なタスクが舞い降りてきました。そのアプリはそれまで、クライアントのデバイスやviewport属性などおかまいなしに、縦横比率の合わない巨大な画像ファイルをWebブラウザに送信していたのです。ページ読み込みでユーザーが待たされるためにユーザーエクスペリエンスが損なわれ、コンバージョン率も低下していました。

この問題を解決するために、画像をリアルタイムで変換するオンザフライ画像サーバーをプロビジョニングしてCDNレイヤーに配置しました。最初のタスクは、このコンセプトでうまくいくかどうかを本番サーバーで小規模に試すことでした。配置した場所は本番サーバーですが、ホームページから離して配置したのでトラフィックはそれほどありませんでした。

最初のバージョン

使われているすべての基本インフラに合うレスポンシブ画像用のgemを探しましたが、今回の目的に合う既存のgemは残念ながら見当たりませんでした。そこで、以下の要件を満たすソリューションの開発をスタートさせました。

  • 1つの画像(ActiveRecordモデル)を、1つのレスポンシブ画像に1対1対応させる
  • レスポンシブ画像が複数フォーマット(HTML/CSS/JSON)に対応する
  • フォーマットはアプリの内部外部ともに共有でき、プリロードやJS統合などに利用できる

解説: レスポンシブ画像とは、異なる画像サイズやデバイス/ピクセル比に応じて複数の画像ファイルをまとめたものです。
しばらくかかって、次のようなResponsiveImageクラスをこしらえました(実物とは異なります)。

class ResponsiveImage
  class << self
    attr_reader :versions

    def inherited(subclass)
      subclass.version ImageVersion.new(:original)
    end

    def version(*args)
      new_version = ImageVersion.new(*args)
      (@versions ||= {}).merge!(new_version.id => new_version)
    end
  end

  version ImageVersion.new(:original)

  def initiliaze(image)
    @image = image
  end

  def url(version_id)
    # version_idに対応するURLを生成
  end

  # 表現形式を展開するメソッドをここに置く...
end

ご覧のとおり、このクラスは次のようなサブクラスに画像のバージョンの定義と収集機能を提供するためのものです。

class FreebieMainImage < ResponsiveImage
  version :sm, 173, 130, [nil, 599]
  version :md, 241, 181, [600, 991]

  # その他のバージョン...
end
# 属性は記事用に簡略化されています

上の2行目で宣言されている173×130ピクセルの画像は、viewportが0〜599ピクセルの場合に使われます。同様に、継承したフックで自動的にすべてのサブクラスの:originalバージョンが1つ宣言されます。宣言されたサイズに応じた画像をリクエストできるよう、次のコードで取得する画像サーバーへのURLはバージョンに応じて変わります。

# :sm バージョンのURLを返す
responsive_image.url(:sm)

ResponsiveImageオブジェクトは、最終的には表現形式の異なる画像を取得するときの単なるプロキシになりますので、特殊化したオブジェクトに次のように委譲されます。

class ResponsiveImage
  # ...

  def to_picture
    PictureTag.new(self)
  end

  # ...
end
# PictureTagなどのオブジェクトは、ResponsiveImageのバージョンとURLを読み出してHTML/CSS/JSON出力を生成する

このときの最終的なビューは次のとおりです。

<%= FreebieMainImage.new(freebie.main_image).to_picture %>

上のERBは<picture>タグを出力します。FreebieMainImageで宣言された全バージョンの表示ルールとURLがこのタグに含まれます。

よくなった点

上の実装は、私たちの問題を解決するのにうってつけでしたが、いくつか問題が残りました。中でも、1つのエンドポイントに対するレスポンシブ画像を使いまわす必要があったことと、そのためにオブジェクトのインスタンス生成を何度も行わなければならないのがよくないと思えました。

画像使い回しのためにコントローラを使うのはいかにも悪手です。他の全コントローラで同じことをするはめになりがちだからです。

class FreebiesController
  def show
    freebie = Freebie.find(params[:id])

    @freebie = FreebiePresenter.new(freebie)
    @main_image = FreebieResponsiveMainImage.new(@freebie.main_image)
  end
end

何らかの形でコードを抽象化して、再利用しやすくする必要がありました。しばらく考えた末、レスポンス画像のファクトリーをモデルでグループ化するのが適切であると思えてきました。

このときのざっくりした設計は「Freebieモデルには1つのmain_imageがあり、コレクションでFreebieMainImage用の適切なプレゼンターを自動的に決定して、結果をハッシュ的なインターフェイス経由で渡す」というものです。
ざっくりとですが、以下のような青写真的コードを作ってみました。

# "freebie"モデルを読み取って、対応する正しい画像に対応付ける
collection = ResponsiveImageCollection.new(freebie)

# 最終的にレスポンシブ画像を出力する
puts collection[:main_image].to_picture

このときは最終的に以下のような実装になりました。

class ResponsiveImageCollection
  def initialize(model)
    @model = model
    @images = {}
  end

  def [](image_id)
    @images[image_id] ||= begin
      image = @model.public_send(image_id)
      klass = find_responsive_image_class(image_id)

      klass.new(image)
    end
  end

  private

  def find_responsive_image_class(image_id)
    "#{@model.class.model_name.name}#{image_id.to_s.camelize}".constantize
  end
end

ご覧のように、画像のレスポンシブなクラスの探索はRailsらしい書き方になっています。モデル名がFreebieで画像名がmain_imageの場合は、FreebieMainImageというクラスを探索します。

FreebiePresenterの以下のメソッドによって、コードでのアクセスが容易になり、コントローラも汚さずに済むようになりました。

class FreebiePresenter
  # ...

  def images
    @images ||= ResponsiveImageCollection.new(self)
  end

  # ...
end

このときのビューは最終的に以下のようになりました。

<%= @freebie.images[:main_image].to_picture %>

最初のバージョンのメリット

以下の理由によってリリースはスムーズに行われ、上のコードも技術的には適切でした。

  • concernが分離されたことでモデルがレスポンシブ対応のために膨れ上がらずに済んだ。この方法は昔のRailsらしいソリューションに似ています。
  • ResponsiveImageは個別のImageと結合していない。
  • #versionメソッドがマクロとしてよくできている。
  • (今見れば言い訳がましいのは承知で)ResponsiveImageCollectionは当初のリリース要件を満たしている。リソースは1対1で対応付けられ、かつコアシステムとの結合はまったく見られなかった。

これでよしということでリリースされました。

第2のバージョン

概念の実証(POC)に成功したこのシステムをデプロイした後、いよいよこのシステムを多数のページで実装することになったのですが、そこで新たに問題点がいくつか判明し、それらについても考慮が必要になりました。

  • 1つのImageに対して複数のResponsiveImageが対応し(1対多)、複数のResponsiveImageが複数のImageに対応する(多対多)

さっそく計画を立案し、新たな変更に対応しました。

問題の発覚

最初のいくつかを実装し始めてみて、レスポンシブサブクラスを何度も何度も宣言しなければならないことに気づきました。

class SaleIndexImage < ResponsiveImage
  # Versions...
end

class SaleMainImage < ResponsiveImage
  # Versions...
end

class SaleFooImage < ResponsiveImage
  # Versions...
end

class SaleBarImage < ResponsiveImage
  # Versions...
end

# まだまだたくさんあります

それでやっと、レスポンシブ画像ごとにいちいちクラスをこしらえるのはかなりダルいということに気づいたのでした。

当初は扱う画像が少なかったため、このダルさに気づけなかったのでした。最小限のコードで問題を解決してリリースにこぎつけられたのですからそれはよしとしましょう。しかしそのソリューションは必然的に新しい要件に発展し、コードの荒い部分を改善しなければならなくなりました。

私たちは何度も自問自答を重ねた末、ひとつ面白いことに気づきました。このサブクラスの役割は、データを集中化することなのではないかと。

ということは、このサブクラスはほぼ継承として振る舞うべきのではないでしょうか?

確かに、「Y is a X」のような「is a」関係の表現に最適なのは継承です。実際、現状のサブクラスは「is a」関係の要件を満たしておらず、代わりに、継承したメソッドにデータを提供するときの設定ハブとして振る舞っています。

どんな場合であっても、メソッドや実装の詳細を単に共有するだけの目的で継承を使ってはならない。この種の間違いが見つかった場合は、設計の改善が必要な可能性が高い。

当初は気づけませんでしたが、実際私たちの方法では、探索をクラス名で行ったりしていた副作用のために後になるほどコードが複雑になってしまいました。SOLID指向のコードであればこれでもよいのですが、今回は該当しません。

解決方法

どうやら今回の問題は、ファクトリーに設定を提供するレジストリで対応する方がふさわしそうです。まだピンとこない方もいると思いますが、しばらくお付き合いください。

問題解決のためのリファクタリングの第一歩は、#versionsをクラスメソッドからインスタンスメソッドに移動することです。ただしクラスメソッドはまだ残しておきます。

変更後も動作は変わらず、さらに、サブクラスをいちいち作らなくてもレスポンシブ画像をオンザフライでインスタンス化できるようになりました。

freebie_main_image = ResponsiveImage.new(freebie.main_image,
  ImageVersion.new(:sm, 173, 130, [nil, 599]),
  ImageVersion.new(:md, 241, 181, [600, 991])
])

#versionsをインスタンスメソッド化し始めたので、次はクラスのすべてのサイト呼び出しをインスタンスメソッド化します(この手順は非掲載です)。

この時点ではまだサブクラスを排除できていません。

FreebieMainImage.new(freebie.main_image)

サブクラスの排除は少々面倒です。最初に、サブクラス中心の設定を取り除くためにレジストリを作成します。

class ResponsiveConfig
  def self.config
    yield instance
  end

  include Singleton

  def initialize
    @config = { default: [] }
  end

  def add(id, versions)
    @config[id] = versions.map { |args| ImageVersion.new(*args) }
  end

  def fetch(id)
    @config.fetch(id)
  end
end

次に、すべてのサブクラスにあるバージョンをレジストリに移動します。具体的には、Railsイニシャライザに以下のコードを配置します。

ResponsiveConfig.config do |config|
  config.add :freebie_main_image, [
    [:sm, 173, 130, [nil, 599]],
    [:md, 241, 181, [600, 991]]
  ]

  config.add :freebie_hero_image, [
    [:sm, 173, 130, [nil, 599]],
    [:md, 241, 181, [600, 991]]
  ]
end

上のコードでは、#addメソッドが設定idとバージョン情報の配列を受け取ります。これらはStructオブジェクトにマップされます。

続いてResponsiveImageCollectionで探索対象をサブクラスから設定キーに変えます。

これでやっと、ResponsiveImage.versionメソッドとResponsiveImage.inheritedメソッドを削除できるようになりました。削除した後のクラスは非常にシンプルです。

ただしまだ作業が残っています。

最後に残った結合

ここまでのリファクタリングは比較的やさしい方でしたが、まだ問題が残っています。今のままでは、同じレスポンシブ設定を2回使いまわそうとしてもできません。探索するコレクションクラスが命名方法にハードコードされてしまっているからです。

たとえば、Projectというモデルがあり、そのmain_imageのバージョンを宣言したい場合、project_main_imageというキーが必要になります。

ResponsiveConfig.config do |config|
  config.add :project_main_image, [
    # ここにバージョンがある...
  ]
end

このままでは柔軟性に欠けているのでスケールアップできませんし、命名方法があいまいで覚えにくいという問題に対処できていません。

画像をレスポンシブに明示的に対応付けられるでしょうか?今回は最終的に、要素の組み合わせや個数を好きなだけ繰り返せるようになりました。

最初に、ResponsiveConfigに似たCollectionConfigクラスを作成しました。

class CollectionConfig
  include Singleton

  CollectionMapping = Struct.new(:id, :responsive_config_id, :image_name)

  def self.config
    yield instance
  end

  def initialize
    @config = { default: [] }
  end

  def add(id, mappings)
    @config[id] = mappings.map { |args| CollectionMapping.new(*args) }
  end

  def fetch(id)
    @config.fetch(id)
  end
end

続いて、以下のようにコレクションマッピングを設定します。

CollectionConfig.config do |config|
  config.add :freebie, [
    [:main_image, :freebie_main_image, :main_image],
    [:hero_image, :freebie_hero_image, :hero_image]
  ]
# 最初のパラメータはID(コレクション内部でレスポンシブ画像の識別に使う)

最後に、コレクションクラスを変更して、マッピングを解釈できるようにします。

ついにmain_imagerandom_imageで再利用できるようになりました!もちろん、他のどの画像でも再利用できます。

今度のコードは、ERB呼び出し側で何も変更されていないので、非常に使いやすくなりました。

<%= freebie.images[:main_image].to_picture %>

もちろん、このコレクションクラスはもっと柔軟にできます。たとえばモデルとの結合をやめてもよいでしょう。コレクションをモデルと結合する理由はもうありません。

ただし、モデルとの結合は機能の一部なのでそのままにしました。現状でも他のコレクションは簡単に作れますし、それに必要な柔軟性は既に備わっているからです。

最後に

今回ご紹介したコードは、ブログ記事用にかなり手を加えてあります。また、実際には実装がここまでややこしくなったわけではありませんのでご了承ください。

要点

  • 実行するタスクの変更量が多い場合は、分割して小さくしてから攻略しよう。
  • 未来に備えすぎないこと: 今解決に必要なコードだけを実装しよう。問題を見据えて、システムが自然に進化するコードを書こう。
  • 継承が常に悪いわけではない: ただし継承がふさわしくない問題もたくさんある
  • サブクラスをデータ共有だけのために使ってないかどうか注意しよう: 最適な方法がないか常に自問自答しよう

最後までお読みいただき、ありがとうございました。今日が皆さまにとってよい日でありますように。

[Rails 5][Rails 4] ‘link_to’ APIドキュメント完全翻訳

$
0
0

こんにちは、hachi8833です。Rails 4と5の#link_toメソッドのAPIドキュメントを翻訳いたしました。

概要

Rails 3、4、5のform_forメソッドを最新の公式APIドキュメントでチェックしてみたところ、現時点でのRails 4とRails 5向けform_forメソッドのAPIドキュメントは完全に一致していました。

また、Rails 3のAPIオプション数はRails 4/5よりも少なくなっています。

Rails 3の#link_toAPIドキュメントについては以下をご覧ください。

link_to APIドキュメント

# API呼び出し(Rails 4、Rails 5共通)
link_to(name = nil, options = nil, html_options = nil, &block)

一連のオプションで作成されたURLと、指定のリンク名を使って、アンカー要素を作成します。正しいオプションについては、url_forのドキュメントを参照してください。

optionsハッシュの代わりにStringも渡せます。その場合は、Stringの値をリンクのhrefとしてanchor要素を生成します。

optionsハッシュの代わりに:backというシンボルを渡すと、リファラへのリンクを生成します。該当のリンク先がない場合は、JavaScriptによってリファラの代わりにバック用リンクが使われます。

namenilを渡すと、リンク自身の値がリンク名として使われます。

シグネチャ

link_to(body, url, html_options = {})
  # url はString(posts_pathなどのURLヘルパーメソッドも使える)

link_to(body, url_options = {}, html_options = {})
  # url_options(ただし:methodは除く)はurl_forに渡される

link_to(options = {}, html_options = {}) do
  # name
end

link_to(url, html_options = {}) do
  # name
end

オプション

data:
カスタムのdata-属性を渡すのに使います。
method:
HTTP verbをシンボルで渡します。この修飾子はHTMLフォームを動的に生成し、指定のHTTP verbで即座にフォームを処理のために送信します。
このオプションは、リンクをクリックしたときにレコード削除などの危険なアクション(検索ボットがサイトからこうしたリンクを探り当てることがあります)をPOST操作で行わせたいときに便利です。
サポートされている操作は:post:delete:patch:putです。ただし、ユーザーがJavaScriptを無効にしている場合は動作がフォールバックし、リクエストはGETで行われます。
href: '#'を指定すると、ユーザーがJavaScriptを無効にしている場合のリンクは無効になります。
アプリがPOSTの動作に依存している場合は、コントローラのアクションで#post?#delete?#patch?#put?を使ってリクエストオブジェクトのメソッドがどれであるかを確認する必要があります。
remote:
trueを指定すると、リクエストをURLに送信する際にリンクをたどるのではなく、Unobtrusive JavaScript(控えめなJavaScript)ドライバを使ってAjaxリクエストを送信します。ドライバはAjaxリクエストの完了をリッスンし、完了後にJavaScript操作を実行しますが、そのメカニズムはドライバの実装に依存します。

data属性に与えられるオプション

confirm: '質問文'
このオプションを指定すると、ユーザーへの問い合わせメッセージ(この場合は「質問文」)をUnobtrusive JavaScriptドライバで表示できるようになります。ユーザーがこれを了承するとリンクは通常どおりに実行され、了承しない場合は何も実行されません。
:disable_with
このパラメータの値は、フォーム送信時の送信ボタンが無効になっているときの送信ボタン名として使われます。この機能はUnobtrusive JavaScriptドライバによって提供されます。

#link_toの動作は#url_forに依存しています。これにより、#link_toでは従来の「コントローラ/アクション/id」スタイルの引数と、新しいRESTfulルーティングのどちらも利用できます。現在のRailsでは可能な限りRESTfulルーティングスタイルを使うことが推奨されていますので、アプリのルーティングをリソースベースで行っている場合は、次のように書けます。

link_to "Profile", profile_path(@profile)
# => <a href="/profiles/1">Profile</a>

次のようにもっと簡潔に書くこともできます。

link_to "Profile", @profile
# => <a href="/profiles/1">Profile</a>

冗長になりますが、以下のようにリソース指向でない従来の方法で書くこともできます。

link_to "Profile", controller: "profiles", action: "show", id: @profile
# => <a href="/profiles/show/1">Profile</a>

同様に、以下の書き方もおすすめです。

link_to "Profiles", profiles_path
# => <a href="/profiles">Profiles</a>

以下は上よりも冗長であり、おすすめしません。

link_to "Profiles", controller: "profiles"
# => <a href="/profiles">Profiles</a>

リンク先がnameパラメータに合わせにくい場合は、次のようにブロックも使えます。ERBの例を示します。

<%= link_to(@profile) do %>
  <strong><%= @profile.name %></strong> -- <span>Check it out!</span>
<% end %>
# => <a href="/profiles/1">
       <strong>David</strong> -- <span>Check it out!</span>
     </a>

CSSのidやclassも以下のように簡単に生成できます。

link_to "Articles", articles_path, id: "news", class: "article"
# => <a href="/articles" class="article" id="news">Articles</a>

RESTfulでない従来のスタイルの場合は、以下のようにリテラルハッシュが必要です。

link_to "Articles", { controller: "articles" }, id: "news", class: "article"
# => <a href="/articles" class="article" id="news">Articles</a>

リテラルハッシュがないと、次のような誤ったリンクが生成されます。

link_to "WRONG!", controller: "articles", id: "news", class: "article"
# => <a href="/articles/index/news?class=article">WRONG!</a>

#link_toでは、anchor:でリンクにアンカーを追加したり、query:でリンクにクエリ文字列を追加することもできます。

link_to "Comment wall", profile_path(@profile, anchor: "wall")
# => <a href="/profiles/1#wall">Comment wall</a>

link_to "Ruby on Rails search", controller: "searches", query: "ruby on rails"
# => <a href="/searches?query=ruby+on+rails">Ruby on Rails search</a>

link_to "Nonsense search", searches_path(foo: "bar", baz: "quux")
# => <a href="/searches?foo=bar&baz=quux">Nonsense search</a>

link_to (:method)固有のオプション(:post:delete:patch:put)は以下のような方法でのみ使えます。

link_to("Destroy", "http://www.example.com", method: :delete)
# => <a href='http://www.example.com' rel="nofollow" data-method="delete">Destroy</a>

data:オプションを使って、カスタムのデータ属性を渡すこともできます。

link_to "Visit Other Site", "http://www.rubyonrails.org/", data: { confirm: "Are you sure?" }
# => <a href="http://www.rubyonrails.org/" data-confirm="Are you sure?">Visit Other Site</a>

他にも、target:rel:type:などで任意のリンク属性を指定することもできます。

link_to "External link", "http://www.rubyonrails.org/", target: "_blank", rel: "nofollow"
# => <a href="http://www.rubyonrails.org/" target="_blank" rel="nofollow">External link</a>

関連記事

Rails 4、Rails 5

Rails 3

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

$
0
0

こんにちは、hachi8833です。Rails 3の#link_toメソッドのAPIドキュメントを翻訳いたしました。

概要

Rails 4とRails 5の#link_toAPIドキュメントについては以下をご覧ください。

link_to APIドキュメント(Rails 3)

# API呼び出し(Rails 3)
link_to(*args, &block)

一連のオプションで作成されたURLと、指定のリンク名を使ってlinkタグを作成します。正しいオプションについては、url_forのドキュメントを参照してください。

optionsハッシュの代わりにStringも渡せます。その場合は、Stringの値をリンクのhrefとしてlinkタグを生成します。

optionsハッシュの代わりに:backというシンボルを渡すと、リファラへのリンクを生成します。該当のリンク先がない場合は、JavaScriptによってリファラの代わりにバック用リンクが使われます。

namenilを渡すと、リンク自身の値がリンク名として使われます。

シグネチャ

link_to(body, url, html_options = {})
  # url はString(posts_pathなどのURLヘルパーメソッドも使える)

link_to(body, url_options = {}, html_options = {})
  # url_options(ただし:confirmと:methodは除く)はurl_forに渡される

link_to(options = {}, html_options = {}) do
  # name
end

link_to(url, html_options = {}) do
  # name
end

オプション

confirm: '質問文'
このオプションを指定すると、ユーザーへの問い合わせメッセージ(この場合は「質問文」)をUnobtrusive JavaScriptドライバで表示できるようになります。ユーザーがこれを了承するとリンクは通常どおりに実行され、了承しない場合は何も実行されません。
method:
HTTP verbをシンボルで渡します。この修飾子はHTMLフォームを動的に生成し、指定のHTTP verbで即座にフォームを処理のために送信します。
このオプションは、リンクをクリックしたときにレコード削除などの危険なアクション(検索ボットがサイトからこうしたリンクを探り当てることがあります)をPOST操作で行わせたいときに便利です。
サポートされている操作は:post:delete:putです。ただし、ユーザーがJavaScriptを無効にしている場合は動作がフォールバックし、リクエストはGETで行われます。
href: '#'を指定すると、ユーザーがJavaScriptを無効にしている場合のリンクは無効になります。
アプリがPOSTの動作に依存している場合は、コントローラのアクションで#post?#delete?#put?を使ってリクエストオブジェクトのメソッドがどれであるかを確認する必要があります。
remote:
trueを指定すると、リクエストをURLに送信する際にリンクをたどるのではなく、Unobtrusive JavaScript(控えめなJavaScript)ドライバを使ってAjaxリクエストを送信します。ドライバはAjaxリクエストの完了をリッスンし、完了後にJavaScript操作を実行しますが、そのメカニズムはドライバの実装に依存します。

#link_toの動作は#url_forに依存しています。これにより、#link_toでは従来の「コントローラ/アクション/id」スタイルの引数と、新しいRESTfulルーティングのどちらも利用できます。現在のRailsでは可能な限りRESTfulルーティングスタイルを使うことが推奨されていますので、アプリのルーティングをリソースベースで行っている場合は、次のように書けます。

link_to "Profile", profile_path(@profile)
# => <a href="/profiles/1">Profile</a>

次のようにもっと簡潔に書くこともできます。

link_to "Profile", @profile
# => <a href="/profiles/1">Profile</a>

冗長になりますが、以下のようにリソース指向でない従来の方法で書くこともできます。

link_to "Profile", :controller => "profiles", :action => "show", :id => @profile
# => <a href="/profiles/show/1">Profile</a>

同様に、以下の書き方もおすすめです。

link_to "Profiles", profiles_path
# => <a href="/profiles">Profiles</a>

以下は上よりも冗長であり、おすすめしません。

link_to "Profiles", :controller => "profiles"
# => <a href="/profiles">Profiles</a>

リンク先がnameパラメータに合わせにくい場合は、次のようにブロックも使えます。ERBの例を示します。

<%= link_to(@profile) do %>
  <strong><%= @profile.name %></strong> -- <span>Check it out!</span>
<% end %>
# => <a href="/profiles/1">
       <strong>David</strong> -- <span>Check it out!</span>
     </a>

CSSのidやclassも以下のように簡単に生成できます。

link_to "Articles", articles_path, :id => "news", :class => "article"
# => <a href="/articles" class="article" id="news">Articles</a>

RESTfulでない従来のスタイルの場合は、以下のようにリテラルハッシュが必要です。

link_to "Articles", { :controller => "articles" }, :id => "news", :class => "article"
# => <a href="/articles" class="article" id="news">Articles</a>

リテラルハッシュがないと、次のような誤ったリンクが生成されます。

link_to "WRONG!", :controller => "articles", :id => "news", :class => "article"
# => <a href="/articles/index/news?class=article">WRONG!</a>

#link_toでは、anchor:でリンクにアンカーを追加したり、query:でリンクにクエリ文字列を追加することもできます。

link_to "Comment wall", profile_path(@profile, :anchor => "wall")
# => <a href="/profiles/1#wall">Comment wall</a>

link_to "Ruby on Rails search", :controller => "searches", :query => "ruby on rails"
# => <a href="/searches?query=ruby+on+rails">Ruby on Rails search</a>

link_to "Nonsense search", searches_path(:foo => "bar", :baz => "quux")
# => <a href="/searches?foo=bar&baz=quux">Nonsense search</a>

link_to (:method)link_to (:confirm)固有のオプションは以下のような方法でのみ使えます。

link_to "Visit Other Site", "http://www.rubyonrails.org/", :confirm => "Are you sure?"
# => <a href="http://www.rubyonrails.org/" data-confirm="Are you sure?"">Visit Other Site</a>

link_to("Destroy", "http://www.example.com", :method => :delete, :confirm => "Are you sure?")
# => <a href='http://www.example.com' rel="nofollow" data-method="delete" data-confirm="Are you sure?">Destroy</a>

関連記事

Rails 4、Rails 5

Rails 3

Viewing all 1383 articles
Browse latest View live