2016/01/30

Deviseで複数モデルを利用した場合、ログアウトすると全てのスコープでログアウトされてしまう

管理者とユーザーでのログイン機能が存在し、別モデルで管理したい場合、Devise を使うとscoped_viewsをtrueにすれば簡単に実装できる。(参考:Ruby - Railsでdeviseひとつで複数モデルを管理しよう - Qiita)

しかし、デフォルトの場合 Devise のログアウトは全てのスコープでログアウトしてしまう。つまり管理者とユーザーでログインしていた場合、管理者としてログアウトすると同時にユーザーとしてもログアウトしてしまう

これを防ぎたい場合、config/initializers/devise.rb にて config.sign_out_all_scopes = false としてやればよい。

初期状態ではこの設定はコメントアウトされているので、コメントアウトを外しfalseに設定するだけで、スコープごとのサインアウトが実装できる。

テストコード込みのサンプルコードはこんな感じ。

2016/01/18

Rails の TimeWithZone を JavaScript で読み込めるフォーマットで出力する

JavaScript で new Date(text) や Date.parse(text) した時に読み込める形で TimeWithZone の値を出力したい。

そのまま to_s すると 2015-01-18 12:34:56 +0900 となるが、これでは Chrome でしか読み込めない。

2015/01/18 12:34:56 +0900 という形式ならば Chrome, IE, Firefox で読み込むことができた

もしくは、せっかく to_json メソッドがあるんだからこれを利用すると安心。しかし余計なダブルクォートは取り除く必要がある。あと、人が見た時は先述のやり方のほうがぱっと見で理解しやすいと思う

to_json 経由だとミリ秒まで含まれているから完全に等価ではないけど、そこらへんは要件しだいということで

まあ、色々やり方はあるので好きなやり方を使えばいいと思う。

2016/01/10

EC2(Amazon Linux)上のRailsアプリにQiita::Markdownをインストール

インストール

Qiita::Markdownはシンタックスハイライトに Pygments を使用しているのでインストールしておく(sudo pip install pygments)

そして、おもむろに Gemfile に gem 'qiita-markdown' として bundle install してみる。

Installing charlock_holmes 0.7.3 with native extensions

Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    /usr/bin/ruby2.2 -r ./siteconf20160110-30152-1igbivj.rb extconf.rb
checking for main() in -licui18n... no
which: no brew in (/home/ec2-user/bin:/home/ec2-user/.rbenv/bin:/home/ec2-user/bin:/home/ec2-user/.rbenv/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin:/opt/aws/bin:/home/ec2-user/.local/bin:/home/ec2-user/bin:/opt/aws/bin:/opt/aws/bin:/home/ec2-user/.local/bin:/home/ec2-user/bin:/opt/aws/bin)
checking for main() in -licui18n... no


***************************************************************************************
*********** icu required (brew install icu4c or apt-get install libicu-dev) ***********
***************************************************************************************
*** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers.  Check the mkmf.log file for more details.  You may
need configuration options.

Provided configuration options:
        --with-opt-dir
        --without-opt-dir
        --with-opt-include
        --without-opt-include=${opt-dir}/include
        --with-opt-lib
        --without-opt-lib=${opt-dir}/lib64
        --with-make-prog
        --without-make-prog
        --srcdir=.
        --curdir
        --ruby=/usr/bin/$(RUBY_BASE_NAME)2.2
        --with-icu-dir
        --without-icu-dir
        --with-icu-include
        --without-icu-include=${icu-dir}/include
        --with-icu-lib
        --without-icu-lib=${icu-dir}/lib64
        --with-icui18nlib
        --without-icui18nlib
        --with-icui18nlib
        --without-icui18nlib

extconf failed, exit code 1

Gem files will remain installed in /home/ec2-user/rails_app/vendor/bundle/ruby/2.2/gems/charlock_holmes-0.7.3 for inspection.
Results logged to /home/ec2-user/rails_app/vendor/bundle/ruby/2.2/extensions/x86_64-linux/2.2/charlock_holmes-0.7.3/gem_make.out
Installing rugged 0.24.0b11 with native extensions

Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    /usr/bin/ruby2.2 -r ./siteconf20160110-30152-1y0cp2j.rb extconf.rb
checking for gmake... yes
checking for cmake... no
ERROR: CMake is required to build Rugged.
*** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers.  Check the mkmf.log file for more details.  You may
need configuration options.

Provided configuration options:
        --with-opt-dir
        --without-opt-dir
        --with-opt-include
        --without-opt-include=${opt-dir}/include
        --with-opt-lib
        --without-opt-lib=${opt-dir}/lib64
        --with-make-prog
        --without-make-prog
        --srcdir=.
        --curdir
        --ruby=/usr/bin/$(RUBY_BASE_NAME)2.2
        --use-system-libraries

extconf failed, exit code 1

Gem files will remain installed in /home/ec2-user/rails_app/vendor/bundle/ruby/2.2/gems/rugged-0.24.0b11 for inspection.
Results logged to /home/ec2-user/rails_app/vendor/bundle/ruby/2.2/extensions/x86_64-linux/2.2/rugged-0.24.0b11/gem_make.out

とまあ、2つのエラーが出た。Amazon Linuxは yum だからlibicu-dev じゃなくて libicu-devel にすればOKかな?

sudo yum install -y libicu-devel cmake して bundle install したらインストールは成功したようだ

ハイライト

インストールは成功したのでこんなコードを書いて使えば Markdown に変換された。ばっちりばっちり

module ApplicationHelper
  def markdown(source)
    Qiita::Markdown::Processor.new.call(source)[:output].to_s.html_safe
  end
end

変換はばっちりだがシンタックスハイライトがされていない。いや、正確にはスタイルシートがない。
richleland/pygments-css あたりでお好みのものを利用させてもらおう

絵文字

シンタックスハイライトも完了した。次は絵文字である。
gemoji | RubyGems.org | your community gem host がインストールされているはずなので、Rakefile に下記の行を加える。

load 'tasks/emoji.rake'
あとは bundle exec rake emoji を実行すれば /public/images/emoji 以下に画像はファイルが配置される。
これだけで絵文字が表示できるようになる。bundle と rake で導入できるので /public/images/emoji は .gitignore に加えておいてもいいかもしれない。大量に画像ファイルをリポジトリに登録したくないもんね

2016/01/09

オブジェクトが存在するときにだけ処理をする

みんな大好き Object#tap

でもこんなソースはあまり書きたくない。

foo.bar.baz.tap do |baz|
  do_something(baz) if baz
end

tap_if とかあれば便利じゃない?

foo.bar.baz.tap_if do |baz|
  do_something(baz)
end

# もしくは
foo.bar.tap_if(:baz) do |baz|
  do_something(baz)
end

似たようなことを考える人はいるものですでにある。→ tap-if | RubyGems.org | your community gem host

そして Rails やる上では try で事足りることがわかった。ブロック取れるのか。

foo.bar.baz.try do |baz|
  do_something(baz)
end

# もしくは
foo.bar.try(:baz) do |baz|
  do_something(baz)
end

結論:Object#try サイコー

2015/12/16

Railsのテストで定数のスタブが欲しい場合

Railsの ActiveSupport::TestCase は minitest を使っていて、minitest/mock には定数をスタブ化する機能はない。
adammck/minitest-stub-const ってのはあるけど、外部ライブラリ入れるほどじゃないよねって場合があるかもしれない。
そんな時はこんな感じのコードでいいんじゃないかな?
こういうトリッキーなのは最終手段的なもので、定数を環境ごとに設定ファイルに定義できるような gem を使うべきだとは思う。
やってみたらできたというお話

assert_change

ユニットテスト時によく使うメソッドの1つに assert_difference がありますね。
でもこれ差分を取るので数値を返す式にしか使えません。使おうとしたら + なんて演算子ないよってエラーが出ます。
仕方なしにこんなテストコードを書いてません?
test 'ユーザーが確認済みになること' do
  assert_not user.confirmed?
  # ここに承認処理
  assert user.confirm?
end
それならば、こんなメソッドを test_helper あたりに定義しておいて
test 'ユーザーが確認済みになること' do
  assert_change 'user.confirmed?' do
    # ここに承認処理
  end
end

# 実際の値を気にしなくていいならこんな使い方も
test '保存したら updated_at が更新される' do
  assert_change 'user.updated_at' do
    user.save
  end
end
と書くとちょっとスッキリしますね。コードはほぼほぼ assert_difference のパクリです。
expression の指定方法は必要になったら広げる感じでいいかな。

2015/12/14

jQuery使ってAJAXでファイルアップロード

なんか毎回毎回うろ覚えでちょろちょろ調べながら書いている気がするのでメモとして 今回はdragoverを補足してキャンセルしておかないとdropイベントを補足してくれなかったのにハマった
スタイル変えたり、イベント伝播のキャンセル処理は共通なので class で処理して共通jsに置く。
ドロップされた時の処理は、アップロード先やアップロード後の処理など画面ごとになる可能性が高いので id を使って個別jsに置くイメージ

2015/12/10

Deviseの有効期限設定をテストする

設定ファイル config/initializers/devise.rb はこんな感じとする。
Devise.setup do |config|
  # 省略
  config.reset_password_within = 30.minutes
  config.confirm_within = 30.days
  # 省略
end
テストコードはこんな感じでかけた。 Recoverable では reset_password_sent_at と reset_password_within.ago を比較している。
ActiveSupport::Duration#agoまで遡ると初期値である Time.current に対して演算していたので、最初は Time.stub(:current, ... のようにしていた。
しかし、Confirmable では、合算値を Time.now と比較している。そのため、Time.current は通らないのでこのやり方ではだめだった。
結局は Time.now をスタブ化したらよかったわけだが、こういう同質的な処理は同じ書き方をしてほしいなーとおもった昼下がり。

2015/12/09

Deviseにてシステムからユーザーのメールアドレスを変更してもメールを送信しない

Devise を使用していて、手順を踏まずにシステムにてメールアドレスを変更すると保存時にメールが送信されてしまう。
メールを送信したくない場合は skip_reconfirmation! を使用する。 なお、新規作成時に確認手順を飛ばしたい場合は skip_confirmation! を使う。

2015/12/04

ArelでのOR検索ついでにごにょごにょいじってみた

1つのキーワードで複数テーブルの複数カラムをあいまい検索ってよくある話ですね。
Arelを使って OR の LIKE 検索って冗長になりがちだけどそこそこパターン化出来そうだなーとつらつらとコード書いてみた。 実際は SimpleFinder までやると適用できるパターンが限定されるので、04_finally みたいなところが落とし所な気もする。
人によっては 03_inject_with_symbol ぐらいが一番可読性がいいって意見もありそう。
Arelを使って OR や LIKE をするメリットは scope にして merge した時に壊れないって記述をよく見ますが、こういう風に動的に対応箇所を増やせるように持っていくのも楽というのもメリットですね。文字列で where 内を書いていたらなかなかこうはできない
あ、こんな処理を他にもたくさん書かなければいけない場合は Squeel 入れたほうがいいと思います。

全然関係ないけど、gist って編集時はインスタンス変数に色つけてくれるのに閲覧時には色つけてくれないの何でだろ

[追記] たまたま Ruby on Rails Advent Calendar 2015 が空いてたので飛び入りしました。