Upgrade to PRO for Only $50/Yearโ€”Limited-Time Offer! ๐Ÿ”ฅ

Rails vs. Phoenix vs. Hanami

Rails vs. Phoenix vs.ย Hanami

This is the deck for a https://www.rubyfuza.org 2019 talk by Stefan Wintermeyer about the three frameworks Ruby on Rails, Phoenix Framework and Hanami.

Avatar for Stefan Wintermeyer

Stefan Wintermeyer

February 07, 2019
Tweet

More Decks by Stefan Wintermeyer

Other Decks in Programming

Transcript

  1. I am a software ๏ฌre๏ฌghter and architect for hire. I

    see a lot of projects which became unmaintainable over the years. @wintermeyer
  2. I am a Rails Dinosaur My ๏ฌrst Rails book. My

    latest Rails book. @wintermeyer
  3. This talk is for normal people. Not for rock stars!

    I will not dive into religious wars. BTW: vim is better than emacs.
  4. Why use a Framework? โ€ข Structure and order within the

    project โ€ข No need to reinvent the wheel every day. Let others do the heavy lifting. โ€ข Documentation โ€ข Easier on boarding of new team members @wintermeyer
  5. The framework is your hammer. All problems have to become

    nails. And thatโ€™s a good thing because everybody in the team knows how to work with nails! @wintermeyer
  6. Inventor/Leader Sponsor Ruby Yukihiro Matsumoto Heroku Elixir Josรฉ Valim Plataformatec

    Ruby on Rails David Heinemeier Hansson Basecamp Phoenix Framework Chris McCord DockYard Hanami Luca Guidi - @wintermeyer
  7. Version Command Ruby 2.5.3p105 ruby -v Elixir 1.8.0 elixir -v

    Ruby on Rails 5.2.2 rails -v Phoenix Framework 1.4.0 mix phx.new --version Hanami 1.3.1 hanami -v @wintermeyer
  8. Elixir was created in 2011 by Josรฉ Valim to be

    a real concurrent language. @wintermeyer
  9. A Ruby programmer needs quite some time to learn Elixir.

    Functional programming is a totally different ball game. @wintermeyer
  10. Hot Deployments! ZERO DOWNTIME! @wintermeyer No need to ๏ฌre up

    a million Docker instances and some fancy HA Proxy setup.
  11. Version How easy is an upgrade to the next version?

    Ruby on Rails 5.2.2 Phoenix Framework 1.4.0 Hanami 1.3.1 2.0 is a big change @wintermeyer
  12. $ rails new my_blog $ cd my_blog $ rails g

    scaffold post title body:text $ rails db:migrate $ rails server # Open http://0.0.0.0:3000/posts @wintermeyer
  13. โ”œโ”€โ”€ app โ”‚ โ”œโ”€โ”€ assets โ”‚ โ”‚ โ”œโ”€โ”€ javascripts โ”‚

    โ”‚ โ”‚ โ””โ”€โ”€ posts.co๏ฌ€ee โ”‚ โ”‚ โ””โ”€โ”€ stylesheets โ”‚ โ”‚ โ”œโ”€โ”€ posts.scss โ”‚ โ”‚ โ””โ”€โ”€ sca๏ฌ€olds.scss โ”‚ โ”œโ”€โ”€ controllers โ”‚ โ”‚ โ””โ”€โ”€ posts_controller.rb โ”‚ โ”œโ”€โ”€ helpers โ”‚ โ”‚ โ””โ”€โ”€ posts_helper.rb โ”‚ โ”œโ”€โ”€ models โ”‚ โ”‚ โ””โ”€โ”€ post.rb โ”‚ โ””โ”€โ”€ views โ”‚ โ””โ”€โ”€ posts โ”‚ โ”œโ”€โ”€ _form.html.erb โ”‚ โ”œโ”€โ”€ _post.json.jbuilder โ”‚ โ”œโ”€โ”€ edit.html.erb โ”‚ โ”œโ”€โ”€ index.html.erb โ”‚ โ”œโ”€โ”€ index.json.jbuilder โ”‚ โ”œโ”€โ”€ new.html.erb โ”‚ โ”œโ”€โ”€ show.html.erb โ”‚ โ””โ”€โ”€ show.json.jbuilder โ”œโ”€โ”€ con๏ฌg โ”‚ โ””โ”€โ”€ routes.rb โ”œโ”€โ”€ db โ”‚ โ””โ”€โ”€ migrate โ”‚ โ””โ”€โ”€ 20190126153653_create_posts.rb โ””โ”€โ”€ test โ”œโ”€โ”€ controllers โ”‚ โ””โ”€โ”€ posts_controller_test.rb โ”œโ”€โ”€ ๏ฌxtures โ”‚ โ””โ”€โ”€ posts.yml โ”œโ”€โ”€ models โ”‚ โ””โ”€โ”€ post_test.rb โ””โ”€โ”€ system โ””โ”€โ”€ posts_test.rb generated 20 ๏ฌles with 631 LoC
  14. class PostsController < ApplicationController before_action :set_post, only: [:show, :edit, :update,

    :destroy] # GET /posts # GET /posts.json def index @posts = Post.all end # GET /posts/1 # GET /posts/1.json def show end # GET /posts/new def new @post = Post.new end # GET /posts/1/edit def edit end # POST /posts # POST /posts.json def create @post = Post.new(post_params) respond_to do |format| if @post.save format.html { redirect_to @post, notice: 'Post was successfully created.' } format.json { render :show, status: :created, location: @post } else format.html { render :new } format.json { render json: @post.errors, status: :unprocessable_entity } end end end # PATCH/PUT /posts/1 # PATCH/PUT /posts/1.json def update respond_to do |format| if @post.update(post_params) format.html { redirect_to @post, notice: 'Post was successfull format.json { render :show, status: :ok, location: @post } else format.html { render :edit } format.json { render json: @post.errors, status: :unprocessabl end end end # DELETE /posts/1 # DELETE /posts/1.json def destroy @post.destroy respond_to do |format| format.html { redirect_to posts_url, notice: 'Post was successfu format.json { head :no_content } end end private # Use callbacks to share common setup or constraints between actio def set_post @post = Post.find(params[:id]) end # Never trust parameters from the scary internet, only allow the w def post_params params.require(:post).permit(:title, :body) end end app/controllers/posts_controller.rb def create @post = Post.new(post_params) respond_to do |format| if @post.save format.html { redirect_to @post, notice: 'Post was successfully created.' } format.json { render :show, status: :created, location: @post } else format.html { render :new } format.json { render json: @post.errors, status: :unprocessable_entity } end end end
  15. def create @post = Post.new(post_params) respond_to do |format| if @post.save

    format.html { redirect_to @post, notice: 'Post was successfully created.' } format.json { render :show, status: :created, location: @post } else format.html { render :new } format.json { render json: @post.errors, status: :unprocessable_entity } end end end @wintermeyer
  16. $ mix phx.new my_blog $ cd my_blog # con๏ฌgure database

    in con๏ฌg/dev.exs (no SQLite ) $ mix ecto.create $ mix phx.gen.html Blog Post posts title body:text # add "resources "/posts", PostController" to router.ex $ mix ecto.migrate $ mix phx.server # Open http://0.0.0.0:4000/posts @wintermeyer
  17. โ”œโ”€โ”€ lib โ”‚ โ”œโ”€โ”€ my_blog โ”‚ โ”‚ โ””โ”€โ”€ blog โ”‚

    โ”‚ โ”œโ”€โ”€ blog.ex โ”‚ โ”‚ โ””โ”€โ”€ post.ex โ”‚ โ””โ”€โ”€ my_blog_web โ”‚ โ”œโ”€โ”€ controllers โ”‚ โ”‚ โ””โ”€โ”€ post_controller.ex โ”‚ โ”œโ”€โ”€ templates โ”‚ โ”‚ โ””โ”€โ”€ post โ”‚ โ”‚ โ”œโ”€โ”€ edit.html.eex โ”‚ โ”‚ โ”œโ”€โ”€ form.html.eex โ”‚ โ”‚ โ”œโ”€โ”€ index.html.eex โ”‚ โ”‚ โ”œโ”€โ”€ new.html.eex โ”‚ โ”‚ โ””โ”€โ”€ show.html.eex โ”‚ โ””โ”€โ”€ views โ”‚ โ””โ”€โ”€ post_view.ex โ”œโ”€โ”€ priv โ”‚ โ””โ”€โ”€ repo โ”‚ โ””โ”€โ”€ migrations โ”œโ”€โ”€ priv โ”‚ โ””โ”€โ”€ repo โ”‚ โ””โ”€โ”€ migrations โ”‚ โ””โ”€โ”€ 20190126174858_create_posts.exs โ””โ”€โ”€ test โ”œโ”€โ”€ my_blog โ”‚ โ””โ”€โ”€ blog โ”‚ โ””โ”€โ”€ blog_test.exs โ””โ”€โ”€ my_blog_web โ””โ”€โ”€ controllers โ””โ”€โ”€ post_controller_test.exs generated 12 ๏ฌles with 430 LoC
  18. defmodule MyBlogWeb.PostController do use MyBlogWeb, :controller alias MyBlog.Blog alias MyBlog.Blog.Post

    def index(conn, _params) do posts = Blog.list_posts() render(conn, "index.html", posts: posts) end def new(conn, _params) do changeset = Blog.change_post(%Post{}) render(conn, "new.html", changeset: changeset) end def create(conn, %{"post" => post_params}) do case Blog.create_post(post_params) do {:ok, post} -> conn |> put_flash(:info, "Post created successfully.") |> redirect(to: Routes.post_path(conn, :show, post)) {:error, %Ecto.Changeset{} = changeset} -> render(conn, "new.html", changeset: changeset) end end def show(conn, %{"id" => id}) do post = Blog.get_post!(id) render(conn, "show.html", post: post) end |> put_flash(:info, "Post deleted successfully.") |> redirect(to: Routes.post_path(conn, :index)) end end def edit(conn, %{"id" => id}) do post = Blog.get_post!(id) changeset = Blog.change_post(post) render(conn, "edit.html", post: post, changeset: chang end def update(conn, %{"id" => id, "post" => post_params}) d post = Blog.get_post!(id) case Blog.update_post(post, post_params) do {:ok, post} -> conn |> put_flash(:info, "Post updated successfully.") |> redirect(to: Routes.post_path(conn, :show, post {:error, %Ecto.Changeset{} = changeset} -> render(conn, "edit.html", post: post, changeset: c end end def delete(conn, %{"id" => id}) do post = Blog.get_post!(id) {:ok, _post} = Blog.delete_post(post) conn |> put_flash(:info, "Post deleted successfully.") |> redirect(to: Routes.post_path(conn, :index)) end end lib/my_blog_web/controllers/post_controller.ex def create(conn, %{"post" => post_params}) do case Blog.create_post(post_params) do {:ok, post} -> conn |> put_flash(:info, "Post created successfully.") |> redirect(to: Routes.post_path(conn, :show, post)) {:error, %Ecto.Changeset{} = changeset} -> render(conn, "new.html", changeset: changeset) end end
  19. def create(conn, %{"post" => post_params}) do case Blog.create_post(post_params) do {:ok,

    post} -> conn |> put_flash(:info, "Post created successfully.") |> redirect(to: Routes.post_path(conn, :show, post)) {:error, %Ecto.Changeset{} = changeset} -> render(conn, "new.html", changeset: changeset) end end @wintermeyer
  20. $ hanami generate model post create lib/hanami_blog/entities/post.rb create lib/hanami_blog/repositories/post_repository.rb create

    db/migrations/20190124185552_create_posts.rb create spec/hanami_blog/entities/post_spec.rb create spec/hanami_blog/repositories/post_repository_spec.rb @wintermeyer
  21. $ hanami g model post create lib/hanami_blog/entities/post.rb create lib/hanami_blog/repositories/post_repository.rb create

    db/migrations/20190124185552_create_posts.rb create spec/hanami_blog/entities/post_spec.rb create spec/hanami_blog/repositories/post_repository_spec.rb Hanami::Model.migration do change do create_table :posts do primary_key :id column :created_at, DateTime, null: false column :updated_at, DateTime, null: false end end end @wintermeyer
  22. $ hanami g model post create lib/hanami_blog/entities/post.rb create lib/hanami_blog/repositories/post_repository.rb create

    db/migrations/20190124185552_create_posts.rb create spec/hanami_blog/entities/post_spec.rb create spec/hanami_blog/repositories/post_repository_spec.rb Hanami::Model.migration do change do create_table :posts do primary_key :id column :title, String column :body, String column :created_at, DateTime, null: false column :updated_at, DateTime, null: false end end end โŒจ @wintermeyer
  23. $ hanami new my_blog $ cd my_blog $ bundle $

    hanami generate model post $ hanami generate action web posts#index --url="/posts" @wintermeyer
  24. $ hanami generate action web posts#index --url="/posts" create apps/web/controllers/posts/index.rb create

    apps/web/views/posts/index.rb create apps/web/templates/posts/index.html.erb create spec/web/controllers/posts/index_spec.rb create spec/web/views/posts/index_spec.rb insert apps/web/con๏ฌg/routes.rb @wintermeyer
  25. $ hanami new my_blog $ cd my_blog $ bundle $

    hanami generate model post $ hanami generate action web posts#index --url="/posts" $ hanami generate action web posts#show --url="/posts" $ hanami generate action web posts#new --url="/posts/new" $ hanami generate action web posts#create --url="/posts" $ hanami generate action web posts#edit --url="/posts/edit" $ hanami generate action web posts#update --url="/posts" $ hanami generate action web posts#destroy โ€”url="/posts" @wintermeyer
  26. โ”œโ”€โ”€ apps โ”‚ โ””โ”€โ”€ web โ”‚ โ”œโ”€โ”€ assets โ”‚ โ”œโ”€โ”€

    con๏ฌg โ”‚ โ”‚ โ””โ”€โ”€ routes.rb โ”‚ โ”œโ”€โ”€ controllers โ”‚ โ”‚ โ””โ”€โ”€ posts โ”‚ โ”‚ โ”œโ”€โ”€ create.rb โ”‚ โ”‚ โ”œโ”€โ”€ destroy.rb โ”‚ โ”‚ โ”œโ”€โ”€ edit.rb โ”‚ โ”‚ โ”œโ”€โ”€ index.rb โ”‚ โ”‚ โ”œโ”€โ”€ new.rb โ”‚ โ”‚ โ”œโ”€โ”€ show.rb โ”‚ โ”‚ โ””โ”€โ”€ update.rb โ”‚ โ”œโ”€โ”€ templates โ”‚ โ”‚ โ””โ”€โ”€ posts โ”‚ โ”‚ โ”œโ”€โ”€ create.html.erb โ”‚ โ”‚ โ”œโ”€โ”€ destroy.html.erb โ”‚ โ”‚ โ”œโ”€โ”€ edit.html.erb โ”‚ โ”‚ โ”œโ”€โ”€ index.html.erb โ”‚ โ”‚ โ”œโ”€โ”€ new.html.erb โ”‚ โ”‚ โ”œโ”€โ”€ show.html.erb โ”‚ โ”‚ โ””โ”€โ”€ update.html.erb โ”‚ โ””โ”€โ”€ views โ”‚ โ””โ”€โ”€ posts โ”‚ โ”œโ”€โ”€ create.rb โ”‚ โ”œโ”€โ”€ destroy.rb โ”‚ โ”œโ”€โ”€ edit.rb โ”‚ โ”œโ”€โ”€ index.rb โ”‚ โ”œโ”€โ”€ new.rb โ”‚ โ”œโ”€โ”€ show.rb โ”‚ โ””โ”€โ”€ update.rb โ”œโ”€โ”€ con๏ฌg โ”œโ”€โ”€ db โ”‚ โ””โ”€โ”€ migrations โ”‚ โ””โ”€โ”€ 20190125105614_create_posts.rb โ”œโ”€โ”€ lib โ”‚ โ””โ”€โ”€ blog โ”‚ โ”œโ”€โ”€ entities โ”‚ โ”‚ โ””โ”€โ”€ post.rb โ”‚ โ”œโ”€โ”€ mailers โ”‚ โ””โ”€โ”€ repositories โ”‚ โ””โ”€โ”€ post_repository.rb โ””โ”€โ”€ spec โ”œโ”€โ”€ blog โ”‚ โ”œโ”€โ”€ entities โ”‚ โ”‚ โ””โ”€โ”€ post_spec.rb โ”‚ โ””โ”€โ”€ repositories โ”‚ โ””โ”€โ”€ post_repository_spec.rb โ””โ”€โ”€ web โ”œโ”€โ”€ controllers โ”‚ โ””โ”€โ”€ posts โ”‚ โ”œโ”€โ”€ create_spec.rb โ”‚ โ”œโ”€โ”€ destroy_spec.rb โ”‚ โ”œโ”€โ”€ edit_spec.rb โ”‚ โ”œโ”€โ”€ index_spec.rb โ”‚ โ”œโ”€โ”€ new_spec.rb โ”‚ โ”œโ”€โ”€ show_spec.rb โ”‚ โ””โ”€โ”€ update_spec.rb โ””โ”€โ”€ views โ””โ”€โ”€ posts โ”œโ”€โ”€ create_spec.rb โ”œโ”€โ”€ destroy_spec.rb โ”œโ”€โ”€ edit_spec.rb โ”œโ”€โ”€ index_spec.rb โ”œโ”€โ”€ new_spec.rb โ”œโ”€โ”€ show_spec.rb โ””โ”€โ”€ update_spec.rb @wintermeyer
  27. $ hanami generate action web posts#index --url="/posts" create apps/web/controllers/posts/index.rb create

    apps/web/views/posts/index.rb create apps/web/templates/posts/index.html.erb create spec/web/controllers/posts/index_spec.rb create spec/web/views/posts/index_spec.rb insert apps/web/con๏ฌg/routes.rb
  28. module Web module Controllers module Posts class Create include Web::Action

    def call(params) end end end end end apps/web/controllers/posts/create.rb
  29. $ hanami new my_blog $ cd my_blog $ bundle $

    hanami generate model post $ hanami generate action web posts#index --url="/posts" $ hanami generate action web posts#show --url="/posts" $ hanami generate action web posts#new --url="/posts/new" $ hanami generate action web posts#create --url="/posts" $ hanami generate action web posts#edit --url="/posts/edit" $ hanami generate action web posts#update --url="/posts" $ hanami generate action web posts#destroy โ€”url="/posts" $ hanami db create $ hanami db migrate # put A LOT of Ruby code in the just generated ๏ฌles $ hanami server # Open http://localhost:2300 @wintermeyer
  30. The 15 Minute Blog @wintermeyer Time (hh:mm) generated LoC Ruby

    on Rails 00:01 631 Phoenix Framework 00:02 430 Hanami 03:30 501
  31. Easy and fast entry for newbies. @wintermeyer When you try

    a new framework you want to see results within the ๏ฌrst 20 minutes.
  32. For teams scaffolding is a big time saver and improves

    code quality by creating a base line. @wintermeyer
  33. Itโ€™s a power tool for refactoring. @wintermeyer If you donโ€™t

    use it often you probably havenโ€™t customized it yet.
  34. Over time Rails became easier and easier to use. Thatโ€™s

    a major factor for itโ€™s success. @wintermeyer
  35. Stefan, tell us more about those customizable scaffold generators! "Never

    send a human to do a machineโ€™s job." by Agent Smith
  36. <% module_namespacing do -%> class <%= class_name %> < <%=

    parent_class_name.classify %> <% attributes.select(&:reference?).each do |attribute| -%> belongs_to :<%= attribute.name %><%= ', polymorphic: true' if attribute.polymorphic? %><%= ', required: true' if attribute.required? %> <% end -%> <% attributes.select(&:token?).each do |attribute| -%> has_secure_token<% if attribute.name != "token" %> :<%= attribute.name %><% end %> <% end -%> <% if attributes.any?(&:password_digest?) -%> has_secure_password <% end -%> end <% end -%> lib/templates/active_record/model/model.rb.tt
  37. Example Enhancement <%- if attributes.map{ |a| a.name }.include?('position') -%> acts_as_list

    <% end -%> <% if attributes.map{ |a| a.name }.include?('name') -%> validates :name, presence: true def to_s name end <% end -%> Should be customized for each project.
  38. lib/templates/ โ”œโ”€โ”€ active_record โ”‚ โ””โ”€โ”€ model โ”‚ โ””โ”€โ”€ model.rb.tt โ”œโ”€โ”€

    erb โ”‚ โ””โ”€โ”€ scaffold โ”‚ โ”œโ”€โ”€ _form.html.erb.tt โ”‚ โ”œโ”€โ”€ edit.html.erb.tt โ”‚ โ”œโ”€โ”€ index.html.erb.tt โ”‚ โ”œโ”€โ”€ new.html.erb.tt โ”‚ โ””โ”€โ”€ show.html.erb.tt โ””โ”€โ”€ rails โ””โ”€โ”€ scaffold_controller โ””โ”€โ”€ controller.rb.tt Just google for the ๏ฌle names to ๏ฌnd the default ones on github.com/rails.
  39. def create(conn, %{<%= inspect schema.singular %> => <%= schema.singular %>_params})

    do case <%= inspect context.alias %>.create_<%= schema.singular %>(<%= schema.singular %>_params) do {:ok, <%= schema.singular %>} -> conn |> put_flash(:info, "<%= schema.human_singular %> created successfully.") |> redirect(to: Routes.<%= schema.route_helper %>_path(conn, :show, <%= schema.singular %>)) {:error, %Ecto.Changeset{} = changeset} -> render(conn, "new.html", changeset: changeset) end end https://github.com/phoenixframework/phoenix/blob/master/priv/templates/ phx.gen.html/controller.ex
  40. Donโ€™t use Hanami (for now). Letโ€™s see what the future

    brings. "Nobody ever got ๏ฌred for buying IBM".gsub(/buying IBM/, 'using Rails')