Skip to content
This repository
tree: c5807728d5
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

file 208 lines (188 sloc) 8.525 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
require 'action_controller/model_naming'

module ActionDispatch
  module Routing
    # Polymorphic URL helpers are methods for smart resolution to a named route call when
    # given an Active Record model instance. They are to be used in combination with
    # ActionController::Resources.
    #
    # These methods are useful when you want to generate correct URL or path to a RESTful
    # resource without having to know the exact type of the record in question.
    #
    # Nested resources and/or namespaces are also supported, as illustrated in the example:
    #
    # polymorphic_url([:admin, @article, @comment])
    #
    # results in:
    #
    # admin_article_comment_url(@article, @comment)
    #
    # == Usage within the framework
    #
    # Polymorphic URL helpers are used in a number of places throughout the \Rails framework:
    #
    # * <tt>url_for</tt>, so you can use it with a record as the argument, e.g.
    # <tt>url_for(@article)</tt>;
    # * ActionView::Helpers::FormHelper uses <tt>polymorphic_path</tt>, so you can write
    # <tt>form_for(@article)</tt> without having to specify <tt>:url</tt> parameter for the form
    # action;
    # * <tt>redirect_to</tt> (which, in fact, uses <tt>url_for</tt>) so you can write
    # <tt>redirect_to(post)</tt> in your controllers;
    # * ActionView::Helpers::AtomFeedHelper, so you don't have to explicitly specify URLs
    # for feed entries.
    #
    # == Prefixed polymorphic helpers
    #
    # In addition to <tt>polymorphic_url</tt> and <tt>polymorphic_path</tt> methods, a
    # number of prefixed helpers are available as a shorthand to <tt>:action => "..."</tt>
    # in options. Those are:
    #
    # * <tt>edit_polymorphic_url</tt>, <tt>edit_polymorphic_path</tt>
    # * <tt>new_polymorphic_url</tt>, <tt>new_polymorphic_path</tt>
    #
    # Example usage:
    #
    # edit_polymorphic_path(@post) # => "/posts/1/edit"
    # polymorphic_path(@post, :format => :pdf) # => "/posts/1.pdf"
    #
    # == Usage with mounted engines
    #
    # If you are using a mounted engine and you need to use a polymorphic_url
    # pointing at the engine's routes, pass in the engine's route proxy as the first
    # argument to the method. For example:
    #
    # polymorphic_url([blog, @post]) # calls blog.post_path(@post)
    # form_for([blog, @post]) # => "/blog/posts/1"
    #
    module PolymorphicRoutes
      include ActionController::ModelNaming

      # Constructs a call to a named RESTful route for the given record and returns the
      # resulting URL string. For example:
      #
      # # calls post_url(post)
      # polymorphic_url(post) # => "http://example.com/posts/1"
      # polymorphic_url([blog, post]) # => "http://example.com/blogs/1/posts/1"
      # polymorphic_url([:admin, blog, post]) # => "http://example.com/admin/blogs/1/posts/1"
      # polymorphic_url([user, :blog, post]) # => "http://example.com/users/1/blog/posts/1"
      # polymorphic_url(Comment) # => "http://example.com/comments"
      #
      # ==== Options
      #
      # * <tt>:action</tt> - Specifies the action prefix for the named route:
      # <tt>:new</tt> or <tt>:edit</tt>. Default is no prefix.
      # * <tt>:routing_type</tt> - Allowed values are <tt>:path</tt> or <tt>:url</tt>.
      # Default is <tt>:url</tt>.
      #
      # ==== Examples
      #
      # # an Article record
      # polymorphic_url(record) # same as article_url(record)
      #
      # # a Comment record
      # polymorphic_url(record) # same as comment_url(record)
      #
      # # it recognizes new records and maps to the collection
      # record = Comment.new
      # polymorphic_url(record) # same as comments_url()
      #
      # # the class of a record will also map to the collection
      # polymorphic_url(Comment) # same as comments_url()
      #
      def polymorphic_url(record_or_hash_or_array, options = {})
        if record_or_hash_or_array.kind_of?(Array)
          record_or_hash_or_array = record_or_hash_or_array.compact
          if record_or_hash_or_array.first.is_a?(ActionDispatch::Routing::RoutesProxy)
            proxy = record_or_hash_or_array.shift
          end
          record_or_hash_or_array = record_or_hash_or_array[0] if record_or_hash_or_array.size == 1
        end

        record = extract_record(record_or_hash_or_array)
        record = convert_to_model(record)

        args = Array === record_or_hash_or_array ?
          record_or_hash_or_array.dup :
          [ record_or_hash_or_array ]

        inflection = if options[:action] && options[:action].to_s == "new"
          args.pop
          :singular
        elsif (record.respond_to?(:persisted?) && !record.persisted?)
          args.pop
          :plural
        elsif record.is_a?(Class)
          args.pop
          :plural
        else
          :singular
        end

        args.delete_if {|arg| arg.is_a?(Symbol) || arg.is_a?(String)}
        named_route = build_named_route_call(record_or_hash_or_array, inflection, options)

        url_options = options.except(:action, :routing_type)
        unless url_options.empty?
          args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options
        end

        args.collect! { |a| convert_to_model(a) }

        (proxy || self).send(named_route, *args)
      end

      # Returns the path component of a URL for the given record. It uses
      # <tt>polymorphic_url</tt> with <tt>:routing_type => :path</tt>.
      def polymorphic_path(record_or_hash_or_array, options = {})
        polymorphic_url(record_or_hash_or_array, options.merge(:routing_type => :path))
      end

      %w(edit new).each do |action|
        module_eval <<-EOT, __FILE__, __LINE__ + 1
def #{action}_polymorphic_url(record_or_hash, options = {}) # def edit_polymorphic_url(record_or_hash, options = {})
polymorphic_url( # polymorphic_url(
record_or_hash, # record_or_hash,
options.merge(:action => "#{action}")) # options.merge(:action => "edit"))
end # end
#
def #{action}_polymorphic_path(record_or_hash, options = {}) # def edit_polymorphic_path(record_or_hash, options = {})
polymorphic_url( # polymorphic_url(
record_or_hash, # record_or_hash,
options.merge(:action => "#{action}", :routing_type => :path)) # options.merge(:action => "edit", :routing_type => :path))
end # end
EOT
      end

      private
        def action_prefix(options)
          options[:action] ? "#{options[:action]}_" : ''
        end

        def routing_type(options)
          options[:routing_type] || :url
        end

        def build_named_route_call(records, inflection, options = {})
          if records.is_a?(Array)
            record = records.pop
            route = records.map do |parent|
              if parent.is_a?(Symbol) || parent.is_a?(String)
                parent
              else
                model_name_from_record_or_class(parent).singular_route_key
              end
            end
          else
            record = extract_record(records)
            route = []
          end

          if record.is_a?(Symbol) || record.is_a?(String)
            route << record
          elsif record
            if inflection == :singular
              route << model_name_from_record_or_class(record).singular_route_key
            else
              route << model_name_from_record_or_class(record).route_key
            end
          else
            raise ArgumentError, "Nil location provided. Can't build URI."
          end

          route << routing_type(options)

          action_prefix(options) + route.join("_")
        end

        def extract_record(record_or_hash_or_array)
          case record_or_hash_or_array
            when Array; record_or_hash_or_array.last
            when Hash; record_or_hash_or_array[:id]
            else record_or_hash_or_array
          end
        end
    end
  end
end

Something went wrong with that request. Please try again.