Tricks that Rails didn't tell you about @ RailsConf 2014

Tricks that Rails didn't tell you about @ RailsConf 2014

77237b97a465ae5a293ad323b7296837?s=128

Carlos Antonio

April 23, 2014
Tweet

Transcript

  1. 14.
  2. 15.
  3. 17.
  4. 18.
  5. 26.

    And it’s usual to learn new things in the process

    We’re always learning, right?
  6. 27.
  7. 28.
  8. 29.
  9. 30.
  10. 31.
  11. 33.

    Rails tricks / features Not used by many Not very

    well known and / or documented
  12. 34.

    What we will see: Active Record Active Model Action Mailer

    Action View Action Controller Bonus Stuff
  13. 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
  14. 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
  15. 38.

    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
  16. 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
  17. 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
  18. 41.

    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
  19. 42.

    Relation#merge class Author < ActiveRecord::Base has_many :articles ! scope :with_draft_articles,

    -> { uniq.joins(:articles). where(articles: { status: 'draft' }) } end
  20. 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'
  21. 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'
  22. 46.

    group counting >> Article.group(:status).count ! SELECT COUNT(*) AS count_all, status

    AS status FROM "articles" GROUP BY status ! => {"draft"=>7, "published"=>2}
  23. 47.

    group counting: multiple attrs >> 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}
  24. 48.

    first! relation = Article.where(title: 'UNKNOWN') ! relation.first or raise(ActiveRecord::RecordNotFound, 'ZOMG

    not found') # => ZOMG Article found (ActiveRecord::RecordNotFound) ! relation.first! # => ActiveRecord::RecordNotFound
  25. 49.

    last! relation = Article.where(title: 'UNKNOWN') ! relation.last or raise(ActiveRecord::RecordNotFound, 'ZOMG

    not found') # => ZOMG Article found (ActiveRecord::RecordNotFound) ! relation.last! # => ActiveRecord::RecordNotFound
  26. 50.

    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')
  27. 51.

    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')
  28. 52.

    eager load strategies >> Article.includes(:author) ! SELECT "articles".* FROM "articles"

    ! SELECT "authors".* FROM "authors" WHERE "authors"."id" IN (980190962, 298486374)
  29. 53.

    eager load strategies >> 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
  30. 54.

    eager load strategies >> 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"
  31. 55.

    eager load strategies >> Article.preload(:author) ! SELECT "articles".* FROM “articles"

    ! SELECT "authors".* FROM “authors" WHERE "authors"."id" IN (980190962, 298486374)
  32. 56.

    eager load strategies >> 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)
  33. 57.

    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
  34. 58.

    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
  35. 62.
  36. 64.

    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"]
  37. 65.

    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"]
  38. 67.

    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]]
  39. 68.

    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
  40. 70.

    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
  41. 71.

    benchmark >> ActiveRecord::Base.benchmark 'SUPER EXPENSIVE OPERATION' do ?> Article.expensive_operation >>

    end SUPER EXPENSIVE OPERATION (2000.9ms) => "did something very expensive"
  42. 72.

    benchmark class Article < ActiveRecord::Base def self.expensive_operation benchmark 'SUPER EXPENSIVE

    OPERATION' do sleep 2 'did something very expensive' end end end
  43. 73.
  44. 76.

    ActiveModel::Model class EmailContact attr_accessor :name, :email, :message ! def deliver

    if name.present? && email.present? && message.present? puts 'deliver email' end end end
  45. 79.

    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
  46. 80.

    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
  47. 82.

    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"
  48. 84.

    Mailer with 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
  49. 85.

    Mailer with 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
  50. 88.

    Mailer with i18n subject class Notifier < ActionMailer::Base default from:

    "from@example.com" ! def new_article @greeting = "Hi" ! mail to: "to@example.org" end end
  51. 89.

    Mailer with 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
  52. 92.

    Mailer with 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
  53. 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>
  54. 100.

    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 %>
  55. 101.

    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 %>
  56. 102.

    truncate text <%= 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>
  57. 103.

    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>
  58. 104.

    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>
  59. 105.

    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>
  60. 106.

    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>
  61. 107.

    benchmark <% benchmark 'Rendering articles' do %> <%= sleep(2); render

    author.articles %> <% end %> ! ! # logger Rendering articles (2005.1ms)
  62. 111.

    exceptions handling # config/application.rb ! # Use the app's own

    router to display error pages. config.exceptions_app = self.routes
  63. 112.

    exceptions handling # config/routes.rb ! get "/404", to: "errors#not_found" get

    "/422", to: "errors#unprocessable_entity" get "/500", to: "errors#server_error"
  64. 113.

    exceptions handling 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
  65. 116.
  66. 117.

    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
  67. 118.

    helper >> helper.time_ago_in_words 30.days.ago => "about 1 month" ! >>

    helper.hello_rails_conf => "Hello RailsConf 2014!" ! >> helper.class => ActionView::Base
  68. 120.

    application console helpers # config/application.rb module RailsConf2014 class Application <

    Rails::Application console do require 'console_helpers' Rails::ConsoleMethods.send :include, ConsoleHelpers end end end
  69. 121.

    application console helpers >> a(113629430) Article Load (0.1ms) SELECT "articles".*

    FROM "articles" WHERE "articles"."id" = ? LIMIT 1 [["id", 113629430]] ! => #<Article id: 113629430, …>
  70. 122.

    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
  71. 123.

    console sandbox $ rails console --sandbox … Any modifications you

    make will be rolled back on exit ! ! ! >> exit rollback transaction
  72. 125.

    annotations class Author < ActiveRecord::Base # TODO: create inactive scope

    scope :active, -> { where active: true } end ! class Article < ActiveRecord::Base # FIXME: Fix for RailsConf scope :published, -> { where status: 'published' } end ! class AuthorsController < ApplicationController # OPTIMIZE: eager load stuff def show @author = Author.find(params[:id]) end end
  73. 126.

    annotations $ rake notes ! app/controllers/authors_controller.rb: * [2] [OPTIMIZE] eager

    load stuff ! app/models/article.rb: * [9] [FIXME] Fix for RailsConf ! app/models/author.rb: * [4] [TODO] create inactive scope
  74. 131.

    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 …
  75. 132.

    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