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. Hello reddotrubyconf!


    View full-size slide

  2. Anton Davydov
    github.com/davydovanton

    twitter.com/anton_davydov
    davydovanton.com

    View full-size slide

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

    View full-size slide

  4. 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 full-size slide

  5. Luca Guidi
    github.com/jodosha

    View full-size slide

  6. General Ideas

    View full-size slide

  7. Modularity
    Forget about fat models

    View full-size slide

  8. Simplicity and Lightweight
    Framework is only tool

    View full-size slide

  9. Architecturally Sound
    Isolation everywhere

    View full-size slide

  10. Pure Objects
    If you know ruby you know hanami

    View full-size slide

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

    View full-size slide

  12. railshurts.com/quiz

    View full-size slide

  13. railshurts.com/quiz

    View full-size slide

  14. hanami != rails


    View full-size slide

  15. Typical parts
    of any web project

    View full-size slide

  16. Web Project
    Business

    logic
    Data
    flow

    View full-size slide

  17. Container Architecture
    Clean Architecture

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  21. App App App
    lib
    App App App
    lib
    App App App
    lib
    request

    View full-size slide

  22. App App App
    lib
    App App
    lib
    App App App
    lib
    request
    App

    View full-size slide

  23. Business Logic

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  28. 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 full-size slide

  29. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  34. 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 full-size slide

  35. 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 full-size slide

  36. Rails and Hanami

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  39. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  42. class User < ActiveRecord::Base

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

    View full-size slide

  43. class User < ActiveRecord::Base

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

    View full-size slide

  44. class User < ActiveRecord::Base

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

    View full-size slide

  45. class User < ActiveRecord::Base

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

    View full-size slide

  46. class User < ActiveRecord::Base

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

    View full-size slide

  47. Model: hanami
    hanami + ROM = ❤

    View full-size slide

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

    View full-size slide

  49. 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 full-size slide

  50. 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 full-size slide

  51. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  55. Pros and Cons

    View full-size slide

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

    View full-size slide

  57. 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 full-size slide

  58. 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 full-size slide

  59. 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 full-size slide

  60. No monkey-patching

    View full-size slide

  61. Best practices

    View full-size slide

  62. Dependency Injection

    View full-size slide

  63. The logic separation

    View full-size slide

  64. Good but not great
    documentation

    View full-size slide

  65. Missing Gems

    View full-size slide

  66. WebSockets

    Pagination

    WebPack

    Devise
    2016

    View full-size slide

  67. WebSockets

    Pagination

    WebPack

    Devise
    Now

    View full-size slide

  68. awesome-hanami.org

    View full-size slide

  69. Good for new contributors
    projects

    View full-size slide

  70. octostar.herokuapp.com

    View full-size slide

  71. contributors.hanamirb.org

    View full-size slide

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

    View full-size slide

  73. github.com/davydovanton

    twitter.com/anton_davydov
    davydovanton.com
    Thank you ❤

    View full-size slide