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

Carlos Antonio

May 17, 2014
Tweet

More Decks by Carlos Antonio

Other Decks in Technology

Transcript

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

    de lidar com esse tipo de código.
  2. Que podem te ajudar a refatorar código existente ou melhorar

    sua vida como desenvolvedor um pouquinho
  3. O que vamos ver: Active Record Active Model Action Mailer

    Action View Action Controller Bonus Stuff
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. 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
  10. Relation#merge class Author < ActiveRecord::Base has_many :articles ! scope :with_draft_articles,

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

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

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

    not found') # => ZOMG Article found (ActiveRecord::RecordNotFound) ! relation.last! # => ActiveRecord::RecordNotFound
  17. 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')
  18. 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')
  19. estratégias de eager load >> Article.includes(:author) ! SELECT "articles".* FROM

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

    “articles" ! SELECT "authors".* FROM “authors" WHERE "authors"."id" IN (980190962, 298486374)
  23. 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)
  24. 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
  25. 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
  26. 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"]
  27. 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"]
  28. 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]]
  29. 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
  30. 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
  31. benchmark >> ActiveRecord::Base.benchmark 'SUPER EXPENSIVE OPERATION' do ?> Article.expensive_operation >>

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

    OPERATION' do sleep 2 'did something very expensive' end end end
  33. ActiveModel::Model class EmailContact attr_accessor :name, :email, :message ! def deliver

    if name.present? && email.present? && message.present? puts 'deliver email' end end end
  34. 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
  35. 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
  36. 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"
  37. 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
  38. 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
  39. 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', title: 'Clarity is king') end end
  40. 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
  41. 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>
  42. render com coleções vazias <% if author.articles.exists? %> <%= render

    author.articles %> <% else %> <p>No article found.</p> <% end %>
  43. 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 %>
  44. 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 %>
  45. 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>
  46. 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>
  47. 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>
  48. 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>
  49. 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>
  50. benchmark <% benchmark 'Rendering articles' do %> <%= sleep(2); render

    author.articles %> <% end %> ! ! # logger Rendering articles (2005.1ms)
  51. tratamento de exceções # config/application.rb ! # Use the app's

    own router to display error pages. config.exceptions_app = self.routes
  52. 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"
  53. 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
  54. 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
  55. helper >> helper.time_ago_in_words 30.days.ago => "about 1 month" ! >>

    helper.hello_rails_conf => "Hello RailsConf 2014!" ! >> helper.class => ActionView::Base
  56. 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
  57. 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, …>
  58. 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
  59. console sandbox $ rails console --sandbox … Any modifications you

    make will be rolled back on exit ! ! ! >> exit rollback transaction
  60. 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
  61. 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
  62. 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 …
  63. 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