日々雑記

旅行の事ばっかり。神社成分多め。
Recent Tweets @

花火

シャッター速度:2.0、f/7.1、ISO:80だとよさげ。

少し調べたのでメモ。

constraintsとは

railsのroutingには”constraints”という機能があり、routingに様々な制限を設定する事が出来る。

設定出来るconstraintsについては以下。

HTTP Verb Constraints

HTTPメソッドでの制限。

match 'users', to: 'users#index', via: [:get, :post]

上記の場合、GETPOSTメソッド以外でusersアクセスしようとするとActionController::RoutingErrorがraiseされる。rails3系から4系に移行する際によく見た書き方ですな。

Segment Constraints

dynamic segmentのフォーマットについて制限を設定する事が出来る。

  get 'users/:id', to: 'users#show', constraints: { id: /[A-Z]\d{5}/ }

上記の場合、”/users/A12345”は通るが、”/users/893”はエラーになる。値を厳密に制限したいときは使える。

なお、正規表現にアンカーは使用出来ない。アンカーを使用すると、ArgumentErrorが起きる

get 'users/:id', to: 'users#show', constraints: { id: /\A[A-Z]\d{5}\z/ }
# => Regexp anchor characters are not allowed in routing requirements: /\A[A-Z]\d{5}\z/ (ArgumentError)

これは、routesが最初にアンカーを設定しており、constraintsでアンカーを設定する必要が無い為、エラーにしているもよう。逆に言うと、部分一致的な事は設定出来ない。(上記の例だと、”/usesr/A123456”はエラーになる。)

Request-Based Constraints

Requestの情報を元にした制限も出来る。Request objectがStringを返すメソッドをconstraintsに設定出来る。

get :users, to: 'users#index', constraints: { port: '3000' }

上記の場合だと、port番号が3000以外の場合エラーになる。他にも、subdomainの値やprotocolでの制限が可能。request objectについて、詳しくはこちら

Advanced Constraints

もっと複雑な事をしたい場合、constraintsに独自のクラスを指定する事も可能。指定するクラスには、matches?メソッドが定義されていればOK。

例えは、特定のIPからのみリクエストを許容したい場合、以下のように設定可能。

class WhitelistConstraint
  IP_LIST = %w(192.168.1.1 127.0.0.1)

  def initialize
  end

  def matches?(request)
    IP_LIST.include?(request.remote_ip)
  end
end


Rails.application.routes.draw do
  get 'admin/index', constraints: WhitelistConstraint.new
end

上記の場合、IPが192.168.1.1、127.0.0.1以外の場合エラーになる。

なお、上記の例だとインスタンスメソッドにしてるが、クラスメソッドでもOK。

class WhitelistConstraint
  IP_LIST = %w(192.168.1.1 127.0.0.1)

  def self.matches?(request)
    IP_LIST.include?(request.remote_ip)
  end
end


Rails.application.routes.draw do
  get 'admin/index', constraints: WhitelistConstraint
end

特に初期化処理が無い場合は、こっちの方がスッキリする気がする。

lambdaでも出来る。

get 'admin/index',  constraints: ->(request) { WhitelistConstraint::IP_LIST.include?(request.remote_ip) }

routingが汚くなってしまうので、余程短くない限りはクラスを作成してmatches?メソッドを定義した方が良いかなあ。

同じconstraintsを複数のroutingに設定する場合、ブロック設定出来る。

  constraints(WhitelistConstraint) do
    get 'admin/index'
    get 'admin/list'
  end

まとめ

あまり使われて無いような気がするんですが、例えば管理画面にアクセスするIPを制限したい、という場合には割と便利かと。
HTTPサーバで設定出来ればその方が良いと思うのですが、例えばHerokuのようなHTTPサーバを触れないPaaSとかでは、controllerにアクセス制限書くより、routingにあった方がスッキリする時もあるあと。

因みに、先に挙げた例だと、WhitelistConstraintクラスをroutes.rbに直書きしているのですが、実際はそんな事をせず、当然別クラスにするのですが、そのファイルをどこに格納するかで微妙に迷っている。

結局、libの下にconstraintsディレクトリを作成しそこに格納しているのですが、もっと良い場所がある、という方がいたら、教えて頂けますと助かります。

minitest-soundというgemを作ったのでご紹介。

内容

名前の通り、minitestのpluginです。ソースはこちら

minitest実行中、及び、テスト成功/失敗それぞれのタイミングでmp3を再生する事が出来ます。それだけ!

mp3の再生には、mpg123というライブラリを使用しています。コマンドラインでmp3再生出来るので便利。

使い方

先に記載した通り、mpg123を使用しているので、mpg123をインストールしてない場合、まずmpg123のインストールをして下さい。 Ubuntuの場合sudo aptitude install mpg123でOK。他のOSも、(多分)パッケージ管理システムからインストール出来る筈。

次に、お決まりの通り、Gemfileにgem 'minitest-sound'の追加及びbundle installを実行して下さい。

最後に、test_helper.rbに以下の内容を設定して下さい。

require 'minitest/sound'

Minitest::Sound.success = '/aaa/bbb/xxx.mp3' # テスト成功時に再生するmp3ファイル
Minitest::Sound.failure = '/aaa/bbb/xxx.mp3' # テスト失敗時に再生するmp3ファイル
Minitest::Sound.during_test = '/aaa/bbb/xxx.mp3' # テスト実行中に再生するmp3ファイル

ファイルのパスは全てフルパスで。以上で設定完了。

後は普通にテストを実行するとmp3が再生されます。再生されない場合、ファイルのパスが間違っている可能性があります。

今後やりたい事

  • テストが100回成功したらマリオの1upのSEを流す、というネタをtwitterで見かけたので同じ事をやりたい
  • mp3ファイルをローカルに置くと、環境が変わった時に困るので、SoundCloud辺りのクラウドサービスから音源を再生出来るようにしたい。ただ、そうすると、今度はアップロードの手間が。うーん。

色々と試してみたのでメモ。なお、下記はrails 4.1.1で検証しています。幾つかのパターンでエラーになる事がありますが、最新のバージョンでは直ってる可能性もあります。

Action Mailer Previewsとは

Rails4.1から入った機能で、名前のとおりemailのpreviewが出来る機能。

同じ機能を持つものとして、mail_viewというgemがあり、Mailer Previewsはこのgemを参考に作られている。

使い方

早速試してみる。まずは、generatorでmailerを作成。

rails g mailer UserMailer mail1
      create  app/mailers/user_mailer.rb
      invoke  erb
      create    app/views/user_mailer
      create    app/views/user_mailer/mail1.text.erb
      create    app/views/user_mailer/mail1.html.erb
      invoke  test_unit
      create    test/mailers/user_mailer_test.rb
      create    test/mailers/previews/user_mailer_preview.rb

mailerを作成すると、test/mailers/previews/xxxx.rbというファイルが作成される。これがpreviewする為のクラスになる。中身は以下の通り。

# Preview all emails at http://localhost:3000/rails/mailers/user_mailer
class UserMailerPreview < ActionMailer::Preview

  # Preview this email at http://localhost:3000/rails/mailers/user_mailer/mail1
  def mail1
    UserMailer.mail1
  end
end

先ほと作成したmailerのメソッドを呼び出しているだけ。

後は、コメントに書いてある通り、http://localhost:3000/rails/mailers/user_mailer/mail1 にアクセスすると、メールの確認が出来る。お手軽。

画面では、From、To、Date、Subject、bodyを確認出来る。また、body部分については、HTML/plain-textを選択する事が出来る。

ActionMailer::Preview クラスにメソッドを追加するとURLが生成されるので、同じメールで引数を変えて試したい、という場合はメソッドを増やしてあげればOK。

# Preview all emails at http://localhost:3000/rails/mailers/user_mailer
class UserMailerPreview < ActionMailer::Preview
  def mail1
    UserMailer.mail1('hi')
  end

  def mail2
    UserMailer.mail1('Yeah!!!')
  end
end

これでhttp://localhost:3000/rails/mailers/user_mailer/mail2にアクセスすると、追加したメソッドの方でメールの確認が出来る。

http://localhost:3000/rails/mailersにアクセスすると、PreviewsのURLが全て確認出来る。

named route もあるので、一覧ページへのリンクを作成したい場合、rails_mailers_url (又はpath)でOK。

mailer previewsクラスの格納先について

先に書いた通り、デフォルトだと test/mailers/previews配下に格納されるが、これは変更可能。

config.action_mailer.preview_path = "#{Rails.root}/lib/mailer_previews"

こんな感じで指定してあげればOK。 確認用URLは変わらず http://localhost:3000/rails/mailers/配下。

因みにデフォルトがtest配下なのは、app配下だとproduction環境でeager loadの対象になってしまうから、と何処かで見た気が…。出典があいまい。

register_preview_interceptor について

ActionMailerではメール送信前に処理を行うインターセプタを登録する事が出来て、MailerPreviewsでも同じように設定が出来る。

API docを見ると、configにregister_preview_interceptorを設定してあげればOKと書いてあるのだが、どうにも動かない…。 ソース見てみたが、configの値を参照してないような…。

ActionMailer::Base.register_preview_interceptorを直接呼び出してあげれば動いた。

# config/initializers/action_mailer.rb
class InterceptorSample
  def self.previewing_email(message)
    # something to do
  end
end

ActionMailer::Base.register_preview_interceptor(InterceptorSample)

docが間違えている気がする。後でもうちょい調べる。

attachments /attachments.inline を使用している場合

この辺りの動きが大分怪しい。

rails4.1.1では、attachmentsを使用している、かつ、HTML/plain-text両方のメールがある場合に、Mailer Previewsがエラーになって動かない。因みに、メールがHTMLだけ、又はplain-textだけの場合は、正常に動作する。

attachments.inlineを使用している場合は、とりあえずエラーになる。

詳細はこのPR参照。対応自体は完了しているように見えるが、Milestoneが4.1.3になっているのが気になる…。

その他注意事項

production環境では動かない、というかdevelopment環境でしか動かない。Rails.env.development? がtrueの時のみpreviewのroutingが追加されるようソース内で設定されている。

特に問題無い気もするが、development以外の環境で確認したい、というケースがあった場合に困る……かなあ。

まとめ

簡単にメールが見れるのはとても便利なのだが、attachments周りで不具合があるのがちと残念。4.1.3位には問題無く使えるのかなーと。

サンプルがこちらにあるので、もし興味ありましたらどうぞー。

宣伝

そんなAction Mailer Previewのソースのコードリーディングを、次回のginzarbで実施予定です。6/17実施なので、宜しければどうぞー。

意外と日本語の情報無かったのでメモ。みんなrspec使ってるからかしら。

幾つかやり方があると思うが、とりあえず公式のREADMEを参考に。

ファイル名

minitest/XXX_plugin.rbという名前にする。

minitest本体でGem.find_files("minitest/*_plugin.rb")を行っており、上記命名規則に則ると自動でrequireしてくれる。

初期化処理

plugin_XXX_initという名前のメソッドを定義すると、テスト起動時に自動で呼び出される。XXXの部分は、ファイル名と一緒である必要がある。違うと呼ばれないので注意が必要。

オプションの設定

plugin_XXX_optionsという名前のメソッドを定義すると、こちらもテスト起動時に自動で呼び出される。XXXについては、初期化処理同様ファイル名と合わせる必要あり。

ここまでをまとめるとこんな感じ(先で説明する部分も含まれてます)。

# minitest/sample_plugin.rb:

require_relative 'sample_reporter'
require_relative 'sample_reporter2'
module Minitest
  def self.plugin_sample_options(opts, options)
    opts.on "--sample", "Sample Plugin" do
      options[:sample] = true
    end
  end

  def self.plugin_sample_init(options)
    self.reporter << SampleReporter.new(options)
    self.reporter << SampleReporter2.new(options)
  end
end

上記の例だと、sampleの部分を合わせる必要がある。

plugin_XXX_optionsの引数が上記例だと少し解りづらい気もするが、第一引数のoptsOptionParserで、第二引数のoptionsがminitest内部で持っているパラメータ(実体はHash)。何か値を設定したい場合は、第二引数のoptionsに値を設定する。

なお、実行順序はplugin_XXX_optionsの方が当然先。

reporter

minitestではテスト結果を表示するのに、複数のrepoterクラスのインスタンスを使用している。

なので、テスト実行時に何か処理を行いたい場合、独自のreporterクラスを定義して、self.reporterに追加すればOK。上記例のplugin_sample_initメソッドでやっている、self.reporter << SampleReporter.new(options)の部分がそれ。

reporterで呼び出されるメソッドについては、AbstractReporterクラスで定義されている。なので、AbstractReporterを継承したクラスを作成し、メソッドをオーバーライドしてあげれば良い。

定義されているメソッドは以下の通り。

  • start  : テスト起動時に呼ばれる
  • record  : 各テスト終了時に呼ばれる
  • report  : 全テスト終了時に呼ばれる
  • passed? : 全テスト終了時に呼ばれる.戻り値にfalseを設定すると、testはfailになる.

recordは引数に実行したテストのインスタンスが渡される。テストが成功/失敗等の情報はそこから取得可能。詳しくはMinitest::Testクラス辺り参照。

passed?は全テスト終了時に呼ばれて、戻り値がtrueならテスト成功、falseならテスト失敗と判定される。テストは全部通ったけど、何らかの理由で失敗とみなしたい場合に使う。

以下サンプル。

module Minitest
  class SampleReporter < AbstractReporter
    attr_accessor :results

    def initialize(options)
      self.results = []
    end

    def start
      $stdout.print "test start!!\n"
    end

    def record(result)
      # print test name and execute time
      $stdout.print "%s#%s: %.3f s\n" % [result.class, result.name, result.time]
      self.results << result
    end

    def report
      $stdout.print "test finished!!\n"
    end

    def passed?
      true
    end
  end
end

これでテスト結果見て色々処理出来るようになったが、AbstractReporterクラスは何も行わないので、単純に”テストが何件失敗したか”等の情報が欲しい場合、自分で計算しなくてはならないので、ちょっと面倒。

そんな時は、StatisticsReporterを使用すると良い。

StatisticsReporterではassertions、count、results、start_time、total_time、failures、errors、skipsの値を保持しているので、そちらを参照するだけで済む。

module Minitest
  class SampleReporter2 < StatisticsReporter
    def report
      super
      $stdout.print "test time: #{total_time}\n"
    end
  end
end

基本的には、StatisticsReporterを継承してreporterを作るのが良いと思われ。StatisticsReporterについて、詳しくはソース参照

こんな感じで。

Web+DB Press Vol.80のCasperJSの記事が面白かったので、UbuntuにCasperJSをインストールしてみました。以下インストールのメモ。

なお、以下はUbuntu 14.04で実施。とはいえ、多分古いバージョンでも問題無いとは思いますが。

Nodejs

ソースをコンパイルしていれるのは嫌だったので、npmを使用する事に。という訳でNodejs。

ついでにNodejsもバージョン管理したかったので、nvmを使用して入れ直し。

nvmのインストールについては、githubを参考に。

nvm インストール後、早速Nodejsをインストール。現時点(2014/04/28)の最新版をインストール。

nvm install 0.10

nodejsは、自動でロードして欲しかったので、profile (~/.bash_profile or ~/.zshrc or ~/.profile)に以下を追加。

nvm use 0.10

これでNodejsの準備は完了。

PhantomJS

CasperJS は PhantomJS のラッパライブラリなので、当然PhantomJSも必要。

npmで一発。

npm install phantomjs

CasperJS

こちらもnpmで。

npm install casperjs

これで完了かと思い、casperJSを実行すると、”Fatal: [Errno 2] No such file or directory; did you install phantomjs?”というエラーが。

casperJSのバグらしく、npmでインストールしたPhantomJSのpathが解決出来ないらしい。

PHANTOMJS_EXECUTABLE を設定すれば良いらしいので、profileに (~/.bash_profile or ~/.zshrc or ~/.profile)以下を追加。

export PHANTOMJS_EXECUTABLE=$HOME/./node_modules/casperjs/node_modules/.bin/phantomjs

これでOK。

$ casperjs --version  # 1.1.0-beta3

これでバージョンが正常に表示されれば、インストール完了。

CasperJS自体については、Web+DB Pressの記事を読むと良いと思いますよ!

高遠城址公園。公園中全てがさくら。

大江戸Ruby会議04に参加しました。大江戸Ruby会議に参加するのは、03に次いで2回目。

江戸東京博物館でカンファレンスが出来る、というのは知らなかったです。駅から近いし、見て回るのも楽しい、良い場所ですねえ。

で、特に印象深かった発表について、メモと感想をつらつら。電源がなくてほとんどメモが取れなかったので、大分適当。

私は如何にして異国でエンジニアとして生き抜いてきたか

  • 言葉は練習し続けてれば出来る
  • エンジニアのカルチャーを学ぶことが大事
  • 生きている英語を学ぶには、redditがおすすめ。特に、TIL(TODAY I LEARNED), IAMA, ELI5(Explain Like I’m five)が良い。

AdequateRecord

https://rubygems.org/gems/RFC7159

  • JSONの新しいRFCに対応するgemを作った
  • 既存のライブラリではRFC7159には対応していな。RFC7159では正常なケースがエラーになってしまったり、その逆もある
  • じゃあ新しく作ったgemを使えば良いかといえば、遅いので実用に耐えられない。今あるライブラリを使う、で良いと思う。

Another language you should learn

  • 英語学ぼう
  • 英語を学ぶには、コツコツ習慣として文化に触れる積み重ねをやるしかない
  • 英語を学ぶには、時間がかかる、という事を理解しよう

Hacking Home

  • 家を作るのは、システム開発に似ている
  • ちゃんと納期に上がる。凄い。
  • OSS活動をやる -> ネットにインタビューとかがのる -> お金を借りることが出来る!

Ruby会議でSQLの話をするのは間違っているだろうか

  • ビックデータ分析といえば、Hadoop/並列RDB
  • MapReduceは処理を書くのが大変。簡単にかける、過去の遺産も引き継げるので、SQL人気
  • HadoopもHive導入して、並列RDBに似てきている
  • MapReduceが死んだかといえば、そうではなくて、SQLからMapReduceを呼ぶ事が出来、これが大分良い
  • npath
  • OSS World と Enterprise World で知識が偏在してる。良いものは良い。
  • 壁を越える事が大事。

感想

  • がっつり技術的な話から、生活的な話まで非常に幅広く話が聞けてとても楽しかったです
  • "壁を越えろ"
  • ペア発表、漫談みたいで非常に面白かった。大分息が合ってないと難しいとは思うのですが、また見てみたい。
  • お布施システムとてもよいですね。参加してから満足度に応じて、参加費をプラスで払う、というのはもっと広まると良いと思います。

高田公園。夜桜が本番。