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

Web development that hurts even less: taking lessons from Rails

Web development that hurts even less: taking lessons from Rails

It has been over ten years since the Rails framework brought Ruby to the forefront of web development. You might think Ruby is synonymous to Rails, but there’s more to the community than that. New languages and frameworks are no longer aiming to merely imitate Ruby and Rails — they are aiming to improve upon it. Lotus, ROM, Phoenix, Ecto, Sequel, Roda, Webpack: they all represent lessons learned about how we build software. It’s time to broaden our horizon (even if only a little bit). As good as Rails is, can we do better?

Arjan van der Gaag

November 14, 2015
Tweet

More Decks by Arjan van der Gaag

Other Decks in Programming

Transcript

  1. View Slide

  2. Arjan van der Gaag
    @avdgaag
    arjanvandergaag.nl
    brightin.nl

    View Slide

  3. Web development that
    hurts even less
    Taking lessons from Rails

    View Slide

  4. View Slide

  5. “Look at all the code I’m
    not writing!”

    View Slide

  6. “So that's why I love software. It's
    not like modern art. You can't just
    keep spouting intellectual nonsense
    forever. At the end of the day, you
    have to translate your prescriptions
    into running code and that's when
    the bullshit meter activates.”

    View Slide

  7. Thanks, great discussion.

    View Slide

  8. “Rails 5 is going to ship
    also with an evented file
    system monitor.”

    View Slide

  9. (˽°□°҂˽Ɨ ˍʓˍ)

    View Slide

  10. View Slide

  11. View Slide

  12. class ArticlesController <
    ApplicationController
    def show
    @article = Article
    .where.not(published_at: nil)
    .find(params[:id])
    end
    end

    View Slide

  13. class ArticlesController <
    ApplicationController
    def show
    @article = Article
    .where.not(published_at: nil)
    .find(params[:id])
    end
    end

    View Slide

  14. class ArticlesController <
    ApplicationController
    def show
    @article = Article
    .where.not(published_at: nil)
    .find(params[:id])
    end
    end

    View Slide

  15. class ArticlesController <
    ApplicationController
    def show
    @article = Article
    .where.not(published_at: nil)
    .find(params[:id])
    end
    end

    View Slide

  16. class ArticlesController <
    ApplicationController
    def show
    @article = Article
    .where.not(published_at: nil)
    .find(params[:id])
    end
    end

    View Slide

  17. class ArticlesController <
    ApplicationController
    def show
    @article = Article
    .where.not(published_at: nil)
    .find(params[:id])
    end
    end

    View Slide

  18. class ArticlesController <
    ApplicationController
    def show
    @article = Article
    .where.not(published_at: nil)
    .find(params[:id])
    end
    end

    View Slide

  19. class ArticlesController <
    ApplicationController
    def show
    @article = Article
    .where.not(published_at: nil)
    .find(params[:id])
    end
    end

    View Slide

  20. module Web::Controllers::Articles
    class Show
    include Web::Action
    expose :article
    def call(params)
    @article = ArticleRepository
    .published
    .find(params[:id])
    end
    end
    end

    View Slide

  21. module Web::Controllers::Articles
    class Create
    include Web::Action
    expose :article
    def call(params)
    @article = ArticleRepository
    .published
    .find(params[:id])
    end
    end
    end

    View Slide

  22. module Web::Controllers::Articles
    class Create
    include Web::Action
    expose :article
    def call(params)
    @article = ArticleRepository
    .published
    .find(params[:id])
    end
    end
    end

    View Slide

  23. module Web::Controllers::Articles
    class Create
    include Web::Action
    expose :article
    def call(params)
    @article = ArticleRepository
    .published
    .find(params[:id])
    end
    end
    end

    View Slide

  24. module Web::Controllers::Articles
    class Create
    include Web::Action
    expose :article
    def call(params)
    @article = ArticleRepository
    .published
    .find(params[:id])
    end
    end
    end

    View Slide

  25. module Web::Controllers::Articles
    class Create
    include Web::Action
    expose :article
    def call(params)
    @article = ArticleRepository
    .published
    .find(params[:id])
    end
    end
    end

    View Slide

  26. module Web::Controllers::Articles
    class Create
    include Web::Action
    expose :article
    def call(params)
    @article = ArticleRepository
    .published
    .find(params[:id])
    end
    end
    end

    View Slide

  27. 1
    plain objects with
    minimal interfaces

    View Slide

  28. <%= form_for @article do |f| %>

    <%= f.text_field :title %>

    <% end %>

    View Slide

  29. <%= form_for @article do |f| %>

    <%= f.text_field :title %>

    <% end %>

    View Slide

  30. <%= form_for @article do |f| %>

    <%= f.text_field :title %>

    <% end %>

    View Slide

  31. <%= form_for @article do |f| %>

    <%= f.text_field :title %>

    <% end %>

    View Slide

  32. <%= form_for @article do |f| %>

    <%= f.text_field :title %>

    <% end %>

    View Slide

  33. <%=
    form_for :article,
    routes.articles_path do
    div do
    text_field :title
    end
    end
    %>

    View Slide

  34. module Web::Views::Articles
    class Index
    include Web::View
    include Lotus::Helpers
    def comments_count
    format_number(
    articles.comments_count
    )
    end
    end
    end

    View Slide

  35. 2
    Explicit and intention
    revealing interfaces

    View Slide

  36. 3
    Avoid monkey
    patching

    View Slide

  37. class Article < ActiveRecord::Base
    validates :title,
    presence: true
    end

    View Slide

  38. class Article < ActiveRecord::Base
    validates :title,
    presence: true,
    unless: :draft?
    end

    View Slide

  39. class Article < ActiveRecord::Base
    validates :title,
    presence: true,
    unless: :draft?
    validates :slug,
    presence: true,
    on: :update
    end

    View Slide

  40. class Article < ActiveRecord::Base
    validates :title,
    presence: true,
    unless: :draft?
    validates :slug,
    presence: true,
    on: :update
    before_validate :set_slug
    end

    View Slide

  41. class Article < ActiveRecord::Base
    validates :title,
    presence: true,
    unless: :draft?
    validates :slug,
    presence: true,
    on: :update
    before_validate :set_slug,
    on: :create
    end

    View Slide

  42. “There are nineteen callbacks
    in total, which give you
    immense power to react and
    prepare for each state in the
    Active Record life cycle.”

    View Slide

  43. class Article
    attr_accessor :title, :slug
    def initialize(title:, slug:)
    @title = title
    @slug = slug
    end
    end

    View Slide

  44. defmodule Article do
    schema "articles" do
    field :title
    end
    @required_fields ~w(title)
    def changeset(article, params \\ :empty) do
    article
    |> cast(params, @required_fields, [])
    |> validate_length(:title, max: 255)
    end
    end

    View Slide

  45. 4
    Favour many small objects
    over few large ones

    View Slide

  46. class UserRepository < ROM::Repository::Base
    relations :users, :tasks
    def with_tasks(id)
    users.by_id(id)
    .combine_children(many: tasks)
    end
    end
    user_repo.with_tasks.to_a
    # [#tasks=[#title="Jane Task">]>]

    View Slide

  47. class PostMapper < ROM::Mapper
    attribute :title,
    from: 'post_title'
    wrap :author do
    attribute :name,
    from: 'post_author'
    attribute :email,
    from: 'post_author_email'
    end
    end

    View Slide

  48. 5
    Don’t manage state,
    transform data

    View Slide

  49. 6
    Embrace, but isolate
    your database

    View Slide

  50. # Gemfile
    gem "jquery-rails"
    gem "sprockets"
    # app/assets/javascripts/application.js
    #= require jquery
    #= require jquery_ujs

    View Slide

  51. config :example_phoenix_app,
    ExamplePhoenixApp.Endpoint,
    http: [port: 4000],
    debug_errors: true,
    code_reloader: true,
    watchers: [node: [
    "node_modules/brunch/bin/brunch",
    "watch",
    "--stdin"
    ]]

    View Slide

  52. 7
    Do one thing,
    and do it well

    View Slide

  53. 8
    When you find yourself
    in a hole, stop digging

    View Slide

  54. class Article < ActiveRecord::Base
    has_many :comments
    end
    article = Article.new
    article.comments
    # => <#ActiveRecord::Relation []>

    View Slide

  55. class Article < ActiveRecord::Base
    has_many :comments
    end
    article = Article.new
    article.comments.build

    View Slide

  56. “Ecto does not provide
    functions like post.comments
    << comment that allows
    mixing persisted data with
    non-persisted data.”

    View Slide

  57. 9
    Be transparent

    View Slide

  58. # app/views/articles/_article.html.erb

    <%= article.title %>

    View Slide

  59. # app/views/articles/_article.html.erb

    <%= article.title %>
    <%= render article.comments %>

    View Slide

  60. query = from a in Article, limit: 1
    article = Repo.one(query)
    article.comments
    # => Ecto.Association.NotLoaded

    View Slide

  61. query = from a in Article,
    preload: [:comments],
    limit: 1
    article = Repo.one(query)
    article.comments
    # => []

    View Slide

  62. “Lazy loading is often a
    source of confusion and
    performance issues and
    Ecto pushes developers to
    do the proper thing.”

    View Slide

  63. 10
    Nudge developers into
    the right direction

    View Slide

  64. View Slide

  65. View Slide

  66. class ArticlesController
    def index
    @articles = Article.recent
    respond_to do |format|
    format.html
    format.xml do
    @articles = @articles.limit(10)
    end
    end
    end
    end

    View Slide

  67. class ArticlesController
    def show
    @article = Article.find(params[:id])
    fresh_when @article, public: true
    end
    end

    View Slide

  68. <%= form_for @article do |f| %>
    <%= f.input :title %>
    <%= f.input :body %>
    <% end %>

    View Slide

  69. protect_from_forgery with: :exception
    config.filter_parameters << :password

    View Slide

  70. Forms, Security
    HTTP, Conventions

    View Slide

  71. Rapid prototyping

    View Slide

  72. Good design nudges us
    in the right direction

    View Slide

  73. Convention over
    configuration

    View Slide

  74. Simplicity over
    convenience

    View Slide

  75. Arjan van der Gaag
    @avdgaag
    arjanvandergaag.nl
    brightin.nl

    View Slide