mapper.rb 26.8 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 539
          end

540 541
          def nested_path
            path
542 543
          end

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

551 552
          def shallow?
            false
553 554
          end

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

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

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

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

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

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

582 583 584 585 586
            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)
587 588 589
            end
          end

J
Joshua Peek 已提交
590
          self
591 592
        end

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

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

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

605 606 607 608 609
            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
610

611 612 613 614 615
            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)
616 617 618
            end
          end

J
Joshua Peek 已提交
619
          self
620 621
        end

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

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

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

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

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

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

654
        def nested
655 656
          unless resource_scope?
            raise ArgumentError, "can't use nested outside resource(s) scope"
657 658 659
          end

          with_scope_level(:nested) do
660 661 662 663 664 665 666 667 668 669 670 671
            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 }
672 673 674 675
            end
          end
        end

676
        def namespace(path)
677
          if resource_scope?
678 679 680 681 682 683
            nested { super }
          else
            super
          end
        end

684 685 686 687 688 689
        def shallow
          scope(:shallow => true) do
            yield
          end
        end

J
Joshua Peek 已提交
690
        def match(*args)
J
Joshua Peek 已提交
691
          options = args.extract_options!
692

693 694
          options[:anchor] = true unless options.key?(:anchor)

695
          if args.length > 1
696
            args.each { |path| match(path, options.dup) }
697 698 699
            return self
          end

700 701
          if [:collection, :member, :new].include?(options[:on])
            args.push(options)
702

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

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

718 719 720 721 722 723 724 725 726
          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
              return match(path, options)
            end
727 728 729 730
          end

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

733
          args.push(options)
J
Joshua Peek 已提交
734
          super
J
Joshua Peek 已提交
735 736
        end

737
        def root(options={})
738 739 740 741 742 743 744 745 746
          if @scope[:scope_level] == :resources
            with_scope_level(:collection) do
              scope(parent_resource.path, :name_prefix => parent_resource.collection_name) do
                super(options)
              end
            end
          else
            super(options)
          end
747 748
        end

749
        protected
750
          def parent_resource #:nodoc:
751 752 753
            @scope[:scope_level_resource]
          end

J
Joshua Peek 已提交
754
        private
755
          def apply_common_behavior_for(method, resources, options, &block)
756 757 758 759 760 761
            if resources.length > 1
              resources.each { |r| send(method, r, options, &block) }
              return true
            end

            if path_names = options.delete(:path_names)
762
              scope(:path_names => path_names) do
763 764 765 766 767
                send(method, resources.pop, options, &block)
              end
              return true
            end

768
            if resource_scope?
769 770 771 772 773 774 775 776 777
              nested do
                send(method, resources.pop, options, &block)
              end
              return true
            end

            false
          end

778 779 780 781
          def resource_scope?
            [:resource, :resources].include?(@scope[:scope_level])
          end

782
          def with_exclusive_scope
783
            begin
784 785
              old_name_prefix, old_path = @scope[:name_prefix], @scope[:path]
              @scope[:name_prefix], @scope[:path] = nil, nil
786 787 788

              yield
            ensure
789
              @scope[:name_prefix], @scope[:path] = old_name_prefix, old_path
790 791 792
            end
          end

793
          def with_scope_level(kind, resource = parent_resource)
J
Joshua Peek 已提交
794
            old, @scope[:scope_level] = @scope[:scope_level], kind
795
            old_resource, @scope[:scope_level_resource] = @scope[:scope_level_resource], resource
J
Joshua Peek 已提交
796 797 798
            yield
          ensure
            @scope[:scope_level] = old
799
            @scope[:scope_level_resource] = old_resource
J
Joshua Peek 已提交
800
          end
801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 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 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910

          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]
              when :collection
                "#{@scope[:path]}/#{action_path(action)}(.:format)"
              when :new
                "#{@scope[:path]}/#{action_path(:new)}/#{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

          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
            when :index
              "#{name_prefix}#{parent_resource.collection_name}"
            when :show
              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}"
            when :update, :create, :destroy
              nil
            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 已提交
911
      end
J
Joshua Peek 已提交
912

913 914 915 916
      include Base
      include HttpHelpers
      include Scoping
      include Resources
J
Joshua Peek 已提交
917 918
    end
  end
J
Joshua Peek 已提交
919
end