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. Tricks that Rails didn’t
    tell you about

    View Slide

  2. Carlos Antonio
    @cantoniodasilva

    View Slide

  3. ~6 years
    on Rails

    View Slide

  4. I’ve participated in
    several projects with
    different contexts

    View Slide

  5. I’ve seen code

    View Slide

  6. I’ve seen complex code

    View Slide

  7. I’ve seen duplicated code

    View Slide

  8. I’ve seen Rails code
    reimplemented in apps

    View Slide

  9. How often do you see them?
    All the time. They're everywhere.

    View Slide

  10. You get used to it after
    some time

    View Slide

  11. And that’s a big problem
    People are afraid to deal with code like that.

    View Slide

  12. Rails tricks / features

    View Slide

  13. That might help you
    refactor existing code
    or improve your developer life a bit

    View Slide

  14. View Slide

  15. View Slide

  16. Web Development
    Consultancy
    Mainly focused in Ruby and Rails
    Based in São Paulo - Brazil

    View Slide

  17. View Slide

  18. ❤️

    View Slide

  19. plataformatec/devise

    View Slide

  20. plataformatec/simple_form

    View Slide

  21. elixir-lang/elixir

    View Slide

  22. Rails Core Team
    ! Carlos Antonio

    View Slide

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

    View Slide

  24. We use pull requests
    and code review
    All the f*cking time.

    View Slide

  25. We do cross project
    code review
    Sometimes, when the time allows us.

    View Slide

  26. And it’s usual to learn
    new things in the process
    We’re always learning, right?

    View Slide

  27. View Slide

  28. View Slide

  29. View Slide

  30. View Slide

  31. View Slide

  32. ಠ_ಠ
    I had to do this talk after this look of disapproval

    View Slide

  33. Rails tricks / features
    Not used by many
    Not very well known and / or documented

    View Slide

  34. What we will see:
    Active Record
    Active Model
    Action Mailer
    Action View
    Action Controller
    Bonus Stuff

    View Slide

  35. Active Record

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  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'

    View Slide

  44. Relation#merge
    class Author < ActiveRecord::Base
    has_many :articles
    !
    scope :with_draft_articles, -> {
    uniq.joins(:articles).merge(Article.draft)
    }
    end

    View Slide

  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'

    View Slide

  46. group counting
    >> Article.group(:status).count
    !
    SELECT COUNT(*) AS count_all, status AS status
    FROM "articles"
    GROUP BY status
    !
    => {"draft"=>7, "published"=>2}

    View Slide

  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}

    View Slide

  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

    View Slide

  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

    View Slide

  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')

    View Slide

  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')

    View Slide

  52. eager load strategies
    >> Article.includes(:author)
    !
    SELECT "articles".* FROM "articles"
    !
    SELECT "authors".* FROM "authors"
    WHERE "authors"."id" IN (980190962, 298486374)

    View Slide

  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

    View Slide

  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"

    View Slide

  55. eager load strategies
    >> Article.preload(:author)
    !
    SELECT "articles".* FROM “articles"
    !
    SELECT "authors".* FROM “authors"
    WHERE "authors"."id" IN (980190962, 298486374)

    View Slide

  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)

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  62. pluck: multiple attrs
    >> Author.pluck(:name, :active)
    !
    SELECT "authors"."name", "authors"."active"
    FROM "authors"
    !
    => [["Author 2", true], ["Author 1", true]]

    View Slide

  63. pluck: distinct values
    >> Author.distinct.pluck(:name)
    !
    SELECT DISTINCT "authors"."name" FROM "authors"
    !
    => ["Author 2", "Author 1"]

    View Slide

  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"]

    View Slide

  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"]

    View Slide

  66. exists?/any? vs present?
    <% if @author.articles.exists? %>
    <%= render @author.articles %>
    <% else %>
    No article found.
    <% end %>

    View Slide

  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]]

    View Slide

  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

    View Slide

  69. benchmark
    class Article < ActiveRecord::Base
    def self.expensive_operation
    sleep 2
    'did something very expensive'
    end
    end

    View Slide

  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

    View Slide

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

    View Slide

  72. benchmark
    class Article < ActiveRecord::Base
    def self.expensive_operation
    benchmark 'SUPER EXPENSIVE OPERATION' do
    sleep 2
    'did something very expensive'
    end
    end
    end

    View Slide

  73. View Slide

  74. http://railsgirlssummerofcode.org/campaign/
    And I got a free hug!

    View Slide

  75. Active Model

    View Slide

  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

    View Slide

  77. ActiveModel::Model
    contact = EmailContact.new
    contact.name = 'Carlos'
    contact.email = '[email protected]'
    contact.message = 'ZOMG'
    contact.deliver

    View Slide

  78. ActiveModel::Model
    <%= form_for @email_contact … %>
    !
    <%= render @email_contact %>

    View Slide

  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

    View Slide

  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

    View Slide

  81. ActiveModel::Model
    contact = EmailContact.new(
    name: 'Carlos',
    email: '[email protected]',
    message: 'ZOMG'
    )
    contact.deliver

    View Slide

  82. ActiveModel::Model
    >> contact = EmailContact.new
    => #
    !
    >> 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"

    View Slide

  83. Action Mailer

    View Slide

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

    View Slide

  85. 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

    View Slide

  86. Mailer with i18n subject
    en:
    subjects:
    new_article: 'New article just published.'

    View Slide

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

    View Slide

  88. Mailer with i18n subject
    class Notifier < ActionMailer::Base
    default from: "[email protected]"
    !
    def new_article
    @greeting = "Hi"
    !
    mail to: "[email protected]"
    end
    end

    View Slide

  89. 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

    View Slide

  90. Mailer with i18n subject
    en:
    subjects:
    new_article: 'New article: %{title}.'

    View Slide

  91. Mailer with i18n subject
    en:
    notifier:
    new_article:
    subject: 'New article %{title}.'

    View Slide

  92. 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

    View Slide

  93. Action View

    View Slide

  94. content_tag_for
    <%= content_tag_for :article, @articles do |article| %>
    <%= article.title %>
    <%= simple_format article.body %>
    <% end %>

    View Slide

  95. content_tag_for

    Rails 4.2.0 is going to be more Adequate
    Rails 4.2.0 is going to be made more Adequate by
    Tenderlove and Gorby Puff


    Rails 4.1.0 Released
    Rails 4.1.0 was just released before RailsConf

    View Slide

  96. content_tag_for
    <%= content_tag_for :section, @author do |author| %>
    <%= author.name %>

    <% end %>

    View Slide

  97. content_tag_for

    Author 2


    View Slide

  98. render empty collections
    <% if author.articles.exists? %>
    <%= render author.articles %>
    <% else %>
    No article found.
    <% end %>

    View Slide

  99. render empty collections
    <%= render(author.articles) ||
    content_tag(:p, 'No article found.') %>

    View Slide

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

    View Slide

  101. partial locals
    <%= render article, full: true %>
    <%= render author.articles %>
    !
    # article partial
    <%= content_tag_for :article, article do |article| %>
    <%= article.title %>
    !
    <% if local_assigns[:full] %>
    <%= simple_format article.body %>
    <% else %>
    <%= truncate article.body %>
    <% end %>
    <% end %>

    View Slide

  102. truncate text
    <%= truncate article.body do %>
    <%= link_to 'read more...', article %>
    <% end %>
    !
    !
    Rails 4.2.0 is going to be ...

    read more…

    View Slide

  103. I18n + html
    <%= link_to I18n.t('read_more'), article %>
    !
    !
    en:
    read_more: 'read more…'
    !
    !

    read more…

    View Slide

  104. I18n + html
    <%= link_to I18n.t('read_more'), article %>
    !
    !
    en:
    read_more: 'read more…'
    !
    !

    read <b>more</b>…

    View Slide

  105. I18n + html
    <%= link_to raw(I18n.t('read_more')), article %>
    !
    !
    en:
    read_more: 'read more…'
    !
    !

    read more…

    View Slide

  106. I18n + html
    <%= link_to I18n.t(‘read_more_html'), article %>
    !
    !
    en:
    read_more_html: 'read more…'
    !
    !

    read more…

    View Slide

  107. benchmark
    <% benchmark 'Rendering articles' do %>
    <%= sleep(2); render author.articles %>
    <% end %>
    !
    !
    # logger
    Rendering articles (2005.1ms)

    View Slide

  108. Action Controller

    View Slide

  109. redirects with params
    get '/articles/:id' => redirect('/articles/%{id}/edit')

    View Slide

  110. exceptions handling
    $ ls public/*.html
    !
    404.html 422.html 500.html

    View Slide

  111. exceptions handling
    # config/application.rb
    !
    # Use the app's own router to display error pages.
    config.exceptions_app = self.routes

    View Slide

  112. exceptions handling
    # config/routes.rb
    !
    get "/404", to: "errors#not_found"
    get "/422", to: "errors#unprocessable_entity"
    get "/500", to: "errors#server_error"

    View Slide

  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

    View Slide

  114. exceptions handling
    app/views/
    errors/
    not_found.html.erb
    server_error.html.erb
    unprocessable_entity.html.erb
    layouts/
    error.html.erb

    View Slide

  115. Bonus Time!

    View Slide

  116. Console

    View Slide

  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

    View Slide

  118. helper
    >> helper.time_ago_in_words 30.days.ago
    => "about 1 month"
    !
    >> helper.hello_rails_conf
    => "Hello RailsConf 2014!"
    !
    >> helper.class
    => ActionView::Base

    View Slide

  119. application console helpers
    # lib/console_helpers.rb
    module ConsoleHelpers
    def a(id)
    Article.find(id)
    end
    end

    View Slide

  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

    View Slide

  121. application console helpers
    >> a(113629430)
    Article Load (0.1ms) SELECT "articles".* FROM "articles"
    WHERE "articles"."id" = ? LIMIT 1 [["id", 113629430]]
    !
    => #

    View Slide

  122. console sandbox
    $ rails console
    >> Article.count
    => 4
    !
    $ rails console --sandbox
    >> Article.create! title: 'I will be rolled back'
    => #
    >> Article.count
    => 5
    !
    $ rails console
    >> Article.count
    => 4

    View Slide

  123. console sandbox
    $ rails console --sandbox

    Any modifications you make will be rolled back on exit
    !
    !
    !
    >> exit
    rollback transaction

    View Slide

  124. Annotations

    View Slide

  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

    View Slide

  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

    View Slide

  127. custom annotations
    class Notifier < ActionMailer::Base
    # RAILSCONF: Hello!
    def new_article
    end
    end

    View Slide

  128. custom annotations
    $ rake notes:custom ANNOTATION=RAILSCONF
    !
    app/mailers/notifier.rb:
    * [4] Hello!

    View Slide

  129. Updating Rails

    View Slide

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

    View Slide

  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

    View Slide

  132. rails update
    $ git status
    On branch master
    Changes not staged for commit:
    (use "git add ..." to update what will be committed)
    (use "git checkout -- ..." 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 ..." to include in what will be committed)
    !
    config/initializers/cookies_serializer.rb
    config/secrets.yml

    View Slide

  133. Every single part of
    Rails has hidden tricks

    View Slide

  134. They are sometimes
    hard to spot

    View Slide

  135. But they can make you
    much more productive

    View Slide

  136. So every time you start
    something new

    View Slide

  137. Before ever considering
    about pattern X, Y or Z

    View Slide

  138. Ask yourself:
    How Rails can help me?

    View Slide

  139. Search
    API
    Guidelines
    Google
    Stack Overflow

    View Slide

  140. Ask
    Stack Overflow
    Rails Talk mailing list
    IRC

    View Slide

  141. Follow
    Changelogs
    Release Notes
    Upgrading Guides
    Commits (if you can)

    View Slide

  142. And if you do find some
    hidden trick, remember…

    View Slide

  143. With great moustache!
    !
    !
    !
    comes great responsibility

    View Slide

  144. Share
    Tweet
    Blog post
    Tell your friends

    View Slide

  145. Contribute
    Back to Rails

    View Slide

  146. And please ping me too!
    !
    @cantoniodasilva

    View Slide

  147. Thank you ❤️

    View Slide

  148. Stickers!
    @plataformatec

    View Slide

  149. Questions?
    Get one of these mugs (5 only :D)

    View Slide

  150. Tricks that Rails!
    didn’t tell you about
    Carlos Antonio - @cantoniodasilva

    View Slide