Slide 1

Slide 1 text

Miha Rekar What's new in Rails?

Slide 2

Slide 2 text

Dockerfile • MRSK

Slide 3

Slide 3 text

Dockerfile • MRSK Kamal • /.env* files are now ignored • gem dockerfile-rails (by Fly)

Slide 4

Slide 4 text

What is Kamal? • Capistrano for Containers • imperative: step-by-step commands are more straightforward and less abstract • a simpler alternative to Kubernetes or Docker Swarm • deploy anywhere, from bare metal to cloud VMs • compress the complexity of going to production • SSHKit + Traefik + Docker

Slide 5

Slide 5 text

Why Kamal? • Cloud !!! • Kubernetes, Nomad, Mesos,… are complicated AF • Ansible, Chef,Puppet,… are configuration management tools, not made for deployment • Docker Swarm is declerative and can get finnicky • Dokku is like Linux: free only if your time has no value

Slide 6

Slide 6 text

Benefits • simple and readable Ruby gem • YAML configuration • Includes server set up, zero-downtime deploys, rolling restarts, easy rollback, deploy locks, logs grep,… • Deploy across multiple clouds or on personal hardware • Support for accessories like Redis, memcached, Postgres, MySql,… via their own Dockerfile in a designated or same server

Slide 7

Slide 7 text

Why would you use it? • Heroku is " • Try Fly.io or Render • Get away from Big Cloud™ • Hetzner, DigitalOcean, Linode,… Raspberry Pi? • Stop dealing with k8s, Dokku,… • Fun to try out and learn something new

Slide 8

Slide 8 text

What next? • https://kamal-deploy.org/ (or https://mrsk.dev) • https://evilmartians.com/chronicles/mrsk-hot-deployment-tool-or-total-game-changer • https://semaphoreci.com/blog/mrsk

Slide 9

Slide 9 text

Async queries • async_count • async_sum • async_minimum • async_maximum • async_average • async_pluck • async_pick • async_ids • async_find_by_sql • async_count_by_sql

Slide 10

Slide 10 text

Async queries • async_count • async_sum • async_minimum • async_maximum • async_average • async_pluck • async_pick • async_ids • async_find_by_sql • async_count_by_sql https://radanskoric.com/articles/understand-rails-async-db-queries

Slide 11

Slide 11 text

Unused routes $ bin/rails routes --unused Found 2 unused routes: Prefix Verb URI Pattern Controller#Action one GET /one(.:format) action#one two GET /two(.:format) action#two

Slide 12

Slide 12 text

Common Table Expressions Post.with( posts_with_comments: Post.where("comments_count > ?", 0), posts_with_tags: Post.where("tags_count > ?", 0) ) # Replaces posts_with_comments_table = Arel::Table.new(:posts_with_comments) posts_with_comments_expression = Post.arel_table.where(posts_with_comments_table[:comments_count].gt(0)) posts_with_tags_table = Arel::Table.new(:posts_with_tags) posts_with_tags_expression = Post.arel_table.where(posts_with_tags_table[:tags_count].gt(0)) Post.all.arel.with([ Arel::Nodes::As.new(posts_with_comments_table, posts_with_comments_expression), Arel::Nodes::As.new(posts_with_tags_table, posts_with_tags_expression) ]) # WITH posts_with_comments AS ( # SELECT * FROM posts WHERE (comments_count > 0) # ), posts_with_tags AS ( # SELECT * FROM posts WHERE (tags_count > 0) # ) # SELECT * FROM posts

Slide 13

Slide 13 text

ActiveRecord::Relation#explain • now accepts options • query.explain(“ANALYZE, COSTS, VERBOSE, BUFFERS, FORMAT JSON") • query.explain("ANALYZE", "COSTS", "VERBOSE", "BUFFERS", "FORMAT JSON”) • https://explain.dalibo.com/

Slide 14

Slide 14 text

#select Accepts a Hash # You can now write selects like this: Post.joins(:comments).select( posts: { id: :post_id, title: :post_title }, comments: { id: :comment_id, body: :comment_body} ) # In place of this: Post.joins(:comments).select( "posts.id as post_id, posts.title as post_title, comments.id as comment_id, comments.body as comment_body" )

Slide 15

Slide 15 text

ActiveRecord::Base.normalizes class User < ApplicationRecord normalizes :email, with: -> email { email.strip.downcase } normalizes :phone, with: -> phone { phone.delete("^0-9").delete_prefix("1") } end # Normalize email address before creating a user user = User.create(email: " [email protected]\n") user.email # => "[email protected]"

Slide 16

Slide 16 text

Composite keys • both at the database and at application level • derived from schema • mainly for many-to-many relationships • virtual primary key via query_constraints • composite foreign key on association

Slide 17

Slide 17 text

BYO Authentication • Devise is " • User.authenticate_by( email: “[email protected]", password: “rails4ever” ) • lazaronixon/authentication-zero • passkeys (WebAuthn) class User < ApplicationRecord has_secure_password generates_token_for :password_reset, expires_in: 15.minutes do # Last 10 characters of password salt, changes when password is updated: password_salt&.last(10) end end user = User.first token = user.generate_token_for(:password_reset) User.find_by_token_for(:password_reset, token) # => user # 16 minutes later... User.find_by_token_for(:password_reset, token) # => nil

Slide 18

Slide 18 text

ActiveSupport::MessagePack • new serializer that integrates with the msgpack gem • can reduce payload size and improve performance compared to JSON and Marshal

Slide 19

Slide 19 text

ActiveJob.perform_all_later • single redis/pg call # Enqueueing individual jobs ActiveJob.perform_all_later( MyJob.new("hello", 42), MyJob.new("world", 0) ) # Enqueueing an array of jobs user_jobs = User.pluck(:id).map { |id| UserJob.new(user_id: id) } ActiveJob.perform_all_later(user_jobs)

Slide 20

Slide 20 text

Rails.env.local? • Goodbye if Rails.env.development? || Rails.env.test? • Hello if Rails.env.local?

Slide 21

Slide 21 text

Allow templates to set strict locals • By default, templates will accept any locals as keyword arguments • Now you can define what locals a template should accept via locals magic comment <%# locals: (message:) -%> <%= message %> You can also set default values for these locals: <%# locals: (message: "Hello, world!") -%> <%= message %> If you want to disable the use of locals entirely, you can do so like this: <%# locals: () %>

Slide 22

Slide 22 text

config.sandbox_by_default • start rails console in sandbox mode by default • useful to set for production environments

Slide 23

Slide 23 text

Support Bun • all-in-one JS/TS runtime & toolkit designed for speed, complete with a bundler, test runner, and Node.js-compatible package manager. • I know nothing about this • other people are excited

Slide 24

Slide 24 text

ICYMI • Ruby 3.3's YJIT is amazing • New register allocator • More code now gets JIT compiled • For Shopify: • 13% faster than 3.2 YJIT • 15% faster than 3.3 without YJIT • For Basecamp: • +24% faster on the median, +12% on the average

Slide 25

Slide 25 text

• https://www.youtube.com/@railsofficial • My favs: • Zeitwerk Internals • Tailwind CSS: It looks awful, and it works • Applying Shape Up in the Real World • Tenderlove (Aaron Patterson)

Slide 26

Slide 26 text

Ready for production?

Slide 27

Slide 27 text

rails app:update just do it

Slide 28

Slide 28 text

Questions?

Slide 29

Slide 29 text

Thanks!