mapper.rb 27.6 KB
Newer Older
1
require 'active_support/core_ext/hash/except'
2
require 'active_support/core_ext/object/blank'
3

J
Joshua Peek 已提交
4 5
module ActionDispatch
  module Routing
J
Joshua Peek 已提交
6
    class Mapper
7
      class Constraints #:nodoc:
8
        def self.new(app, constraints, request = Rack::Request)
9
          if constraints.any?
10
            super(app, constraints, request)
11 12 13 14 15
          else
            app
          end
        end

16 17
        def initialize(app, constraints, request)
          @app, @constraints, @request = app, constraints, request
18 19 20
        end

        def call(env)
21
          req = @request.new(env)
22 23 24

          @constraints.each { |constraint|
            if constraint.respond_to?(:matches?) && !constraint.matches?(req)
J
Joshua Peek 已提交
25
              return [ 404, {'X-Cascade' => 'pass'}, [] ]
26
            elsif constraint.respond_to?(:call) && !constraint.call(req)
J
Joshua Peek 已提交
27
              return [ 404, {'X-Cascade' => 'pass'}, [] ]
28 29 30 31 32 33 34
            end
          }

          @app.call(env)
        end
      end

35
      class Mapping #:nodoc:
36
        IGNORE_OPTIONS = [:to, :as, :controller, :action, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow]
37

38 39 40
        def initialize(set, scope, args)
          @set, @scope    = set, scope
          @path, @options = extract_path_and_options(args)
41
        end
J
Joshua Peek 已提交
42

43
        def to_route
44
          [ app, conditions, requirements, defaults, @options[:as], @options[:anchor] ]
45
        end
J
Joshua Peek 已提交
46

47 48
        private
          def extract_path_and_options(args)
49
            options = args.extract_options!
50

51
            if using_to_shorthand?(args, options)
52 53 54 55 56
              path, to = options.find { |name, value| name.is_a?(String) }
              options.merge!(:to => to).delete(path) if path
            else
              path = args.first
            end
J
Joshua Peek 已提交
57

58 59 60 61 62 63 64 65
            if @scope[:module] && options[:to]
              if options[:to].to_s.include?("#")
                options[:to] = "#{@scope[:module]}/#{options[:to]}"
              elsif @scope[:controller].nil?
                options[:to] = "#{@scope[:module]}##{options[:to]}"
              end
            end

66
            path = normalize_path(path)
67
            path_without_format = path.sub(/\(\.:format\)$/, '')
68

69 70 71
            if using_match_shorthand?(path_without_format, options)
              options[:to] ||= path_without_format[1..-1].sub(%r{/([^/]*)$}, '#\1')
              options[:as] ||= path_without_format[1..-1].gsub("/", "_")
72 73 74
            end

            [ path, options ]
75
          end
76

77 78 79 80
          # match "account" => "account#index"
          def using_to_shorthand?(args, options)
            args.empty? && options.present?
          end
81

82
          # match "account/overview"
83
          def using_match_shorthand?(path, options)
84
            path && options.except(:via, :anchor, :to, :as).empty? && path =~ %r{^/[\w\/]+$}
85
          end
86

87
          def normalize_path(path)
88 89
            raise ArgumentError, "path is required" if @scope[:path].blank? && path.blank?
            Mapper.normalize_path("#{@scope[:path]}/#{path}")
90
          end
91

92 93 94
          def app
            Constraints.new(
              to.respond_to?(:call) ? to : Routing::RouteSet::Dispatcher.new(:defaults => defaults),
95 96
              blocks,
              @set.request_class
97
            )
98 99
          end

100 101 102
          def conditions
            { :path_info => @path }.merge(constraints).merge(request_method_condition)
          end
J
Joshua Peek 已提交
103

104
          def requirements
105
            @requirements ||= (@options[:constraints] || {}).tap do |requirements|
106 107 108 109
              requirements.reverse_merge!(@scope[:constraints]) if @scope[:constraints]
              @options.each { |k, v| requirements[k] = v if v.is_a?(Regexp) }
            end
          end
110

111
          def defaults
112 113 114 115 116 117 118 119 120
            @defaults ||= (@options[:defaults] || {}).tap do |defaults|
              defaults.merge!(default_controller_and_action)
              defaults.reverse_merge!(@scope[:defaults]) if @scope[:defaults]
              @options.each { |k, v| defaults[k] = v unless v.is_a?(Regexp) || IGNORE_OPTIONS.include?(k.to_sym) }
            end
          end

          def default_controller_and_action
            if to.respond_to?(:call)
121 122 123 124 125 126 127
              { }
            else
              defaults = case to
              when String
                controller, action = to.split('#')
                { :controller => controller, :action => action }
              when Symbol
128
                { :action => to.to_s }
129
              else
130
                {}
131
              end
J
Joshua Peek 已提交
132

133 134 135 136
              defaults[:controller] ||= default_controller

              defaults.delete(:controller) if defaults[:controller].blank?
              defaults.delete(:action)     if defaults[:action].blank?
137

138 139 140
              if defaults[:controller].blank? && segment_keys.exclude?("controller")
                raise ArgumentError, "missing :controller"
              end
J
Joshua Peek 已提交
141

142 143 144
              if defaults[:action].blank? && segment_keys.exclude?("action")
                raise ArgumentError, "missing :action"
              end
J
Joshua Peek 已提交
145

146 147 148
              defaults
            end
          end
149

150 151 152 153 154 155
          def blocks
            if @options[:constraints].present? && !@options[:constraints].is_a?(Hash)
              block = @options[:constraints]
            else
              block = nil
            end
J
Joshua Peek 已提交
156 157

            ((@scope[:blocks] || []) + [ block ]).compact
158
          end
J
Joshua Peek 已提交
159

160 161 162
          def constraints
            @constraints ||= requirements.reject { |k, v| segment_keys.include?(k.to_s) || k == :controller }
          end
163

164 165 166 167 168 169
          def request_method_condition
            if via = @options[:via]
              via = Array(via).map { |m| m.to_s.upcase }
              { :request_method => Regexp.union(*via) }
            else
              { }
170
            end
171
          end
J
Joshua Peek 已提交
172

173 174
          def segment_keys
            @segment_keys ||= Rack::Mount::RegexpWithNamedGroups.new(
175 176
              Rack::Mount::Strexp.compile(@path, requirements, SEPARATORS)
            ).names
177
          end
178

179 180 181
          def to
            @options[:to]
          end
J
Joshua Peek 已提交
182

183
          def default_controller
184 185 186 187 188
            if @options[:controller]
              @options[:controller].to_s
            elsif @scope[:controller]
              @scope[:controller].to_s
            end
189
          end
190
      end
191

192
      # Invokes Rack::Mount::Utils.normalize path and ensure that
193 194
      # (:locale) becomes (/:locale) instead of /(:locale). Except
      # for root cases, where the latter is the correct one.
195 196
      def self.normalize_path(path)
        path = Rack::Mount::Utils.normalize_path(path)
197
        path.sub!(%r{/(\(+)/?:}, '\1/:') unless path =~ %r{^/\(+:.*\)$}
198 199 200
        path
      end

201
      module Base
202
        def initialize(set) #:nodoc:
203 204
          @set = set
        end
205

206 207 208
        def root(options = {})
          match '/', options.reverse_merge(:as => :root)
        end
209

210
        def match(*args)
211 212
          mapping = Mapping.new(@set, @scope, args).to_route
          @set.add_route(*mapping)
213 214
          self
        end
215

216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
        def mount(app, options = nil)
          if options
            path = options.delete(:at)
          else
            options = app
            app, path = options.find { |k, v| k.respond_to?(:call) }
            options.delete(app) if app
          end

          raise "A rack application must be specified" unless path

          match(path, options.merge(:to => app, :anchor => false))
          self
        end

231 232 233 234
        def default_url_options=(options)
          @set.default_url_options = options
        end
        alias_method :default_url_options, :default_url_options=
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
      end

      module HttpHelpers
        def get(*args, &block)
          map_method(:get, *args, &block)
        end

        def post(*args, &block)
          map_method(:post, *args, &block)
        end

        def put(*args, &block)
          map_method(:put, *args, &block)
        end

        def delete(*args, &block)
          map_method(:delete, *args, &block)
        end

254 255 256
        def redirect(*args, &block)
          options = args.last.is_a?(Hash) ? args.pop : {}

257 258 259
          path      = args.shift || block
          path_proc = path.is_a?(Proc) ? path : proc { |params| path % params }
          status    = options[:status] || 301
260
          body      = 'Moved Permanently'
261 262

          lambda do |env|
263
            req = Request.new(env)
264 265 266 267 268

            params = [req.symbolized_path_parameters]
            params << req if path_proc.arity > 1

            uri = URI.parse(path_proc.call(*params))
269 270
            uri.scheme ||= req.scheme
            uri.host   ||= req.host
271
            uri.port   ||= req.port unless req.port == 80
272 273 274 275 276 277 278

            headers = {
              'Location' => uri.to_s,
              'Content-Type' => 'text/html',
              'Content-Length' => body.length.to_s
            }
            [ status, headers, [body] ]
279
          end
280 281 282 283 284 285 286 287 288 289 290 291 292
        end

        private
          def map_method(method, *args, &block)
            options = args.extract_options!
            options[:via] = method
            args.push(options)
            match(*args, &block)
            self
          end
      end

      module Scoping
293
        def initialize(*args) #:nodoc:
294 295 296 297 298 299
          @scope = {}
          super
        end

        def scope(*args)
          options = args.extract_options!
300
          options = options.dup
301 302 303 304 305 306 307 308

          case args.first
          when String
            options[:path] = args.first
          when Symbol
            options[:controller] = args.first
          end

309
          recover = {}
310

311 312 313
          options[:constraints] ||= {}
          unless options[:constraints].is_a?(Hash)
            block, options[:constraints] = options[:constraints], {}
314
          end
315

316 317 318 319 320
          scope_options.each do |option|
            if value = options.delete(option)
              recover[option] = @scope[option]
              @scope[option]  = send("merge_#{option}_scope", @scope[option], value)
            end
321 322
          end

323 324
          recover[:block] = @scope[:blocks]
          @scope[:blocks] = merge_blocks_scope(@scope[:blocks], block)
325

326 327
          recover[:options] = @scope[:options]
          @scope[:options]  = merge_options_scope(@scope[:options], options)
328 329 330 331

          yield
          self
        ensure
332 333 334 335 336 337
          scope_options.each do |option|
            @scope[option] = recover[option] if recover.has_key?(option)
          end

          @scope[:options] = recover[:options]
          @scope[:blocks]  = recover[:block]
338 339 340 341 342 343
        end

        def controller(controller)
          scope(controller.to_sym) { yield }
        end

344 345 346
        def namespace(path)
          path = path.to_s
          scope(:path => path, :name_prefix => path, :module => path) { yield }
347 348 349 350 351 352
        end

        def constraints(constraints = {})
          scope(:constraints => constraints) { yield }
        end

353 354 355 356
        def defaults(defaults = {})
          scope(:defaults => defaults) { yield }
        end

357 358 359 360 361
        def match(*args)
          options = args.extract_options!

          options = (@scope[:options] || {}).merge(options)

362 363
          if @scope[:name_prefix] && !options[:as].blank?
            options[:as] = "#{@scope[:name_prefix]}_#{options[:as]}"
364
          elsif @scope[:name_prefix] && options[:as] == ""
365
            options[:as] = @scope[:name_prefix].to_s
366 367 368 369 370
          end

          args.push(options)
          super(*args)
        end
371 372 373 374 375 376 377

        private
          def scope_options
            @scope_options ||= private_methods.grep(/^merge_(.+)_scope$/) { $1.to_sym }
          end

          def merge_path_scope(parent, child)
378
            Mapper.normalize_path("#{parent}/#{child}")
379 380 381
          end

          def merge_name_prefix_scope(parent, child)
382
            parent ? "#{parent}_#{child}" : child
383 384
          end

385
          def merge_module_scope(parent, child)
386 387 388 389
            parent ? "#{parent}/#{child}" : child
          end

          def merge_controller_scope(parent, child)
390
            @scope[:module] ? "#{@scope[:module]}/#{child}" : child
391 392
          end

393
          def merge_path_names_scope(parent, child)
394 395 396 397 398 399 400
            merge_options_scope(parent, child)
          end

          def merge_constraints_scope(parent, child)
            merge_options_scope(parent, child)
          end

401 402 403 404
          def merge_defaults_scope(parent, child)
            merge_options_scope(parent, child)
          end

405 406 407 408 409 410 411
          def merge_blocks_scope(parent, child)
            (parent || []) + [child]
          end

          def merge_options_scope(parent, child)
            (parent || {}).merge(child)
          end
412 413 414 415

          def merge_shallow_scope(parent, child)
            child ? true : false
          end
416 417
      end

J
Joshua Peek 已提交
418
      module Resources
419
        class Resource #:nodoc:
420 421 422 423
          def self.default_actions
            [:index, :create, :new, :show, :update, :destroy, :edit]
          end

424
          attr_reader :controller, :path, :options
425 426

          def initialize(entities, options = {})
427 428 429 430
            @name       = entities.to_s
            @path       = options.delete(:path) || @name
            @controller = options.delete(:controller) || @name.to_s.pluralize
            @options    = options
431 432
          end

433 434 435 436 437 438
          def default_actions
            self.class.default_actions
          end

          def actions
            if only = options[:only]
439
              Array(only).map(&:to_sym)
440
            elsif except = options[:except]
441
              default_actions - Array(except).map(&:to_sym)
442 443 444 445 446
            else
              default_actions
            end
          end

447
          def name
448
            options[:as] || @name
449 450
          end

451 452 453 454 455 456
          def plural
            name.to_s.pluralize
          end

          def singular
            name.to_s.singularize
457 458 459
          end

          def member_name
460
            singular
461 462
          end

463 464
          alias_method :nested_name, :member_name

465
          # Checks for uncountable plurals, and appends "_index" if they're.
466
          def collection_name
467
            singular == plural ? "#{plural}_index" : plural
468 469
          end

470 471
          def shallow?
            options[:shallow] ? true : false
472
          end
473 474 475 476 477 478 479 480 481 482 483 484 485 486

          def constraints
            options[:constraints] || {}
          end

          def id_constraint?
            options[:id] && options[:id].is_a?(Regexp) || constraints[:id] && constraints[:id].is_a?(Regexp)
          end

          def id_constraint
            options[:id] || constraints[:id]
          end

          def collection_options
487 488 489 490
            (options || {}).dup.tap do |opts|
              opts.delete(:id)
              opts[:constraints] = options[:constraints].dup if options[:constraints]
              opts[:constraints].delete(:id) if options[:constraints].is_a?(Hash)
491 492 493
            end
          end

494 495
          def nested_path
            "#{path}/:#{singular}_id"
496 497
          end

498
          def nested_options
499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523
            {}.tap do |opts|
              opts[:name_prefix] = member_name
              opts["#{singular}_id".to_sym] = id_constraint if id_constraint?
              opts[:options] = { :shallow => shallow? } unless options[:shallow].nil?
            end
          end

          def resource_scope
            [{ :controller => controller }]
          end

          def collection_scope
            [path, collection_options]
          end

          def member_scope
            ["#{path}/:id", options]
          end

          def new_scope
            [path]
          end

          def nested_scope
            [nested_path, nested_options]
524
          end
525 526 527
        end

        class SingletonResource < Resource #:nodoc:
528 529 530 531
          def self.default_actions
            [:show, :create, :update, :destroy, :new, :edit]
          end

532
          def initialize(entity, options = {})
533
            super
534 535
          end

536 537
          def member_name
            name
538
          end
539
          alias_method :collection_name, :member_name
540

541 542
          def nested_path
            path
543 544
          end

545 546 547 548 549
          def nested_options
            {}.tap do |opts|
              opts[:name_prefix] = member_name
              opts[:options] = { :shallow => shallow? } unless @options[:shallow].nil?
            end
550
          end
551

552 553
          def shallow?
            false
554 555
          end

556 557
          def member_scope
            [path, options]
558
          end
559 560
        end

561
        def initialize(*args) #:nodoc:
562
          super
563
          @scope[:path_names] = @set.resources_path_names
564 565
        end

J
Joshua Peek 已提交
566
        def resource(*resources, &block)
J
Joshua Peek 已提交
567
          options = resources.extract_options!
568
          options = (@scope[:options] || {}).merge(options)
569
          options[:shallow] = true if @scope[:shallow] && !options.has_key?(:shallow)
J
Joshua Peek 已提交
570

571
          if apply_common_behavior_for(:resource, resources, options, &block)
572 573 574
            return self
          end

575 576
          resource_scope(SingletonResource.new(resources.pop, options)) do
            yield if block_given?
577

578 579 580 581
            collection_scope do
              post :create if parent_resource.actions.include?(:create)
              get  :new if parent_resource.actions.include?(:new)
            end
582

583 584 585 586 587
            member_scope  do
              get    :show if parent_resource.actions.include?(:show)
              put    :update if parent_resource.actions.include?(:update)
              delete :destroy if parent_resource.actions.include?(:destroy)
              get    :edit if parent_resource.actions.include?(:edit)
588 589 590
            end
          end

J
Joshua Peek 已提交
591
          self
592 593
        end

J
Joshua Peek 已提交
594
        def resources(*resources, &block)
J
Joshua Peek 已提交
595
          options = resources.extract_options!
596
          options = (@scope[:options] || {}).merge(options)
597
          options[:shallow] = true if @scope[:shallow] && !options.has_key?(:shallow)
598

599
          if apply_common_behavior_for(:resources, resources, options, &block)
600 601 602
            return self
          end

603 604
          resource_scope(Resource.new(resources.pop, options)) do
            yield if block_given?
J
Joshua Peek 已提交
605

606 607 608 609 610
            collection_scope do
              get  :index if parent_resource.actions.include?(:index)
              post :create if parent_resource.actions.include?(:create)
              get  :new if parent_resource.actions.include?(:new)
            end
611

612 613 614 615 616
            member_scope  do
              get    :show if parent_resource.actions.include?(:show)
              put    :update if parent_resource.actions.include?(:update)
              delete :destroy if parent_resource.actions.include?(:destroy)
              get    :edit if parent_resource.actions.include?(:edit)
617 618 619
            end
          end

J
Joshua Peek 已提交
620
          self
621 622
        end

J
Joshua Peek 已提交
623 624 625
        def collection
          unless @scope[:scope_level] == :resources
            raise ArgumentError, "can't use collection outside resources scope"
626 627
          end

628 629
          collection_scope do
            yield
J
Joshua Peek 已提交
630
          end
631
        end
J
Joshua Peek 已提交
632

J
Joshua Peek 已提交
633
        def member
634 635
          unless resource_scope?
            raise ArgumentError, "can't use member outside resource(s) scope"
J
Joshua Peek 已提交
636
          end
J
Joshua Peek 已提交
637

638 639
          member_scope do
            yield
640 641 642 643 644 645 646
          end
        end

        def new
          unless resource_scope?
            raise ArgumentError, "can't use new outside resource(s) scope"
          end
647

648
          with_scope_level(:new) do
649
            scope(*parent_resource.new_scope) do
650 651 652
              scope(action_path(:new)) do
                yield
              end
J
Joshua Peek 已提交
653 654
            end
          end
J
Joshua Peek 已提交
655 656
        end

657
        def nested
658 659
          unless resource_scope?
            raise ArgumentError, "can't use nested outside resource(s) scope"
660 661 662
          end

          with_scope_level(:nested) do
663 664 665 666 667 668 669 670 671 672 673 674
            if parent_resource.shallow?
              with_exclusive_scope do
                if @scope[:module].blank?
                  scope(*parent_resource.nested_scope) { yield }
                else
                  scope(@scope[:module], :name_prefix => @scope[:module].tr('/', '_')) do
                    scope(*parent_resource.nested_scope) { yield }
                  end
                end
              end
            else
              scope(*parent_resource.nested_scope) { yield }
675 676 677 678
            end
          end
        end

679
        def namespace(path)
680
          if resource_scope?
681 682 683 684 685 686
            nested { super }
          else
            super
          end
        end

687 688 689 690 691 692
        def shallow
          scope(:shallow => true) do
            yield
          end
        end

J
Joshua Peek 已提交
693
        def match(*args)
J
Joshua Peek 已提交
694
          options = args.extract_options!
695

696 697
          options[:anchor] = true unless options.key?(:anchor)

698
          if args.length > 1
699
            args.each { |path| match(path, options.dup) }
700 701 702
            return self
          end

703 704
          if [:collection, :member, :new].include?(options[:on])
            args.push(options)
705

706 707 708 709 710 711 712
            case options.delete(:on)
            when :collection
              return collection { match(*args) }
            when :member
              return member { match(*args) }
            when :new
              return new { match(*args) }
713 714 715
            end
          end

716 717
          if @scope[:scope_level] == :resource
            args.push(options)
J
Joshua Peek 已提交
718 719
            return member { match(*args) }
          end
J
Joshua Peek 已提交
720

721 722 723 724 725 726 727
          path_names = options.delete(:path_names)

          if args.first.is_a?(Symbol)
            path = path_for_action(args.first, path_names)
            options = options_for_action(args.first, options)

            with_exclusive_scope do
728 729 730 731 732 733 734 735 736 737 738
              return super(path, options)
            end
          elsif resource_method_scope?
            path = path_for_custom_action
            options[:as] = name_for_action(options[:as]) if options[:as]
            args.push(options)

            with_exclusive_scope do
              scope(path) do
                return super
              end
739
            end
740 741 742 743
          end

          if resource_scope?
            raise ArgumentError, "can't define route directly in resource(s) scope"
J
Joshua Peek 已提交
744
          end
J
Joshua Peek 已提交
745

746
          args.push(options)
J
Joshua Peek 已提交
747
          super
J
Joshua Peek 已提交
748 749
        end

750
        def root(options={})
751
          if @scope[:scope_level] == :resources
752
            with_scope_level(:nested) do
753 754 755 756 757 758 759
              scope(parent_resource.path, :name_prefix => parent_resource.collection_name) do
                super(options)
              end
            end
          else
            super(options)
          end
760 761
        end

762
        protected
763
          def parent_resource #:nodoc:
764 765 766
            @scope[:scope_level_resource]
          end

J
Joshua Peek 已提交
767
        private
768
          def apply_common_behavior_for(method, resources, options, &block)
769 770 771 772 773 774
            if resources.length > 1
              resources.each { |r| send(method, r, options, &block) }
              return true
            end

            if path_names = options.delete(:path_names)
775
              scope(:path_names => path_names) do
776 777 778 779 780
                send(method, resources.pop, options, &block)
              end
              return true
            end

781
            if resource_scope?
782 783 784 785 786 787 788 789 790
              nested do
                send(method, resources.pop, options, &block)
              end
              return true
            end

            false
          end

791 792 793 794
          def resource_scope?
            [:resource, :resources].include?(@scope[:scope_level])
          end

795 796 797 798
          def resource_method_scope?
            [:collection, :member, :new].include?(@scope[:scope_level])
          end

799
          def with_exclusive_scope
800
            begin
801 802
              old_name_prefix, old_path = @scope[:name_prefix], @scope[:path]
              @scope[:name_prefix], @scope[:path] = nil, nil
803

804 805 806
              with_scope_level(:exclusive) do
                yield
              end
807
            ensure
808
              @scope[:name_prefix], @scope[:path] = old_name_prefix, old_path
809 810 811
            end
          end

812
          def with_scope_level(kind, resource = parent_resource)
J
Joshua Peek 已提交
813
            old, @scope[:scope_level] = @scope[:scope_level], kind
814
            old_resource, @scope[:scope_level_resource] = @scope[:scope_level_resource], resource
J
Joshua Peek 已提交
815 816 817
            yield
          ensure
            @scope[:scope_level] = old
818
            @scope[:scope_level_resource] = old_resource
J
Joshua Peek 已提交
819
          end
820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864

          def resource_scope(resource)
            with_scope_level(resource.is_a?(SingletonResource) ? :resource : :resources, resource) do
              scope(*parent_resource.resource_scope) do
                yield
              end
            end
          end

          def collection_scope
            with_scope_level(:collection) do
              scope(*parent_resource.collection_scope) do
                yield
              end
            end
          end

          def member_scope
            with_scope_level(:member) do
              scope(*parent_resource.member_scope) do
                yield
              end
            end
          end

          def path_for_action(action, path_names)
            case action
            when :index, :create
              "#{@scope[:path]}(.:format)"
            when :show, :update, :destroy
              if parent_resource.shallow?
                "#{@scope[:module]}/#{parent_resource.path}/:id(.:format)"
              else
                "#{@scope[:path]}(.:format)"
              end
            when :new
              "#{@scope[:path]}/#{action_path(:new)}(.:format)"
            when :edit
              if parent_resource.shallow?
                "#{@scope[:module]}/#{parent_resource.path}/:id/#{action_path(:edit)}(.:format)"
              else
                "#{@scope[:path]}/#{action_path(:edit)}(.:format)"
              end
            else
              case @scope[:scope_level]
865
              when :collection, :new
866 867 868 869 870 871 872 873 874 875 876
                "#{@scope[:path]}/#{action_path(action)}(.:format)"
              else
                if parent_resource.shallow?
                  "#{@scope[:module]}/#{parent_resource.path}/:id/#{action_path(action)}(.:format)"
                else
                  "#{@scope[:path]}/#{action_path(action)}(.:format)"
                end
              end
            end
          end

877 878 879 880 881 882 883 884 885 886 887 888 889
          def path_for_custom_action
            case @scope[:scope_level]
            when :collection, :new
              @scope[:path]
            else
              if parent_resource.shallow?
                "#{@scope[:module]}/#{parent_resource.path}/:id"
              else
                @scope[:path]
              end
            end
          end

890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906
          def action_path(name, path_names = nil)
            path_names ||= @scope[:path_names]
            path_names[name.to_sym] || name.to_s
          end

          def options_for_action(action, options)
            options.reverse_merge(
              :to => action,
              :as => name_for_action(action)
            )
          end

          def name_for_action(action)
            name_prefix = @scope[:name_prefix].blank? ? "" : "#{@scope[:name_prefix]}_"
            shallow_prefix = @scope[:module].blank? ? "" : "#{@scope[:module].tr('/', '_')}_"

            case action
907
            when :index, :create
908
              "#{name_prefix}#{parent_resource.collection_name}"
909
            when :show, :update, :destroy
910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938
              if parent_resource.shallow?
                "#{shallow_prefix}#{parent_resource.member_name}"
              else
                "#{name_prefix}#{parent_resource.member_name}"
              end
            when :edit
              if parent_resource.shallow?
                "edit_#{shallow_prefix}#{parent_resource.member_name}"
              else
                "edit_#{name_prefix}#{parent_resource.member_name}"
              end
            when :new
              "new_#{name_prefix}#{parent_resource.member_name}"
            else
              case @scope[:scope_level]
              when :collection
                "#{action}_#{name_prefix}#{parent_resource.collection_name}"
              when :new
                "#{action}_new_#{name_prefix}#{parent_resource.member_name}"
              else
                if parent_resource.shallow?
                  "#{action}_#{shallow_prefix}#{parent_resource.member_name}"
                else
                  "#{action}_#{name_prefix}#{parent_resource.member_name}"
                end
              end
            end
          end

J
Joshua Peek 已提交
939
      end
J
Joshua Peek 已提交
940

941 942 943 944
      include Base
      include HttpHelpers
      include Scoping
      include Resources
J
Joshua Peek 已提交
945 946
    end
  end
J
Joshua Peek 已提交
947
end