Save 37% off PRO during our Black Friday Sale! »

The Pragmatic Hanami

B49aa473d5cd7f08cdce3d56ef837f29?s=47 kbaba1001
November 30, 2017

The Pragmatic Hanami

B49aa473d5cd7f08cdce3d56ef837f29?s=128

kbaba1001

November 30, 2017
Tweet

Transcript

  1. The Pragmatic Hanami Nov. 29. 2017 kbaba1001

  2. BIO • kbaba1001(Kazuki Baba) • Ruby 6 years • Digital

    nomad • Rubyist Magazine ◦ HanamiはRubyの救世主(メシア)となるか、愚かな星と散る のか
  3. Today, talk about... • Hanami Introduction • Hanami Architecture •

    Hanami Tips
  4. Hanami Introduction

  5. What is Hanami ? • Full-stack Ruby web framework •

    DDD (Domain Driven Design) • April 06, 2017, release v1.0.0
  6. Hanami vs Rails • Maintainability • Pure Object (Zero monkey-patching)

    • Multiple application support
  7. Hanami depends on gems • Rack • Tilt • DRY

    rb • ROM rb • Sequel
  8. Hanami Architecture

  9. None
  10. Hanami project directories project_name/ ├── apps/ │ ├── admin/ │

    └── web/ ├── config/ ├── db/ ├── lib/ │ ├── project_name/ │ └── project_name.rb ├── spec/ ├── public/
  11. “apps” and “lib” directories apps/web/ ├── application.rb ├── assets/ ├──

    config/ ├── controllers/ ├── templates/ └── views/ lib/ ├── project_name/ │ ├── entities/ │ ├── repositories/ │ └── interactors/ └── project_name.rb Application Layer Domain Layer
  12. Application Layer

  13. Application Layer • Routes • Controller • View/Template

  14. Routes get '/proc', to: ->(env) { [200, {}, ['Hello from

    Hanami!']] } get '/action', to: "home#index" get '/middleware', to: Middleware get '/rack-app', to: RackApp.new get '/rails', to: ActionControllerSubclass.action(:new)
  15. RESTful Resources resources :books, only: [:new, :create, :show] resource :account

  16. Routes file path • Hanami ◦ apps/web/config/routes.rb ◦ apps/admin/config/routes.rb ◦

    … and so on • Rails ◦ config/routes.rb
  17. Controller module Web::Controllers::Diaries class New include Web::Action def call(params) #=>

    [status, header, body] end end end
  18. View • View ◦ Ruby Class • Template ◦ HTML

    Template (erb/haml/slim...)
  19. View Class module Web::Views::Dashboard class Index include Web::View def title

    'Dashboard' end end end
  20. Template <h1><%= title %></h1> %h1= title Erb Haml

  21. Domain Layer

  22. Domain Layer • Model ◦ Entity ◦ Repository • Service

    (Interactor)
  23. Generate model Run hanami g model foo command, generate: •

    migration • entity • repository
  24. Migration # db/migrations/20170908025424_create_diaries.rb Hanami::Model.migration do change do create_table :diaries do

    primary_key :id column :body, String, null: false column :title, String column :created_at, DateTime, null: false column :updated_at, DateTime, null: false end end end
  25. Entity class Diary < Hanami::Entity end diary = Diary.new(title: 'learn

    ruby') diary.title #=> "learn ruby" diary.title = 'learn english' #=> NoMethodError: undefined method `title='
  26. Repository class DiaryRepository < Hanami::Repository associations do has_many :comments end

    def find_with_comment(id) aggregate(:comments).where(diaries_id: id).map_to(Diary).one end end
  27. Service(Interactor) module DiaryInteractor class Create include Hanami::Interactor expose :params, :diary

    def initialize(params) @params = params end def call @diary = DiaryRepository.new.create(params) end end end
  28. Hanami Tips

  29. Validation

  30. Validation (in action) module Web::Controllers::Users class Create include Web::Action params

    do required(:user).schema do required(:email) { filled? } end end # … end end
  31. Issue params do # do not work predicate :email?, message:

    'invalid email format' do |value| # ... end required(:user).schema do required(:email) { filled? & email? } end end
  32. Issue params Class.new(Hanami::Action::Params) do predicate :email?, message: 'invalid email format'

    do |value| # ... end validations do required(:user).schema do required(:email) { filled? & email? } end end end
  33. Issue params Class.new(Hanami::Action::Params) do predicate :email?, message: 'invalid email format'

    do |value| # ... end validations do required(:user).schema do required(:email) { filled? & email? } end end end
  34. Issue • depends on the implementation of Hanami::Action#params • Class.new(

    ) { } is not readable
  35. Solution • Independent validation class • Validate with service instead

    of action
  36. Independent validation class class DiaryInteractor::Create::Validation include Hanami::Validations predicate :email?, message:

    'invalid email format' do |value| # ... end validations do required(:email) { filled? & email? } end end
  37. Service Layer class DiaryInteractor::Create include Hanami::Interactor expose :params, :diary def

    initialize(params) @params = params end def call @diary = DiaryRepository.new.create(params) end def valid? DiaryInteractor::Create::Validation.new(@params).validate.success? end
  38. Timezone

  39. Timezone # config/initializers/sequel.rb Sequel.application_timezone = :tokyo Sequel.database_timezone = :utc Sequel.typecast_timezone

    = :utc
  40. i18n

  41. initializer # 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'
  42. view helper module LocaleHelper def t(key, options = {}) ::I18n.t(key,

    default_options(key).merge(options)) end def default_options(key) if key.start_with?('.') app, _, controller, action = self.class.name.split('::').map {|class_name| Hanami::Utils::String.new(class_name).underscore } {scope: "#{app}.#{controller}.#{action}"} else {} end
  43. Validation class module AbstractValidation def self.included(klass) klass.class_eval do include Hanami::Validations

    messages :i18n end end end class DiaryInteractor::Create::Validation include AbstractValidation end
  44. Webpack

  45. webpack • Output builded files under public directory • Use

    webpack-manifest-plugin ◦ output manifest.json • Load output files to view template
  46. manifest.json example { "web.css": "web-a95de7f402e5c515bf63.css", "web.js": "web-a95de7f402e5c515bf63.js" }

  47. view helper module ViewHelper def webpack_asset_path(filepath) manifest = JSON.parse(manifest_filepath.read) bundled_filename

    = manifest[filename.to_s] raw(Hanami.public_directory.join(bundled_filename)) end end
  48. Alternative ActiveSupport

  49. Alternative ActiveSuport • Hanami::Util ◦ https://github.com/hanami/utils • Time Math 2

    ◦ https://github.com/zverok/time_math2
  50. Hanami::Util https://github.com/hanami/utils

  51. Hanami::Util Example require 'hanami/utils/blank' Hanami::Utils::Blank.blank?('') # => true Hanami::Utils::Blank.blank?(nil) #

    => true Hanami::Utils::Blank.blank?('a') # => false
  52. Time Math 2 Time Math 2: TimeMath(Time.now).floor(:day).decrease(:week, 1).call ActiveSupport: 1.week.ago.beginning_of_day

  53. conclusion

  54. Today, talk about... • Hanami Introduction • Hanami Architecture •

    Hanami Tips
  55. important!! Please give me a Hanami’s job. http://www.gupipo.co.jp/