$30 off During Our Annual Pro Sale. View Details »

What's New in Rails 5?

karol.galanciak
September 28, 2016
180

What's New in Rails 5?

karol.galanciak

September 28, 2016
Tweet

Transcript

  1. What's new in Rails 5? Karol Galanciak Full Stack Developer

    at Twitter: @Azdaroth Github: Azdaroth https://karolgalanciak.com
  2. ActionCable

  3. ActionCable • Integration of WebSockets with the rest of your

    application
  4. ActionCable • Integration of WebSockets with the rest of your

    application • Both client-side and server-side tools
  5. ActionCable - server-side

  6. ActionCable - server-side • Connections - connection between consumer and

    the sever
  7. ActionCable - server-side • Connections - connection between consumer and

    the sever • Authentication / authorization goes here
  8. ActionCable - connections module ApplicationCable class Connection < ActionCable::Connection::Base identified_by

    :current_user def connect self.current_user = find_user! end private def find_user! User.find_by(id: cookies.signed[:user_id]) or raise UnauthorizedConnectionError end end end
  9. ActionCable - server-side • Channels - endpoints where the actual

    logic happens (~ controllers)
  10. ActionCable - channels class ChatChannel < ActionCable::Channel::Base def subscribed stream_from

    "chat_#{params[:room]}" end def receive(payload) ActionCable.server.broadcast("chat_#{params[:room]}", payload) end end
  11. ActionCable - client-side

  12. ActionCable - client-side //= require action_cable this.App || (this.App =

    {}); App.cable = ActionCable.createConsumer(); App.chatChannel = App.cable.subscriptions.create({ channel: "ChatChannel", room: "Rails Core" }, { received: function(data) { $(".chat").append("<p>" + data["sent_by"] + ": " + data["body"] + "</p>") } }); App.chatChannel.send({ sent_by: "DHH", body: "OMG, it works!" });
  13. Rails API

  14. Rails API • rails new rails_5_app --api

  15. Rails API • rails new rails_5_app —api • ApplicationController inherits

    from ActionController::API • Modifies the generators (no views, helpers and assets)
  16. Attributes API

  17. Attributes API class Order < ActiveRecord::Base attribute :price_in_cents, :money attribute

    :description, :string, default: "Default description" attribute :ordered_at, :datetime, default: -> { Time.current } attribute :tags_not_backed_by_db, :string, array: true end class MoneyType < ActiveRecord::Type::Integer def deserialize(value) if value.to_s.include?("$") price_in_dollars = value.gsub(/\$/, "").to_d super(price_in_dollars * 100) else super end end end ActiveRecord::Type.register(:money, MoneyType)
  18. Attributes API class Order < ActiveRecord::Base attribute :price_in_cents, :money attribute

    :description, :string, default: "Default description" attribute :ordered_at, :datetime, default: -> { Time.current } attribute :tags_not_backed_by_db, :string, array: true end order = Order.new order.price_in_cents = "$10.0" order.price_in_cents => 1000 order.description => "Default description" order.ordered_at => "2016-01-01 12:00:00 +0200" order.tags_not_backed_by_db = ["WhateverItTakes", "5%", "Rich Piana"] order.tags_not_backed_by_db => ["WhateverItTakes", "5%", "Rich Piana"]
  19. Attributes API • custom types (requires #deserialize method for assigning

    values and #serialize when querying database)
  20. Attributes API • custom types (requires #deserialize method for assigning

    values and #serialize when querying database) • attributes don’t need to be backed by database
  21. Attributes API • custom types (requires #deserialize method for assigning

    values and #serialize when querying database) • attributes don’t need to be backed by database • supports dirty tracking
  22. Class / module variables per thread • cattr_accessor / mattr_accessor

    - but per thread - thread_cattr_accessor / thread_mattr_accessor
  23. Class / module variables per thread module OmgGlobal thread_mattr_accessor :current_user

    end class ApplicationController < ActionController::Base # Please, don't do it in real code! before_action :set_current_user_globally def set_current_user_globally OmgGlobal.current_user = current_user end end class Post < ActiveRecord::Base after_save :create_version private def create_version Version.create(model: self.class.to_s, attributes: attributes, who_did_it: OmgGlobal.current_user.id) end end
  24. Assets silenced by default in development

  25. Assets silenced by default in development • quite_assets gem

  26. Assets silenced by default in development • quite_assets gem •

    No more garbage in logs
  27. Assets silenced by default in development • quite_assets gem •

    No more garbage in logs Started GET "/assets/public/home.js?body=1" for 127.0.0.1 at 2016-09-27 19:31:46 +0200 Started GET "/assets/bootstrap/collapse.js?body=1" for 127.0.0.1 at 2016-09-27 19:32:04 +0200 Started GET "/assets/font-awesome/fontawesome-webfont.woff2?v=4.5.0" for 127.0.0.1 at 2016-09-27 19:32:04 +0200 Started GET "/assets/public/locales.js?body=1" for 127.0.0.1 at 2016-09-27 19:32:04 +0200
  28. ApplicationRecord

  29. ApplicationRecord • New base class for models

  30. ApplicationRecord class ApplicationRecord < ActiveRecord::Base include SoftDelete self.abstract_class = true

    has_paper_trail end class User < ApplicationRecord end
  31. Rendering outside controllers

  32. Rendering outside controllers OrdersController.render(:show, assigns: { order: Order.find(order_id) })

  33. Rendering outside controllers OrdersController.render(:show, assigns: { order: Order.find(order_id) }) OrdersController.render(:_order,

    assigns: { order: Order.find(order_id) })
  34. Rake / Rails commands

  35. Rake / Rails commands • All rails commands can be

    executed with rake and all rake commands can be executed with rails
  36. Rake / Rails commands rails db:migrate rake routes | grep

    some_route -> rails routes -g some_route
  37. Caching collections

  38. Caching collections • ActiveRecord::Collection#cache_key

  39. Caching collections posts.cache_key => "posts/query-c60f5cf40b4d0d6f492ea6961c43ceb6-100-20160927121435182129"

  40. Caching collections <% cache(@posts) do %> <% @posts.each do |post|

    %> <p> <%= render post %> </p> <% end %> <% end %>
  41. ActiveRecord no longer halts execution of the callbacks when returning

    false
  42. ActiveRecord no longer halts execution of the callbacks when returning

    false class Product < ActiveRecord::Base before_save :set_availability_status def set_availability_status self.available = somehow_compute_availability_status_which_returns_false end end product.save! => ActiveRecord::RecordNotSaved: ActiveRecord::RecordNotSaved
  43. ActiveRecord no longer halts execution of the callbacks when returning

    false class Product < ActiveRecord::Base before_save :set_availability_status def set_availability_status self.available = somehow_compute_availability_status_which_returns_false throw(:abort) end end
  44. ActiveRecord no longer halts execution of the callbacks when returning

    false • configurable with ActiveSupport.halt_callback_chains_on_return_false
  45. Belongs-to association required by default

  46. Belongs-to association required by default class Comment < ApplicationRecord belongs_to

    :author end
  47. Belongs-to association required by default class Comment < ApplicationRecord belongs_to

    :author, optional: true end
  48. Belongs-to association required by default • Configurable with Rails.application.config.active_record.belongs_to_r equired_by_default

  49. redirect_back method

  50. redirect_back method class UsersController < ApplicationController def update user =

    find_user user.update(params[:user]) redirect_to :back end end
  51. redirect_back method class UsersController < ApplicationController def update user =

    find_user user.update(params[:user]) redirect_back(fallback_location: root_path) end end
  52. ActiveRecord::Base.Suppress

  53. ActiveRecord::Base.Suppress

  54. ActiveRecord::Base.Suppress class Comment < ApplicationRecord belongs_to :author has_many :notifications after_save

    :send_notification def publish! update!(published_at: Time.current) end private def send_notification notifications.create(message: content, author: author) end end
  55. ActiveRecord::Base.Suppress class Comment < ApplicationRecord belongs_to :author has_many :notifications after_save

    :send_notification def publish! update!(published_at: Time.current) end def publish_without_notification! Notification.suppress do publish! end end private def send_notification notifications.create(message: content, author: author) end end
  56. Left Outer Join Support

  57. Left Outer Join Support User.left_outer_joins(:posts, :comments, :notifications) User.left_joins(:posts, :comments, :notifications)

  58. Config for UUIDs as primary keys

  59. Config for UUIDs as primary keys create_table :orders, id: :uuid

    do |t| end
  60. Config for UUIDs as primary keys • Configurable with: config.active_record.primary_key

    = :uuid
  61. Queue adapter configurable per job

  62. Queue adapter configurable per job config.active_job.queue_adapter = :delayed_job

  63. Queue adapter configurable per job class DoSomethingExpensiveInBackgroundJob < ActiveJob::Base self.queue_adapter

    = :sidekiq end
  64. ApplicationJob base class

  65. ApplicationJob base class class ApplicationJob < ActiveJob::Base end

  66. Validation of multiple contexts

  67. Validation of multiple contexts class User < ActiveRecord::Base validate :validate_confirmation_info,

    on: :confirmation validate :validate_updatable, on: :update end
  68. Validation of multiple contexts • Before: user.valid?(:confirmation) && user.valid?(:update)

  69. Validation of multiple contexts • Now: user.valid?([:confirmation, :update])

  70. Additional details for validations

  71. Additional details for validations comment = Comment.new comment.valid? => false

    comment.errors.messages => {content: ["can’t be blank"]} comment.errors.details => {content:=>[{error: :blank}]} comment.errors.add(:content, :too_short, "Content is too short") comment.errors.details => {content: [{error: :blank}, {error: :too_short}]}
  72. Saving records without timestamp update

  73. Saving records without timestamp update user = User.first user.save(touch: false)

  74. Bidirectional destroy

  75. Bidirectional destroy • Doesn’t cause SystemStackError

  76. Bidirectional destroy class Account < ActiveRecord::Base has_one :billing_address, dependent: :destroy

    end class BillingAddress < ActiveRecord::Base belongs_to :account, dependent: :destroy end
  77. ActiveModel::AttributesAssi gnment

  78. ActiveModel::AttributesAssi gnment class UserForm include ActiveModel::AttributeAssignment attr_accessor :email, :password, :password_confirmation

    end form = UserForm.new form.assign_attributes(email: "email@example.com")
  79. ActionController::Parameters is not a HashWithIndifferentAccess

  80. ActionController::Parameters is not a HashWithIndifferentAccess • Gotcha: def to_h if

    permitted? @parameters.to_h else slice(*self.class.always_permitted_parameters).permit!.to_h end end
  81. ActionController::Parameters is not a HashWithIndifferentAccess • Gotcha: def to_h if

    permitted? @parameters.to_h else slice(*self.class.always_permitted_parameters).permit!.to_h end end def user_params params.permit!.to_h end def user_params params.to_unsafe_h end
  82. Comments in migrations

  83. Comments in migrations class CreateOrders < ActiveRecord::Migration[5.0] def change create_table

    :orders, comment: "Orders table" do |t| t.string :description, comment: "Description of the order" t.integer :owner_id, comment: "Owner of the order" t.timestamps end add_index :orders, :owner_id, name: "index_name", comment: "Index for owner_id" end end
  84. Comments in migrations ActiveRecord::Schema.define(version: 20160928091043) do create_table "orders", force: :cascade,

    comment: "Orders table" do |t| t.string "description", comment: "Description of the order" t.integer "owner_id", comment: "Owner of the order" t.index ["owner_id"], name: "index_name",using: :btree, comment: "Index for owner_id" end end
  85. Updating collection with callbacks and validations

  86. Updating collection with callbacks and validations • ActiveRecord::Relation.update_all doesn’t trigger

    validations and callbacks
  87. Updating collection with callbacks and validations • ActiveRecord::Relation.update_all doesn’t trigger

    validations and callbacks • But new ActiveRecord::Relation.update does ;)
  88. Updating collection with callbacks and validations • ActiveRecord::Relation.update_all doesn’t trigger

    validations and callbacks • But new ActiveRecord::Relation.update does ;) Comment.limit(10).update(content: "")
  89. Expression indexes

  90. Expression indexes Author.where("lower(fullname) = ?", fullname.to_s.downcase)

  91. Expression indexes add_index :authors, "lower(fullname)"

  92. Indexed errors for nested attributes

  93. Indexed errors for nested attributes class Invoice < ApplicationRecord has_many

    :items accepts_nested_attributes_for :items end class Item < ApplicationRecord validates :description, presence: true end > invoice = Invoice.new > item_1 = Item.new(description: "$$$$") > item_2 = Item.new > invoice.items = [item_1, item_2] > invoice.save > invoice.error.messages => {:"items.description"=>["can't be blank"]}
  94. Indexed errors for nested attributes class Invoice < ApplicationRecord has_many

    :items, index_errors: true accepts_nested_attributes_for :items end class Item < ApplicationRecord validates :description, presence: true end >> invoice = Invoice.new >> item_1 = Item.new(description: "$$$$") >> item_2 = Item.new >> invoice.items = [item_1, item_2] >> invoice.save >> invoice.error.messages => {:"items[1].description"=>["can’t be blank"]}
  95. Indexed errors for nested attributes • Configurable with config.active_record.index_nested_attribute_errors

  96. No more ambiguous columns errors

  97. No more ambiguous columns errors class Post < ApplicationRecord belongs_to

    :author has_many :comments end class Comment < ApplicationRecord belongs_to :author belongs_to :post end Post.joins(:comments).group(:author_id).count
  98. New option for find_in_batches: finish

  99. New option for find_in_batches: finish Comment.find_in_batches(start: 1001, finish: 10000, batch_size:

    1000) do |group| group.each { |comment| do_some_stuff_with_comment(comment) } end
  100. OR support in queries

  101. OR support in queries

  102. OR support in queries Article.where(author_name: "DHH").or(Article.where(category_name: "Basecamp"))

  103. Acceptance validations works properly ;)

  104. Acceptance validations works properly ;) • Problem: validates_acceptance_of accepts only

    "1" as truthy value
  105. Acceptance validations works properly ;) • Problem: validates_acceptance_of accepts only

    "1" as truthy value • Now it supports both "1" and true
  106. Acceptance validations works properly ;)

  107. Acceptance validations works properly ;) • Bonus: configurable values

  108. Acceptance validations works properly ;) • Bonus: configurable values class

    Comment < ActiveRecord::Base validates_acceptance_of :agreement, accept: [true, "y", "yes", "1", 1, "Rails"] end
  109. fields_for accepts lambdas for child_index

  110. fields_for accepts lambdas for child_index • My first Rails contribution

    ;)
  111. fields_for accepts lambdas for child_index • My first Rails contribution

    ;) <%= f.simple_fields_for :items, item, child_index: -> { Time.current.to_i } do |builder| %> <% end %>
  112. fields_for accepts lambdas for child_index • My first Rails contribution

    ;) <%= f.simple_fields_for :items, item, child_index: -> { Time.current.to_i } do |builder| %> <% end %> '<input id="invoice_items_attributes_abc_description" name="invoice[items_attributes] [1475065682][description]" type="text" value="" />'