mapper.rb 28.0 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, :shallow_path, :shallow_prefix]
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].is_a?(Hash) ? @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
        def namespace(path)
          path = path.to_s
346
          scope(:path => path, :name_prefix => path, :module => path, :shallow_path => path, :shallow_prefix => 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
          end

381 382 383 384
          def merge_shallow_path_scope(parent, child)
            Mapper.normalize_path("#{parent}/#{child}")
          end

385
          def merge_name_prefix_scope(parent, child)
386
            parent ? "#{parent}_#{child}" : child
387 388
          end

389 390 391 392
          def merge_shallow_prefix_scope(parent, child)
            parent ? "#{parent}_#{child}" : child
          end

393
          def merge_module_scope(parent, child)
394 395 396 397
            parent ? "#{parent}/#{child}" : child
          end

          def merge_controller_scope(parent, child)
398
            @scope[:module] ? "#{@scope[:module]}/#{child}" : child
399 400
          end

401
          def merge_path_names_scope(parent, child)
402 403 404 405 406 407 408
            merge_options_scope(parent, child)
          end

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

409 410 411 412
          def merge_defaults_scope(parent, child)
            merge_options_scope(parent, child)
          end

413 414 415 416 417 418 419
          def merge_blocks_scope(parent, child)
            (parent || []) + [child]
          end

          def merge_options_scope(parent, child)
            (parent || {}).merge(child)
          end
420 421 422 423

          def merge_shallow_scope(parent, child)
            child ? true : false
          end
424 425
      end

J
Joshua Peek 已提交
426
      module Resources
427
        class Resource #:nodoc:
428 429 430 431
          def self.default_actions
            [:index, :create, :new, :show, :update, :destroy, :edit]
          end

432
          attr_reader :controller, :path, :options
433 434

          def initialize(entities, options = {})
435 436 437 438
            @name       = entities.to_s
            @path       = options.delete(:path) || @name
            @controller = options.delete(:controller) || @name.to_s.pluralize
            @options    = options
439 440
          end

441 442 443 444 445 446
          def default_actions
            self.class.default_actions
          end

          def actions
            if only = options[:only]
447
              Array(only).map(&:to_sym)
448
            elsif except = options[:except]
449
              default_actions - Array(except).map(&:to_sym)
450 451 452 453 454
            else
              default_actions
            end
          end

455
          def name
456
            options[:as] || @name
457 458
          end

459 460 461 462 463 464
          def plural
            name.to_s.pluralize
          end

          def singular
            name.to_s.singularize
465 466 467
          end

          def member_name
468
            singular
469 470
          end

471 472
          alias_method :nested_name, :member_name

473
          # Checks for uncountable plurals, and appends "_index" if they're.
474
          def collection_name
475
            singular == plural ? "#{plural}_index" : plural
476 477
          end

478 479
          def shallow?
            options[:shallow] ? true : false
480
          end
481 482 483 484 485 486 487 488 489 490 491 492 493 494

          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
495 496 497 498
            (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)
499 500 501
            end
          end

502 503
          def nested_path
            "#{path}/:#{singular}_id"
504 505
          end

506
          def nested_options
507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531
            {}.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]
532
          end
533 534 535
        end

        class SingletonResource < Resource #:nodoc:
536 537 538 539
          def self.default_actions
            [:show, :create, :update, :destroy, :new, :edit]
          end

540
          def initialize(entity, options = {})
541
            super
542 543
          end

544 545
          def member_name
            name
546
          end
547
          alias_method :collection_name, :member_name
548

549 550
          def nested_path
            path
551 552
          end

553 554 555 556 557
          def nested_options
            {}.tap do |opts|
              opts[:name_prefix] = member_name
              opts[:options] = { :shallow => shallow? } unless @options[:shallow].nil?
            end
558
          end
559

560 561
          def shallow?
            false
562 563
          end

564 565
          def member_scope
            [path, options]
566
          end
567 568
        end

569
        def initialize(*args) #:nodoc:
570
          super
571
          @scope[:path_names] = @set.resources_path_names
572 573
        end

J
Joshua Peek 已提交
574
        def resource(*resources, &block)
J
Joshua Peek 已提交
575
          options = resources.extract_options!
576
          options = (@scope[:options] || {}).merge(options)
577
          options[:shallow] = true if @scope[:shallow] && !options.has_key?(:shallow)
J
Joshua Peek 已提交
578

579
          if apply_common_behavior_for(:resource, resources, options, &block)
580 581 582
            return self
          end

583 584
          resource_scope(SingletonResource.new(resources.pop, options)) do
            yield if block_given?
585

586 587 588 589
            collection_scope do
              post :create if parent_resource.actions.include?(:create)
              get  :new if parent_resource.actions.include?(:new)
            end
590

591 592 593 594 595
            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)
596 597 598
            end
          end

J
Joshua Peek 已提交
599
          self
600 601
        end

J
Joshua Peek 已提交
602
        def resources(*resources, &block)
J
Joshua Peek 已提交
603
          options = resources.extract_options!
604
          options = (@scope[:options] || {}).merge(options)
605
          options[:shallow] = true if @scope[:shallow] && !options.has_key?(:shallow)
606

607
          if apply_common_behavior_for(:resources, resources, options, &block)
608 609 610
            return self
          end

611 612
          resource_scope(Resource.new(resources.pop, options)) do
            yield if block_given?
J
Joshua Peek 已提交
613

614 615 616 617 618
            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
619

620 621 622 623 624
            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)
625 626 627
            end
          end

J
Joshua Peek 已提交
628
          self
629 630
        end

J
Joshua Peek 已提交
631 632 633
        def collection
          unless @scope[:scope_level] == :resources
            raise ArgumentError, "can't use collection outside resources scope"
634 635
          end

636 637
          collection_scope do
            yield
J
Joshua Peek 已提交
638
          end
639
        end
J
Joshua Peek 已提交
640

J
Joshua Peek 已提交
641
        def member
642 643
          unless resource_scope?
            raise ArgumentError, "can't use member outside resource(s) scope"
J
Joshua Peek 已提交
644
          end
J
Joshua Peek 已提交
645

646 647
          member_scope do
            yield
648 649 650 651 652 653 654
          end
        end

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

656
          with_scope_level(:new) do
657
            scope(*parent_resource.new_scope) do
658 659 660
              scope(action_path(:new)) do
                yield
              end
J
Joshua Peek 已提交
661 662
            end
          end
J
Joshua Peek 已提交
663 664
        end

665
        def nested
666 667
          unless resource_scope?
            raise ArgumentError, "can't use nested outside resource(s) scope"
668 669 670
          end

          with_scope_level(:nested) do
671 672
            if parent_resource.shallow?
              with_exclusive_scope do
673
                if @scope[:shallow_path].blank?
674 675
                  scope(*parent_resource.nested_scope) { yield }
                else
676
                  scope(@scope[:shallow_path], :name_prefix => @scope[:shallow_prefix]) do
677 678 679 680 681 682
                    scope(*parent_resource.nested_scope) { yield }
                  end
                end
              end
            else
              scope(*parent_resource.nested_scope) { yield }
683 684 685 686
            end
          end
        end

687
        def namespace(path)
688
          if resource_scope?
689 690 691 692 693 694
            nested { super }
          else
            super
          end
        end

695 696 697 698 699 700
        def shallow
          scope(:shallow => true) do
            yield
          end
        end

J
Joshua Peek 已提交
701
        def match(*args)
J
Joshua Peek 已提交
702
          options = args.extract_options!
703

704 705
          options[:anchor] = true unless options.key?(:anchor)

706
          if args.length > 1
707
            args.each { |path| match(path, options.dup) }
708 709 710
            return self
          end

711 712
          if [:collection, :member, :new].include?(options[:on])
            args.push(options)
713

714 715 716 717 718 719 720
            case options.delete(:on)
            when :collection
              return collection { match(*args) }
            when :member
              return member { match(*args) }
            when :new
              return new { match(*args) }
721 722 723
            end
          end

724 725
          if @scope[:scope_level] == :resource
            args.push(options)
J
Joshua Peek 已提交
726 727
            return member { match(*args) }
          end
J
Joshua Peek 已提交
728

729 730 731 732 733 734 735
          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
736 737 738 739 740 741 742 743 744 745 746
              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
747
            end
748 749 750 751
          end

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

754
          args.push(options)
J
Joshua Peek 已提交
755
          super
J
Joshua Peek 已提交
756 757
        end

758
        def root(options={})
759
          if @scope[:scope_level] == :resources
760
            with_scope_level(:nested) do
761 762 763 764 765 766 767
              scope(parent_resource.path, :name_prefix => parent_resource.collection_name) do
                super(options)
              end
            end
          else
            super(options)
          end
768 769
        end

770
        protected
771
          def parent_resource #:nodoc:
772 773 774
            @scope[:scope_level_resource]
          end

J
Joshua Peek 已提交
775
        private
776
          def apply_common_behavior_for(method, resources, options, &block)
777 778 779 780 781 782
            if resources.length > 1
              resources.each { |r| send(method, r, options, &block) }
              return true
            end

            if path_names = options.delete(:path_names)
783
              scope(:path_names => path_names) do
784 785 786 787 788
                send(method, resources.pop, options, &block)
              end
              return true
            end

789
            if resource_scope?
790 791 792 793 794 795 796 797 798
              nested do
                send(method, resources.pop, options, &block)
              end
              return true
            end

            false
          end

799 800 801 802
          def resource_scope?
            [:resource, :resources].include?(@scope[:scope_level])
          end

803 804 805 806
          def resource_method_scope?
            [:collection, :member, :new].include?(@scope[:scope_level])
          end

807
          def with_exclusive_scope
808
            begin
809 810
              old_name_prefix, old_path = @scope[:name_prefix], @scope[:path]
              @scope[:name_prefix], @scope[:path] = nil, nil
811

812 813 814
              with_scope_level(:exclusive) do
                yield
              end
815
            ensure
816
              @scope[:name_prefix], @scope[:path] = old_name_prefix, old_path
817 818 819
            end
          end

820
          def with_scope_level(kind, resource = parent_resource)
J
Joshua Peek 已提交
821
            old, @scope[:scope_level] = @scope[:scope_level], kind
822
            old_resource, @scope[:scope_level_resource] = @scope[:scope_level_resource], resource
J
Joshua Peek 已提交
823 824 825
            yield
          ensure
            @scope[:scope_level] = old
826
            @scope[:scope_level_resource] = old_resource
J
Joshua Peek 已提交
827
          end
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

          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?
859
                "#{@scope[:shallow_path]}/#{parent_resource.path}/:id(.:format)"
860 861 862 863 864 865 866
              else
                "#{@scope[:path]}(.:format)"
              end
            when :new
              "#{@scope[:path]}/#{action_path(:new)}(.:format)"
            when :edit
              if parent_resource.shallow?
867
                "#{@scope[:shallow_path]}/#{parent_resource.path}/:id/#{action_path(:edit)}(.:format)"
868 869 870 871 872
              else
                "#{@scope[:path]}/#{action_path(:edit)}(.:format)"
              end
            else
              case @scope[:scope_level]
873
              when :collection, :new
874 875 876
                "#{@scope[:path]}/#{action_path(action)}(.:format)"
              else
                if parent_resource.shallow?
877
                  "#{@scope[:shallow_path]}/#{parent_resource.path}/:id/#{action_path(action)}(.:format)"
878 879 880 881 882 883 884
                else
                  "#{@scope[:path]}/#{action_path(action)}(.:format)"
                end
              end
            end
          end

885 886 887 888 889 890
          def path_for_custom_action
            case @scope[:scope_level]
            when :collection, :new
              @scope[:path]
            else
              if parent_resource.shallow?
891
                "#{@scope[:shallow_path]}/#{parent_resource.path}/:id"
892 893 894 895 896 897
              else
                @scope[:path]
              end
            end
          end

898 899 900 901 902 903 904 905 906 907 908 909 910 911
          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]}_"
912
            shallow_prefix = @scope[:shallow_prefix].blank? ? "" : "#{@scope[:shallow_prefix]}_"
913 914

            case action
915
            when :index, :create
916
              "#{name_prefix}#{parent_resource.collection_name}"
917
            when :show, :update, :destroy
918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946
              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 已提交
947
      end
J
Joshua Peek 已提交
948

949 950 951 952
      include Base
      include HttpHelpers
      include Scoping
      include Resources
J
Joshua Peek 已提交
953 954
    end
  end
J
Joshua Peek 已提交
955
end