Slide 1

Slide 1 text

Tricks that Rails didn’t tell you about

Slide 2

Slide 2 text

Carlos Antonio @cantoniodasilva

Slide 3

Slide 3 text

~6 years on Rails

Slide 4

Slide 4 text

I’ve participated in several projects with different contexts

Slide 5

Slide 5 text

I’ve seen code

Slide 6

Slide 6 text

I’ve seen complex code

Slide 7

Slide 7 text

I’ve seen duplicated code

Slide 8

Slide 8 text

I’ve seen Rails code reimplemented in apps

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

You get used to it after some time

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

Rails tricks / features

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

❤️

Slide 19

Slide 19 text

plataformatec/devise

Slide 20

Slide 20 text

plataformatec/simple_form

Slide 21

Slide 21 text

elixir-lang/elixir

Slide 22

Slide 22 text

Rails Core Team ! Carlos Antonio

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

No content

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

Active Record

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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'

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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'

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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}

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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"

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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)

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

exists?/any? vs present? <% if @author.articles.exists? %> <%= render @author.articles %> <% else %>

No article found.

<% end %>

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

No content

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

Active Model

Slide 76

Slide 76 text

ActiveModel::Model class EmailContact attr_accessor :name, :email, :message ! def deliver if name.present? && email.present? && message.present? puts 'deliver email' end end end

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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"

Slide 83

Slide 83 text

Action Mailer

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

Action View

Slide 94

Slide 94 text

content_tag_for <%= content_tag_for :article, @articles do |article| %>

<%= article.title %>

<%= simple_format article.body %> <% end %>

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

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

<%= author.name %>

… <% end %>

Slide 97

Slide 97 text

content_tag_for

Author 2

Slide 98

Slide 98 text

render empty collections <% if author.articles.exists? %> <%= render author.articles %> <% else %>

No article found.

<% end %>

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

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

Slide 108

Slide 108 text

Action Controller

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

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

Slide 111

Slide 111 text

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

Slide 112

Slide 112 text

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

Slide 113

Slide 113 text

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

Slide 114

Slide 114 text

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

Slide 115

Slide 115 text

Bonus Time!

Slide 116

Slide 116 text

Console

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

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

Slide 119

Slide 119 text

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

Slide 120

Slide 120 text

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

Slide 121

Slide 121 text

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

Slide 122

Slide 122 text

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

Slide 123

Slide 123 text

console sandbox $ rails console --sandbox … Any modifications you make will be rolled back on exit ! ! ! >> exit rollback transaction

Slide 124

Slide 124 text

Annotations

Slide 125

Slide 125 text

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

Slide 126

Slide 126 text

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

Slide 127

Slide 127 text

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

Slide 128

Slide 128 text

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

Slide 129

Slide 129 text

Updating Rails

Slide 130

Slide 130 text

rails update gem 'rails', '4.1.0' ! ! $ bundle update …

Slide 131

Slide 131 text

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 …

Slide 132

Slide 132 text

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

Slide 133

Slide 133 text

Every single part of Rails has hidden tricks

Slide 134

Slide 134 text

They are sometimes hard to spot

Slide 135

Slide 135 text

But they can make you much more productive

Slide 136

Slide 136 text

So every time you start something new

Slide 137

Slide 137 text

Before ever considering about pattern X, Y or Z

Slide 138

Slide 138 text

Ask yourself: How Rails can help me?

Slide 139

Slide 139 text

Search API Guidelines Google Stack Overflow …

Slide 140

Slide 140 text

Ask Stack Overflow Rails Talk mailing list IRC …

Slide 141

Slide 141 text

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

Slide 142

Slide 142 text

And if you do find some hidden trick, remember…

Slide 143

Slide 143 text

With great moustache! ! ! ! comes great responsibility

Slide 144

Slide 144 text

Share Tweet Blog post Tell your friends …

Slide 145

Slide 145 text

Contribute Back to Rails

Slide 146

Slide 146 text

And please ping me too! ! @cantoniodasilva

Slide 147

Slide 147 text

Thank you ❤️

Slide 148

Slide 148 text

Stickers! @plataformatec

Slide 149

Slide 149 text

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

Slide 150

Slide 150 text

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