rbngzlv
1/11/2017 - 2:50 PM

Medium : Rails : nested routes, polymorphic associations and controllers (https://medium.com/@loickartono/rails-nested-routes-polymorphic-as

Medium : Rails : nested routes, polymorphic associations and controllers (https://medium.com/@loickartono/rails-nested-routes-polymorphic-associations-and-controllers-8ade7249fa49)

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
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
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