Skip to content

Commit 5d67a6b

Browse files
committed
Multi controllers per entity + REST actions only
1 parent d63945f commit 5d67a6b

File tree

11 files changed

+118
-95
lines changed

11 files changed

+118
-95
lines changed

README.md

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,51 +16,48 @@ _**Eighteen versions**_ (gradually implemented) of a Web and REST API app made w
1616
## 💡 Summary
1717

1818
<table>
19-
<tr><td><strong>Branch</strong></td><td>020-multi-controllers-per-entity</td></tr>
20-
<tr><td><strong>Lines of Code</strong></td><td>1342</td></tr>
21-
<tr><td><strong>Rubycritic Score</strong></td><td>91.34</td></tr>
19+
<tr><td><strong>Branch</strong></td><td>021-multi-controllers-per-entity_rest-actions-only</td></tr>
20+
<tr><td><strong>Lines of Code</strong></td><td>1361</td></tr>
21+
<tr><td><strong>Rubycritic Score</strong></td><td>91.56</td></tr>
2222
</table>
2323

24-
The previous version demonstrates how concerns can help safely move code around, facilitating a better understanding of the different responsibilities in an implementation.
24+
This version ensures that all controllers only have REST actions.
2525

26-
These were the created concerns:
27-
- `UserRegistrationsConcern`
28-
- `UserSessionsConcern`
29-
- `UserPasswordsConcern`
30-
- `UserTokensConcern`
31-
- `UserProfilesConcern`
26+
To accomplish this the `task_items#complete` and `task_items#incomplete` actions were moved to their own controller:
3227

33-
However, since the concerns are mixins, we need to ensure that all method names are unique. After all, if any are repeated, they will overwrite each other.
28+
| From | To |
29+
| -------------------------------- | -------------------------------------- |
30+
| `TaskItemsComtroller#complete` | `CompleteTaskItemsController#update` |
31+
| `TaskItemsComtroller#incomplete` | `IncompleteTaskItemsController#update` |
3432

35-
And here is what this version does. It uses the concerns categorization to implement dedicated routes and controllers.
33+
Beyond this change, concern was created to share code between the `CompleteTaskItemsController,` `IncompleteTaskItemsController`, and `TaskItemsController.`
3634

37-
See how the controllers turned out:
35+
See how the task items controllers are now:
3836

3937
```sh
40-
110 app/controllers/application_controller.rb
41-
130 app/controllers/task_items_controller.rb
42-
82 app/controllers/task_lists_controller.rb
43-
60 app/controllers/user_passwords_controller.rb
44-
41 app/controllers/user_profiles_controller.rb
45-
49 app/controllers/user_registrations_controller.rb
46-
50 app/controllers/user_sessions_controller.rb
47-
25 app/controllers/user_tokens_controller.rb
48-
547 total
38+
app/controllers/
39+
├── concerns/
40+
│ └── task_items_concern.rb
41+
├── complete_task_items_controller.rb
42+
├── incomplete_task_items_controller.rb
43+
└── task_items_controller.rb
4944
```
5045

5146
### 🤔 What was changed? <!-- omit in toc -->
5247

53-
The Rubycritic score increased from `90.34` to `91.34`.
48+
The Rubycritic score increased from `91.34` to `91.56`.
5449

55-
This happened because each controller allowed the isolation of each action and callback and allowed the definition of methods with the same name. (Example: `user_params` instead of `user_registration_params`, `user_session_params`, `user_password_params`...).
50+
This happened because each cohesion has been increased, and the controllers are more specialized.
5651

57-
Another benefit was the routing definition. It became more readable and easier to understand as it was possible to declare them using only default `REST` actions (index, show, new, create, edit, update, destroy).
52+
But lets be honest, the routes are worse than before. 😅
5853

5954
### 🔎 What the next version will have? <!-- omit in toc -->
6055

61-
It shows how the restriction of REST actions can enforce cohesion and ensure controllers are responsible for specific contexts/concepts.
56+
Let's do what DHH taught us over a decade ago: https://jeromedalbert.com/how-dhh-organizes-his-rails-controllers/
6257

63-
`Next version`: [021-multi-controllers-per-entity_rest-actions-only](https://github.com/solid-process/rails-way-app/tree/021-multi-controllers-per-entity_rest-actions-only?tab=readme-ov-file).
58+
This will improve the routes and put the controllers in a better structure.
59+
60+
`Next version`: [030-resources-within-namespaces](https://github.com/solid-process/rails-way-app/tree/030-resources-within-namespaces?tab=readme-ov-file).
6461

6562
## 📣 Important info
6663

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# frozen_string_literal: true
2+
3+
class CompleteTaskItemsController < ApplicationController
4+
include TaskItemsConcern
5+
6+
before_action :authenticate_user!
7+
before_action :require_task_list!
8+
before_action :set_task_item
9+
10+
def update
11+
@task_item.complete!
12+
13+
respond_to do |format|
14+
format.html do
15+
redirect_to(next_location, notice: "Task item was successfully marked as completed.")
16+
end
17+
format.json { render "task_items/show", status: :ok, location: task_item_url(@task_item) }
18+
end
19+
end
20+
end
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# frozen_string_literal: true
2+
3+
module TaskItemsConcern
4+
extend ActiveSupport::Concern
5+
6+
included do
7+
rescue_from ActiveRecord::RecordNotFound do |exception|
8+
raise exception unless request.format.json?
9+
10+
render_json_with_error(status: :not_found, message: "Task list or item not found")
11+
end
12+
end
13+
14+
private
15+
16+
def require_task_list!
17+
raise ActiveRecord::RecordNotFound unless Current.task_list_id
18+
end
19+
20+
def set_task_item
21+
@task_item = Current.task_items.find(params[:id])
22+
end
23+
24+
def task_items_url(...)
25+
task_list_task_items_url(Current.task_list_id, ...)
26+
end
27+
28+
def task_item_url(...)
29+
task_list_task_item_url(Current.task_list_id, ...)
30+
end
31+
32+
def next_location
33+
case params[:filter]
34+
when "completed" then task_items_url(filter: "completed")
35+
when "incomplete" then task_items_url(filter: "incomplete")
36+
when "show" then task_item_url(@task_item)
37+
else task_items_url
38+
end
39+
end
40+
end
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# frozen_string_literal: true
2+
3+
class IncompleteTaskItemsController < ApplicationController
4+
include TaskItemsConcern
5+
6+
before_action :authenticate_user!
7+
before_action :require_task_list!
8+
before_action :set_task_item
9+
10+
def update
11+
@task_item.incomplete!
12+
13+
respond_to do |format|
14+
format.html do
15+
redirect_to(next_location, notice: "Task item was successfully marked as incomplete.")
16+
end
17+
format.json { render "task_items/show", status: :ok, location: task_item_url(@task_item) }
18+
end
19+
end
20+
end

app/controllers/task_items_controller.rb

Lines changed: 2 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
# frozen_string_literal: true
22

33
class TaskItemsController < ApplicationController
4+
include TaskItemsConcern
5+
46
before_action :authenticate_user!
57
before_action :require_task_list!
68
before_action :set_task_item, except: %i[index new create]
79

8-
rescue_from ActiveRecord::RecordNotFound do |exception|
9-
raise exception unless request.format.json?
10-
11-
render_json_with_error(status: :not_found, message: "Task list or item not found")
12-
end
13-
1410
def index
1511
task_items = Current.task_items
1612

@@ -75,56 +71,9 @@ def destroy
7571
end
7672
end
7773

78-
def complete
79-
@task_item.complete!
80-
81-
respond_to do |format|
82-
format.html do
83-
redirect_to(next_location, notice: "Task item was successfully marked as completed.")
84-
end
85-
format.json { render :show, status: :ok, location: task_item_url(@task_item) }
86-
end
87-
end
88-
89-
def incomplete
90-
@task_item.incomplete!
91-
92-
respond_to do |format|
93-
format.html do
94-
redirect_to(next_location, notice: "Task item was successfully marked as incomplete.")
95-
end
96-
format.json { render :show, status: :ok, location: task_item_url(@task_item) }
97-
end
98-
end
99-
10074
private
10175

102-
def require_task_list!
103-
raise ActiveRecord::RecordNotFound unless Current.task_list_id
104-
end
105-
106-
def set_task_item
107-
@task_item = Current.task_items.find(params[:id])
108-
end
109-
11076
def task_item_params
11177
params.require(:task_item).permit(:name, :description, :completed)
11278
end
113-
114-
def task_items_url(...)
115-
task_list_task_items_url(Current.task_list_id, ...)
116-
end
117-
118-
def task_item_url(...)
119-
task_list_task_item_url(Current.task_list_id, ...)
120-
end
121-
122-
def next_location
123-
case params[:filter]
124-
when "completed" then task_items_url(filter: "completed")
125-
when "incomplete" then task_items_url(filter: "incomplete")
126-
when "show" then task_item_url(@task_item)
127-
else task_items_url
128-
end
129-
end
13079
end

app/controllers/user_passwords_controller.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def update
4444
private
4545

4646
def set_user_by_token
47-
@user = User.find_by_token_for(:reset_password, params[:id]) || User.find_by(id: params[:id])
47+
@user = User.find_by_token_for(:reset_password, params[:id])
4848

4949
return if @user
5050

app/views/task_items/_toggle_status_action.html.erb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<% if task_item.incomplete? %>
22
<%= link_to(
33
"✅",
4-
complete_task_list_task_item_path(Current.task_list_id, task_item, filter: params[:filter]),
4+
task_list_complete_task_item_path(Current.task_list_id, task_item, filter: params[:filter]),
55
title: "Mark as complete",
66
data: { turbo_method: :put },
77
class: 'button',
@@ -10,7 +10,7 @@
1010
<% else %>
1111
<%= link_to(
1212
"❌",
13-
incomplete_task_list_task_item_path(Current.task_list_id, task_item, filter: params[:filter]),
13+
task_list_incomplete_task_item_path(Current.task_list_id, task_item, filter: params[:filter]),
1414
title: "Mark as incomplete",
1515
data: { turbo_method: :put },
1616
class: 'button',

app/views/task_items/show.html.erb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@
88
<% if @task_item.incomplete? %>
99
<%= link_to(
1010
"✅ Complete",
11-
complete_task_list_task_item_path(Current.task_list_id, @task_item, filter: "show"),
11+
task_list_complete_task_item_path(Current.task_list_id, @task_item, filter: "show"),
1212
title: "Mark as complete",
1313
data: { turbo_method: :put },
1414
style: "text-decoration: none;"
1515
) %>
1616
<% else %>
1717
<%= link_to(
1818
"❌ Incomplete",
19-
incomplete_task_list_task_item_path(Current.task_list_id, @task_item, filter: "show"),
19+
task_list_incomplete_task_item_path(Current.task_list_id, @task_item, filter: "show"),
2020
title: "Mark as incomplete",
2121
data: { turbo_method: :put },
2222
style: "text-decoration: none;"

config/routes.rb

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,9 @@
2020
resource :user_tokens, only: [ :edit, :update ]
2121

2222
resources :task_lists do
23-
resources :task_items do
24-
member do
25-
put :complete
26-
put :incomplete
27-
end
28-
end
23+
resources :task_items
24+
resources :complete_task_items, only: [ :update ]
25+
resources :incomplete_task_items, only: [ :update ]
2926
end
3027

3128
# Defines the root path route ("/")

docs/01_REST_API_DOC.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,15 +224,15 @@ curl -X PUT "$API_HOST/task_lists/1/task_items/1" \
224224
#### Marking as incomplete
225225

226226
```bash
227-
curl -X PUT "$API_HOST/task_lists/1/task_items/1/incomplete" \
227+
curl -X PUT "$API_HOST/task_lists/1/incomplete_task_items/1" \
228228
-H "Content-Type: application/json" \
229229
-H "Authorization: Bearer $API_TOKEN"
230230
```
231231

232232
#### Marking as completed
233233

234234
```bash
235-
curl -X PUT "$API_HOST/task_lists/1/task_items/1/complete" \
235+
curl -X PUT "$API_HOST/task_lists/1/complete_task_items/1" \
236236
-H "Content-Type: application/json" \
237237
-H "Authorization: Bearer $API_TOKEN"
238238
```

0 commit comments

Comments
 (0)