Last active
October 24, 2024 09:50
-
-
Save softmonkeyjapan/6143ff56b25df2d6fd1c to your computer and use it in GitHub Desktop.
Medium : Rails : nested routes, polymorphic associations and controllers (https://medium.com/@loickartono/rails-nested-routes-polymorphic-associations-and-controllers-8ade7249fa49)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class CategoriesController < ApplicationController | |
include Behaveable::ResourceFinder | |
include Behaveable::RouteExtractor | |
# Response type. | |
respond_to :json | |
# Get categories. | |
# | |
# GET (/:categorizable/:categorizable_id)/categories(.:format) | |
# | |
# ==== Returns | |
# * <tt>Response</tt> - JSON serialized categories. | |
def index | |
categories = categorizable.all | |
respond_with categories, status: :ok, location: extract(@behaveable) | |
end | |
# Get a category. | |
# | |
# GET (/:categorizable/:categorizable_id)/categories/:id(.:format) | |
# | |
# ==== Returns | |
# * <tt>Response</tt> - JSON serialized category. | |
def show | |
category = categorizable.find(params[:id]) | |
respond_with category, status: :ok, location: extract(@behaveable, category) | |
end | |
# Create a category. | |
# | |
# POST (/:categorizable/:categorizable_id)/categories(.:format) | |
# | |
# ==== Returns | |
# * <tt>Response</tt> - JSON serialized category or errors if any. | |
def create | |
category = categorizable.new(category_params) | |
respond_to do |format| | |
category.transaction do | |
if category.save | |
categorizable << category if @behaveable | |
format.json { render json: category, status: :created } | |
else | |
format.json { render errors_for(category) } | |
end | |
end | |
end | |
end | |
# Update a category. | |
# | |
# PATCH (/:categorizable/:categorizable_id)/categories/:id(.:format) | |
# | |
# ==== Returns | |
# * <tt>Response</tt> - JSON serialized category or errors if any. | |
def update | |
category = categorizable.find(params[:id]) | |
respond_to do |format| | |
if category.update(category_params) | |
format.json { render json: category, status: :ok } | |
else | |
format.json { render errors_for(category) } | |
end | |
end | |
end | |
# Delete a category. | |
# | |
# DELETE (/:categorizable/:categorizable_id)/categories/:id(.:format) | |
# | |
# ==== Returns | |
# * <tt>Response</tt> - 204 no content. | |
def destroy | |
category = categorizable.find(params[:id]) | |
category.destroy if category | |
respond_to do |format| | |
format.json { head :no_content } | |
end | |
end | |
private | |
# Get Category context object. | |
# | |
# ==== Returns | |
# * <tt>ActiveRecord</tt> - Categorizable's categories or Category. | |
def categorizable | |
@behaveable ||= behaveable | |
@behaveable ? @behaveable.categories : Category | |
end | |
# ActiveRecord object errors. | |
# TODO: Should be placed at ApplicationController level ??. | |
# | |
# ==== Parameters | |
# * <tt>object</tt> - ActiveRecord object. | |
# | |
# ==== Returns | |
# * <tt>Hash</tt> - Hash containing object errors if any. | |
def errors_for(object) | |
{ json: { errors: object.errors }, status: :unprocessable_entity } | |
end | |
# Sanitize request data. | |
# | |
# ==== Returns | |
# * <tt>Hash</tt> - Sanitized request params. | |
def category_params | |
params.require(:category).permit(:name) | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Behaveable | |
module ResourceFinder | |
# Get the behaveable object. | |
# | |
# ==== Returns | |
# * <tt>ActiveRecord::Model</tt> - Behaveable instance object. | |
def behaveable | |
klass, param = behaveable_class | |
klass.find(params[param.to_sym]) if klass | |
end | |
private | |
# Lookup behaveable class. | |
# | |
# ==== Returns | |
# * <tt>Response</tt> - Behaveable class object or nil if not found. | |
def behaveable_class | |
params.each do |name, _value| | |
if name =~ /(.+)_id$/ | |
model = name.match(%r{([^\/.]*)_id$}) | |
return model[1].classify.constantize, name | |
end | |
end | |
nil | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Behaveable | |
module RouteExtractor | |
# Generate url location. | |
# | |
# ==== Parameters | |
# * <tt>behaveable</tt> - Behaveable object. | |
# * <tt>resource</tt> - Resource object. (member routes). | |
# | |
# ==== Returns | |
# * <tt>Route</tt> - Url location. | |
def extract(behaveable = nil, resource = nil) | |
resource_name = resource_name_from(params) | |
behaveable_name = behaveable_name_from(behaveable) | |
location_url = "#{resource_name}_url" | |
return regular(location_url, resource) unless behaveable | |
location_url = "#{behaveable_name}_#{resource_name}_url" | |
nested(location_url, behaveable, resource) | |
end | |
private | |
# Handle non-nested url location. | |
# | |
# ==== Parameters | |
# * <tt>location_url</tt> - Url route as string. | |
# * <tt>resource</tt> - Resource object. | |
# | |
# ==== Returns | |
# * <tt>Route</tt> - Url location. | |
def regular(location_url, resource) | |
return send(location_url) unless resource | |
send(location_url, resource) | |
end | |
# Handle nested url location. | |
# | |
# ==== Parameters | |
# * <tt>location_url</tt> - Url route as string. | |
# * <tt>behaveable</tt> - Behaveable object. | |
# * <tt>resource</tt> - Resource object. | |
# | |
# ==== Returns | |
# * <tt>Route</tt> - Url location. | |
def nested(location_url, behaveable, resource) | |
return send(location_url, behaveable) unless resource | |
send(location_url, behaveable, resource) | |
end | |
# Get resource name from params. | |
# | |
# ==== Parameters | |
# * <tt>params</tt> - ApplicationController's params. | |
# | |
# ==== Returns | |
# * <tt>String</tt> - Resource name (singular or plural). | |
def resource_name_from(params) | |
inflection = params[:id].present? ? 'singular' : 'plural' | |
params[:controller].split('/').last.send("#{inflection}ize") | |
end | |
# Get behaveable class name. | |
# | |
# ==== Parameters | |
# * <tt>behaveable</tt> - Behaveable object. | |
# | |
# ==== Returns | |
# * <tt>String</tt> - Behaveable class snake case name or nil. | |
def behaveable_name_from(behaveable) | |
return unless behaveable | |
behaveable.class.name.underscore | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@softmonkeyjapan I get this error in my History Controller when implementing your example (swapping category for history)
Unable to autoload constant Behaveable::ResourceFinder, expected /Users/cj/RubymineProjects/ad_rfp/app/controllers/concerns/behaveable/resource_finder.rb to define it
here is the history controller: