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

Os truques que o Rails não te contou @ TDC Floripa 2014

Os truques que o Rails não te contou @ TDC Floripa 2014

77237b97a465ae5a293ad323b7296837?s=128

Carlos Antonio

May 17, 2014
Tweet

Transcript

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

  2. Carlos Antonio @cantoniodasilva

  3. ~6 anos com Rails

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

  5. Eu já vi código

  6. Eu já vi código complexo

  7. Eu já vi código duplicado

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

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

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

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

    de lidar com esse tipo de código.
  12. Rails tricks / features

  13. Que podem te ajudar a refatorar código existente ou melhorar

    sua vida como desenvolvedor um pouquinho
  14. None
  15. None
  16. Consultoria em Desenvolvimento Web Com foco principal em Ruby e

    Rails São Paulo - Brazil
  17. ❤️

  18. plataformatec/devise

  19. plataformatec/simple_form

  20. elixir-lang/elixir

  21. Rails Core Team ! Carlos Antonio

  22. Rails Core Team José Valim Carlos Antonio Rafael França

  23. Nós usamos pull requests ! e code review All the

    f*cking time.
  24. Nós fizemos code review de diferentes projetos Sempre que o

    tempo nos permite.
  25. E é comum aprender coisas novas no processo Estamos sempre

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

  32. Rails tricks / features Não usadas por muitos Não muito

    conhecidas e / ou documentadas
  33. O que vamos ver: Active Record Active Model Action Mailer

    Action View Action Controller Bonus Stuff
  34. Active Record

  35. 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
  36. 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
  37. migrations: change column null class ChangeNameToNotNullOnAuthors < ActiveRecord::Migration def change

    change_column :authors, :name, :string, null: false end end # => ActiveRecord::IrreversibleMigration ! class ChangeNameToNotNullOnAuthors < ActiveRecord::Migration def change change_column_null :authors, :name, false end end
  38. 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
  39. 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
  40. migrations: change column default class ChangeStatusDefaultOnArticles < ActiveRecord::Migration def change

    change_column :articles, :status, :string, default: 'draft' end end # => ActiveRecord::IrreversibleMigration ! class ChangeStatusDefaultOnArticles < ActiveRecord::Migration def change change_column_default :articles, :status, 'draft' end end # => ActiveRecord::IrreversibleMigration
  41. Relation#merge class Author < ActiveRecord::Base has_many :articles ! scope :with_draft_articles,

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

    -> { uniq.joins(:articles).merge(Article.draft) } end
  44. 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'
  45. group counting >> Article.group(:status).count ! SELECT COUNT(*) AS count_all, status

    AS status FROM "articles" GROUP BY status ! => {"draft"=>7, "published"=>2}
  46. 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}
  47. first! relation = Article.where(title: 'UNKNOWN') ! relation.first or raise(ActiveRecord::RecordNotFound, 'ZOMG

    not found') # => ZOMG Article found (ActiveRecord::RecordNotFound) ! relation.first! # => ActiveRecord::RecordNotFound
  48. last! relation = Article.where(title: 'UNKNOWN') ! relation.last or raise(ActiveRecord::RecordNotFound, 'ZOMG

    not found') # => ZOMG Article found (ActiveRecord::RecordNotFound) ! relation.last! # => ActiveRecord::RecordNotFound
  49. 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')
  50. 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')
  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. 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
  57. 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
  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. to_param class Article < ActiveRecord::Base def to_param "#{id}-#{title.parameterize}" end end

    ! >> Article.all.map(&:to_param) => ["113629430-autoload", "281110143-rails-4-2-0-is-going-to-be-more-adequate", "298486374-rails-4-1-0-released", "980190962-rails-tricks"]
  64. to_param class Article < ActiveRecord::Base to_param :title end ! !

    ! >> Article.all.map(&:to_param) => ["113629430-autoload", "281110143-rails-4-2-0-is", "298486374-rails-4-1-0-released", "980190962-rails-tricks"]
  65. exists?/any? vs present? <% if @author.articles.exists? %> <%= render @author.articles

    %> <% else %> <p>No article found.</p> <% end %>
  66. exists?/any? vs present? >> author = Author.first ! >> author.articles.exists?

    ! SELECT 1 AS one FROM "articles" WHERE "articles"."author_id" = ? LIMIT 1 [["author_id", 298486374]] ! => true ! >> author.articles.each {} ! SELECT "articles".* FROM "articles" WHERE "articles"."author_id" = ? [["author_id", 298486374]]
  67. exists?/any? vs present? >> author = Author.first ! >> author.articles.present?

    ! SELECT "articles".* FROM "articles" WHERE "articles"."author_id" = ? [["author_id", 298486374]] ! => true ! >> author.articles.each {} ! # => already loaded, so no query
  68. benchmark class Article < ActiveRecord::Base def self.expensive_operation sleep 2 'did

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

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

    OPERATION' do sleep 2 'did something very expensive' end end end
  72. None
  73. http://railsgirlssummerofcode.org/campaign/ Free hug!

  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", subject: I18n.t('subjects.new_article') end end
  85. Mailer com i18n subject en: subjects: new_article: 'New article just

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

    just published.'
  87. 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
  88. 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
  89. Mailer com i18n subject en: subjects: new_article: 'New article: %{title}.'

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

    %{title}.'
  91. 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
  92. Action View

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

    %></h2> <%= simple_format article.body %> <% end %>
  94. 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>
  95. content_tag_for <%= content_tag_for :section, @author do |author| %> <h1><%= author.name

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

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

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

    found.') %>
  99. partial locals <%= render article, full: true %> <%= render

    author.articles, full: false %> ! # article partial <%= content_tag_for :article, article do |article| %> <h2><%= article.title %></h2> ! <% if full %> <%= simple_format article.body %> <% else %> <%= truncate article.body %> <% end %> <% end %>
  100. partial locals <%= render article, full: true %> <%= render

    author.articles %> ! # article partial <%= content_tag_for :article, article do |article| %> <h2><%= article.title %></h2> ! <% if local_assigns[:full] %> <%= simple_format article.body %> <% else %> <%= truncate article.body %> <% end %> <% end %>
  101. 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>
  102. I18n + html <%= link_to I18n.t('read_more'), article %> ! !

    en: read_more: 'read more…' ! ! <a href="/articles/281110143-rails-4-2-0-is"> read more… </a>
  103. I18n + html <%= link_to I18n.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>
  104. I18n + html <%= link_to raw(I18n.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>
  105. I18n + html <%= link_to I18n.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>
  106. benchmark <% benchmark 'Rendering articles' do %> <%= sleep(2); render

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

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

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

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

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

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

  114. Bonus Time!

  115. Console

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

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

    Article.find(id) end end
  119. console helpers da aplicação # config/application.rb module RailsConf2014 class Application

    < Rails::Application console do require 'console_helpers' Rails::ConsoleMethods.send :include, ConsoleHelpers end end end
  120. 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, …>
  121. console sandbox $ rails console >> 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 console >> Article.count => 4
  122. console sandbox $ rails console --sandbox … Any modifications you

    make will be rolled back on exit ! ! ! >> exit rollback transaction
  123. Annotations

  124. notas class Author < ActiveRecord::Base # TODO: create inactive scope

    scope :active, -> { where active: true } end ! class Article < ActiveRecord::Base # FIXME: Fix this scope :published, -> { where status: 'published' } end ! class AuthorsController < ApplicationController # OPTIMIZE: eager load stuff def show @author = Author.find(params[:id]) end end
  125. notas $ rake notes ! app/controllers/authors_controller.rb: * [2] [OPTIMIZE] eager

    load stuff ! app/models/article.rb: * [9] [FIXME] Fix this ! app/models/author.rb: * [4] [TODO] create inactive scope
  126. notas personalizadas class Notifier < ActionMailer::Base # ZOMG: Hello! def

    new_article end end
  127. notas personalizadas $ rake notes:custom ANNOTATION=ZOMG ! app/mailers/notifier.rb: * [4]

    Hello!
  128. Atualizando Rails

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

  130. 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 …
  131. 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
  132. Cada parte do Rails tem seus truques escondidos

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

  134. Mas eles podem te deixar muito mais produtivo

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

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

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

  138. Pesquise API Guidelines Google Stack Overflow …

  139. Pergunte Stack Overflow Rails Talk mailing list IRC …

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

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

  142. With great moustache! ! ! ! comes great responsibility

  143. Compartilhe Tweet Blog post Diga aos seus amigos …

  144. Contribua De volta para o Rails

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

  146. Os truques que o Rails não te contou Carlos Antonio

    - @cantoniodasilva