diff --git a/STYLE.md b/STYLE.md index bf9dd3be3..498a8e720 100644 --- a/STYLE.md +++ b/STYLE.md @@ -135,56 +135,37 @@ class SomeModule end ``` -## CRUD operations from controllers -In general, we favor a vanilla Rails approach to CRUD operations. We create and update models from Rails controllers passing the parameters directly to the model constructor or update method. We do not use services or form objects to handle these operations. +## Controller and model interactions -There are exceptional scenarios where we need to perform more complex operations, and we use form objects or higher-level service methods to handle them. We use the same pattern for both creations and updates. +In general, we favor a [vanilla Rails](https://dev.37signals.com/vanilla-rails-is-plenty/) approach with thin controllers directly invoking a rich domain model. In general, we don't use services or other artifacts to connect the two. -Related to this, we prefer to avoid [nested attributes](https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html). If you find yourself wanting to use `accepts_nested_attributes_for`, that's a good smell that you might want to consider using a form object instead. - -As an example, you can check how we create and update messages in HEY's: `MessagesController`: +Invoking plain Active Record operations is totally fine: ```ruby -class MessagesController < ApplicationController +class Cards::CommentsController < ApplicationController def create - @entry = Entry.enter \ - new_message, - on: new_topic, - status: :drafted, - address: entry_addressed_param, - scheduled_delivery_at: entry_scheduled_delivery_at_param, - scheduled_bubble_up_on: entry_scheduled_bubble_up_on_param - - respond_to_saved_entry @entry - end - - def update - previously_scheduled = @entry.scheduled_delivery - - @entry.revise \ - message_params, - status: :drafted, - is_delivery_imminent: !entry_status_param.drafted?, - address: entry_addressed_param, - scheduled_delivery_at: entry_scheduled_delivery_at_param, - scheduled_bubble_up_on: entry_scheduled_bubble_up_on_param - - respond_to_saved_entry(@entry, previously_scheduled: previously_scheduled) + @comment = @card.comments.create!(comment_params) end end +``` -class Entry < ApplicationRecord - def self.enter(*args, **kwargs) - Entry::Enter.new(*args, **kwargs).perform - end +For more complex behavior, we prefer clear, intention-revealing model APIs that controllers call directly: - def revise(*args, **kwargs) - Entry::Revise.new(self, *args, **kwargs).perform +```ruby +class Cards::GoldnessesController < ApplicationController + def create + @card.gild end end ``` +When justified, it is fine to use services or form objects, but don't treat those as special artifacts: + +```ruby +Signup.new(email_address: email_address).create_identity +``` + ## Run async operations in jobs As a general rule, we write shallow job classes that delegate the logic itself to domain models: