19
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Hanami関係の小ネタまとめ

Last updated at Posted at 2017-10-10

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::Applicationapp メソッドで動かします。(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

上記の 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.tI18n.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::Validationshttps://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.paramsHanami::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 があります。

19
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?