Review styles for controller/models
This commit is contained in:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user