Rails 5 and its new features by examples

Cff27a35a78ec87b4ca1a25100888330?s=47 Yoshio Teruia
September 27, 2016

Rails 5 and its new features by examples

Cff27a35a78ec87b4ca1a25100888330?s=128

Yoshio Teruia

September 27, 2016
Tweet

Transcript

  1. Rails 5 And its new features by examples

  2. Yoshio Teruia

  3. None
  4. None
  5. Estamos contratando! http://plataformatec.workable.com

  6. Rails 5 And its new features by examples

  7. E o Action Cable?

  8. É uma integração do websocket com o Rails que possibilita

    o desenvolvimento de real-time features. Action Cable
  9. goo.gl/qT8DIs

  10. goo.gl/Slautj

  11. goo.gl/UpwmRD

  12. CLI

  13. Agora o `rails` pode ser usado para rodar rake tasks

    na CLI. Comandos do Rails centralizado
  14. rails db:migrate rails db:create rails test rails routes

  15. Testes

  16. Executar os testes ficou mais simples. Test Runner

  17. Executar os testes ficou mais simples. Rodar um único teste

  18. rails test test/models/user_test.rb:4

  19. rails test test/models/user_test.rb:4

  20. rails test test/models/user_test.rb:4

  21. Não é mais necessário adicionar uma gem. Opção fail fast

  22. rails test -f

  23. rails test -f

  24. A fase de exercise nos testes de integração ficam mais

    explícitas. Kwarg nos métodos de requisição
  25. Rails 4 class UserListTest < ActionDispatch::IntegrationTest test 'returns all users'

    do … get users_url, nil, { 'X-Custom-Header' => 'Header Value', formats: :json } … end end
  26. Rails 4 class UserListTest < ActionDispatch::IntegrationTest test 'returns all users'

    do … get users_url, nil, { 'X-Custom-Header' => 'Header Value’, formats: :json } … end end
  27. Rails 4 class UserListTest < ActionDispatch::IntegrationTest test 'returns all users'

    do … get users_url, nil, { 'X-Custom-Header' => 'Header Value’, formats: :json } … end end
  28. Rails 5 class UserListTest < ActionDispatch::IntegrationTest test 'returns all users'

    do … get users_url, headers: { 'X-Custom-Header' => 'Header Value’ }, as: :json … end end
  29. Rails 5 class UserListTest < ActionDispatch::IntegrationTest test 'returns all users'

    do … get users_url, headers: { 'X-Custom-Header' => 'Header Value’ }, as: :json … end end
  30. Asset Pipeline

  31. Os logs ficam mais enxuto no modo de desenvolvimento. Possibilidade

    de desabilitar os logs dos assets
  32. Started GET "/users" for ::1 at 2016-09-23 04:14:49 -0300 ActiveRecord::SchemaMigration

    Load (0.7ms) SELECT "schema_migrations".* FROM "schema_migrations" Processing by UsersController#index as HTML Rendering users/index.html.erb within layouts/application User Load (0.5ms) SELECT "users".* FROM "users" Rendered users/index.html.erb within layouts/application (16.8ms) Completed 200 OK in 366ms (Views: 340.1ms | ActiveRecord: 5.0ms) Started GET "/assets/jquery.self-bd7ddd393353a8d2480a622e80342adf488fb6006d667e8b42e4c0073393abee.js? body=1" for ::1 at 2016-09-23 04:14:50 -0300 Started GET "/assets/turbolinks.self-c5acd7a204f5f25ce7a1d8a0e4d92e28d34c9e2df2c7371cd7af88e147e4ad82.js? body=1" for ::1 at 2016-09-23 04:14:50 -0300 Started GET "/assets/application.self- b89234cf2659d7fedea75bca0b8d231ad7dfc2f3f57fcbaf5f44ed9dc384137b.js?body=1" for ::1 at 2016-09-23 04:14:50 -0300 Started GET "/assets/jquery_ujs.self-784a997f6726036b1993eb2217c9cb558e1cbb801c6da88105588c56f13b466a.js? body=1" for ::1 at 2016-09-23 04:14:50 -0300 Started GET "/assets/ action_cable.self-1641ec3e8ea24ed63601e86efcca7f9266e09f390e82199d56aa7e4bd50e1aa9.js?body=1" for ::1 at 2016-09-23 04:14:50 -0300 Started GET "/assets/application.self- e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.css?body=1" for ::1 at 2016-09-23 04:14:50 -0300 Started GET "/assets/cable.self-6e0514260c1aa76eaf252412ce74e63f68819fd19bf740595f592c5ba4c36537.js? body=1" for ::1 at 2016-09-23 04:14:50 -0300
  33. Rails 4 A única forma de desabilitar os logs dos

    assets era instalando uma gem externa.
  34. Rails 5 Existe uma configuração para desabilitar os logs dos

    assets e que vem habilitado por padrão.
  35. Rails.application.configure do config.cache_classes = false # Do not eager load

    code on boot. config.eager_load = false config.assets.debug = true # Suppress logger output for asset requests. config.assets.quiet = true … end
  36. Rails.application.configure do config.cache_classes = false # Do not eager load

    code on boot. config.eager_load = false config.assets.debug = true # Suppress logger output for asset requests. config.assets.quiet = true … end
  37. Started GET "/users" for ::1 at 2016-09-23 04:24:36 -0300 ActiveRecord::SchemaMigration

    Load (0.4ms) SELECT "schema_migrations".* FROM "schema_migrations" Processing by UsersController#index as HTML Rendering users/index.html.erb within layouts/application User Load (0.4ms) SELECT "users".* FROM "users" Rendered users/index.html.erb within layouts/application (12.2ms) Completed 200 OK in 315ms (Views: 293.4ms | ActiveRecord: 3.9ms)
  38. Funciona no Rails 4.2.7‼ Essa funcionalidade foi portada para o

    sprocket-rails 3.1.0, ou seja, essa configuração funciona no Rails 4.2.7 sem a necessidade da gem quiet_assets.
  39. Active Record

  40. Utilizar o UUID como padrão de chave primária

  41. Rails 4 Já havia a possibilidade de utilizar o UUID

    como chave primária mas era um pouco verboso.
  42. rails g model user name:string test/models/user_t

  43. rails g model user name:string class CreateUsers < ActiveRecord::Migration def

    change create_table :users do |t| t.string :name t.timestamps null: false end end end
  44. rails g model user name:string class CreateUsers < ActiveRecord::Migration def

    change create_table :users, id: :uuid do |t| t.string :name t.timestamps null: false end end end
  45. Rails 5 É possível adicionar uma configuração para que todas

    as tabelas utilizem o UUID como pk.
  46. rails g model user name:string test/models/user_t

  47. module Rails5 class Application < Rails::Application config.generators do |g| g.orm

    :active_record, primary_key_type: :uuid end end end
  48. module Rails5 class Application < Rails::Application config.generators do |g| g.orm

    :active_record, primary_key_type: :uuid end end end
  49. class CreateUsers < ActiveRecord::Migration def change create_table :users, id: :uuid

    do |t| t.string :name t.timestamps null: false end end end
  50. ⚠Alguns métodos como o `.first` e o `.last` não funcionam

    mais como o esperado.
  51. Índice nos erros dos nested attributes

  52. Rails 4 Não há uma forma de relacionar os erros

    com as instâncias da associação.
  53. class User < ActiveRecord::Base has_many :addresses validates :name, presence: true

    accepts_nested_attributes_for :addresses end class Address < ActiveRecord::Base belongs_to :user validates :street, :city, presence: true end
  54. u = User.new(name: 'João') u.addresses.new(street: 'Rua joãozinho') u.valid? => false

    u.errors.messages => {:"addresses.city"=>["can't be blank"]} u.addresses.new u.valid? => false u.errors.messages => { :"addresses.city"=>["can't be blank”], :"addresses.street"=>["can't be blank”] }
  55. u = User.new(name: 'João') u.addresses.new(street: 'Rua joãozinho') u.valid? => false

    u.errors.messages => {:"addresses.city"=>["can't be blank"]} u.addresses.new u.valid? => false u.errors.messages => { :"addresses.city"=>["can't be blank”], :"addresses.street"=>["can't be blank”] }
  56. u = User.new(name: 'João') u.addresses.new(street: 'Rua joãozinho') u.valid? => false

    u.errors.messages => {:"addresses.city"=>["can't be blank"]} u.addresses.new u.valid? => false u.errors.messages => { :"addresses.city"=>["can't be blank”], :"addresses.street"=>["can't be blank”] }
  57. u = User.new(name: 'João') u.addresses.new(street: 'Rua joãozinho') u.valid? => false

    u.errors.messages => {:"addresses.city"=>["can't be blank"]} u.addresses.new u.valid? => false u.errors.messages => { :"addresses.city"=>["can't be blank”], :"addresses.street"=>["can't be blank”] }
  58. u = User.new(name: 'João') u.addresses.new(street: 'Rua joãozinho') u.valid? => false

    u.errors.messages => {:"addresses.city"=>["can't be blank"]} u.addresses.new u.valid? => false u.errors.messages => { :"addresses.city"=>["can't be blank”], :"addresses.street"=>["can't be blank”] }
  59. u = User.new(name: 'João') u.addresses.new(street: 'Rua joãozinho') u.valid? => false

    u.errors.messages => {:"addresses.city"=>["can't be blank"]} u.addresses.new u.valid? => false u.errors.messages => { :"addresses.city"=>["can't be blank”], :"addresses.street"=>["can't be blank”] }
  60. u = User.new(name: 'João') u.addresses.new(street: 'Rua joãozinho') u.valid? => false

    u.errors.messages => {:"addresses.city"=>["can't be blank"]} u.addresses.new u.valid? => false u.errors.messages => { :"addresses.city"=>["can't be blank”], :"addresses.street"=>["can't be blank”] }
  61. Rails 5 Um índice pode ser adicionado para cada erro.

  62. class Address < ActiveRecord::Base belongs_to :user validates :street, :city, presence:

    true end class User < ActiveRecord::Base has_many :addresses, index_errors: true validates :name, presence: true accepts_nested_attributes_for :addresses end
  63. class Address < ActiveRecord::Base belongs_to :user validates :street, :city, presence:

    true end class User < ActiveRecord::Base has_many :addresses, index_errors: true validates :name, presence: true accepts_nested_attributes_for :addresses end
  64. module Rails5 class Application < Rails::Application … config.active_record.index_nested_attribute_errors = true

    end end
  65. module Rails5 class Application < Rails::Application … config.active_record.index_nested_attribute_errors = true

    end end
  66. u = User.new(name: 'João') u.addresses.new(street: 'Rua joãozinho') u.valid? => false

    u.errors.messages => { :"addresses[0].user"=>["must exist”], :"addresses[0].city"=>["can't be blank”] } u.addresses.new u.valid? => false u.errors.messages => { :"addresses[0].user"=>["must exist”], :"addresses[0].city"=>["can't be blank”], :"addresses[1].user"=>["must exist”], :"addresses[1].street"=>["can't be blank”], :"addresses[1].city"=>["can't be blank”] }
  67. u = User.new(name: 'João') u.addresses.new(street: 'Rua joãozinho') u.valid? => false

    u.errors.messages => { :"addresses[0].user"=>["must exist”], :"addresses[0].city"=>["can't be blank”] } u.addresses.new u.valid? => false u.errors.messages => { :"addresses[0].user"=>["must exist”], :"addresses[0].city"=>["can't be blank”], :"addresses[1].user"=>["must exist”], :"addresses[1].street"=>["can't be blank”], :"addresses[1].city"=>["can't be blank”] }
  68. u = User.new(name: 'João') u.addresses.new(street: 'Rua joãozinho') u.valid? => false

    u.errors.messages => { :"addresses[0].user"=>["must exist”], :"addresses[0].city"=>["can't be blank”] } u.addresses.new u.valid? => false u.errors.messages => { :"addresses[0].user"=>["must exist”], :"addresses[0].city"=>["can't be blank”], :"addresses[1].user"=>["must exist”], :"addresses[1].street"=>["can't be blank”], :"addresses[1].city"=>["can't be blank”] }
  69. u = User.new(name: 'João') u.addresses.new(street: 'Rua joãozinho') u.valid? => false

    u.errors.messages => { :"addresses[0].user"=>["must exist”], :"addresses[0].city"=>["can't be blank”] } u.addresses.new u.valid? => false u.errors.messages => { :"addresses[0].user"=>["must exist”], :"addresses[0].city"=>["can't be blank”], :"addresses[1].user"=>["must exist”], :"addresses[1].street"=>["can't be blank”], :"addresses[1].city"=>["can't be blank”] }
  70. u = User.new(name: 'João') u.addresses.new(street: 'Rua joãozinho') u.valid? => false

    u.errors.messages => { :"addresses[0].user"=>["must exist”], :"addresses[0].city"=>["can't be blank”] } u.addresses.new u.valid? => false u.errors.messages => { :"addresses[0].user"=>["must exist”], :"addresses[0].city"=>["can't be blank”], :"addresses[1].user"=>["must exist”], :"addresses[1].street"=>["can't be blank”], :"addresses[1].city"=>["can't be blank”] }
  71. u = User.new(name: 'João') u.addresses.new(street: 'Rua joãozinho') u.valid? => false

    u.errors.messages => { :"addresses[0].user"=>["must exist”], :"addresses[0].city"=>["can't be blank”] } u.addresses.new u.valid? => false u.errors.messages => { :"addresses[0].user"=>["must exist”], :"addresses[0].city"=>["can't be blank”], :"addresses[1].user"=>["must exist”], :"addresses[1].street"=>["can't be blank”], :"addresses[1].city"=>["can't be blank”] }
  72. u = User.new(name: 'João') u.addresses.new(street: 'Rua joãozinho') u.valid? => false

    u.errors.messages => { :"addresses[0].user"=>["must exist”], :"addresses[0].city"=>["can't be blank”] } u.addresses.new u.valid? => false u.errors.messages => { :"addresses[0].user"=>["must exist”], :"addresses[0].city"=>["can't be blank”], :"addresses[1].user"=>["must exist”], :"addresses[1].street"=>["can't be blank”], :"addresses[1].city"=>["can't be blank”] }
  73. u = User.new(name: 'João') u.addresses.new(street: 'Rua joãozinho') u.valid? => false

    u.errors.messages => { :"addresses[0].user"=>["must exist”], :"addresses[0].city"=>["can't be blank”] } u.addresses.new u.valid? => false u.errors.messages => { :"addresses[0].user"=>["must exist”], :"addresses[0].city"=>["can't be blank”], :"addresses[1].user"=>["must exist”], :"addresses[1].street"=>["can't be blank”], :"addresses[1].city"=>["can't be blank”] }
  74. u = User.new(name: 'João') u.addresses.new(street: 'Rua joãozinho') u.valid? => false

    u.errors.messages => { :"addresses[0].user"=>["must exist”], :"addresses[0].city"=>["can't be blank”] } u.addresses.new u.valid? => false u.errors.messages => { :"addresses[0].user"=>["must exist”], :"addresses[0].city"=>["can't be blank”], :"addresses[1].user"=>["must exist”], :"addresses[1].street"=>["can't be blank”], :"addresses[1].city"=>["can't be blank”] }
  75. u = User.new(name: 'João') u.addresses.new(street: 'Rua joãozinho') u.valid? => false

    u.errors.messages => { :"addresses[0].user"=>["must exist”], :"addresses[0].city"=>["can't be blank”] } u.addresses.new u.valid? => false u.errors.messages => { :"addresses[0].user"=>["must exist”], :"addresses[0].city"=>["can't be blank”], :"addresses[1].user"=>["must exist”], :"addresses[1].street"=>["can't be blank”], :"addresses[1].city"=>["can't be blank”] }
  76. u = User.new(name: 'João') u.addresses.new(street: 'Rua joãozinho') u.valid? => false

    u.errors.messages => { :"addresses[0].user"=>["must exist”], :"addresses[0].city"=>["can't be blank”] } u.addresses.new u.valid? => false u.errors.messages => { :"addresses[0].user"=>["must exist”], :"addresses[0].city"=>["can't be blank”], :"addresses[1].user"=>["must exist”], :"addresses[1].street"=>["can't be blank”], :"addresses[1].city"=>["can't be blank”] }
  77. u = User.new(name: 'João') u.addresses.new(street: 'Rua joãozinho') u.valid? => false

    u.errors.messages => { :"addresses[0].user"=>["must exist”], :"addresses[0].city"=>["can't be blank”] } u.addresses.new u.valid? => false u.errors.messages => { :"addresses[0].user"=>["must exist”], :"addresses[0].city"=>["can't be blank”], :"addresses[1].user"=>["must exist”], :"addresses[1].street"=>["can't be blank”], :"addresses[1].city"=>["can't be blank”] }
  78. ActiveRelation#or

  79. Rails 4 A única forma de fazer o `or` é

    escrevendo sql.
  80. class Order < ActiveRecord::Base enum status: [:posted, :in_transit, :delivered] scope

    :pending, -> do where( 'status = :posted OR status = :in_transit', { posted: statuses[:posted], in_transit: statuses[:in_transit] } ) end end
  81. class Order < ActiveRecord::Base enum status: [:posted, :in_transit, :delivered] scope

    :pending, -> do where( 'status = :posted OR status = :in_transit', { posted: statuses[:posted], in_transit: statuses[:in_transit] } ) end end
  82. Rails 5 Tem o ActiveRelation#or.

  83. class Order < ApplicationRecord enum status: [:posted, :in_transit, :delivered] scope

    :pending, -> { posted.or(in_transit) } end
  84. class Order < ApplicationRecord enum status: [:posted, :in_transit, :delivered] scope

    :pending, -> { posted.or(in_transit) } end
  85. Left outer join

  86. Rails 4 A única forma de fazer o `left join`

    é escrevendo sql.
  87. class User < ApplicationRecord User.joins('LEFT JOIN posts ON posts.user_id =

    users.id') end
  88. class User < ApplicationRecord User.joins('LEFT JOIN posts ON posts.user_id =

    users.id') end
  89. Rails 5 Tem um método exclusivo para fazer left joins.

  90. class User < ApplicationRecord has_many :posts User.left_outer_joins(:posts) end

  91. Uma forma prática de identificar os atributos que estão sendo

    utilizadas na view. #accessed_fields
  92. class UsersController < ApplicationController def index @users = User.all end

    end
  93. <table> <thead> <th>Nome</th> <th>Idade</th> </thead> <tbody> <% @users.each do |user|

    %> <tr> <td><%= user.name %></td> <td><%= user.age %></td> </tr> <% end %> </tbody> </table>
  94. class UsersController < ApplicationController after_action :print_accessed_fields def index @users =

    User.all end private def print_accessed_fields p @users.first.accessed_fields end end
  95. class UsersController < ApplicationController after_action :print_accessed_fields def index @users =

    User.all end private def print_accessed_fields p @users.first.accessed_fields end end
  96. Started GET "/users" for ::1 at 2016-09-17 23:47:25 -0300 ActiveRecord::SchemaMigration

    Load (0.7ms) SELECT "schema_migrations".* FROM "schema_migrations" Processing by UsersController#index as HTML Rendering users/index.html.erb within layouts/ application User Load (0.4ms) SELECT "users".* FROM "users" Rendered users/index.html.erb within layouts/ application (20.5ms) ["name", "age"] Completed 200 OK in 395ms (Views: 361.8ms | ActiveRecord: 4.2ms)
  97. Started GET "/users" for ::1 at 2016-09-17 23:47:25 -0300 ActiveRecord::SchemaMigration

    Load (0.7ms) SELECT "schema_migrations".* FROM "schema_migrations" Processing by UsersController#index as HTML Rendering users/index.html.erb within layouts/ application User Load (0.4ms) SELECT "users".* FROM "users" Rendered users/index.html.erb within layouts/ application (20.5ms) ["name", "age"] Completed 200 OK in 395ms (Views: 361.8ms | ActiveRecord: 4.2ms)
  98. class UsersController < ApplicationController def index @users = User.select(:name, :age)

    end end
  99. ⚠Deve ser usado apenas no ambiente de desenvolvimento.

  100. ⚠Deve ser usado apenas no ambiente de desenvolvimento. ⚠Apenas os

    campos que foram lidos são identificados, então cuidado com os que estão dentro de condicionais.
  101. As queries ficaram mais legíveis nos logs. Colourful query logs

  102. Rails 4 User Load (2.8ms) SELECT "users"."id", "users"."gender", "users"."name" FROM

    "users" Rails 5 User Load (11.1ms) SELECT "users"."id", "users"."gende "users"."name" FROM "users"
  103. Rails 4 User Load (2.8ms) SELECT "users"."id", "users"."gender", "users"."name" FROM

    "users" Rails 5 User Load (11.1ms) SELECT "users"."id", "users"."gender", "users"."name" FROM "users"
  104. Rails 5 Cada query tem uma cor.

  105. User Load (11.1ms) SELECT "users"."id", "users"."gender", "users"."name" FROM "users" Select

  106. SQL (11.3ms) INSERT INTO "users" ("name", "created_at", "updated_at") VALUES ($1,

    $2, $3) RETURNING "id" Insert
  107. Update SQL (0.4ms) UPDATE "users" SET "name" = $1, "updated_at"

    = $2 WHERE "users"."id" = $3
  108. Delete SQL (6.6ms) DELETE FROM "users" WHERE "users"."id" = $1

  109. Attributes API Os atributos do banco de dados podem ser

    convertidos para um objeto do Ruby.
  110. irb(main):002:0> User.first.address User Load (0.3ms) SELECT "users".* FROM "users" ORDER

    BY "users"."id" ASC LIMIT $1 [["LIMIT", 1]] => {"city"=>"São Paulo", "street"=>"Rua marília"}
  111. irb(main):002:0> User.first.address User Load (0.3ms) SELECT "users".* FROM "users" ORDER

    BY "users"."id" ASC LIMIT $1 [["LIMIT", 1]] => {"city"=>"São Paulo", "street"=>"Rua marília"}
  112. class Address def initialize(street:, city:) @street = street @city =

    city end … end
  113. class AddressType < ActiveRecord::Type::Value def serialize(value) ::ActiveSupport::JSON.encode(value) end def deserialize(value)

    if value address_attrs = JSON.parse(value) ::Address.new( street: address_attrs[‘street'], city: address_attrs[‘city'] ) end end end
  114. class User < ApplicationRecord attribute :address, AddressType.new end

  115. User.first.address User Load (0.5ms) SELECT "users".* FROM "users" ORDER BY

    "users"."id" ASC LIMIT $1 [["LIMIT", 1]] => #<Address:0x007fb03a73b738 @street="Rua marília", @city="São Paulo">
  116. User.first.address User Load (0.5ms) SELECT "users".* FROM "users" ORDER BY

    "users"."id" ASC LIMIT $1 [["LIMIT", 1]] => #<Address:0x007fb03a73b738 @street="Rua marília", @city="São Paulo">
  117. Referências

  118. goo.gl/oRfJzd

  119. goo.gl/mS7e8M

  120. goo.gl/J18on9

  121. http://blog.bigbinary.com/

  122. Obrigado