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

viewing ruby blossom rdrc2017

viewing ruby blossom rdrc2017

Anton Davydov

June 23, 2017
Tweet

More Decks by Anton Davydov

Other Decks in Programming

Transcript

  1. View Slide



  2. Hello reddotrubyconf!


    View Slide

  3. View Slide

  4. View Slide

  5. Anton Davydov
    github.com/davydovanton

    twitter.com/anton_davydov
    davydovanton.com

    View Slide

  6. View Slide

  7. View Slide

  8. View Slide

  9. OpenSource

    View Slide

  10. Hanami core

    View Slide

  11. ruby
    rom-rb
    dry-rb
    rails
    crystal
    etc

    View Slide

  12. moscow.rb

    View Slide

  13. hanami (花⾒見見)
    Hanami (花⾒見見, lit. "flower viewing") is the
    Japanese traditional custom of enjoying
    the transient beauty of flowers, flowers
    ("hana") in this case almost always
    referring to those of the cherry ("sakura")
    or, less frequently, plum ("ume") trees.

    View Slide

  14. View Slide

  15. View Slide

  16. Luca Guidi
    github.com/jodosha

    View Slide

  17. View Slide

  18. General Ideas

    View Slide

  19. Modularity
    Forget about fat models

    View Slide

  20. View Slide

  21. Simplicity and Lightweight
    Framework is only tool

    View Slide

  22. Architecturally Sound
    Isolation everywhere

    View Slide

  23. Pure Objects
    If you know ruby you know hanami

    View Slide

  24. Zero Monkey-Patching
    Don’t think framework or language

    View Slide

  25. railshurts.com/quiz

    View Slide

  26. railshurts.com/quiz

    View Slide

  27. Threadsafe

    View Slide

  28. View Slide

  29. hanami != rails


    View Slide

  30. Typical parts
    of any web project

    View Slide

  31. Web Project
    Business

    logic
    Data
    flow

    View Slide

  32. Data flow

    View Slide

  33. Container Architecture
    Clean Architecture

    View Slide

  34. apps/
    ├── admin
    │ ├── application.rb
    │ ├── assets
    │ │ └── ...
    │ ├── config
    │ │ └── ...
    │ ├── controllers
    │ │ └── ...
    │ ├── templates
    │ │ └── ...
    │ └── views
    │ └── ...
    └── web
    ├── ...

    View Slide

  35. apps/
    ├── admin
    │ ├── application.rb
    │ ├── assets
    │ │ └── ...
    │ ├── config
    │ │ └── ...
    │ ├── controllers
    │ │ └── ...
    │ ├── templates
    │ │ └── ...
    │ └── views
    │ └── ...
    └── web
    ├── ...

    View Slide

  36. apps/
    ├── admin
    │ ├── application.rb
    │ ├── assets
    │ │ └── ...
    │ ├── config
    │ │ └── ...
    │ ├── controllers
    │ │ └── ...
    │ ├── templates
    │ │ └── ...
    │ └── views
    │ └── ...
    └── web
    ├── ...

    View Slide

  37. App App App
    lib
    App App App
    lib
    App App App
    lib
    request

    View Slide

  38. App App App
    lib
    App App
    lib
    App App App
    lib
    request
    App

    View Slide

  39. Business Logic

    View Slide

  40. lib/
    ├── project_name
    │ ├── interactors
    │ │ └── create_user.rb
    │ ├── entities
    │ │ └── user.rb
    │ ├── mailers
    │ │ └── templates
    │ └── repositories
    │ └── user_repository.rb
    └── project_name.rb

    View Slide

  41. lib/
    ├── project_name
    │ ├── interactors
    │ │ └── create_user.rb
    │ ├── entities
    │ │ └── user.rb
    │ ├── mailers
    │ │ └── templates
    │ └── repositories
    │ └── user_repository.rb
    └── project_name.rb

    View Slide

  42. lib/
    ├── project_name
    │ ├── interactors
    │ │ └── create_user.rb
    │ ├── entities
    │ │ └── user.rb
    │ ├── mailers
    │ │ └── templates
    │ └── repositories
    │ └── user_repository.rb
    └── project_name.rb

    View Slide

  43. lib/
    ├── project_name
    │ ├── interactors
    │ │ └── create_user.rb
    │ ├── entities
    │ │ └── user.rb
    │ ├── mailers
    │ │ └── templates
    │ └── repositories
    │ └── user_repository.rb
    └── project_name.rb

    View Slide

  44. Gems

    View Slide

  45. hanami - Base repository, CLI
    router - Rack compatible HTTP router for Ruby
    controller - Full featured and fast actions for Rack
    utils - Ruby core extensions and class utilities
    model - Persistence with entities and repositories

    View Slide

  46. validations - Validations mixin for Ruby objects
    helpers - View helpers for Ruby applications
    view - Presentation with a separation
    assets - Assets management for Ruby
    mailer - Mail for Ruby applications

    View Slide

  47. Differences

    View Slide

  48. # rack
    class HelloApp
    def call(env)
    [200, { **env }, ['Hello!']]
    end
    end

    View Slide

  49. # hanami-router
    class HelloApp
    def call(env)
    [200, { **env }, ['Hello!']]
    end
    end
    router = Hanami::Router.new
    router.get '/', to: 'hello_app'

    View Slide

  50. # sinatra
    class Hello < Sinatra
    get '/' do
    'Hello!'
    end
    end

    View Slide

  51. # hanami
    Hanami::Router.new do
    get '/' do
    [200, { **env }, ['Hello!']]
    end
    end

    View Slide

  52. class InstitutionsController < ApplicationController
    load_and_authorize_resource :only => [:destroy,:edit,:new,:create,:update]
    before_filter :authenticate_user!, :except =>
    [:student_registration, :show, :validate_registration_pin, :result, :admission, :buy_registration_pin,:paygate_callback_failure, :p
    aygate_cancel, :paygate_pending, :paygate_callback_success, :pin_transaction_info_print]
    before_filter :find_institution, :except =>
    [:show,:index, :new, :create, :semesters_for_institute_type, :start_end_date_for_assessment_period, :courses_for_batch, :paygate_ca
    llback_failure, :paygate_cancel, :paygate_pending, :paygate_callback_success, :pin_transaction_info_print]
    before_filter :add_bread_crumb,:except => [:show]
    def paygate_callback_success
    @pay_gate_config = YAML::load(File.open("#{Rails.root}/config/pay_gate_config.yml"))[Rails.env]
    @payment = TransactionRecord.find_by_order_number(params[:OrderID])
    uri = URI("https://fidelitypaygate.fidelitybankplc.com/cipg/MerchantServices/UpayTransactionStatus.ashx")
    parameters = {:MERCHANT_ID => "#{@pay_gate_config['merchant_id']}", :ORDER_ID => "#{@payment.order_number}"}
    uri.query = URI.encode_www_form(parameters)
    result =open(uri).read
    result_hash = Hash.from_xml(result)
    record_payment_details(result_hash)
    if result_hash["CIPG"]["StatusCode"] == PaymentRecord::PAYMENT_SUCCESS_CODE
    if @payment.transactionable_type.eql?("PaymentRecord")
    redirect_to institution_fees_path(@payment.transactionable_type.fee.institution), :notice => "Payment transaction has been
    #{result_hash['CIPG']['Status']}"
    elsif @payment.transactionable_type.eql?("PinBuyerInfo")
    unless @payment.transactionable.pin_id.present?
    @registration = @payment.transactionable.registration
    @valid_registration_pin_groups = @registration.valid_registration_pin_groups
    @online_valid_registration_pin_groups = @valid_registration_pin_groups.where(:pin_available_type => 'Online')
    @offline_valid_registration_pin_groups = @valid_registration_pin_groups.where(:pin_available_type => 'Offline')
    @available_pin = nil
    @online_valid_registration_pin_groups.each do |vpg|
    if vpg.available_pins.present?
    @available_pin = vpg.available_pins.first
    break
    else
    next
    end
    end
    if !@available_pin.present?
    @offline_valid_registration_pin_groups.each do |vpg|
    if vpg.available_pins.present?
    @available_pin = vpg.available_pins.first
    break
    else
    next
    end
    end

    View Slide

  53. class InstitutionsController < ApplicationController
    load_and_authorize_resource :only => [:destroy,:edit,:new,:create,:update]
    before_filter :authenticate_user!, :except =>
    [:student_registration, :show, :validate_registration_pin, :result, :admission, :buy_registration_pin,:paygate_callback_failure, :p
    aygate_cancel, :paygate_pending, :paygate_callback_success, :pin_transaction_info_print]
    before_filter :find_institution, :except =>
    [:show,:index, :new, :create, :semesters_for_institute_type, :start_end_date_for_assessment_period, :courses_for_batch, :paygate_ca
    llback_failure, :paygate_cancel, :paygate_pending, :paygate_callback_success, :pin_transaction_info_print]
    before_filter :add_bread_crumb,:except => [:show]
    def paygate_callback_success
    @pay_gate_config = YAML::load(File.open("#{Rails.root}/config/pay_gate_config.yml"))[Rails.env]
    @payment = TransactionRecord.find_by_order_number(params[:OrderID])
    uri = URI("https://fidelitypaygate.fidelitybankplc.com/cipg/MerchantServices/UpayTransactionStatus.ashx")
    parameters = {:MERCHANT_ID => "#{@pay_gate_config['merchant_id']}", :ORDER_ID => "#{@payment.order_number}"}
    uri.query = URI.encode_www_form(parameters)
    result =open(uri).read
    result_hash = Hash.from_xml(result)
    record_payment_details(result_hash)
    if result_hash["CIPG"]["StatusCode"] == PaymentRecord::PAYMENT_SUCCESS_CODE
    if @payment.transactionable_type.eql?("PaymentRecord")
    redirect_to institution_fees_path(@payment.transactionable_type.fee.institution), :notice => "Payment transaction has been
    #{result_hash['CIPG']['Status']}"
    elsif @payment.transactionable_type.eql?("PinBuyerInfo")
    unless @payment.transactionable.pin_id.present?
    @registration = @payment.transactionable.registration
    @valid_registration_pin_groups = @registration.valid_registration_pin_groups
    @online_valid_registration_pin_groups = @valid_registration_pin_groups.where(:pin_available_type => 'Online')
    @offline_valid_registration_pin_groups = @valid_registration_pin_groups.where(:pin_available_type => 'Offline')
    @available_pin = nil
    @online_valid_registration_pin_groups.each do |vpg|
    if vpg.available_pins.present?
    @available_pin = vpg.available_pins.first
    break
    else
    next
    end
    end
    if !@available_pin.present?
    @offline_valid_registration_pin_groups.each do |vpg|
    if vpg.available_pins.present?
    @available_pin = vpg.available_pins.first
    break
    else
    next
    end
    end

    View Slide

  54. Rails and Hanami

    View Slide

  55. Controllers

    View Slide

  56. class UsersController < AC
    def new
    end
    def send_sms
    end
    def user_params
    end
    end
    Controllers: Rails

    View Slide

  57. class Cats::CruelsController < AC
    def index
    end
    def show
    end
    def user_params
    end
    end
    Controllers: DHH style

    View Slide

  58. Controllers: hanami action
    module Web::Controllers::Board
    class Index
    include Web::Action
    params do
    required(:email).filled
    end
    def call(params)
    end
    end
    end

    View Slide

  59. Controllers: hanami
    module Web::Controllers::Board
    class Index
    include Web::Action
    params do
    required(:email).filled
    end
    def call(params)
    end
    end
    end

    View Slide

  60. Controllers: hanami
    module Web::Controllers::Board
    class Index
    include Web::Action
    params do
    required(:email).filled
    end
    def call(params)
    end
    end
    end

    View Slide

  61. Model

    View Slide

  62. class User < ActiveRecord::Base

    include Gravtastic
    before_destroy :yank_gems
    has_many :rubygems, through: :ownerships
    validates :name, presence: true
    # ...
    end
    Model: Rails

    View Slide

  63. class User < ActiveRecord::Base

    include Gravtastic
    before_destroy :yank_gems
    has_many :rubygems, through: :ownerships
    validates :name, presence: true
    # ...
    end
    Model: Rails

    View Slide

  64. class User < ActiveRecord::Base

    include Gravtastic
    before_destroy :yank_gems
    has_many :rubygems, through: :ownerships
    validates :name, presence: true
    # ...
    end
    Model: Rails

    View Slide

  65. class User < ActiveRecord::Base

    include Gravtastic
    before_destroy :yank_gems
    has_many :rubygems, through: :ownerships
    validates :name, presence: true
    # ...
    end
    Model: Rails

    View Slide

  66. class User < ActiveRecord::Base

    include Gravtastic
    before_destroy :yank_gems
    has_many :rubygems, through: :ownerships
    validates :name, presence: true
    # ...
    end
    Model: Rails

    View Slide

  67. Model: hanami
    hanami + ROM = ❤

    View Slide

  68. Model: hanami entity
    class User < Hanami::Entity
    # ...
    end

    View Slide

  69. Model: hanami entity
    >> user = User.new(id: 1)
    => #1}>
    >> user.id
    => 1
    >> user.id = 1
    NoMethodError: undefined method `id=' for
    #1}>
    Did you mean? id

    View Slide

  70. Model: hanami entity
    >> user = User.new(id: 1)
    => #1}>
    >> user.id
    => 1
    >> user.id = 1
    NoMethodError: undefined method `id=' for
    #1}>
    Did you mean? id

    View Slide

  71. class UserRepository < Hanami::Repository
    associations do
    has_many :books
    end
    def find_by_name(name)
    users # => ROM relation
    users.where(name: name).limit(1).one
    end
    end
    Model: hanami repository

    View Slide

  72. >> repo = UserRepository.new
    => #
    >> repo.find(1)
    => #
    Model: hanami repository

    View Slide

  73. View

    View Slide

  74. View: Rails
    rails view (partials?)
    +
    rails helper

    View Slide

  75. View: Hanami
    hanami view (ruby class)
    +
    templates

    View Slide

  76. Assets

    View Slide

  77. Pros and Cons

    View Slide

  78. No magic

    View Slide

  79. module Web::Controllers::Board
    class Index
    include Web::Action
    def call(params)
    end
    end
    end

    View Slide

  80. Action test
    describe Web::Controllers::Board::Index do
    let(:action){ Board::Index.new }
    let(:params){ Hash[] }
    it 'is successful' do
    response = action.call(params)
    response[0].must_equal 200
    end
    end

    View Slide

  81. Action test
    describe Web::Controllers::Board::Index do
    let(:action){ Board::Index.new }
    let(:params){ Hash[] }
    it 'is successful' do
    response = action.call(params)
    response[0].must_equal 200
    end
    end

    View Slide

  82. Action test
    describe Web::Controllers::Board::Index do
    let(:action){ Board::Index.new }
    let(:params){ Hash[] }
    it 'is successful' do
    response = action.call(params)
    response[0].must_equal 200
    end
    end

    View Slide

  83. No monkey-patching

    View Slide

  84. Best practices

    View Slide

  85. Dependency Injection

    View Slide

  86. View Slide

  87. The logic separation

    View Slide

  88. View Slide

  89. TDD

    View Slide

  90. View Slide

  91. View Slide

  92. TDD

    View Slide

  93. Good but not great
    documentation

    View Slide

  94. Missing Gems

    View Slide

  95. WebSockets

    Pagination

    WebPack

    Devise
    2016

    View Slide

  96. WebSockets

    Pagination

    WebPack

    Devise
    Now

    View Slide

  97. View Slide

  98. awesome-hanami.org

    View Slide

  99. Good for new contributors
    projects

    View Slide

  100. octostar.herokuapp.com

    View Slide

  101. contributors.hanamirb.org

    View Slide

  102. Contacts
    hanamirb.org
    gitter.im/hanami/chat
    discuss.hanamirb.org

    View Slide

  103. github.com/davydovanton

    twitter.com/anton_davydov
    davydovanton.com
    Thank you ❤

    View Slide