If there is data that transitions to a state, this kind of code will occur frequently.
erb:view.html.erb
<%#It seems that approval cannot be made if the state of the report is 1 or 2, but what are the states of 1 and 2?%>
<%= button_tag('Approval', disabled: (report.state == 1 || report.state == 2)) %>
reports_controller.rb
def accept
report = Report.find(params[:id])
#The same conditional statement as view is required for handling!
if report.state == 1 || report.state == 2
flash[:error] = 'Cannot approve'
redirect_to root_url
end
report.state = 3 #Approval value!
report.save!
end
If you use a constant, it will improve a little, but ...
erb:view.html.erb
<%= button_tag('Approval', disabled: (report.state == Report::STATE_DRAFT || report.state == Report::STATE_TRASH)) %>
reports_controller.rb
def accept
report = Report.find(params[:id])
#After all, the same conditional statement as view is required!
if report.state == Report::STATE_DRAFT || report.state == Report::STATE_TRASH
flash[:error] = 'Cannot approve'
redirect_to root_url
end
report.state = Report::STATE_APPROBAL
report.save!
end
report.rb
class Report < ApplicationRecord
#If there are many columns that require constants, many constants will be required.
#Besides, the scope is too wide to see the purpose.
STATE_DRAFT = 1
STATE_TRASH = 2
STATE_APPROBAL = 3
end
There is a way to use ActiveRecord :: Enum instead of a constant, but it doesn't solve the need for the same conditional statement in various places ... You can solve it by adding a method to the ActiveRecord model.
report.rb
class Report < ApplicationRecord
STATE_DRAFT = 1
STATE_TRASH = 2
STATE_APPROBAL = 3
def can_accept?
state != STATE_DRAFT && state != STATE_TRASH
end
end
But this is the way to the Fat Model ... So ...
Avoid Fat Modeling of ActiveRecord models and create state transition classes with clear responsibilities.
It would be nice if the conditional statement had a state change method called report_state.to_approval
, with the feeling ofreport_state.approval_in_next?
[^ 1](can it be in the approved state).
[^ 1]: The method name is intended to be "Is there approval in the next state?", But since it is an English shit coarse fish, there may be other good method names.
reports_controller.rb
def accept
report = Report.find(params[:id])
report_state = report.state_object
#Clean
unless report_state.approval_in_next?
flash[:error] = 'Cannot approve'
redirect_to root_url
end
#This is a simple example with only one column, but even if the state transition process is complicated, to_*Can be hidden in the method!
new_report_state = report_state.to_approval
report.state = new_report_state.state
report.save!
end
report.rb
class Report < ApplicationRecord
# state_object=It may be convenient to make a method
def state_object
ReportState.new(state)
end
end
The actual ReportState class looks like this.
report_state.rb
class ReportState
attr_accessor :state
#Move constants from Report class. It fits better all the time
DRAFT = 1
TRASH = 2
APPROBAL = 3
def initialize(state)
self.state = state
end
def approval_in_next?
state != DRAFT && state != TRASH
end
def to_approval
raise 'Illegal state transition!' unless approval_in_next?
self.class.new(APPROBAL)
end
end
Now, even if the conditions that can be approved become complicated or the state transition process to the approval status becomes complicated, all you have to do is change here!
This entry is the State Transition Diagram, State Transition Table of Requirements Analysis Driven Design. #% E7% 8A% B6% E6% 85% 8B% E9% 81% B7% E7% A7% BB% E5% 9B% B3% E7% 8A% B6% E6% 85% 8B% E9% 81% B7% E7% A7% BB% E8% A1% A8) has been reorganized and rewritten into general-purpose content. It's a long story, but it's a story about DDD-like things in Rails.
Recommended Posts