Hanamiで異なるappのroutesを使う場合

Hanami Guides / 1.1 / Routing / Basic Usage を見ると、Railsと同じように、
(Hanamiガイドより引用)

<%= routes.path(:greeting) %>
<%= routes.url(:greeting) %>
Or
<%= routes.greeting_path %>
<%= routes.greeting_url %>

としてroutingのパスが取得できる。

ただし、これはあくまでそのapp内でconfig/routes.rbで定義したルーティングのパスを取得だけとなる。異なるappのルーティングのパスを取得したい場合は、「app名.routes.ルーティング名_path」で指定ができる。

(例)
apps
 ├ gnote ← Gnoteアプリ
 ├ gnote_api ← GnoteApiアプリ

とあった場合に、Gnoteアプリから、GnoteApiアプリのlikesアクションのルーティングのパスを取得する場合は、Gnoteアプリ内で、「GnoteApi.routes.likes_path」と指定する。

なお、ルーティング名は、Railsと同じように、ターミナルから、「bundle exec hanami routes」を打てば表示される。その中のNameがルーティング名になる。

■参考サイト
Referencing a different app’s routes in Hanami

HanamiのRepositoryで作成した独自SQLクエリーのデータにアクセスする

Repositories: SQL Queries – Hanami に書かれているとおり、Repositoryに定義するだけだが、このままだとviewに渡されるまでメソッドが実行されないので、InteractorsとかControllerでクエリー結果のデータにアクセスしようと思っても、アクセスできない。

■HanamiのDBクエリー

HanamiのDBクエリーは、Ruby Object Mapper(ORM)Sequel が使われている。

以下、Hanami Guide v1.1からの引用。

Hanami queries are based on gems from ROM project, namely rom-repository and rom-sql. The gem rom-sql is itself based on Sequel project.
Learn more on how to craft queries with ROM and Sequel.

■最初から用意されているメソッドはアクセス可能

Repositories Overview #Interface – Hanami に書かれている、最初から用意されているallとかfirstなどのメソッドは、おそらくすぐにcallされているためか、データにアクセス可能。

■データにアクセスする方法

.call.collectionをつけると、クエリーを即時実行してデータにアクセスができる。

(例)
lib/gnote/repositories/message_repository.rb
—————————————————————–
class GnoteMessageRepository < Hanami::Repository
・・・
 def most_recent_posts(most_recent_start_date)
    gnote_messages
      .where(
        created_at: most_recent_start_date..CURRENT_DATE,
        is_deleted: nil
      )
      .order{ created_at.desc }
      .call.collection
  end
・・・
end
—————————————————————–

lib/gnote/interactors/index_messages.rb
—————————————————————–
require ‘hanami/interactor’
・・・
    def call
      @most_recent = @repository.most_recent_posts(MOST_RECENT_START_DATE) # call  lib/gnote/repositories/message_repository.rb
      Hanami.logger.debug @most_recent.inspect
    end
・・・
end
—————————————————————–

コンソールの出力結果
—————————————————————–
[tanebox] [DEBUG] [2018-01-05 23:02:48 +0700] [#2,
:created_at=>2018-01-05 07:51:54 UTC, :updated_at=>2018-01-05
07:51:54 UTC, :author_hash_ip=>nil, :content=>”カレー作ったえらい”,
:is_deleted=>nil}>, #1,
:created_at=>2018-01-04 13:35:21 UTC, :updated_at=>2018-01-04
13:35:21 UTC, :author_hash_ip=>nil, :content=>”早起きできた!”,
:is_deleted=>nil}>]
—————————————————————–

■.call.collectionをつけない場合

コンソールの出力結果(クエリー文が出力される)
—————————————————————–
[tanebox] [DEBUG] [2018-01-05 22:43:34 +0700] #= ‘2017-12-29’) AND (`created_at` <= ‘2018-01-06’) AND (`is_deleted` IS NULL)) ORDER BY `created_at` DESC”>>
—————————————————————–

■.callのみつけた場合

コンソールの出力結果(クエリー文とデータオブジェクトが出力される)
—————————————————————–
[tanebox] [DEBUG] [2018-01-05 22:53:52 +0700] #@source
=#= ‘2017-12-29’) AND (`created_at` <= ‘2018-01-06’) AND (`is_deleted` IS NULL)) ORDER BY `created_at` DESC”>>, @collection=[#2,
:created_at=>2018-01-05 07:51:54 UTC, :updated_at=>2018-01-05
07:51:54 UTC, :author_hash_ip=>nil, :content=>”カレー作ったえらい”,
:is_deleted=>nil}>, #1,
:created_at=>2018-01-04 13:35:21 UTC, :updated_at=>2018-01-04
13:35:21 UTC, :author_hash_ip=>nil, :content=>”早起きできた!”,
:is_deleted=>nil}>]>
—————————————————————–

■.callと.collectionの意味(推測)

ROMとSequelの両方のAPIドキュメントを見たがメソッドの解説を発見できなかったので以下推測。
・call → rubyのcallメソッドと同じ意味で、メソッドを即時実行(評価)する。.callをつけるまで実行されず(遅延評価)、逆にタイミングのよいときに実行させることができる。
・collection → collectionのattributeにアクセスする。.collectionをつけないと、@source(クエリー文)と@collection(データオブジェクト)が出力される。

■.callと.collectionをつけなかった場合のクエリー実行場所(推測)

Hanamiのソースコードを読んでないのであっているかわからないが、各レイヤーでdebugした結果だと以下のとおり。

ユーザーアクセス → Routes → Controller → Interactor → Repository, Entity → Interactor → Controller → |Viewレイヤーでクエリー実行| View, Template → 画面描画

■参考ソースコード

pinfluence/lib/pinfluence/repositories/moment_repository.rb

Hanamiのデバッグは Hanami.logger.debug

Hanamiでデバッグするときは、
「Hanami.logger.debug 調べたいもの.inspect」とすると、コンソールに「[web] [DEBUG] [2018-01-05 10:14:35 +0700] {:a=>1, :b=>2, :c=>3}」みたいな感じで 、中身が出力される。 (Raisだと、Rails.logger.debugっていうやつ)

(例)Hanami.logger.debug @post.inspect

■参考情報
Each project has a global logger available at Hanami.logger that can be used like this: Hanami.logger.debug “Hello”
Hanami – Logging

Hanamiでcustom helper

Custom Helpers – Hanami を参考に独自のヘルパーを作成したが、「NameError: uninitialized constant Helpers」が表示されてハマった。
原因は、Custom Helpers Guide – load_paths order issue #112 で書かれている通り、apps/web/application.rb の load_paths に記述する時に、helpersを最初に記述しておかないとダメだった。自分の場合はhelpersの記述を一番最後にしていたのでエラーが表示されていた。ちなみに同ファイルの「view.prepare」の記述については、特に順番は関係ない。

■設定のメモ

apps/web/application.rb
———————————————–
      view.prepare do
        include Hanami::Helpers # Hanamiのヘルパーモジュール読み込み
        include Web::Assets::Helpers # app/web/assets/helpersを作成して利用想定?
        include Web::Helpers::FormatDate # 今回カスタムヘルパーとして新しく記述した
      end
———————————————–

■注意事項

load_pathsとかの設定をしたら、Cmd-C、bundle exec hanami sで再起動すること。設定系なので記述したものがうまく反映されない場合があった。

■その他メモ

・appごとにヘルパー作成するのもよし、コードを共通化する場所に置いて使うかもよし。「In our settings (apps/web/application.rb), there is a code block that allows to share the code for all the views of our application.」 – Share Code – Hanami
・helpersを設置している方のコード。この方はload_pathsは使わずにヘルパー使いたい場所でincludeして読み込んでいる。 nfilzi/housing-list
・ヘルパーは全然関係ないけど、、.、Hanamiのアーキテクチャーの考え方とDry Rubyの実装方法 hanami-architecture – hanami

RubyのHanamiでInteractorを使う

シンプルでスケールしやすく、軽量で早い、そしてDDD(Domain Driven Development)思想を取り入れているフレームワークのHanamiでInteractorを使う方法についてのメモです。

■HanamiのGetting Started

HanamiのGetting StartedはInteractorを使うところがなく、Controllerにビジネスロジックを書いていますが、単一責任の考えより、HanamiではService層にあたるInteractorにロジックを切り出して書くことを推奨しています。

Hanami v1.1 Getting Started
※他の方が日本語で説明されているページもありますが本筋と関係ないので省略

■HanamiのInteractor

Getting Startedでは、Interactorの実装まで書かれておらず、個別のページで説明されています。これはまだ日本語版はないようです。内容はGetting Startedの続きのような形で書かれています。

Hanami v1.1 Architecture – Interactors

■Interactorの実装

上記をそのまま実装するだけですが、Controller側の実装は書かれていなかったため(テストコードはありましたが)、以下にController含めた、ポイントになる箇所のソースコードを記載します。

[lib/bookshelf/interactors/add_book.rb]
———————————————————————————-
require ‘hanami/interactor’

class AddBook
  include Hanami::Interactor

  expose :book

  def initialize(repository: BookRepository.new, mailer: Mailers::BookAddNotification.new)
    @repository = repository
    @mailer = mailer
  end

  def call(title:, author:)
    @book = @repository.create({title: title, author: author})
    @mailer.deliver
  end
end
———————————————————————————-
↑ Dependency Injection(依存性の注入)の考えより、initializeとcallで処理を分けています。これを分けずにcallに全部書くことも可能です。その場合は、initializeにparamsを引数にしてexposeし(@params = params)、callは何も引数を取らずリポジトリもcall内で取得、という書き方になります。

[apps/web/controllers/books/create.rb]
———————————————————————————-
module Web::Controllers::Books
  class Create
    include Web::Action

    expose :book

    params do
      required(:book).schema do
        required(:title).filled(:str?)
        required(:author).filled(:str?)
      end
    end

    def call(params)
      if params.valid?
        @book = AddBook.new.call(params[:book])

        redirect_to “/books”
      else
        self.status = 422
      end
    end
  end
end
———————————————————————————

↑ 念のため補足しますと、HanamiのInteractor説明ページのテストコード(Specのコード)をControllerにほとんど転記しているだけです。

以下引用:Hanami Interactor – Creating Book

Edit spec/bookshelf/interactors/add_book_spec.rb:
require 'spec_helper'

describe AddBook do
  let(:interactor) { AddBook.new }
  let(:attributes) { Hash[author: "James Baldwin", title: "The Fire Next Time"] }

  describe "good input" do
    let(:result) { interactor.call(attributes) }

    it "succeeds" do
      expect(result).to be_a_success
    end

    it "creates a Book with correct title and author" do
      expect(result.book.title).to eq("The Fire Next Time")
      expect(result.book.author).to eq("James Baldwin")
    end
  end
end

■エラーメッセージの例

・ArgumentError: unknown keyword: book
→ newにparamsを入れていませんか?(例)AddBook.new(params).call
newではなくcallに引数を入れます。

・ArgumentError: unknown keyword: title, author
→ newにparams[:book]を入れていませんか?(例)AddBook.new(params[:book]).call
newではなくcallに引数を入れます。

■参考になるサイト

以下はInteractorのcallではなくinitializeにparamsを引数にした書き方になっています。

Rubyist Magazine – HanamiはRubyの救世主(メシア)となるか、愚かな星と散るのか
→ Interactorの説明が非常に詳しく、分かりやすく書かれています

The Pragmatic Hanami by kbaba1001
→ 上記と同じ方が書かれていますが、こちらも分かりやすいです

ossboard – lib/interactorossboard – apps/controller
→ Hanamiで作っているossboardというプログラムがGitHubに載っているのでInteractorの実装方法を見ることができます

■その他

Hanami Bookshelf (example application) 
→ ちなみにHanamiのGetting StartedのソースはGitHubにあります

■(オマケ)initializeにparamsを引数にする場合のInteractorの書き方

[lib/bookshelf/interactors/add_book.rb]
———————————————————————————-
require ‘hanami/interactor’

class AddBook
  include Hanami::Interactor

  expose :book_attributes

  def initialize(params)
    @params = params
  end

  def call
    @book_attributes = BookRepository.new.create(@params[:book])
  end
end
———————————————————————————- 

[apps/web/controllers/books/create.rb]
———————————————————————————-
module Web::Controllers::Books
  class Create
    include Web::Action

    expose :book

    params do
      required(:book).schema do
        required(:title).filled(:str?)
        required(:author).filled(:str?)
      end
    end

    def call(params)
      if params.valid?
        #@book = BookRepository.new.create(params[:book]) #コメントアウト
        result = AddBook.new(params).call
        @book = result.book_attributes #.book_attributesを忘れずに

        redirect_to routes.books_path
      else
        self.status = 422
      end
    end
  end
end
———————————————————————————-

BracketsでRuby実行する拡張機能・インデント時にスペース2つ幅設定

■Bracketsとは
非常に優秀で使いやすくて高機能なSublime Textの代替できるレベルのAdobe製テキストエディター Brackets。これが無料は本当にすごい。拡張機能も豊富でなんでもできるし、デザインも素敵。
http://brackets.io/

■Brackets上でRubyプログラムを実行
拡張機能のBrackets Builderをインストールすると実行できるようになる。
(Sublime Textだと Command+B でbuild実行できるやつです)

1)ファイル – 拡張機能マネージャ – 検索窓で「Integrated Development」を検索してインストール
2)Bracketsを再起動(これ重要)
3)Command+Alt+B でRubyプログラムが実行できる(下にRun Resultが表示される)

【補足】
同じくプログラム実行できる拡張機能で「Brackets Builder」と「Brackets Builder Extended」(2つの違いはわからず)があるが、どちらも実行時の出力がちゃんとできず、文字列がおかしな表示になった。

■拡張機能のIntegrated Developmentで古いRubyのバージョンが実行される場合
設定ファイルの cmd が実行ファイルの場所を指定しているので、それを書き換えるだけ。
メニューの 編集 – Edit Builder で、実行したいRubyファイルの場所を指定する。

(例)
“cmd”: “/Users/***/.rbenv/shims/ruby $FILE”,

デフォルトだと「”cmd”: “/Users/***/.rbenv/shims/ruby $FILE”,」になっている。

Rubyでバージョンを確認するには、rubyファイル作って(.rbファイル)、「p RUBY_VERSION」を記述し、Integrated Development拡張機能をインストールした上で、Command+Alt+Bで確認できる。

【備考】
・自分の場合は、ちゃんとrbenvでインストールした$PATHを指定しているのに(しかも/usr/bin/よりも先頭に定義していて、$which rubyするとrbenvのパスが表示される)、なぜか/usr/bin/rubyのバージョンが優先実行されてしまったため、設定ファイルを編集した。
・Integrated Developmentの設定ファイルを編集する際は、jsonファイルなのでコメントアウト(#とか//とか)は使えません。直接書き換えるのがよいです。

■Bracketsでインデント幅をスペース2つ分に設定
1.タブの見え方を変更する場合
下部のステータスバーの一番右(画面から見るとつまり右下)の「タブ幅」を4から2に変更するだけ。ただし、見え方だけスペース2つ分になっているだけなので、他のエディターにコピペすると通常のタブと同じ幅になる。

2.スペース2つ分にする場合

Rubyなどでインデントする時にタブではなく、スペース2つ分の幅に設定をしたい場合は、右下の「タブ幅」をクリックすると、「スペース」に表示が切り替わるので、そこで数値を4から2に変更することでインデントがスペース2つ分の幅に変更される。

「”useTabChar”」という設定で制御しているらしく、Brackets – 環境設定 で「Brackets.json」ファイル(Bracketsの設定ファイル)を開いた状態で、上記の「タブ幅」から「スペース」に切り替えると、「”useTabChar”」の値がtrueからfalseにリアルタイムに変わる。