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

Let's Hanami

y-yagi
April 18, 2017

Let's Hanami

About Hanami framework.

y-yagi

April 18, 2017
Tweet

More Decks by y-yagi

Other Decks in Technology

Transcript

  1. HANAMI "Hanami is a full-stack, lightweight, yet powerful web framework

    for Ruby" Webサービス用フレームワーク。MVCモデル。
  2. 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
  3. 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
  4. LIB DIRECTORY lib ├── bookshelf │ ├── entities │ │

    └── book.rb │ ├── mailers │ │ └── templates │ └── repositories │ └── book_repository.rb └── bookshelf.rb
  5. 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
  6. 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
  7. 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
  8. 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の書き方に大分近い
  9. 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クラスで行う(後述)
  10. 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] # => "[email protected]" puts params[:password] # => "secret" puts params[:address][:country] # => "Italy"
  11. 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?
  12. VIEWS Viewの定義はView Classで行う # apps/web/views/dashboard/index.rb module Web::Views::Dashboard class Index include

    Web::View end end クラス名にそのviewを使用するアクション名を指定する その為、アクション毎にClassが必要になる
  13. 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"が必 要 + 変数しか使えない
  14. 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
  15. 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"クラスのインスタンス
  16. ASSOCIATIONS 一応ある。が、 "As of the current version, Hanami supports associations

    as an experimental feature only for the SQL adapter. " とのこと
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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 %>
  22. 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>
  23. MAILERS class Mailers::Welcome include Hanami::Mailer from '[email protected]' to '[email protected]' subject

    'Welcome to Bookshelf' end Mailers::Welcome.deliver Mailers::Welcome.deliver(format: :html) Mailers::Welcome.deliver(format: :txt)
  24. 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
  25. 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"
  26. 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
  27. HANAMIの今後について Remove Global State Components IoC/Dependency Injection Immutability Converge Apps

    Settings to Project Settings Improve CLI commands Native Webpack Support 等が検討されている