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

The Pragmatic Hanami

kbaba1001
November 30, 2017

The Pragmatic Hanami

kbaba1001

November 30, 2017
Tweet

More Decks by kbaba1001

Other Decks in Technology

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/