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

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

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

Carlos Antonio

April 23, 2014
Tweet

More Decks by Carlos Antonio

Other Decks in Technology

Transcript

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

    We’re always learning, right?
  2. Rails tricks / features Not used by many Not very

    well known and / or documented
  3. What we will see: 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: 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}
  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. eager load strategies >> Article.includes(:author) ! SELECT "articles".* FROM "articles"

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

    ! SELECT "authors".* FROM “authors" WHERE "authors"."id" IN (980190962, 298486374)
  23. 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)
  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 with 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 with 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 with 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 with 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. 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 %>
  43. 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 %>
  44. 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>
  45. 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>
  46. 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>
  47. 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>
  48. 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>
  49. benchmark <% benchmark 'Rendering articles' do %> <%= sleep(2); render

    author.articles %> <% end %> ! ! # logger Rendering articles (2005.1ms)
  50. exceptions handling # config/application.rb ! # Use the app's own

    router to display error pages. config.exceptions_app = self.routes
  51. exceptions handling # config/routes.rb ! get "/404", to: "errors#not_found" get

    "/422", to: "errors#unprocessable_entity" get "/500", to: "errors#server_error"
  52. 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
  53. 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
  54. helper >> helper.time_ago_in_words 30.days.ago => "about 1 month" ! >>

    helper.hello_rails_conf => "Hello RailsConf 2014!" ! >> helper.class => ActionView::Base
  55. 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
  56. application console helpers >> a(113629430) Article Load (0.1ms) SELECT "articles".*

    FROM "articles" WHERE "articles"."id" = ? LIMIT 1 [["id", 113629430]] ! => #<Article id: 113629430, …>
  57. 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
  58. console sandbox $ rails console --sandbox … Any modifications you

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