Review styles for controller/models

This commit is contained in:
Jorge Manrubia
2025-11-28 09:03:46 +01:00
parent 94c49fb3f6
commit c87f8a87f5
+17 -36
View File
@@ -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: