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?

241eb3a089132c5a0c65e765558a6735?s=128

Arjan van der Gaag

November 14, 2015
Tweet

Transcript

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

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

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

  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.”
  7. Thanks, great discussion.

  8. “Rails 5 is going to ship also with an evented

    file system monitor.”
  9. (˽°□°҂˽Ɨ ˍʓˍ)

  10. None
  11. None
  12. class ArticlesController < ApplicationController def show @article = Article .where.not(published_at:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    @article = ArticleRepository .published .find(params[:id]) end end end
  27. 1 plain objects with minimal interfaces

  28. <%= form_for @article do |f| %> <div class="input"> <%= f.text_field

    :title %> </div> <% end %>
  29. <%= form_for @article do |f| %> <div class="input"> <%= f.text_field

    :title %> </div> <% end %>
  30. <%= form_for @article do |f| %> <div class="input"> <%= f.text_field

    :title %> </div> <% end %>
  31. <%= form_for @article do |f| %> <div class="input"> <%= f.text_field

    :title %> </div> <% end %>
  32. <%= form_for @article do |f| %> <div class="input"> <%= f.text_field

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

    end %>
  34. module Web::Views::Articles class Index include Web::View include Lotus::Helpers def comments_count

    format_number( articles.comments_count ) end end end
  35. 2 Explicit and intention revealing interfaces

  36. 3 Avoid monkey patching

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

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

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

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

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

    validates :slug, presence: true, on: :update before_validate :set_slug, on: :create end
  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.”
  43. class Article attr_accessor :title, :slug def initialize(title:, slug:) @title =

    title @slug = slug end end
  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
  45. 4 Favour many small objects over few large ones

  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 # [#<ROM::Struct[User] id=1 name="Jane" tasks=[#<ROM::Struct[Task] id=2 user_id=1 title="Jane Task">]>]
  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
  48. 5 Don’t manage state, transform data

  49. 6 Embrace, but isolate your database

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

    jquery #= require jquery_ujs
  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" ]]
  52. 7 Do one thing, and do it well

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

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

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

    article.comments.build
  56. “Ecto does not provide functions like post.comments << comment that

    allows mixing persisted data with non-persisted data.”
  57. 9 Be transparent

  58. # app/views/articles/_article.html.erb <div class="article"> <h1><%= article.title %></h1> </div>

  59. # app/views/articles/_article.html.erb <div class="article"> <h1><%= article.title %></h1> <p><%= render article.comments

    %></p> </div>
  60. query = from a in Article, limit: 1 article =

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

    article = Repo.one(query) article.comments # => []
  62. “Lazy loading is often a source of confusion and performance

    issues and Ecto pushes developers to do the proper thing.”
  63. 10 Nudge developers into the right direction

  64. None
  65. None
  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
  67. class ArticlesController def show @article = Article.find(params[:id]) fresh_when @article, public:

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

    <%= f.input :body %> <% end %>
  69. protect_from_forgery with: :exception config.filter_parameters << :password

  70. Forms, Security HTTP, Conventions

  71. Rapid prototyping

  72. Good design nudges us in the right direction

  73. Convention over configuration

  74. Simplicity over convenience

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