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

[Sin City Ruby 2024] Seven deadly Rails anti-patterns

[Sin City Ruby 2024] Seven deadly Rails anti-patterns

Developing web applications with Ruby on Rails is known to be hellishly productive. What’s the price of this deal? Let’s talk about design patterns leveraged by the framework responsible for increased productivity and at the same time often acclaimed for being anti-patterns.

Vladimir Dementyev

March 22, 2024
Tweet

More Decks by Vladimir Dementyev

Other Decks in Programming

Transcript

  1. Callbacks core_ext Concerns Current Page refreshes Helpers @vars in views

    Seven deadly Rails anti-patterns Sin City Ruby 2024 Vladimir Dementyev Evil Martians ?
  2. palkan_tula palkan 2 Rails anti-patterns Software design patterns and ideas

    employed and/or encouraged by the framework that make developers hellishly productive (but come with a risk of poor maintainability in the long term) the framework productive poor maintainability Deal with the Devil
  3. 3

  4. 8

  5. 14

  6. “The deeper callbacks lie in the architecture layer hierarchy, the

    more dangerous they can be” –Me @ Layered Rails 15
  7. palkan_tula palkan 20 class User < ApplicationRecord before_validation :normalize_email after_create

    :generate_initial_project after_create_commit :send_welcome_email end Maintainability
  8. palkan_tula palkan 21 class User < ApplicationRecord before_validation :slugify, on:

    :create before_validation :normalize_email after_create :generate_initial_project after_create_commit :send_welcome_email end Maintainability
  9. palkan_tula palkan 22 class User < ApplicationRecord before_validation :slugify, on:

    :create before_validation :normalize_email after_create :generate_initial_project, after_create_commit :send_welcome_email after_create_commit :send_analytics_event after_commit :sync_with_crm end Maintainability
  10. palkan_tula palkan 23 class User < ApplicationRecord before_validation :slugify, on:

    :create before_validation :normalize_email after_create :generate_initial_project, after_create_commit :send_welcome_email after_create_commit :send_analytics_event, if: :tracking_consent? after_commit :sync_with_crm end Maintainability
  11. palkan_tula palkan class User < ApplicationRecord before_validation :slugify, on: :create

    before_validation :normalize_email after_create :generate_initial_project, unless: :admin? after_create_commit :send_welcome_email after_create_commit :send_analytics_event, if: :tracking_consent? after_commit :sync_with_crm end 24 Maintainability
  12. + palkan_tula palkan 25 (De-)Normalization Essential side-effects (audit, etc.) Conditional

    Peripheral side-effects (third-party integrations, etc.) Model Callbacks
  13. “A concern is a module that you extract to split

    the implementation of a class in coherent chunks, instead of having one big class body.” –Xavier Noria 27 coherent chunks
  14. palkan_tula palkan 28 class Account < ApplicationRecord include Account::Associations include

    Account::Validations include Account::Scopes include Account::Callbacks end Cohesion Maintainability
  15. palkan_tula palkan Coherent Chunks 29 class Identity < ApplicationRecord include

    Accessor, Authenticable, Boxes, Clearance, Preapproval, Clipper, Creator, Eventable, Examiner, Filer, Member, PasswordReset, Poster, Reaching, Regional, TopicMerger end God-blessed concerns Maintainability
  16. + palkan_tula palkan 30 No isolation (potential hidden/circular dependencies) Naming

    is hard (modules, methods) Complicated testing Many Modules Maintainability
  17. + palkan_tula palkan Many 32 No isolation (potential hidden/circular dependencies)

    Naming is hard (modules, methods) Complicated testing Many callbacks Concerns Maintainability
  18. + palkan_tula palkan 34 Value objects Workflows (state machines) Associated

    models Store models Extracting Prefer objects over concerns... gem "active_record-associated_object" gem "store_model"
  19. + palkan_tula palkan 36 Associations Active Record extensions (e.g., soft

    deletion) Gluing associated objects and host models the Rails Way Concerning ...but remember to stay productive
  20. palkan_tula palkan 37 module Account::Terminable def terminate purge_or_incinerate if terminable?

    end private def purge_or_incinerate purgeable? ? purge : incinerate end # def purge # def incinerate # ... end
  21. palkan_tula palkan 38 class Account::Terminator def initialize(account) = @account =

    account def perform purge_or_incinerate if terminable? end private def purge_or_incinerate purgeable? ? purge : incinerate end # def purge # def incinerate # ... end
  22. palkan_tula palkan 40 class Account::Terminator def initialize(account) = @account =

    account def perform purge_or_incinerate if terminable? end private def purge_or_incinerate purgeable? ? purge : incinerate end # def purge # def incinerate # ... end
  23. palkan_tula palkan 41 class Account class Terminator # ... end

    module Terminable def terminate = Terminator.new(self).perform end end
  24. + palkan_tula palkan 42 Concerns to sweep complexity under the

    rug Concerns to provide Rails-ish interface and to integrate with Active Record Objects to truly isolate functionality Concerns
  25. + palkan_tula palkan 44 Bags of random methods Form a

    single module shared by all views Helpers # Did you know? config.action_controller.include_all_helpers = false
  26. + palkan_tula palkan 47 Keep the number of current attributes

    small Always use Current.set(..., &) Write as upper on the abstraction stack as possible Current Don't let currents take you under
  27. + palkan_tula palkan 50 Instance variables in controller action templates

    are fine (show.html, etc.) Instance variables in partials Strict locals in partials Become friends with erb-lint @vars in views Be strict with them (but not too strict)
  28. 52

  29. + palkan_tula palkan 54 Learn them, not fight Adding your

    own lib/core_ext!* core_ext * Or start using refinements
  30. palkan_tula palkan Page refreshes 56 <%# locals: (conversations:, total_count:) -%>

    <%= turbo_stream.update "counter" do %> <%= render partial: "counter", locals: {total_count:} %> <% end %> <%= turbo_stream.append "items" do %> <%= render partial: "items", locals: {current_conversation: Current.conversation, conversations:} %> <% end %> <%= turbo_stream.update "scroll" do %> <%= render partial: "scroll", locals: {id: :conversations, scroll_url: conversation_action_path(operation: :scroll, page: next_page)} %> <% end %> <%= turbo_stream.update "list" do %> <%= render partial: "list", locals: {conversations:, total_count:, current_conversation: Current.conversation} %> <% end %>
  31. + palkan_tula palkan 65 class Conversation < ApplicationRecord # Triggers

    page refresh on EVERY # record update broadcasts_refreshes end Deadly Combo Callbacks meet page refreshes
  32. + palkan_tula palkan 66 class Conversation < ApplicationRecord after_commit :broadcast_refresh_later,

    if: :should_broadcast? private def should_broadcast? # check changed attributes, # throttle, etc. end end Deadly Combo Be specific, ship Union Pacific selective updates
  33. 67

  34. + palkan_tula palkan 68 <%= cable_ready_updates_for conversation, only: %w[topic members_count],

    debounce: 200 do %> <% # ... %> <% end %> Cable Ready? Is Hotwire really that hot?
  35. + palkan_tula palkan 69 Beware of multi-user updates Beware of

    frequent updates SolidCache is not an accident Page refreshes
  36. + palkan_tula palkan 70 Callbacks Concerns Helpers Core extensions The

    Seven Caution! Sharp knives, use responsibly Current @vars in views Page refreshes
  37. –Me @ Sin City Ruby 2024 “Hell is full of

    Rails applications trying to go against the Rails Way.” 72