Slide 1

Slide 1 text

Vipul A M RAILS 6! WHAT'S NEW IN

Slide 2

Slide 2 text

Vipul A M Saeloun

Slide 3

Slide 3 text

https://blog.saeloun.com/

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

Kyoto Typhoon last year

Slide 6

Slide 6 text

Alaska 7.2 Earthquake from 9th Floor

Slide 7

Slide 7 text

NY Manhattan Blackout last week

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

9

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

11

Slide 12

Slide 12 text

Vipul A M RAILS 6! WHAT'S NEW IN

Slide 13

Slide 13 text

13

Slide 14

Slide 14 text

14 13!

Slide 15

Slide 15 text

NEW 15

Slide 16

Slide 16 text

ACTION MAILBOX 16

Slide 17

Slide 17 text

17

Slide 18

Slide 18 text

class Mailman < ActionMailer::Base def receive(email) page = Page.find_by(address: email.to.first) page.emails.create( subject: email.subject, body: email.body ) if email.has_attachments? email.attachments.each do |attachment| page.attachments.create({file: attachment,
 description:email.subject}) end end end end $ rails runner 'Mailman.receive(STDIN.read)' 18

Slide 19

Slide 19 text

rails action_mailbox:install rails db:migrate # config/environments/ production.rb config.action_mailbox.ingress = :mailgun 19

Slide 20

Slide 20 text

-SMTP Relay -Mailgun -Mandrill -Postfix -Postmark -Qmail -Sendgrid 20

Slide 21

Slide 21 text

# app/mailboxes/application_mailbox.rb class ApplicationMailbox < ActionMailbox::Base routing /forward@/i => :forwards routing /contact@/i => :contacts routing /leads@/i => :leads end 21

Slide 22

Slide 22 text

# Generate new mailbox rails generate mailbox forwards # frozen_string_literal: true class ForwardsMailbox < ApplicationMailbox def process if forwarder record_forward else bounce_with Forwards::BounceMailer.missing_forward( inbound_email, forwarder: forwarder ) end end private def forwarder Person.where(email_address: mail.from) end def record_forward Forward.new forwarder: forwarder, subject: message.subject, content: mail.content end end 22

Slide 23

Slide 23 text

-bounced! -bounce_with -Incineration / IncinerationJob 23

Slide 24

Slide 24 text

class ForwardsMailboxTest < ActionMailbox::TestCase test "directly recording a client forward for a forwarder and forwardee corresponding to one project" do assert_difference -> { people(:david).buckets.first.recordings.count } do receive_inbound_email_from_mail \ to: 'save@example.com', from: people(:david).email_address, subject: "Fwd: Status update?", body: <<~BODY --- Begin forwarded message --- From: Frank Holland What's the status? BODY end recording = people(:david).buckets.first.recordings.last assert_equal people(:david), recording.creator assert_equal "Status update?", recording.forward.subject assert_match "What's the status?", recording.forward.content.to_s end end 24

Slide 25

Slide 25 text

NEW 25

Slide 26

Slide 26 text

ZIETWERK 26 @ fxn

Slide 27

Slide 27 text

$ spring stop && rails s 27

Slide 28

Slide 28 text

28 https://github.com/rails/rails/issues?q=is%3Aissue+autoload+is%3Aclosed

Slide 29

Slide 29 text

$ bin/rails zeitwerk:check Hold on, I am eager loading the application. All is good! -classic mode infers file names from missing constant names (underscore): 
 "FOO".underscore is "foo" -zeitwerk mode infers constant names from file names (camelize): 
 "foo".camelize is "Foo", not "FOO"

Slide 30

Slide 30 text

# Autoloading in this class' body matches Ruby semantics now. class Admin::UsersController < ApplicationController # ... end # Before class Foo::Bar Baz end 
 # Now class Foo::Bar Foo::Baz end 30

Slide 31

Slide 31 text

app/models app/models/concerns app/models/concerns/foo.rb
 => Foo not Concerns::Foo 31

Slide 32

Slide 32 text

# One file one constant # app/models/foo.rb class Foo end class Bar end 32

Slide 33

Slide 33 text

-Constant autoloading is thread-safe in zeitwerk mode. -Works with Bootsnap > 1.4.2 33

Slide 34

Slide 34 text

#app/models/foo.rb class Bar end Autoload vs Eager load in class vs zietwerk mode
 
 34

Slide 35

Slide 35 text

# config/application.rb config.load_defaults "6.0" config.autoloader = :classic 35

Slide 36

Slide 36 text

NEW 36

Slide 37

Slide 37 text

ACTION TEXT 37

Slide 38

Slide 38 text

rails action_text:install # app/models/message.rb class Message < ApplicationRecord has_rich_text :content end <%# app/views/messages/_form.html.erb %> <%= form_with(model: message) do |form| %>
<%= form.label :content %> <%= form.rich_text_area :content %>
<% end %> 38

Slide 39

Slide 39 text

# Display <%= @message.content %> class MessagesController < ApplicationController def create message = Message.create
 params.require(:message).permit(:title, :content) redirect_to message end end 39

Slide 40

Slide 40 text

In Action 40

Slide 41

Slide 41 text

RAILTIES 41

Slide 42

Slide 42 text

- Webpacker is the new default JavaScript compiler - Guard against DNS rebinding attacks by default - bin/rails db:system:change --to=postgresql - Secrets => Credentials and multi-env credentials 42

Slide 43

Slide 43 text

https://indiancybersecuritysolutions.com/wp-content/uploads/2018/03/DNSRebindingAttackReadpermitted_it%E2%80%99sthesameorigin.jpg 43

Slide 44

Slide 44 text

ActionDispatch::HostAuthorization Rails.application.config.hosts = [ IPAddr.new("0.0.0.0/0"), # All IPv4 addresses. IPAddr.new("::/0"), # All IPv6 addresses. "localhost" # The localhost reserved domain. ] # Allow requests only from `product.com` Rails.application.config.hosts << "product.com" # Allow requests from subdomains like `www.product.com` and # `beta1.product.com`. Rails.application.config.hosts << /.*\.product\.com/ 44

Slide 45

Slide 45 text

$ rails routes --expanded --[ Route 1 ]------------------------------------------------------------ Prefix | high_scores Verb | GET URI | /high_scores(.:format) Controller#Action | high_scores#index --[ Route 2 ]------------------------------------------------------------ Prefix | new_high_score Verb | GET URI | /high_scores/new(.:format) Controller#Action | high_scores#new --[ Route 3 ]------------------------------------------------------------ Prefix | blog Verb | URI | /blog Controller#Action | Blog::Engine [ Routes for Blog::Engine ] --[ Route 1 ]------------------------------------------------------------ Prefix | cart Verb | GET URI | /cart(.:format) Controller#Action | cart#show 45

Slide 46

Slide 46 text

ACTION PACK 46

Slide 47

Slide 47 text

=> cookies.signed[:lastname] = {value: "Smith", expires: Time.now + 1.hour } 47 - Purpose and expiry metadata is now embedded inside 
 signed and encrypted cookies for increased security

Slide 48

Slide 48 text

class SomeControllerTest < ActionController::TestCase def test_some_action post :action, body: { foo: 'bar' } assert_equal({ "foo" => "bar" }, response.parsed_body) end end 48

Slide 49

Slide 49 text

- Add ActionController::Parameters#each_value - Expose ActionController::Parameters#each_key 49

Slide 50

Slide 50 text

ACTION VIEW 50

Slide 51

Slide 51 text

- image_alt has been removed - Rails npm packages moved into a @rails scope.
 @rails/webpacker
 @rails/rails-ujs 51

Slide 52

Slide 52 text

ACTIVE STORAGE 52

Slide 53

Slide 53 text

- mini_magick replaced by image_processing gem - Wrapper for mini_magick and vips gem(libvips) - New variant formats: BMP, TIFF, progressive JPEG 53

Slide 54

Slide 54 text

class User < ApplicationRecord has_many_attached :highlights end user.highlights.attach(filename: "funky.jpg", ...) user.higlights.count # => 1 blob = ActiveStorage::Blob.create_after_upload!(filename: "town.jpg", user.update!(highlights: [ blob ]) user.highlights.count # => 2 user.highlights.first.filename # => "funky.jpg" user.highlights.second.filename # => "town.jpg" 54

Slide 55

Slide 55 text

user.highlights.attach(filename: "funky.jpg", ...) user.highlights.count # => 1 blob = ActiveStorage::Blob.create_after_upload!(filename: "town.jpg", user.update!(highlights: [ blob ]) user.highlights.count # => 1 user.highlights.first.filename # => "town.jpg" 55

Slide 56

Slide 56 text

NEW 56

Slide 57

Slide 57 text

PARALLEL TESTING 57

Slide 58

Slide 58 text

https://github.com/grosser/parallel_tests 58

Slide 59

Slide 59 text

class ActiveSupport::TestCase parallelize(workers: 2) end # On CI PARALLEL_WORKERS=15 rails test 59

Slide 60

Slide 60 text

class ActiveSupport::TestCase parallelize_setup do |worker| # setup databases end parallelize_teardown do |worker| # cleanup databases end parallelize(workers: :number_of_processors, with: :threads) end 60

Slide 61

Slide 61 text

ACTIVE JOB 61

Slide 62

Slide 62 text

ACTIVE SUPPORT 62

Slide 63

Slide 63 text

- Lots of improvements due to removal of AS::Multibyte::Unicode table - Remove ActiveSupport::Multibyte::Unicode#downcase/upcase/swapcase - Removes ActiveSupport::Multibyte::Chars wrappers around String methods: - #downcase, #upcase, #swapcase, and #capitalize.
 - Reduce memory usage(about 1M) - Reduce repo/gem size(about 1M) 63

Slide 64

Slide 64 text

- before? and after? methods to Date, DateTime, Time, and TimeWithZone
 Time.current.before?(2.weeks.ago) - Add ActiveSupport::ParameterFilter
 ActiveSupport::ParameterFilter.new(["credit_card.code"]) => replaces { credit_card: {code: "xxxx"} } with "[FILTERED]", does not change { file: { code: "xxxx"} } 64

Slide 65

Slide 65 text

[ 1, 2, 3, 4, 5 ].excluding([4, 5]) => [ 1, 2, 3 ] [1, 2, 3 ].including(4, 5) => [ 1, 2, 3, 4, 5 ] post.authors.including(Current.person) numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] odd_numbers = numbers.extract! { |number| number.odd? } 
 # => [1, 3, 5, 7, 9] numbers # => [0, 2, 4, 6, 8] 65

Slide 66

Slide 66 text

ACTIVE MODEL 66

Slide 67

Slide 67 text

ACTIVE RECORD 67

Slide 68

Slide 68 text

68

Slide 69

Slide 69 text

Vipul A M ACTIVE RECORD WHAT'S NEW IN

Slide 70

Slide 70 text

- rails db:prepare to create a database if it doesn't exist, and run its migrations. - rails db:seed:replant that truncates tables of each database 
 for the current environment and loads the seeds 70

Slide 71

Slide 71 text

-touch_all -destroy_by / delete_by -reselect
 => unscope(:select).select(fields)
 -insert_all/insert_all!/upsert_all
 => Post.insert_all([row1, row2]
 -account.memberships.extract_associated(:user) => account.memberships.preload(:user).collect(&:user)
 -after_save_commit 
 => after_commit :hook, on: [ :create, :update ]
 -Person.where(id: 1).pick(:name) 
 => Person.limit(1).pluck(column_name).first 71 AR RELATION

Slide 72

Slide 72 text

- Relation#annotate for SQL comments
 
 Post.annotate("this is a comment").where(id: 123).to_sql
 => # SELECT "posts".* FROM "posts" WHERE "posts"."id" = 123 /* this is a comment */ - Add support for setting Optimizer Hints on databases
 class Job < ApplicationRecord default_scope { optimizer_hints("MAX_EXECUTION_TIME(50000) NO_INDEX_MERGE(jobs)") } end 72

Slide 73

Slide 73 text

-Lots of changes to support Multi-DB -Add the ability to automatically switch database connections. -while_preventing_writes => prevent writes to DB in block -ActiveRecord::Base.find_or_create_by/! => ActiveRecord::Base.create_or_find_by/! / SELECT/INSERT race condition 73

Slide 74

Slide 74 text

74

Slide 75

Slide 75 text

NEW 75

Slide 76

Slide 76 text

ACTION CABLE / TESTING 76

Slide 77

Slide 77 text

# app/channels/application_cable/connection.rb module ApplicationCable class Connection < ActionCable::Connection::Base identified_by :current_user def connect self.current_user = find_verified_user end private def find_verified_user ... end end end 77

Slide 78

Slide 78 text

class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase test "connects with params" do # Simulate a connection opening by calling the `connect` method connect params: { user_id: 42 } # You can access the Connection object via `connection` in tests assert_equal connection.user_id, "42" end test "rejects connection without params" do # Use `assert_reject_connection` matcher to verify that # connection is rejected assert_reject_connection { connect } end end 78

Slide 79

Slide 79 text

# app/channels/application_cable/channel.rb module ApplicationCable class Channel < ActionCable::Channel::Base end end # app/channels/chat_channel.rb class ChatChannel < ApplicationCable::Channel end 79

Slide 80

Slide 80 text

require "test_helper" class ChatChannelTest < ActionCable::Channel::TestCase test "subscribes and stream for room" do # Simulate a subscription creation by calling `subscribe` subscribe room: "15" # You can access the Channel object via `subscription` in tests assert subscription.confirmed? assert_has_stream "chat_15" end end 80

Slide 81

Slide 81 text

# app/jobs/chat_relay_job.rb class ChatRelayJob < ApplicationJob def perform_later(room, message) ChatChannel.broadcast_to room, text: message end end # test/jobs/chat_relay_job_test.rb require 'test_helper' class ChatRelayJobTest < ActiveJob::TestCase include ActionCable::TestHelper test "broadcast message to room" do room = rooms(:all) assert_broadcast_on(ChatChannel.broadcasting_for(room), text: "Hi!") do ChatRelayJob.perform_now(room, "Hi!") end end end 81

Slide 82

Slide 82 text

Rails 6 rc2 82

Slide 83

Slide 83 text

THANK YOU