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 |
@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:
class HistoryController < ApplicationController
include Behaveable::ResourceFinder
include Behaveable::RouteExtractor
def history
# @behaveable ||= behaveable
# @behaveable ? @behaveable.versions : PaperTrail::Version
end
end
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi!
This is really cool and seems really useful for a bunch of things I am working on. After messing with it work a little while it looks like you forgot to add a '+' in your regex capture see below:
original:
[3] pry(#)> model = name.match(%r{([^\/.])_id$}) if name =~ /(.+)_id$/
=> #<MatchData "n_id" 1:"n">
with plus sign: %r{([^\/.]+)_id$}
[5] pry(#)> model = name.match(%r{([^\/.]+)_id$}) if name =~ /(.+)_id$/
=> #<MatchData "organization_id" 1:"organization">
Now I am just wondering what is the resource_url can use that will reflect the proper polymorphic reflection?
I can get it to work by using a routed url like:
form_for @project, url: organization_projects_path do |f|
but I must be doing something wrong because it seems like this should be something like resource_url ?
Thanks in advance for this cool post! Definitely like to make use of it.
Cheers,
Mark