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

77237b97a465ae5a293ad323b7296837?s=128

Carlos Antonio

August 29, 2014
Tweet

Transcript

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

  2. Carlos Antonio @cantoniodasilva

  3. None
  4. ~6 anos Ruby / Rails

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

  6. Eu já vi código

  7. Eu já vi código complexo

  8. Eu já vi código duplicado

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

  10. Com que frequência você vê eles? Todo o tempo. Eles

    estão em toda parte.
  11. Você se acostuma com o código após algum tempo

  12. E isso é um grande problema As pessoas têm medo

    de lidar com esse tipo de código.
  13. Rails tricks / features Não usadas por muitos Não muito

    conhecidas e / ou documentadas
  14. Que podem te ajudar a refatorar código existente ou melhorar

    sua vida como desenvolvedor um pouquinho.
  15. TDD IS DEAD

  16. None
  17. Devise Rails Summit 2009

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

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

  22. None
  23. A Plataformatec usa pull requests e code review All the

    f*cking time.
  24. E é comum aprender coisas novas no processo Estamos sempre

    aprendendo, certo?
  25. None
  26. None
  27. None
  28. None
  29. None
  30. ಠ_ಠ Eu tive que criar este talk depois disso.

  31. O que vamos ver: Active Record Active Model Action Mailer

    ! Action View Action Controller Bonus Stuff
  32. Active Record

  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
  34. migrations: revert class DropAuthorsActive < ActiveRecord::Migration def change remove_column :authors,

    :active end end # => ActiveRecord::IrreversibleMigration
  35. migrations: revert class DropAuthorsActive < ActiveRecord::Migration def change revert do

    add_column :authors, :active, :boolean, default: true end end end
  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
  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
  38. migrations: change column null class ChangeNameToNotNullOnAuthors < ActiveRecord::Migration def change

    change_column_null :authors, :name, false end end
  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
  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
  41. migrations: change column default class ChangeStatusDefaultOnArticles < ActiveRecord::Migration def change

    change_column_default :articles, :status, 'draft' end end # => ActiveRecord::IrreversibleMigration
  42. Relation#merge class Author < ActiveRecord::Base has_many :articles ! scope :with_draft_articles,

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

    -> { uniq.joins(:articles).merge(Article.draft) } end
  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'
  46. find_by >> Article.where(title: 'ZOMG').first => #<Article id: 113629430 ...> !

    >> Article.find_by(title: 'ZOMG') => #<Article id: 113629430 ...>
  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')
  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')
  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
  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
  51. estratégias de eager load >> Article.includes(:author) ! SELECT "articles".* FROM

    "articles" ! SELECT "authors".* FROM "authors" WHERE "authors"."id" IN (980190962, 298486374)
  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
  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"
  54. estratégias de eager load >> Article.preload(:author) ! SELECT "articles".* FROM

    “articles" ! SELECT "authors".* FROM “authors" WHERE "authors"."id" IN (980190962, 298486374)
  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)
  56. group counting >> Article.group(:status).count ! SELECT COUNT(*) AS count_all, status

    AS status FROM "articles" GROUP BY status ! => {"draft"=>7, “published"=>2}
  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}
  58. pluck >> Author.all.map(&:name) ! SELECT "authors".* FROM "authors" ! =>

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

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

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

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

    "authors" ! => ["Author 2", "Author 1"]
  63. exists?/any? vs present? <% if @author.articles.exists? %> <%= render @author.articles

    %> <% else %> <p>No article found.</p> <% end %>
  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
  65. exists?/any? vs present? >> author.articles.each {} ! SELECT "articles".* FROM

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

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

    carregado, não dispara outra query
  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]]
  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
  70. benchmark class Article < ActiveRecord::Base def self.expensive_operation sleep 2 'did

    something very expensive' end end
  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
  72. benchmark >> ActiveRecord::Base.benchmark 'SUPER EXPENSIVE OPERATION' do ?> Article.expensive_operation >>

    end SUPER EXPENSIVE OPERATION (2000.9ms) => "did something very expensive"
  73. benchmark class Article < ActiveRecord::Base def self.expensive_operation benchmark 'SUPER EXPENSIVE

    OPERATION' do sleep 2 'did something very expensive' end end end
  74. Active Model

  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
  76. ActiveModel::Model contact = EmailContact.new contact.name = 'Carlos' contact.email = 'carlos@example.com'

    contact.message = 'ZOMG' contact.deliver
  77. ActiveModel::Model <%= form_for @email_contact … %> ! <%= render @email_contact

    %>
  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
  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
  80. ActiveModel::Model contact = EmailContact.new( name: 'Carlos', email: 'carlos@example.com', message: 'ZOMG'

    ) contact.deliver
  81. ActiveModel::Model >> contact = EmailContact.new => #<EmailContact:0x007fb90e268350> ! >> 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"
  82. Action Mailer

  83. Mailer com i18n subject class Notifier < ActionMailer::Base default from:

    "from@example.com" ! # 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: "to@example.org" end end
  84. Mailer com i18n subject class Notifier < ActionMailer::Base default from:

    "from@example.com" ! def new_article @greeting = "Hi" ! mail to: "to@example.org" end end
  85. Mailer com i18n subject class Notifier < ActionMailer::Base default from:

    "from@example.com" ! def new_article @greeting = "Hi" ! mail to: "to@example.org", subject: I18n.t('subjects.new_article') end end
  86. Mailer com i18n subject en: subjects: new_article: 'New article just

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

    just published.'
  88. Mailer com i18n subject class Notifier < ActionMailer::Base default from:

    "from@example.com" ! def new_article @greeting = "Hi" ! mail to: "to@example.org" end end
  89. Mailer com i18n subject class Notifier < ActionMailer::Base default from:

    "from@example.com" ! def new_article @greeting = "Hi" ! mail to: "to@example.org", subject: I18n.t('subjects.new_article', title: 'Clarity is king') end end
  90. Mailer com i18n subject en: subjects: new_article: 'New article: %{title}.'

  91. Mailer com i18n subject en: notifier: new_article: subject: 'New article

    %{title}.'
  92. Mailer com i18n subject class Notifier < ActionMailer::Base default from:

    "from@example.com" ! def new_article @greeting = "Hi" ! mail to: "to@example.org", subject: default_i18n_subject(title: 'Clarity is king') end end
  93. Action View

  94. content_tag_for <%= content_tag_for :article, @articles do |article| %> <h2><%= article.title

    %></h2> <%= simple_format article.body %> <% end %>
  95. content_tag_for <article class="article" id="article_281110143"> <h2>Rails 4.2.0 is going to be

    more Adequate</h2> <p>Rails 4.2.0 is going to be made more Adequate by Tenderlove and Gorby Puff</p> </article> <article class="article" id="article_298486374"> <h2>Rails 4.1.0 Released</h2> <p>Rails 4.1.0 was just released before RailsConf</p> </article>
  96. content_tag_for <%= content_tag_for :section, @author do |author| %> <h1><%= author.name

    %></h1> … <% end %>
  97. content_tag_for <section class="author" id="author_298486374"> <h1>Author 2</h1> … </section>

  98. render com coleções vazias <% if author.articles.exists? %> <%= render

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

    found.') %>
  100. truncar textos <%= truncate article.body do %> <%= link_to 'read

    more...', article %> <% end %> ! ! Rails 4.2.0 is going to be ... <a href="/articles/281110143-rails-4-2-0-is"> read more… </a>
  101. I18n + html <%= link_to t('read_more'), article %> ! en:

    read_more: 'read more…' ! ! <a href="/articles/281110143-rails-4-2-0-is"> read more… </a>
  102. I18n + html <%= link_to t('read_more'), article %> ! en:

    read_more: 'read <b>more</b>…' ! ! <a href="/articles/281110143-rails-4-2-0-is"> read &lt;b&gt;more&lt;/b&gt;… </a>
  103. I18n + html <%= link_to raw(t('read_more')), article %> ! en:

    read_more: 'read <b>more</b>…' ! ! <a href="/articles/281110143-rails-4-2-0-is"> read <b>more</b>… </a>
  104. I18n + html <%= link_to t(‘read_more_html'), article %> ! en:

    read_more_html: 'read <b>more</b>…' ! ! <a href="/articles/281110143-rails-4-2-0-is"> read <b>more</b>… </a>
  105. benchmark <% benchmark 'Rendering articles' do %> <%= sleep(2); render

    author.articles %> <% end %> ! ! # logger Rendering articles (2005.1ms)
  106. Action Controller

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

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

  109. tratamento de exceções # config/application.rb ! # Use the app's

    own router to display error pages. config.exceptions_app = self.routes
  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"
  111. tratamento de exceções class ErrorsController < ApplicationController layout "error" !

    def not_found render status: :not_found end ! … end
  112. tratamento de exceções app/views/ errors/ not_found.html.erb server_error.html.erb unprocessable_entity.html.erb layouts/ error.html.erb

  113. Console

  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
  115. helper >> helper.time_ago_in_words 30.days.ago => "about 1 month" ! >>

    helper.hello_ruby_conf => "Hello RubyConfBR 2014!" ! >> helper.class => ActionView::Base
  116. console helpers da aplicação # lib/console_helpers.rb module ConsoleHelpers def a(id)

    Article.find(id) end end
  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
  118. console helpers da aplicação >> a(113629430) Article Load (0.1ms) SELECT

    "articles".* FROM "articles" WHERE "articles"."id" = ? LIMIT 1 [["id", 113629430]] ! => #<Article id: 113629430, …>
  119. console sandbox $ rails runner 'puts Article.count' 4 ! $

    rails console --sandbox >> Article.create! title: 'I will be rolled back' => #<Article id: 980190968, title: "I will be rolled back", …> >> Article.count => 5 ! $ rails runner 'puts Article.count' 4
  120. console sandbox $ rails console --sandbox … Any modifications you

    make will be rolled back on exit ! ! ! >> exit rollback transaction
  121. Atualizando Rails

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

  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 …
  124. rails update $ git status On branch master Changes not

    staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." 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 <file>..." to include in what will be committed) ! config/initializers/cookies_serializer.rb config/secrets.yml
  125. Cada parte do Rails tem seus truques escondidos

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

  127. Mas eles podem te deixar muito mais produtivo

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

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

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

  131. Pesquise API Guidelines Google Stack Overflow …

  132. Pergunte Stack Overflow Rails Talk mailing list IRC …

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

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

  135. None
  136. Compartilhe Tweet Blog post Diga aos seus amigos …

  137. Contribua De volta para o Rails

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

  139. Carlos Antonio https://speakerdeck.com/carlosantoniodasilva @cantoniodasilva Os truques que o Rails não

    te contou carlosantoniodasilva@gmail.com