Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Let's Hanami

E67dcdafbf6ec9af7b0fac8835dd2f58?s=47 y-yagi
April 18, 2017

Let's Hanami

About Hanami framework.

E67dcdafbf6ec9af7b0fac8835dd2f58?s=128

y-yagi

April 18, 2017
Tweet

Transcript

  1. LET'S HANAMI y-yagi@Ginza.rb 第46回

  2. おしながき Hanamiとは Hanami v1.0.0 各ライブラリ概要 Hanamiの今後

  3. HANAMI 作者は 氏 昔はフリーランスだったが、今は の中の人 最初のリリースは2014/01/01 最新バージョンは1.0.0(2017/04/06)リリース Luca Guidi DNSimple

    Welcome Luca Guidi - DNSimple Blog
  4. HANAMI "Hanami is a full-stack, lightweight, yet powerful web framework

    for Ruby" Webサービス用フレームワーク。MVCモデル。
  5. HANAMI Hanamiを作成した経緯についてはLuca Guidi氏のブログ に説明がある (超意訳すると)モノリシックアプリケーションが辛くなったか ら Introducing Lotus

  6. HANAMI 最初は"Lotus"という名前だったが、2016/01/22 に"Hanami"に変更された IBMの中の人に指摘された なんやかんやあってHanamiになった 詳細な説明は 参照 Project name is

    unauthorized use of a registered trademark? Lotus is now Hanami
  7. HANAMI 全体的に作り大分シンプル。普通にRubyのオブジェクトで 処理を行うようになっている。 メタ的な処理も少ない

  8. V1.0.0 production ready new core team automatic logging logger rotation

    enhanced model generator faster boot 等々
  9. V1.0.0 色々と細かな対応が入ったものの、production readyで ある事を表明する為に1.0.0にしたのではと思われる バラバラだった各モジュールのバージョンも、このタイミ ングで1.0.0に統一された あとCore Teamに一気に人が増えた "Community" 担当がいるのが珍しい印象

    Hanami | Team
  10. V1.0.0 なお、今後のRelease Managementについてはまだ相談 中らしい 今後メジャーバージョンが上がっても、アップグレードが 辛い事にならないよう気をつけたいとは思っているらし い Hanami Release Management

    - Proposal - Hanami
  11. ARCHITECTURES

  12. ARCHITECTURES Hanamiのarchitectureは と をベースに考えられている(らしい) Application CoreとDelivery Mechanismsを分離出来る ようになっている The Clean

    Architecture MonolithFirst Hanami | Guides - Architectures: Container
  13. DIRECTORY "bookshelf" というプロジェクトがあった場合、ルートディレク トリ配下は下記のような構成になる tree -L 2 . ├── Gemfile

    ├── Gemfile.lock ├── Rakefile ├── apps │ └── web ├── config │ ├── boot.rb │ ├── environment.rb │ └── initializers ├── config.ru ├── db │ ├── migrations │ └── schema.sql ├── lib │ ├── bookshelf
  14. DIRECTORY "apps"配下にはcontroller / view / assets 等が格納さ れる "libs"配下にはentity /

    repository / mailerが格納される
  15. APPS DIRECTORY アプリ名(下記例だと"web")配下に各種ファイルが格納され る apps └── web ├── application.rb ├──

    assets │ ├── favicon.ico │ ├── images │ ├── javascripts │ └── stylesheets ├── config │ └── routes.rb ├── controllers │ ├── books │ │ ├── create.rb │ │ ├── index.rb │ │ └── new.rb │ └── home │ └── index.rb
  16. LIB DIRECTORY lib ├── bookshelf │ ├── entities │ │

    └── book.rb │ ├── mailers │ │ └── templates │ └── repositories │ └── book_repository.rb └── bookshelf.rb
  17. DIRECTORY "Application Core"となるファイルをlibに、"Delivery Mechanisms"の為のファイルを"apps"に格納するように なっている となると、"mailer"が"lib"にあるのはちょっと違和感があ るのだが、今後"apps"配下に移動予定とのこと https://discourse.hanamirb.org/t/hanami-2-0- ideas/306/10

  18. DIRECTORY "admin"用のUIを追加した場合は下記のようになる apps ├── admin │ ├── application.rb │ ├──

    assets │ │ ├── favicon.ico │ │ ├── images │ │ ├── javascripts │ │ └── stylesheets │ ├── config │ │ └── routes.rb │ ├── controllers │ ├── templates │ │ └── application.html.erb │ └── views │ └── application_layout.rb └── web ├── application.rb
  19. DIRECTORY 先のディレクトリ構成の場合"bookshelf"というプロジェク ト配下に、 "web"と"admin"という2つのアプリケーション がいる状態になる 異なるアプリ間で、Coreとなるファイルを共有出来るよう に、"lib"配下にmodelが存在するようになっている

  20. GEMFILE デフォルトで生成されるGem leは下記の通り(DBに PostgresSQLを指定した場合) source 'https://rubygems.org' gem 'rake' gem 'hanami',

    '1.0.0.rc1' gem 'hanami-model', '~> 1.0.0.rc1' gem 'pg' group :development do # Code reloading # See: http://hanamirb.org/guides/projects/code-reloading gem 'shotgun' end group :test, :development do gem 'dotenv', '~> 2.0' end
  21. APPLICATION.RB アプリの設定は(rootディレクトリ、load path等)は全 て"application.rb"に定義する # apps/web/application.rb require 'hanami/helpers' require 'hanami/assets'

    module Web class Application < Hanami::Application configure do ## # BASIC # # Define the root path of this application. # All paths specified in this configuration are relative to path below. # root __dir__ # Relative load paths where this application will recursively load the
  22. APPLICATION.RB デフォルトでdotenvを使うようになっており、環境ごとの値 (DBの接続先、session用secret 等)はそちらを使用して定義 するようになっている

  23. ROUTING

  24. ROUTING Rackコンパチ HTTP verb、path、endpointという書き方 HTTP verbは、GET, POST, PUT, PATCH, DELETE,

    TRACE 及び OPTIONSが使用出来る
  25. ROUTING "resources"や"namespace"も使える get '/hello', to: endpoint get '/', to: 'home#index',

    as: :home get '/books/:id', to: 'books#show' resources :books resources :books, only: [:new, :create, :show] resources :books, except: [:index, :edit, :update, :destroy] namespace 'docs' do get '/installation', to: 'docs#installation' get '/usage', to: 'docs#usage' end Railsのroutingの書き方に大分近い
  26. ROUTING Routingを取得する為のヘルパーメソッドもある <%= routes.path(:greeting) %> <%= routes.url(:greeting) %> または <%=

    routes.greeting_path %> <%= routes.greeting_url %>
  27. ACTIONS

  28. ACTIONS Railsと異なり、1つのクラスに1つのアクションを定義する "index"アクションを定義する場合 # apps/web/controllers/dashboard/index.rb module Web::Controllers::Dashboard class Index include

    Web::Action def call(params) self.body = 'OK' end end end 必要なのは、"Action" moduleのincludeと、"call"メソッド を定義する事だけ "view"の定義はViewクラスで行う(後述)
  29. ACTIONS 普通にRubyのクラスとして扱われる為、コンストラクタを定 義する事も出来る # apps/web/controllers/dashboard/index.rb module Web::Controllers::Dashboard class Index include

    Web::Action def initialize(greeting: Greeting.new) @greeting = greeting end def call(params) self.body = @greeting.message end end end
  30. PARAMS params変数を操作すればOK module Web::Controllers::Dashboard class Index include Web::Action def call(params)

    self.body = "Query string: #{ params[:q] }" end end end
  31. PARAMS ホワイトリスト処理は "params"メソッドで行う module Web::Controllers::Signup class Create include Web::Action params

    do required(:email).filled required(:password).filled required(:address).schema do required(:country).filled end end def call(params) puts params[:email] # => "alice@example.org" puts params[:password] # => "secret" puts params[:address][:country] # => "Italy"
  32. PARAMS validationも書ける # apps/web/controllers/signup/create.rb module Web::Controllers::Signup class Create include Web::Action

    MEGABYTE = 1024 ** 2 params do required(:name).filled(:str?) required(:email).filled(:str?, format?: /@/).confirmation required(:password).filled(:str?).confirmation required(:terms_of_service).filled(:bool?) required(:age).filled(:int?, included_in?: 18..99) optional(:avatar).filled(size?: 1..(MEGABYTE * 3)) end def call(params) if params.valid?
  33. PARAMS validation処理にはHanami::Validationsを使用している Hanami::Validationsは のラッパーとい う位置づけになっており、実際のvalidate処理はdry- validationで行われている 最初は独自で実装を行おうとしていたが、色々な議論の果 てにdry-validationを使うようになった 詳細は、 を参照

    dry-rbについては 参照 dry-validation Predicates by jodosha · Pull Request #98 · hanami/validations Introduction_to_dry-rb
  34. EXPOSURES Railsと違い、インスタンス変数を宣言しただけではviewで その変数を使用する事が出来ない viewで使いたい変数は、明示的に"expose"メソッドを使っ て宣言する必要がある # apps/web/controllers/dashboard/index.rb module Web::Controllers::Dashboard class

    Index include Web::Action expose :greeting def call(params) @greeting = "Hello" @foo = 23 end end end 上記例だと、"greeting"はviewから使えるが、"foo"はview からは使えない
  35. その他 "request" / "reponse"は普通にRackのクラスが使用出 来る 独自middlewareの追加 cookies sessions デフォルトでは無効になってる アクション毎の前処理(before)

    / 後処理(after) Error Handling Module Inclusion 等々の機能がある
  36. VIEWS

  37. VIEWS Viewの定義はView Classで行う # apps/web/views/dashboard/index.rb module Web::Views::Dashboard class Index include

    Web::View end end クラス名にそのviewを使用するアクション名を指定する その為、アクション毎にClassが必要になる
  38. VIEWS View Classとtemplateはコンテキストが一緒なので、 View Classで定義したメソッドは、templateが呼び出す事 が可能 # apps/web/templates/dashboard/index.html.erb <h1><%= title

    %></h1> # apps/web/views/dashboard/index.rb module Web::Views::Dashboard class Index include Web::View def title 'Dashboard' end end end 当然controllerで定義する事も可能だが、"expose"が必 要 + 変数しか使えない
  39. TEMPLATE デフォルトではクラス名に指定された名前のファイルを使 用する "Web::Views::Dashboard::New" というクラス名だった 場合、デフォルト は"web/templates/dashboard/new.html.erb" 任意のテンプレートを使用したい場合は、"template"メソッ ドで指定する module

    Web::Views::Books class Create include Web::View template 'books/new' end end
  40. TEMPLATE デフォルトのtemplate engineはerb 当然haml / slimもgemを追加すれば使用可能 サポートしているtemplate engineについては、 参照 Hanami

    | Guides - View Templates
  41. LAYOUT Layoutも指定可能 デフォルトでは"application.html.erb"が使用される Layoutの指定は、グローバル、アクション単位、どちらでも 可能 アクション単位の場合は、view classでlayout"メソッドを 呼び出す module Web::Views::UserSessions

    class New include Web::View layout :login end end
  42. LAYOUT Layoutもクラスで管理されているので、Layoutを追加した い場合、Layout用のクラスを追加する必要がある # app/web/views/login_layout.rb module Web module Views class

    LoginLayout include Web::Layout end end end
  43. MODELS

  44. MODELS Railsと異なり、エンティティとpersistence layerに対する 処理を定義するクラスが明確に分かれている それぞれ "entities"、"repositories"配下に定義する

  45. MODELS "book"というmodelがあった場合 # db/migrations/20170326014641_create_books.rb Hanami::Model.migration do change do create_table :books

    do primary_key :id column :title, String, null: false column :author, String, null: false column :created_at, DateTime, null: false column :updated_at, DateTime, null: false end end end # lib/bookshelf/entities/book.rb class Book < Hanami::Entity
  46. MODELS "book" に対するCRUDは"BookRepository"を使用して行う repository = BookRepository.new # => #<BookRepository relations=[:books]>

    book = repository.create(title: "Hanami", author: "Luca Guidi") # => #<Book:0x0056004d6addc8 @attributes={:id=>4, :title=>"Hanami", :author= book = repository.find(book.id) # => #<Book:0x0056004d6a3c10 @attributes={:id=>4, :title=>"Hanami", :author= book = repository.update(book.id, title: "Hanami Book") # => #<Book:0x0056004d67ef50 @attributes={:id=>4, :title=>"Hanami Book" repository.all # => [#<Book:0x0056004d66b9a0 @attributes={:id=>1, :title=>"TDD", :author=> 戻り値は"Book"クラスのインスタンス
  47. MODELS engineには を使用している ROMについては を参照 昔(Lotus時代)はストレージにRDBMS以外も使用出来る ようになっていたが、現状サポートしているのはRDBMSの み rom側がRDBMS以外もサポートしているので、いずれ サポートするかも

    ROM New Engine (ROM) Introduction_to_rom-rb
  48. ASSOCIATIONS 一応ある。が、 "As of the current version, Hanami supports associations

    as an experimental feature only for the SQL adapter. " とのこと
  49. ASSOCIATIONS # db/migrations/20170402002401_create_users.rb Hanami::Model.migration do change do create_table :users do

    primary_key :id column :name, String, null: false column :created_at, DateTime, null: false column :updated_at, DateTime, null: false end end end # db/migrations/20170402002500_create_todos.rb Hanami::Model.migration do change do create_table :todos do
  50. ASSOCIATIONS class UserRepository < Hanami::Repository associations do has_many :todos end

    def create_with_todos(data) assoc(:todos).create(data) end def find_with_todos(id) aggregate(:todos).where(id: id).as(User).one end end
  51. ASSOCIATIONS repository = UserRepository.new # => #<UserRepository relations=[:users :todos]> repository.create_with_todos(name:

    "Alexandre Dumas", todos: [{title: # => #<User:0x00564d5ec98900 @attributes={:id=>1, :name=>"Alexandre Dumas", user.todos # => [#<Todo:0x00564d5ebd5fb8 @attributes={:id=>1, :user_id=>1, :title=>"Cre user = repository.find(user.id) user.todos # => nil user = repository.find_with_todos(user.id) user.todos # => [#<Todo:0x00564d5ebd5fb8 @attributes={:id=>1, :user_id=>1, :title=>"Cre
  52. ASSOCIATIONS Active Recordのようによしなにしてくれる訳ではないの で、自前で色々頑張る必要がある

  53. MIGRATIONS

  54. MIGRATIONS Hanami::Model.migration do change do create_table :books do primary_key :id

    foreign_key :author_id, :authors, on_delete: :cascade, null: false column :code, String, null: false, unique: true, size: 128 column :title, String, null: false column :price, Integer, null: false, default: 100 # cents check { price > 0 } end end end
  55. MIGRATIONS "change"はRails同様、リバーシブルでよしなにやってくれ る "up"/"down"も使える migrationはSequel のメソッドを使用しているだけなので、 詳細はSequelのdoc( )参照 Sequel自体については 参照

    schema_modi cation.rdoc About Sequel
  56. HELPERS

  57. HELPERS view用のhelpersメソッドも提供されている 分Railsを意識したメソッド名になっている "link_to"とか、"form_for"とか

  58. HELPERS <%= form_for :book, routes.books_path do div class: 'input' do

    label :title text_field :title end div class: 'input' do label :author text_field :author end div class: 'controls' do submit 'Create Book' end end %>
  59. HELPERS <form action="/books" method="POST" accept-charset="utf-8" id="book-form" <input type="hidden" name="_csrf_token" value="c5dcffa7fcff2e35ed7e3da2c01

    <div class="input"> <label for="book-title">Title</label> <input type="text" name="book[title]" id="book-title" value=""> </div> <div class="input"> <label for="book-author">Author</label> <input type="text" name="book[author]" id="book-author" value=""> </div> <div class="controls"> <button type="submit">Create Book</button> </div> </form>
  60. MAILERS

  61. MAILERS 一つのメール毎に一つのメールクラスを定義する Railsのように、一つのMailerクラス複数メソッドを定義 は出来ない

  62. MAILERS class Mailers::Welcome include Hanami::Mailer from 'noreply@bookshelf.org' to 'user@example.com' subject

    'Welcome to Bookshelf' end Mailers::Welcome.deliver Mailers::Welcome.deliver(format: :html) Mailers::Welcome.deliver(format: :txt)
  63. MAILERS メール文面はRails同様templateで管理 デフォルトはerb バックグラウンドジョブとかは今は無いので、その辺りは自 前で頑張る必要がある

  64. COMMANDS

  65. COMMANDS 最低限必要そうな物は提供されている感じ 各種generator railsにおけるsca old相当のgeneratorは無い generateはあるがdestoryは無い db系(migrate、drop、create) routes railsのようにbin配下にコマンドは提供していないの で"bundle

    exec hanami"で実行する
  66. COMMANDS bundle exec hanami --help Commands: hanami assets [SUBCOMMAND] #

    Manage assets hanami console # Starts a hanami console hanami db [SUBCOMMAND] # Manage set of DB operations hanami destroy [SUBCOMMAND] # Destroy hanami classes hanami generate [SUBCOMMAND] # Generate hanami classes hanami help [COMMAND] # Describe available commands or one specifi hanami new PROJECT_NAME # Generate a new hanami project hanami rackserver # [private] hanami routes # Prints the routes hanami server # Starts a hanami server hanami version # Prints hanami version
  67. ASSETS

  68. ASSETS assetファイル(JS, css, image等)を管理する為の仕組み sprockets的な assetsのコンパイル(scss -> cssこの変換)やFingerprint を付与する為の仕組みがある

  69. ASSETS # apps/web/application.rb ... configure do # ... assets do

    end end configure :production do assets do fingerprint true end end ... <%= javascript 'application' %> # => <script src="/assets/application-d1829dc353b734e3adc24855693b70f9.js" <%= stylesheet 'application' %> # => <link href="/assets/application-d1829dc353b734e3adc24855693b70f9.css"
  70. ASSETS Hanamiはプリプロセッサに を使用しており、tiltがサポ ートしているEngineであれば一通り使える babelも使える "hanami assets precompile" コマンドでコンパイルを明 示的に行う事も可能

    tilt
  71. CDN con gにCDNの設定を書くことが出来る # apps/web/application.rb assets do # ... fingerprint

    true # CDN settings scheme 'https' host '123.cloudfront.net' port 443 end <%= stylesheet 'application' %> # => <link href="https://123.cloudfront.net/assets/application-9ab4d1f57027f0
  72. HANAMIの今後について

  73. FUTURE OF HANAMI Hanamiはdiscourseを使用しており、今はHanami 2.0に 向けてのアイディア集めが行われている Hanami 2.0 Ideas -

    Proposal - Hanami
  74. HANAMIの今後について Remove Global State Components IoC/Dependency Injection Immutability Converge Apps

    Settings to Project Settings Improve CLI commands Native Webpack Support 等が検討されている
  75. まとめ 相変わらず各レイヤーが綺麗に分かれている Railsのようなfat xxx にはなりづらそう 0.5.0の頃から比べると大分使いやすくなった印象 エンティティのアトリビュートとDBのカラムのマッピング を自分でやらなくて良くなったのは良かった

  76. まとめ i18nのようにまだ足りない機能はあるものの、使えるレベル にはなっている印象 Railsが辛くなった方は利用を検討してみると良いのではと 思います