An Endless Method Use Case
An example of where I think the endless method helps with making the code concise and easy to read
While preparing my article for the series that I started about Open Source Ruby, I found an example of code that I think could benefit from using the endless method.
Here is a video version of this article:
or continue reading for the text version ๐
The original code
The original code is based on Mastodon ApplicationController:
# https://github.com/mastodon/mastodon/blob/main/app/controllers/application_controller.rb#L3
class ApplicationController < ActionController::Base
# other code ...
def forbidden
respond_with_error(403)
end
def not_found
respond_with_error(404)
end
def gone
respond_with_error(410)
end
def unprocessable_entity
respond_with_error(422)
end
def not_acceptable
respond_with_error(406)
end
def bad_request
respond_with_error(400)
end
def internal_server_error
respond_with_error(500)
end
def service_unavailable
respond_with_error(503)
end
def too_many_requests
respond_with_error(429)
end
# other code ...
end
Refactoring
Use the endless method
# https://github.com/mastodon/mastodon/blob/main/app/controllers/application_controller.rb#L3
class ApplicationController < ActionController::Base
# other code ...
def forbidden = respond_with_error(403)
def not_found = respond_with_error(404)
def gone = respond_with_error(410)
def unprocessable_entity = respond_with_error(422)
def not_acceptable = respond_with_error(406)
def bad_request = respond_with_error(400)
def internal_server_error = respond_with_error(500)
def service_unavailable = respond_with_error(503)
def too_many_requests = respond_with_error(429)
# other code ...
end
In this specific case, the endless method is the best to be used here.
It is almost like aliasing the same method but giving it different names based on the parameters passed.
Let's take a look at a side-by-side comparison:
Extract to a concern
I think we can take this one step further and extract it all to a concern or simple module if you want to:
module RespondWithError
extend ActiveSupport::Concern
private
def forbidden = respond_with_error(403)
def not_found = respond_with_error(404)
def gone = respond_with_error(410)
def unprocessable_entity = respond_with_error(422)
def not_acceptable = respond_with_error(406)
def bad_request = respond_with_error(400)
def internal_server_error = respond_with_error(500)
def service_unavailable = respond_with_error(503)
def too_many_requests = respond_with_error(429)
def respond_with_error(code)
respond_to do |format|
format.any { render "errors/#{code}", layout: 'error', status: code, formats: [:html] }
format.json { render json: { error: Rack::Utils::HTTP_STATUS_CODES[code] }, status: code }
end
end
end
This way all behavior about responding with error status code is contained in a single file. Adding or changing this behaviour could be made in this one single place.
Group together by status code
Based on the reply from Russell Garner and Bradley Schaefer we can refactor this more by removing new lines between endless methods and grouping them by status:
module RespondWithError
extend ActiveSupport::Concern
private
# Client error responses 4xx
def bad_request = respond_with_error(400)
def forbidden = respond_with_error(403)
def not_found = respond_with_error(404)
def not_acceptable = respond_with_error(406)
def gone = respond_with_error(410)
def unprocessable_entity = respond_with_error(422)
def too_many_requests = respond_with_error(429)
# Server error responses 5xx
def internal_server_error = respond_with_error(500)
def service_unavailable = respond_with_error(503)
def respond_with_error(code)
respond_to do |format|
format.any { render "errors/#{code}", layout: 'error', status: code, formats: [:html] }
format.json { render json: { error: Rack::Utils::HTTP_STATUS_CODES[code] }, status: code }
end
end
end
Read more about the endless method
I strongly recommend this article from Victor Shepelev about the endless method:
https://zverok.substack.com/p/useless-ruby-sugar-endless-one-line
You will find there more cases and examples about when to use and when not to use this construct.
Enjoyed this article?
๐ Join my Short Ruby News newsletter for weekly Ruby updates from the community and visit rubyandrails.info, a directory with learning content about Ruby.
๐ Subscribe to my Ruby and Ruby on rails courses over email at learn.shortruby.com - effortless learning anytime, anywhere
๐ค Let's connect on Ruby.social or Linkedin or Twitter where I post mainly about Ruby and Rails.
๐ฅ Follow me on my YouTube channel for short videos about Ruby
Updates
- 2023-12-06: Added a new refactoring variant based on replies to my share on Ruby.social - see
Group together by status code