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

Os truques que o Rails não te contou @ RubyConf Brasil 2014

Os truques que o Rails não te contou @ RubyConf Brasil 2014

Carlos Antonio

August 29, 2014
Tweet

More Decks by Carlos Antonio

Other Decks in Technology

Transcript

  1. Os truques que o Rails
    não te contou

    View Slide

  2. Carlos Antonio
    @cantoniodasilva

    View Slide

  3. View Slide

  4. ~6 anos
    Ruby / Rails

    View Slide

  5. Eu já participei em
    vários projetos com
    contextos diferentes

    View Slide

  6. Eu já vi código

    View Slide

  7. Eu já vi código complexo

    View Slide

  8. Eu já vi código duplicado

    View Slide

  9. !
    Eu já vi código do Rails
    reimplementado em apps

    View Slide

  10. Com que frequência você vê eles?
    Todo o tempo. Eles estão em toda parte.

    View Slide

  11. Você se acostuma com o
    código após algum tempo

    View Slide

  12. E isso é um grande problema
    As pessoas têm medo de lidar com esse tipo de código.

    View Slide

  13. Rails tricks / features
    Não usadas por muitos
    Não muito conhecidas e / ou documentadas

    View Slide

  14. Que podem te ajudar a
    refatorar código existente
    ou melhorar sua vida como desenvolvedor um pouquinho.

    View Slide

  15. TDD
    IS DEAD

    View Slide

  16. View Slide

  17. Devise
    Rails Summit 2009

    View Slide

  18. View Slide

  19. View Slide

  20. http://blog.plataformatec.com.br/2014/08/thank-you-carlos-antonio/

    View Slide

  21. https://www.enjoei.com.br

    View Slide

  22. View Slide

  23. A Plataformatec usa
    pull requests e code review
    All the f*cking time.

    View Slide

  24. E é comum aprender
    coisas novas no processo
    Estamos sempre aprendendo, certo?

    View Slide

  25. View Slide

  26. View Slide

  27. View Slide

  28. View Slide

  29. View Slide

  30. ಠ_ಠ
    Eu tive que criar este talk depois disso.

    View Slide

  31. O que vamos ver:
    Active Record
    Active Model
    Action Mailer
    !
    Action View
    Action Controller
    Bonus Stuff

    View Slide

  32. Active Record

    View Slide

  33. migrations: revert
    class DropAuthorsActive < ActiveRecord::Migration
    def up
    remove_column :authors, :active
    end
    !
    def down
    add_column :authors, :active, :boolean, default: true
    end
    end

    View Slide

  34. migrations: revert
    class DropAuthorsActive < ActiveRecord::Migration
    def change
    remove_column :authors, :active
    end
    end
    # => ActiveRecord::IrreversibleMigration

    View Slide

  35. migrations: revert
    class DropAuthorsActive < ActiveRecord::Migration
    def change
    revert do
    add_column :authors, :active, :boolean, default: true
    end
    end
    end

    View Slide

  36. migrations: change column null
    class ChangeNameToNotNullOnAuthors < ActiveRecord::Migration
    def up
    change_column :authors, :name, :string, null: false
    end
    !
    def down
    change_column :authors, :name, :string, null: true
    end
    end

    View Slide

  37. migrations: change column null
    class ChangeNameToNotNullOnAuthors < ActiveRecord::Migration
    def up
    change_column_null :authors, :name, false
    end
    !
    def down
    change_column_null :authors, :name, true
    end
    end

    View Slide

  38. migrations: change column null
    class ChangeNameToNotNullOnAuthors < ActiveRecord::Migration
    def change
    change_column_null :authors, :name, false
    end
    end

    View Slide

  39. migrations: change column default
    class ChangeStatusDefaultOnArticles < ActiveRecord::Migration
    def up
    change_column :articles, :status, :string, default: 'draft'
    end
    !
    def down
    change_column :articles, :status, :string, default: 'published'
    end
    end

    View Slide

  40. migrations: change column default
    class ChangeStatusDefaultOnArticles < ActiveRecord::Migration
    def up
    change_column_default :articles, :status, 'draft'
    end
    !
    def down
    change_column_default :articles, :status, 'published'
    end
    end

    View Slide

  41. migrations: change column default
    class ChangeStatusDefaultOnArticles < ActiveRecord::Migration
    def change
    change_column_default :articles, :status, 'draft'
    end
    end
    # => ActiveRecord::IrreversibleMigration

    View Slide

  42. Relation#merge
    class Author < ActiveRecord::Base
    has_many :articles
    !
    scope :with_draft_articles, -> {
    uniq.joins(:articles).
    where(articles: { status: 'draft' })
    }
    end

    View Slide

  43. Relation#merge
    >> Author.with_draft_articles.to_sql
    !
    SELECT DISTINCT "authors".*
    FROM "authors"
    INNER JOIN "articles" ON "articles"."author_id" = "authors"."id"
    WHERE "articles"."status" = 'draft'

    View Slide

  44. Relation#merge
    class Author < ActiveRecord::Base
    has_many :articles
    !
    scope :with_draft_articles, -> {
    uniq.joins(:articles).merge(Article.draft)
    }
    end

    View Slide

  45. Relation#merge
    >> Author.with_draft_articles.to_sql
    !
    SELECT DISTINCT "authors".*
    FROM "authors"
    INNER JOIN "articles" ON "articles"."author_id" = "authors"."id"
    WHERE "articles"."status" = 'draft'

    View Slide

  46. find_by
    >> Article.where(title: 'ZOMG').first
    => #
    !
    >> Article.find_by(title: 'ZOMG')
    => #

    View Slide

  47. where.not
    class Article < ActiveRecord::Base
    scope :not_draft, -> { where 'status != ?', 'draft' }
    end
    !
    >> Article.not_draft.to_sql
    !
    SELECT “articles".* FROM "articles"
    WHERE (status != 'draft')

    View Slide

  48. where.not
    class Article < ActiveRecord::Base
    scope :not_draft, -> { where.not status: 'draft' }
    end
    !
    >> Article.not_draft.to_sql
    !
    SELECT “articles".* FROM "articles"
    WHERE ("articles"."status" != 'draft')

    View Slide

  49. order :asc/:desc
    class Article < ActiveRecord::Base
    scope :recent, -> { order 'created_at DESC' }
    end
    !
    >> Article.recent
    !
    SELECT "articles".* FROM "articles"
    ORDER BY created_at DESC

    View Slide

  50. order :asc/:desc
    class Article < ActiveRecord::Base
    scope :recent, -> { order created_at: :desc }
    end
    !
    >> Article.recent
    !
    SELECT "articles".* FROM "articles"
    ORDER BY "articles"."created_at" DESC

    View Slide

  51. estratégias de eager load
    >> Article.includes(:author)
    !
    SELECT "articles".* FROM "articles"
    !
    SELECT "authors".* FROM "authors"
    WHERE "authors"."id" IN (980190962, 298486374)

    View Slide

  52. estratégias de eager load
    >> Article.includes(:author).order('authors.name')
    !
    SELECT "articles"."id" AS t0_r0 ………… "authors"."updated_at" AS t1_r4
    FROM "articles"
    LEFT OUTER JOIN "authors" ON "authors"."id" = “articles"."author_id"
    ORDER BY authors.name

    View Slide

  53. estratégias de eager load
    >> Article.eager_load(:author)
    !
    SELECT "articles"."id" AS t0_r0 ………… "authors"."updated_at" AS t1_r4
    FROM "articles"
    LEFT OUTER JOIN "authors" ON "authors"."id" = “articles”."author_id"

    View Slide

  54. estratégias de eager load
    >> Article.preload(:author)
    !
    SELECT "articles".* FROM “articles"
    !
    SELECT "authors".* FROM “authors"
    WHERE "authors"."id" IN (980190962, 298486374)

    View Slide

  55. estratégias de eager load
    >> Article.preload(:author).
    joins(:author).order('authors.name')
    !
    SELECT "articles".* FROM "articles"
    INNER JOIN "authors" ON "authors"."id" = "articles"."author_id"
    ORDER BY authors.name
    !
    SELECT "authors".* FROM "authors"
    WHERE "authors"."id" IN (980190962, 298486374)

    View Slide

  56. group counting
    >> Article.group(:status).count
    !
    SELECT COUNT(*) AS count_all, status AS status
    FROM "articles"
    GROUP BY status
    !
    => {"draft"=>7, “published"=>2}

    View Slide

  57. group counting: múltiplos atributos
    >> Article.group(:status, :category).count
    !
    SELECT COUNT(*) AS count_all, status AS status, category AS category
    FROM "articles"
    GROUP BY status, category
    !
    => {["draft", nil]=>5, ["draft", "Talks"]=>2,
    ["published", "News"]=>1, ["published", "Releases"]=>1}

    View Slide

  58. pluck
    >> Author.all.map(&:name)
    !
    SELECT "authors".* FROM "authors"
    !
    => ["Author 2", "Author 1"]

    View Slide

  59. pluck
    >> Author.select(:name).map(&:name)
    !
    SELECT "authors"."name" FROM "authors"
    !
    => ["Author 2", "Author 1"]

    View Slide

  60. pluck
    >> Author.pluck(:name)
    !
    SELECT "authors"."name" FROM "authors"
    !
    => ["Author 2", "Author 1"]

    View Slide

  61. pluck: múltiplos atributos
    >> Author.pluck(:name, :active)
    !
    SELECT "authors"."name", "authors"."active"
    FROM "authors"
    !
    => [["Author 2", true], ["Author 1", true]]

    View Slide

  62. pluck: valores distintos
    >> Author.distinct.pluck(:name)
    !
    SELECT DISTINCT "authors"."name" FROM "authors"
    !
    => ["Author 2", "Author 1"]

    View Slide

  63. exists?/any? vs present?
    <% if @author.articles.exists? %>
    <%= render @author.articles %>
    <% else %>
    No article found.
    <% end %>

    View Slide

  64. exists?/any? vs present?
    >> author = Author.first
    !
    >> author.articles.exists?
    !
    SELECT 1 AS one FROM "articles"
    WHERE "articles"."author_id" = ? LIMIT 1 [["author_id", 1]]
    !
    => true

    View Slide

  65. exists?/any? vs present?
    >> author.articles.each {}
    !
    SELECT "articles".* FROM "articles"
    WHERE "articles"."author_id" = ? [["author_id", 1]]

    View Slide

  66. exists?/any? vs present?
    >> author = Author.first
    !
    >> author.articles.present?
    !
    SELECT "articles".* FROM "articles"
    WHERE "articles"."author_id" = ? [["author_id", 1]]
    !
    => true

    View Slide

  67. exists?/any? vs present?
    >> author.articles.each {}
    !
    # => já carregado, não dispara outra query

    View Slide

  68. association count vs size
    >> author = Author.first
    !
    >> author.articles.count
    SELECT COUNT(*) FROM "articles"
    WHERE "articles"."author_id" = ? [["author_id", 1]]
    !
    >> author.articles.size
    SELECT COUNT(*) FROM "articles"
    WHERE "articles"."author_id" = ? [["author_id", 1]]

    View Slide

  69. association count vs size
    >> author = Author.includes(:articles).first
    !
    >> author.articles.count
    SELECT COUNT(*) FROM "articles"
    WHERE "articles"."author_id" = ? [["author_id", 1]]
    !
    >> author.articles.size
    !
    # => já carregado, não dispara outra query

    View Slide

  70. benchmark
    class Article < ActiveRecord::Base
    def self.expensive_operation
    sleep 2
    'did something very expensive'
    end
    end

    View Slide

  71. benchmark
    >> start = Time.now
    => 2014-04-21 10:48:30 -0500
    >> Article.expensive_operation
    => "did something very expensive"
    >> finish = Time.now
    => 2014-04-21 10:48:32 -0500
    >> finish - start
    => 2.02356

    View Slide

  72. benchmark
    >> ActiveRecord::Base.benchmark 'SUPER EXPENSIVE OPERATION' do
    ?> Article.expensive_operation
    >> end
    SUPER EXPENSIVE OPERATION (2000.9ms)
    => "did something very expensive"

    View Slide

  73. benchmark
    class Article < ActiveRecord::Base
    def self.expensive_operation
    benchmark 'SUPER EXPENSIVE OPERATION' do
    sleep 2
    'did something very expensive'
    end
    end
    end

    View Slide

  74. Active Model

    View Slide

  75. ActiveModel::Model
    class EmailContact
    attr_accessor :name, :email, :message
    !
    def deliver
    if name.present? && email.present? && message.present?
    puts 'deliver email'
    end
    end
    end

    View Slide

  76. ActiveModel::Model
    contact = EmailContact.new
    contact.name = 'Carlos'
    contact.email = '[email protected]'
    contact.message = 'ZOMG'
    contact.deliver

    View Slide

  77. ActiveModel::Model
    <%= form_for @email_contact … %>
    !
    <%= render @email_contact %>

    View Slide

  78. ActiveModel::Model
    class EmailContact
    extend ActiveModel::Naming
    extend ActiveModel::Translation
    include ActiveModel::Validations
    include ActiveModel::Conversion
    !
    attr_accessor :name, :email, :message
    validates :name, :email, :message, presence: true
    !
    def deliver
    if valid?
    puts 'deliver email'
    end
    end
    !
    def persisted?
    false
    end
    end

    View Slide

  79. ActiveModel::Model
    class EmailContact
    include ActiveModel::Model
    !
    attr_accessor :name, :email, :message
    validates :name, :email, :message, presence: true
    !
    def deliver
    if valid?
    puts 'deliver email'
    end
    end
    end

    View Slide

  80. ActiveModel::Model
    contact = EmailContact.new(
    name: 'Carlos',
    email: '[email protected]',
    message: 'ZOMG'
    )
    contact.deliver

    View Slide

  81. ActiveModel::Model
    >> contact = EmailContact.new
    => #
    !
    >> contact.valid?
    => false
    !
    >> contact.errors.full_messages.to_sentence
    => "Name can't be blank, Email can't be blank, and Message
    can't be blank"

    View Slide

  82. Action Mailer

    View Slide

  83. Mailer com i18n subject
    class Notifier < ActionMailer::Base
    default from: "[email protected]"
    !
    # Subject can be set in your I18n file at config/locales/en.yml
    # with the following lookup:
    #
    # en.notifier.new_article.subject
    #
    def new_article
    @greeting = "Hi"
    !
    mail to: "[email protected]"
    end
    end

    View Slide

  84. Mailer com i18n subject
    class Notifier < ActionMailer::Base
    default from: "[email protected]"
    !
    def new_article
    @greeting = "Hi"
    !
    mail to: "[email protected]"
    end
    end

    View Slide

  85. Mailer com i18n subject
    class Notifier < ActionMailer::Base
    default from: "[email protected]"
    !
    def new_article
    @greeting = "Hi"
    !
    mail to: "[email protected]", subject: I18n.t('subjects.new_article')
    end
    end

    View Slide

  86. Mailer com i18n subject
    en:
    subjects:
    new_article: 'New article just published.'

    View Slide

  87. Mailer com i18n subject
    en:
    notifier:
    new_article:
    subject: 'New article just published.'

    View Slide

  88. Mailer com i18n subject
    class Notifier < ActionMailer::Base
    default from: "[email protected]"
    !
    def new_article
    @greeting = "Hi"
    !
    mail to: "[email protected]"
    end
    end

    View Slide

  89. Mailer com i18n subject
    class Notifier < ActionMailer::Base
    default from: "[email protected]"
    !
    def new_article
    @greeting = "Hi"
    !
    mail to: "[email protected]example.org",
    subject: I18n.t('subjects.new_article', title: 'Clarity is king')
    end
    end

    View Slide

  90. Mailer com i18n subject
    en:
    subjects:
    new_article: 'New article: %{title}.'

    View Slide

  91. Mailer com i18n subject
    en:
    notifier:
    new_article:
    subject: 'New article %{title}.'

    View Slide

  92. Mailer com i18n subject
    class Notifier < ActionMailer::Base
    default from: "[email protected]"
    !
    def new_article
    @greeting = "Hi"
    !
    mail to: "[email protected]",
    subject: default_i18n_subject(title: 'Clarity is king')
    end
    end

    View Slide

  93. Action View

    View Slide

  94. content_tag_for
    <%= content_tag_for :article, @articles do |article| %>
    <%= article.title %>
    <%= simple_format article.body %>
    <% end %>

    View Slide

  95. content_tag_for

    Rails 4.2.0 is going to be more Adequate
    Rails 4.2.0 is going to be made more Adequate by
    Tenderlove and Gorby Puff


    Rails 4.1.0 Released
    Rails 4.1.0 was just released before RailsConf

    View Slide

  96. content_tag_for
    <%= content_tag_for :section, @author do |author| %>
    <%= author.name %>

    <% end %>

    View Slide

  97. content_tag_for

    Author 2


    View Slide

  98. render com coleções vazias
    <% if author.articles.exists? %>
    <%= render author.articles %>
    <% else %>
    No article found.
    <% end %>

    View Slide

  99. render com coleções vazias
    <%= render(author.articles) ||
    content_tag(:p, 'No article found.') %>

    View Slide

  100. truncar textos
    <%= truncate article.body do %>
    <%= link_to 'read more...', article %>
    <% end %>
    !
    !
    Rails 4.2.0 is going to be ...

    read more…

    View Slide

  101. I18n + html
    <%= link_to t('read_more'), article %>
    !
    en:
    read_more: 'read more…'
    !
    !

    read more…

    View Slide

  102. I18n + html
    <%= link_to t('read_more'), article %>
    !
    en:
    read_more: 'read more…'
    !
    !

    read <b>more</b>…

    View Slide

  103. I18n + html
    <%= link_to raw(t('read_more')), article %>
    !
    en:
    read_more: 'read more…'
    !
    !

    read more…

    View Slide

  104. I18n + html
    <%= link_to t(‘read_more_html'), article %>
    !
    en:
    read_more_html: 'read more…'
    !
    !

    read more…

    View Slide

  105. benchmark
    <% benchmark 'Rendering articles' do %>
    <%= sleep(2); render author.articles %>
    <% end %>
    !
    !
    # logger
    Rendering articles (2005.1ms)

    View Slide

  106. Action Controller

    View Slide

  107. redirects com parâmetros
    get '/articles/:id' => redirect('/articles/%{id}/edit')

    View Slide

  108. tratamento de exceções
    $ ls public/*.html
    !
    404.html 422.html 500.html

    View Slide

  109. tratamento de exceções
    # config/application.rb
    !
    # Use the app's own router to display error pages.
    config.exceptions_app = self.routes

    View Slide

  110. tratamento de exceções
    # config/routes.rb
    !
    get "/404", to: "errors#not_found"
    get "/422", to: "errors#unprocessable_entity"
    get "/500", to: "errors#server_error"

    View Slide

  111. tratamento de exceções
    class ErrorsController < ApplicationController
    layout "error"
    !
    def not_found
    render status: :not_found
    end
    !

    end

    View Slide

  112. tratamento de exceções
    app/views/
    errors/
    not_found.html.erb
    server_error.html.erb
    unprocessable_entity.html.erb
    layouts/
    error.html.erb

    View Slide

  113. Console

    View Slide

  114. app
    >> app.author_path(Author.first)
    => “/authors/298486374"
    !
    >> app.get _
    Started GET "/authors/298486374" for 127.0.0.1 at 2014-04-22 19:31:04 -0500
    Processing by AuthorsController#show as HTML

    Rendered authors/show.html.erb within layouts/application (1.1ms)
    Completed 200 OK in 9ms (Views: 7.7ms | ActiveRecord: 0.2ms)
    => 200
    !
    >> app.class
    => ActionDispatch::Integration::Session

    View Slide

  115. helper
    >> helper.time_ago_in_words 30.days.ago
    => "about 1 month"
    !
    >> helper.hello_ruby_conf
    => "Hello RubyConfBR 2014!"
    !
    >> helper.class
    => ActionView::Base

    View Slide

  116. console helpers da aplicação
    # lib/console_helpers.rb
    module ConsoleHelpers
    def a(id)
    Article.find(id)
    end
    end

    View Slide

  117. console helpers da aplicação
    # config/application.rb
    module RubyConf2014
    class Application < Rails::Application
    console do
    require 'console_helpers'
    Rails::ConsoleMethods.send :include, ConsoleHelpers
    end
    end
    end

    View Slide

  118. console helpers da aplicação
    >> a(113629430)
    Article Load (0.1ms) SELECT "articles".* FROM "articles" WHERE
    "articles"."id" = ? LIMIT 1 [["id", 113629430]]
    !
    => #

    View Slide

  119. console sandbox
    $ rails runner 'puts Article.count'
    4
    !
    $ rails console --sandbox
    >> Article.create! title: 'I will be rolled back'
    => #
    >> Article.count
    => 5
    !
    $ rails runner 'puts Article.count'
    4

    View Slide

  120. console sandbox
    $ rails console --sandbox

    Any modifications you make will be rolled back on exit
    !
    !
    !
    >> exit
    rollback transaction

    View Slide

  121. Atualizando Rails

    View Slide

  122. rails update
    gem 'rails', '4.1.0'
    !
    !
    $ bundle update

    View Slide

  123. rails update
    $ rake rails:update
    identical config/boot.rb
    exist config
    conflict config/routes.rb
    ./railsconf2014-404/config/routes.rb? (enter "h" for help) [Ynaqdh] h
    Y - yes, overwrite
    n - no, do not overwrite
    a - all, overwrite this and all others
    q - quit, abort
    d - diff, show the differences between the old and the new
    h - help, show this help
    ./railsconf2014-404/config/routes.rb? (enter "h" for help) [Ynaqdh] a
    force config/routes.rb

    exist config/initializers
    identical config/initializers/backtrace_silencers.rb
    create config/initializers/cookies_serializer.rb
    identical config/initializers/filter_parameter_logging.rb

    View Slide

  124. rails update
    $ git status
    On branch master
    Changes not staged for commit:
    (use "git add ..." to update what will be committed)
    (use "git checkout -- ..." to discard changes in working directory)
    !
    modified: Gemfile
    modified: Gemfile.lock
    modified: config/environment.rb
    modified: config/environments/development.rb
    modified: config/environments/production.rb
    modified: config/environments/test.rb
    modified: config/initializers/mime_types.rb
    modified: config/initializers/session_store.rb
    modified: config/routes.rb
    !
    Untracked files:
    (use "git add ..." to include in what will be committed)
    !
    config/initializers/cookies_serializer.rb
    config/secrets.yml

    View Slide

  125. Cada parte do Rails tem
    seus truques escondidos

    View Slide

  126. Eles são às vezes
    difíceis de achar

    View Slide

  127. Mas eles podem te deixar
    muito mais produtivo

    View Slide

  128. Então toda vez que você
    começar algo novo

    View Slide

  129. Antes mesmo de considerar
    usar o pattern X, Y ou Z

    View Slide

  130. Pergunte-se: Como o Rails
    pode me ajudar?

    View Slide

  131. Pesquise
    API
    Guidelines
    Google
    Stack Overflow

    View Slide

  132. Pergunte
    Stack Overflow
    Rails Talk mailing list
    IRC

    View Slide

  133. Siga
    Changelogs
    Release Notes
    Upgrading Guides
    Commits (se puder)

    View Slide

  134. E se você encontrar algum
    truque, lembre-se…

    View Slide

  135. View Slide

  136. Compartilhe
    Tweet
    Blog post
    Diga aos seus amigos

    View Slide

  137. Contribua
    De volta para o Rails

    View Slide

  138. E por favor me avise! :)
    !
    @cantoniodasilva

    View Slide

  139. Carlos Antonio
    https://speakerdeck.com/carlosantoniodasilva
    @cantoniodasilva
    Os truques que o
    Rails não te contou
    [email protected]

    View Slide