SmartAdapters
Smart Adapters neatly decouple the controllers from the views independently by the request format. In the Ruby on Rails documentation, the controller decides the format of the answer based on the (Content-Type) request:
# app/controllers/people_controller.rb
def index
@people = Person.all
respond_to do |format|
format.html
format.xml { render :xml => @people.to_xml }
end
end
The problem here is that over time the controllers inherit unnecessary complexity due to the evolution of the project (new logics and/or formats). The Smart Adapters solve the problem responding to a request with the appropriate format by using purposely designed classes.
# Controller
# app/controllers/people_controller.rb
def index
current_adapter.success Person.all
end
# HTML Adapter
# app/models/smart_adapters/people/index/html_adapter.rb
def success(people)
render 'people/show', locals: { people: people }
end
# XML Adapter
# app/models/smart_adapters/people/index/xml_adapter.rb
def success(people)
render xml: people, status: :ok
end
The application can now be kept dry while introducing new formats and functionalities. For instance, in case of an XML request, the adapter could add more details to the people
collection or track some metrics without bloating the controller.
# app/models/smart_adapters/people/index/xml_adapter.rb
def success(people)
metric_tracker.push('New XML request')
people.filter_private_details!
render xml: people, status: :ok
end
Installation
Add this line to your application's Gemfile:
gem 'smart_adapters'
And then execute:
$ bundle
Update the ApplicationController
by adding the SmartAdapters
concern:
class ApplicationController < ActionController::Base
include SmartAdapters
end
Add the adapters for your controller/action/format.
Formats supported
- HTML
- JSON
- JS
- XML
- CSV
- TXT
Example
app/controlles/users_controller.rb
class UsersController < ApplicationController
# GET /users
def index
current_adapter.success User.all
end
# GET /users/1
def show
if user
current_adapter.success(user)
else
current_adapter.failure(user)
end
end
# PATCH/PUT /users/1
def update
if user.update_attributes(update_params)
current_adapter.success(user)
else
current_adapter.failure(user)
end
end
private
def update_params
params.require(:user).permit(:first_name, :last_name)
end
def user
@user ||= User.find_by(id: params[:id])
end
end
app/models/smart_adapters/users/index/html_adapter.rb
module SmartAdapters
module Users
module Index
class HtmlAdapter < SimpleDelegator
include SmartAdapters::Util::Adapters::Html::Default
def success(resource)
render 'users/show', locals: { resource: resource }
end
end
end
end
end
app/models/smart_adapters/users/index/json_adapter.rb
module SmartAdapters
module Users
module Index
class JsonAdapter < SimpleDelegator
include SmartAdapters::Util::Adapters::Json::Default
def success(resource)
render json: resource, status: :ok
end
end
end
end
end
app/models/smart_adapters/users/show/html_adapter.rb
module SmartAdapters
module Users
module Show
class HtmlAdapter < SimpleDelegator
include SmartAdapters::Util::Adapters::Html::Default
def success(resource)
render 'objects/show', locals: { resource: resource }
end
def failure(resource)
redirect_back fallback_location: root_path, flash: { error: 'Resource not found' }
end
end
end
end
end
app/models/smart_adapters/users/show/json_adapter.rb
module SmartAdapters
module Users
module Show
class JsonAdapter < SimpleDelegator
include SmartAdapters::Util::Adapters::Json::Default
def success(resource)
render json: resource, status: :ok
end
def failure(resource)
render json: {}, status: :not_found
end
end
end
end
end
app/models/smart_adapters/users/update/html_adapter.rb
module SmartAdapters
module Users
module Update
class HtmlAdapter < SimpleDelegator
include SmartAdapters::Util::Adapters::Html::Default
def success(resource)
render 'objects/show', locals: { resource: resource }
end
def failure(resource)
unless resource.present?
return redirect_back fallback_location: root_path, flash: { error: 'Resource not found' }
end
redirect_back fallback_location: root_path, flash: { error: resource.errors }
end
end
end
end
end
app/models/smart_adapters/users/update/json_adapter.rb
module SmartAdapters
module Users
module Update
class JsonAdapter < SimpleDelegator
include SmartAdapters::Util::Adapters::Json::Default
def success(resource)
render json: resource, status: :ok
end
def failure(resource)
return render json: {}, status: :not_found unless resource.present?
render json: { errors: resource.errors }, status: :bad_request
end
end
end
end
end
TODO
- Add remaining formats: ~~
:text
~~,:ics
, ~~:csv
~~,:yaml
,:rss
,:atom
- Add generator