lib/inherited_resources/polymorphic_helpers.rb



module InheritedResources

  # = polymorphic associations
  #
  # In some cases you have a resource that belongs to two different resources
  # but not at the same time. For example, let's suppose you have File, Message
  # and Task as resources and they are all commentable.
  #
  # Polymorphic associations allows you to create just one controller that will
  # deal with each case.
  #
  #   class Comment < InheritedResources::Base
  #     belongs_to :file, :message, :task, :polymorphic => true
  #   end
  #
  # Your routes should be something like:
  #
  #   m.resources :files,    :has_many => :comments #=> /files/13/comments
  #   m.resources :tasks,    :has_many => :comments #=> /tasks/17/comments
  #   m.resources :messages, :has_many => :comments #=> /messages/11/comments
  #
  # When using polymorphic associations, you get some free helpers:
  #
  #   parent?         #=> true
  #   parent_type     #=> :task
  #   parent_class    #=> Task
  #   parent          #=> @task
  #
  # This polymorphic controllers thing is a great idea by James Golick and he
  # built it in resource_controller. Here is just a re-implementation.
  #
  # = optional polymorphic associations
  #
  # Let's take another break from ProjectsController. Let's suppose we are
  # building a store, which sell products.
  #
  # On the website, we can show all products, but also products scoped to
  # categories, brands, users. In this case case, the association is optional, and
  # we deal with it in the following way:
  #
  #   class ProductsController < InheritedResources::Base
  #     belongs_to :category, :brand, :user, :polymorphic => true, :optional => true
  #   end
  #
  # This will handle all those urls properly:
  #
  #   /products/1
  #   /categories/2/products/5
  #   /brands/10/products/3
  #   /user/13/products/11
  #
  # = nested polymorphic associations
  #
  # You can have polymorphic associations with nested resources. Let's suppose
  # that our File, Task and Message resources in the previous example belongs to
  # a project.
  #
  # This way we can have:
  #
  #   class CommentsController < InheritedResources::Base
  #     belongs_to :project {
  #       belongs_to :file, :message, :task, :polymorphic => true
  #     }
  #   end
  #
  # Or:
  #
  #   class CommentsController < InheritedResources::Base
  #     nested_belongs_to :project
  #     nested_belongs_to :file, :message, :task, :polymorphic => true
  #   end
  #
  # Choose the syntax that makes more sense to you. :)
  #
  # Finally your routes should be something like:
  #
  #   map.resources :projects do |m|
  #     m.resources :files,    :has_many => :comments #=> /projects/1/files/13/comments
  #     m.resources :tasks,    :has_many => :comments #=> /projects/1/tasks/17/comments
  #     m.resources :messages, :has_many => :comments #=> /projects/1/messages/11/comments
  #   end
  #
  # The helpers work in the same way as above.
  #
  module PolymorphicHelpers

    protected

      # Returns the parent type. A Comments class can have :task, :file, :note
      # as parent types.
      #
      def parent_type
        @parent_type
      end

      def parent_class
        parent.class if @parent_type
      end

      # Returns the parent object. They are also available with the instance
      # variable name: @task, @file, @note...
      #
      def parent
        instance_variable_get("@#{@parent_type}") if @parent_type
      end

      # If the polymorphic association is optional, we might not have a parent.
      #
      def parent?
        if resources_configuration[:polymorphic][:optional]
          parents_symbols.size > 1 || !@parent_type.nil?
        else
          true
        end
      end

    private

      # Maps parents_symbols to build association chain.
      #
      # If the parents_symbols find :polymorphic, it goes through the
      # params keys to see which polymorphic parent matches the given params.
      #
      # When optional is given, it does not raise errors if the polymorphic
      # params are missing.
      #
      def symbols_for_association_chain #:nodoc:
        polymorphic_config = resources_configuration[:polymorphic]

        parents_symbols.map do |symbol|
          if symbol == :polymorphic
            params_keys = params.keys

            key = polymorphic_config[:symbols].find do |poly|
              params_keys.include? resources_configuration[poly][:param].to_s
            end

            if key.nil?
              raise ScriptError, "Could not find param for polymorphic association. The request" <<
                                 "parameters are #{params.keys.inspect} and the polymorphic " <<
                                 "associations are #{polymorphic_config[:symbols].inspect}." unless polymorphic_config[:optional]

              nil
            else
              @parent_type = key.to_sym
            end
          else
            symbol
          end
        end.compact
      end

  end
end