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

What's New in Rails 5?

karol.galanciak
September 28, 2016
240

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 • Integration of WebSockets with the rest of your

    application • Both client-side and server-side tools
  3. ActionCable - server-side • Connections - connection between consumer and

    the sever • Authentication / authorization goes here
  4. 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
  5. 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
  6. 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!" });
  7. Rails API • rails new rails_5_app —api • ApplicationController inherits

    from ActionController::API • Modifies the generators (no views, helpers and assets)
  8. 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)
  9. 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"]
  10. Attributes API • custom types (requires #deserialize method for assigning

    values and #serialize when querying database) • attributes don’t need to be backed by database
  11. 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
  12. Class / module variables per thread • cattr_accessor / mattr_accessor

    - but per thread - thread_cattr_accessor / thread_mattr_accessor
  13. 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
  14. 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
  15. Rake / Rails commands • All rails commands can be

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

    some_route -> rails routes -g some_route
  17. Caching collections <% cache(@posts) do %> <% @posts.each do |post|

    %> <p> <%= render post %> </p> <% end %> <% end %>
  18. 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
  19. 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
  20. ActiveRecord no longer halts execution of the callbacks when returning

    false • configurable with ActiveSupport.halt_callback_chains_on_return_false
  21. redirect_back method class UsersController < ApplicationController def update user =

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

    find_user user.update(params[:user]) redirect_back(fallback_location: root_path) end end
  23. 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
  24. 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
  25. 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}]}
  26. Bidirectional destroy class Account < ActiveRecord::Base has_one :billing_address, dependent: :destroy

    end class BillingAddress < ActiveRecord::Base belongs_to :account, dependent: :destroy end
  27. 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
  28. 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
  29. 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
  30. 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
  31. Updating collection with callbacks and validations • ActiveRecord::Relation.update_all doesn’t trigger

    validations and callbacks • But new ActiveRecord::Relation.update does ;)
  32. 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: "")
  33. 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"]}
  34. 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"]}
  35. 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
  36. 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
  37. Acceptance validations works properly ;) • Problem: validates_acceptance_of accepts only

    "1" as truthy value • Now it supports both "1" and true
  38. Acceptance validations works properly ;) • Bonus: configurable values class

    Comment < ActiveRecord::Base validates_acceptance_of :agreement, accept: [true, "y", "yes", "1", 1, "Rails"] end
  39. 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 %>
  40. 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="" />'