Hanami で社内アプリの開発をやっているので、それでわかった小ネタを書きます。
DBのタイムゾーン
DBのタイムゾーンは Sequel の設定で行います。 config/initializers/sequel.rb
を作成して次のように設定しましょう。
Sequel.application_timezone = :tokyo
Sequel.database_timezone = :utc
Sequel.typecast_timezone = :utc
DB 中では UTC、データを取り出すときは JST で使えるようになります。
参考: http://sequel.jeremyevans.net/rdoc/classes/Sequel/Timezones.html
db rollback
Hanami で migration するときは hanami db migrate
コマンドを実行しますが、 hanami db rollback
はありません。
rollback
したいときは、マイグレーションバージョンを指定して migrate します。
hanami db migrate 20170911231450
環境変数
Hanami では dotenv を使って環境変数を読み込みます。しかし、 .env.local
や .env.development.local
のような local
系のファイルの読み込みに対応していません(今後改善されるかも: https://discourse.hanamirb.org/t/hanami-2-0-ideas/306/31 )。
また、 dotenv は Ruby の中でしか使えないので、 node で assets まわりの処理を行う場合等には環境変数を読めません。
これに関しては direnv を併用することにしました。direnv は .envrc
に定義した環境変数を使えるようにします。これを .env.local
の代わりに使います。 direnv であれば node からも環境変数を読み込めます。
そこで、 HANAMI::ENV
に依存して変わる環境変数は .env.development
と .env.test
に定義して、それ以外は .envrc
に定義するようにします。 .envrc
は gitignore してそれ以外はコミットします。
.env.development # コミットする
.env.test # コミットする
.envrc # ignore
.envrc.sample # コミットする
ActiveSupport 的なもの
https://github.com/hanami/utils にいくつか便利機能があります。
ただ、 Ruby そのものも機能が増えているので、例えば blank?
程度であれば hoge&.empty?
で十分な場合も多々あります。
ActiveSupport::Time 的なもの
時間に関する比較は https://github.com/zverok/time_math2 が Hanami の設計思想に近くて使いやすいように思いました。
ActiveSupportとの比較もあります http://zverok.github.io/blog/2016-06-27-time_math2.html
Session の利用
Session はデフォルトでは使用できないので http://hanamirb.org/guides/1.0/actions/sessions/ を読んで使えるようにしてください。
Rack::Test
Hanami の Controller のテストは http://hanamirb.org/guides/1.0/actions/testing/ にあるように call
メソッドの戻り値で行うことが出来ます。ただ、 session を利用する場合、この方法では session 関係のメソッドをスタブしなければならなくなるので、わりと微妙です。そこでもう素直に Rack::Test を使います。
Rack::Test
を動かすには次のように Web::Application
を app
メソッドで動かします。(test-unitの例なので minitest や rspec の場合はよしなにやってください。)
require_relative 'test_helper'
require 'rack/test'
class RackTestHelper < Test::Unit::TestCase
include Rack::Test::Methods
def app
Rack::Builder.new.run Web::Application.new
end
end
i18n
- i18n
- i18n-debug ... i18nのキーを表示するgem
上記の gem を使います。 i18n-debug はなくてもいいんですけど、あると便利です。
config/initializers/i18n.rb
を次のように定義します。
require 'i18n'
require 'i18n/debug' if ENV['I18N_DEBUG'] == 'true'
I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
I18n.load_path = Dir[
Hanami.root.join('config/locales/*.yml').to_s,
Hanami.root.join('config/locales/**/*.yml').to_s
]
I18n.backend.load_translations
I18n.enforce_available_locales = false
I18n.config.default_locale = 'ja'
これで I18n.t
や I18n.l
のようなメソッドを使えます。
i18n を便利に使えるようにヘルパーメソッドを定義します。
# lib/locale_helper.rb
require 'hanami/utils/string'
module LocaleHelper
def t(key, options = {})
::I18n.t(key, default_options(key).merge(options))
end
private
def default_options(key)
if key.start_with?('.')
# split view class name
app, _, controller, action = self.class.name.split('::').map {|class_name|
Hanami::Utils::String.new(class_name).underscore
}
{scope: "#{app}.#{controller}.#{action}"}
else
{}
end
end
end
こんな感じの module を用意しておいて、 apps/web/application.rb
で読み込みます。
module Web
class Application < Hanami::Application
configure do
view.prepare do
include Hanami::Helpers
include Web::Assets::Helpers
include LocaleHelper
end
これにより view の中で t('.submit')
のようにすると path に対応したキーを読み込んで i18n を動かします。
バリデーションエラーの i18n
Hanami は Action で parameter のバリデーションを書けます。
http://hanamirb.org/guides/1.0/actions/parameters/#whitelisting
しかし、実のところこの実装はちょっと微妙です。 controller の params
メソッドでは Hanami::Validation の custum predicate や i18n 機能を使えません。
controller の params
が何をやっているかというと、 Hanami::Validation の validations
に処理を委譲するのとほぼ同じことをやっています。
この部分の実装は https://github.com/hanami/controller/blob/v1.1.0.beta1/lib/hanami/action/validatable.rb#L97 です。
def params(klass = nil, &blk)
if klass.nil?
klass = const_set(PARAMS_CLASS_NAME, Class.new(Params))
klass.class_eval { params(&blk) }
end
@params_class = klass
end
このコードは少しわかりづらいですが、引数の klass
を渡さない場合、 Hanami::Action::Params.params
に引数 blk
を委譲します。
Hanami::Action::Params.params
の実装は https://github.com/hanami/controller/blob/v1.1.0.beta1/lib/hanami/action/params.rb#L57 で
def self.params(&blk)
validations(&blk || ->() {})
end
こうなっていて、この validations
というのは Hanami::Validations
の https://github.com/hanami/validations/blob/v1.1.0.beta1/lib/hanami/validations.rb#L93 この部分になります。
このメソッドは本来、
class Signup
include Hanami::Validations
validations do
required(:name).filled
end
end
のように使います。
そして、 i18n の設定は、
class Signup
include Hanami::Validations
messages :i18n
validations do
required(:name).filled
end
end
このように記述します。
Hanami::Action::Params.params
は Hanami::Validations.validations
とほぼ同じものなので、 params
のなかに messages
を書くと
class Signup
include Hanami::Validations
validations do
messages :i18n
required(:name).filled
end
end
と、書いていることと同じことになり、うまく動きません。
まぁ、ちょっとどうかと思う実装なのですが、そういう仕様なので仕方がありません。
ではどうすればいいかというと、最初の Hanami::action.params
の引数 klass
を渡してやります。
module Web::Controllers::Books
class Create
# 略
params Class.new(Hanami::Action::Params) do
messages :i18n
validations do
required(:book).schema do
required(:title).filled(:str?)
required(:author).filled(:str?)
end
end
end
# 略
end
end
i18n設定とかはどうせどの params でも使うので、下記の issue のコメントに書いたような感じで
Hanami::Action::Paramsを継承した基底クラスを作って、設定をまとめておくと便利です。
https://github.com/hanami/controller/issues/209#issuecomment-300771890
Interactorによるi18n
僕としては Controller で params を処理するより Hanami::Interactor
を用いてサービス層を作る方が良いのではないかと思っています。
http://magazine.rubyist.net/?0056-hanami#l15 ここで作った Interactor を前提にして Validation の基底 module を作ります。
require 'dry/validation/messages/i18n'
module AbstractValidation
def self.included(klass)
klass.class_eval do
include Hanami::Validations
messages :i18n
end
end
end
で、これを使って Interactor を作ります。
# lib/bookshelf/interactors/user_Interactor/create.rb
module UserInteractor
class Create
class Validation
include AbstractValidation
validations do
required(:email).value(:filled?)
required(:password).value(:filled?, size?: 8..40, format?: /\A[\w!$%@#123]+\z/).confirmation
required(:password_confirmation).filled(:str?)
end
end
File Upload
https://github.com/katafrakt/hanami-shrine これでできます。ただ、 plugin を色々増やせるというものなので、ちょっと冗長な気もします。
認証関係
次のような Rack ベースの gem は普通に使えます。
- Warden
- OmniAuth
参考: https://www.monterail.com/blog/2016/hanami-with-oauth
omniauth-hanami という gem もありますが、素で OmniAuth を使うのとあまり変わらないように感じたので使いませんでした。
Warden は strategy 使わないならあまり使う理由ないので session 使って自分で実装しても対して手間は変わりません。
また http://awesome-hanami.org/#awesome-hanami-hanami-gem-list-authentication-and-oauth にも認証関係の gem があります。