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

Powerful Rails Features You Might Not Know

excid3
October 10, 2023

Powerful Rails Features You Might Not Know

This talk was given at Rails World 2023.

If all you've got is a hammer, everything looks like a nail. In tech, there is a constant stream of new features being added every day. Keeping up with the latest Ruby on Rails functionality can help you and your team be far more productive than you might normally be.

In this talk, we walk through a bunch of lesser known or easy to miss features in Ruby on Rails that you can use to improve your skills.

excid3

October 10, 2023
Tweet

More Decks by excid3

Other Decks in Programming

Transcript

  1. ActiveRecord Excluding User.where.not(id: users.map(&:id)) # SELECT "users".* FROM "users" WHERE

    "users"."id" NOT IN (1,2) User.all.excluding(users) # SELECT "users".* FROM "users" WHERE "users"."id" NOT IN (1,2)
  2. ActiveRecord Strict Loading class Project < ApplicationRecord has_many :comments, strict_loading:

    true end project = Project.first project.comments ActiveRecord::StrictLoadingViolationError `Project` is marked as strict_loading. The Comment association named `:comments` cannot be lazily loaded.
  3. ActiveRecord Strict Loading project = Project.includes(:comments).first Project Load (0.3ms) SELECT

    "projects".* FROM "projects" ORDER BY "projects"."id" ASC LIMIT ? [["LIMIT", 1]] Comment Load (0.1ms) SELECT "comments".* FROM "comments" WHERE "comments"."project_id" = ? [["project_id", 1]] => #<Project:0x00000001054af780 id: 1> project.comments => [#<Comment:0x00000001054aed80 id: 1, project_id: 1, body: "Hello RailsWorld">]
  4. Generated Columns class AddNameVirtualColumnToUsers < ActiveRecord::Migration[7.0] def change add_column :users,

    :full_name, :virtual, type: :string, as: "first_name || ' ' || last_name", stored: true end end
  5. with_options class Account < ActiveRecord::Base with_options dependent: :destroy do has_many

    :customers has_many :products has_many :invoices has_many :expenses end end
  6. Searching Users json.array! @users do |user| json.sgid user.attachable_sgid json.content render(

    partial: "users/user", locals: {user: user}, formats: [:html] ) end
  7. ActionText Embeds import Trix from "trix" let attachment = new

    Trix.Attachment({ content: "<div><img src=“…”> Chris Oliver</div>", sgid: "BAh7CEkiCG…" }) element.editor.insertAttachment(attachment)
  8. Serialize Coders class ActionText::Content def self.load(content) new(content) if content end

    def self.dump(content) case content when nil nil when self content.to_html end end end
  9. class RepliesMailbox < ApplicationMailbox MATCHER = /^reply-(\d+)@replies\./ def process conversation.posts.create!(

    author: author, body: body, message_id: mail.message_id ) end private def conversation Conversation.find(conversation_id) end def conversation_id mail.recipients.find { |recipient| MATCHER.match?(recipient) }[MATCHER, 1] end end ActionMailbox Inbound Emails
  10. Routing constraints authenticated :user, -> { _1.admin? } do resource

    :admin end authenticated :user do root "dashboard#show" end root "homepage#show"
  11. Draw Routes Rails.application.routes.draw do draw :api # ... end #

    config/routes/api.rb namespace :api, defaults: {format: :json} do namespace :v1 do resources :accounts end end
  12. Custom Generators $ bin/rails generate generator ApiClient creates an ApiClient

    generator: lib/generators/api_client/ lib/generators/api_client/api_client_generator.rb lib/generators/api_client/USAGE lib/generators/api_client/templates/ test/lib/generators/api_client_generator_test.rb
  13. Custom Turbo Stream Actions import "@hotwired/turbo-rails" Turbo.StreamActions.notification = function() {

    Notification.requestPermission(function(status) { if (status == "granted") { new Notification(this.getAttribute("title")) } }) }
  14. truncate_words content = 'And they found that many people were

    sleeping better.' content.truncate_words(5, omission: '... (continued)') # => "And they found that many... (continued)"
  15. Time.current.all_day #=> Fri, 06 Oct 2023 00:00:00 UTC +00:00.. Fri,

    06 Oct 2023 23:59:59 UTC +00:00 Time.current.all_week #=> Mon, 02 Oct 2023 00:00:00 UTC +00:00.. Sun, 08 Oct 2023 23:59:59 UTC +00:00 Time.current.all_month #=> Sun, 01 Oct 2023 00:00:00 UTC +00:00.. Tue, 31 Oct 2023 23:59:59 UTC +00:00 Time Helpers
  16. Abbreviated numbers number_to_human(123) # => "123" number_to_human(12345) # => "12.3

    Thousand" number_to_human(1234567) # => "1.23 Million" number_to_human(489939, precision: 2) # => "490 Thousand" number_to_human(489939, precision: 4) # => "489.9 Thousand" number_to_human(1234567, precision: 4, significant: false)# => "1.2346 Million"
  17. def number_to_social(number) return number_with_delimiter(number) if number < 10_000 number_to_human(number, precision:

    1, round_mode: :down, significant: false, format: "%n%u", units: {thousand: "K", million: "M", billion: "B"} ) end Abbreviated numbers
  18. Unused routes $ rails routes --unused Found 4 unused routes:

    Prefix Verb URI Pattern Controller#Action edit_comment GET /comments/:id/edit(.:format) comments#edit PATCH /comments/:id(.:format) comments#update PUT /comments/:id(.:format) comments#update DELETE /comments/:id(.:format) comments#destroy
  19. Template strict locals <%# locals: (message:) -%> <%= tag.div id:

    dom_id(message) do %> <%= message %> <% end %> <%= render partial: “message” %> ArgumentError: missing local: :message <%= render partial: “message”, locals: {message: @message} %>
  20. Template strict locals <%# locals: (message: “Hello”) -%> <div> <%=

    message %> </div> <%= render partial: “message” %> <%= render partial: “message”, locals: {message: “Hey”} %>
  21. Template strict locals <%# locals: () -%> <div> Hello RailsWorld!

    </div> <%= render partial: “message” %>
  22. normalizes class User < ApplicationRecord end normalizes :email, with: ->(email)

    { email.strip.downcase } normalizes :email, with: ->{ _1.strip.downcase }
  23. generates_token_for class User < ApplicationRecord generates_token_for :password_reset, expires_in: 15.minutes do

    # BCrypt salt changes when password is updated BCrypt::Password.new(password_digest).salt[-10..] end end
  24. Named variants class User < ApplicationRecord has_one_attached :avatar do |attachable|

    attachable.variant :thumbnail, resize_to_limit: [200, 200] end end
  25. Preprocessed variants class User < ApplicationRecord has_one_attached :avatar do |attachable|

    attachable.variant :thumbnail, resize_to_limit: [200, 200], preprocessed: true end end