Skip to content
This repository
Browse code

Add :only/:except options to map.resources

This allows people with huge numbers of resource routes to cut down on the memory consumption caused by the generated code.

Signed-off-by: Michael Koziarski <michael@koziarski.com>
[#1215 state:committed]
  • Loading branch information...
commit 44a3009ff068bf080de6764a8c884fbf0ceb920e 1 parent c65075f
Tom Stuart authored November 12, 2008 NZKoz committed November 12, 2008
122  actionpack/lib/action_controller/resources.rb
@@ -42,7 +42,11 @@ module ActionController
42 42
   #
43 43
   # Read more about REST at http://en.wikipedia.org/wiki/Representational_State_Transfer
44 44
   module Resources
  45
+    INHERITABLE_OPTIONS = :namespace, :shallow, :only, :except
  46
+
45 47
     class Resource #:nodoc:
  48
+      DEFAULT_ACTIONS = :index, :create, :new, :edit, :show, :update, :destroy
  49
+
46 50
       attr_reader :collection_methods, :member_methods, :new_methods
47 51
       attr_reader :path_prefix, :name_prefix, :path_segment
48 52
       attr_reader :plural, :singular
@@ -57,6 +61,7 @@ def initialize(entities, options)
57 61
 
58 62
         arrange_actions
59 63
         add_default_actions
  64
+        set_allowed_actions
60 65
         set_prefixes
61 66
       end
62 67
 
@@ -113,6 +118,10 @@ def uncountable?
113 118
         @singular.to_s == @plural.to_s
114 119
       end
115 120
 
  121
+      def has_action?(action)
  122
+        !DEFAULT_ACTIONS.include?(action) || action_allowed?(action)
  123
+      end
  124
+
116 125
       protected
117 126
         def arrange_actions
118 127
           @collection_methods = arrange_actions_by_methods(options.delete(:collection))
@@ -125,6 +134,30 @@ def add_default_actions
125 134
           add_default_action(new_methods, :get, :new)
126 135
         end
127 136
 
  137
+        def set_allowed_actions
  138
+          only, except = @options.values_at(:only, :except)
  139
+          @allowed_actions ||= {}
  140
+
  141
+          if only == :all || except == :none
  142
+            only = nil
  143
+            except = []
  144
+          elsif only == :none || except == :all
  145
+            only = []
  146
+            except = nil
  147
+          end
  148
+
  149
+          if only
  150
+            @allowed_actions[:only] = Array(only).map(&:to_sym)
  151
+          elsif except
  152
+            @allowed_actions[:except] = Array(except).map(&:to_sym)
  153
+          end
  154
+        end
  155
+
  156
+        def action_allowed?(action)
  157
+          only, except = @allowed_actions.values_at(:only, :except)
  158
+          (!only || only.include?(action)) && (!except || !except.include?(action))
  159
+        end
  160
+
128 161
         def set_prefixes
129 162
           @path_prefix = options.delete(:path_prefix)
130 163
           @name_prefix = options.delete(:name_prefix)
@@ -353,6 +386,25 @@ def initialize(entity, options)
353 386
     #
354 387
     #   map.resources :users, :has_many => { :posts => :comments }, :shallow => true
355 388
     #
  389
+    # * <tt>:only</tt> and <tt>:except</tt> - Specify which of the seven default actions should be routed to.
  390
+    #
  391
+    # <tt>:only</tt> and <tt>:except</tt> may be set to <tt>:all</tt>, <tt>:none</tt>, an action name or a
  392
+    # list of action names. By default, routes are generated for all seven actions.
  393
+    #
  394
+    # For example:
  395
+    #
  396
+    #   map.resources :posts, :only => [:index, :show] do |post|
  397
+    #     post.resources :comments, :except => [:update, :destroy]
  398
+    #   end
  399
+    #   # --> GET /posts (maps to the PostsController#index action)
  400
+    #   # --> POST /posts (fails)
  401
+    #   # --> GET /posts/1 (maps to the PostsController#show action)
  402
+    #   # --> DELETE /posts/1 (fails)
  403
+    #   # --> POST /posts/1/comments (maps to the CommentsController#create action)
  404
+    #   # --> PUT /posts/1/comments/1 (fails)
  405
+    #
  406
+    # The <tt>:only</tt> and <tt>:except</tt> options are inherited by any nested resource(s).
  407
+    #
356 408
     # If <tt>map.resources</tt> is called with multiple resources, they all get the same options applied.
357 409
     #
358 410
     # Examples:
@@ -478,7 +530,7 @@ def map_resource(entities, options = {}, &block)
478 530
           map_associations(resource, options)
479 531
 
480 532
           if block_given?
481  
-            with_options(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], :shallow => options[:shallow], &block)
  533
+            with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
482 534
           end
483 535
         end
484 536
       end
@@ -495,7 +547,7 @@ def map_singleton_resource(entities, options = {}, &block)
495 547
           map_associations(resource, options)
496 548
 
497 549
           if block_given?
498  
-            with_options(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], :shallow => options[:shallow], &block)
  550
+            with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
499 551
           end
500 552
         end
501 553
       end
@@ -507,7 +559,7 @@ def map_associations(resource, options)
507 559
         name_prefix = "#{options.delete(:name_prefix)}#{resource.nesting_name_prefix}"
508 560
 
509 561
         Array(options[:has_one]).each do |association|
510  
-          resource(association, :path_prefix => path_prefix, :name_prefix => name_prefix, :namespace => options[:namespace], :shallow => options[:shallow])
  562
+          resource(association, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => path_prefix, :name_prefix => name_prefix))
511 563
         end
512 564
       end
513 565
 
@@ -522,7 +574,7 @@ def map_has_many_associations(resource, associations, options)
522 574
             map_has_many_associations(resource, association, options)
523 575
           end
524 576
         when Symbol, String
525  
-          resources(associations, :path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], :shallow => options[:shallow], :has_many => options[:has_many])
  577
+          resources(associations, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :has_many => options[:has_many]))
526 578
         else
527 579
         end
528 580
       end
@@ -531,41 +583,39 @@ def map_collection_actions(map, resource)
531 583
         resource.collection_methods.each do |method, actions|
532 584
           actions.each do |action|
533 585
             [method].flatten.each do |m|
534  
-              action_options = action_options_for(action, resource, m)
535  
-              map_named_routes(map, "#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}", action_options)
  586
+              map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action}", "#{action}_#{resource.name_prefix}#{resource.plural}", m)
536 587
             end
537 588
           end
538 589
         end
539 590
       end
540 591
 
541 592
       def map_default_collection_actions(map, resource)
542  
-        index_action_options = action_options_for("index", resource)
543 593
         index_route_name = "#{resource.name_prefix}#{resource.plural}"
544 594
 
545 595
         if resource.uncountable?
546 596
           index_route_name << "_index"
547 597
         end
548 598
 
549  
-        map_named_routes(map, index_route_name, resource.path, index_action_options)
550  
-
551  
-        create_action_options = action_options_for("create", resource)
552  
-        map_unnamed_routes(map, resource.path, create_action_options)
  599
+        map_resource_routes(map, resource, :index, resource.path, index_route_name)
  600
+        map_resource_routes(map, resource, :create, resource.path)
553 601
       end
554 602
 
555 603
       def map_default_singleton_actions(map, resource)
556  
-        create_action_options = action_options_for("create", resource)
557  
-        map_unnamed_routes(map, resource.path, create_action_options)
  604
+        map_resource_routes(map, resource, :create, resource.path)
558 605
       end
559 606
 
560 607
       def map_new_actions(map, resource)
561 608
         resource.new_methods.each do |method, actions|
562 609
           actions.each do |action|
563  
-            action_options = action_options_for(action, resource, method)
564  
-            if action == :new
565  
-              map_named_routes(map, "new_#{resource.name_prefix}#{resource.singular}", resource.new_path, action_options)
566  
-            else
567  
-              map_named_routes(map, "#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}#{resource.action_separator}#{action}", action_options)
  610
+            route_path = resource.new_path
  611
+            route_name = "new_#{resource.name_prefix}#{resource.singular}"
  612
+
  613
+            unless action == :new
  614
+              route_path = "#{route_path}#{resource.action_separator}#{action}"
  615
+              route_name = "#{action}_#{route_name}"
568 616
             end
  617
+
  618
+            map_resource_routes(map, resource, action, route_path, route_name, method)
569 619
           end
570 620
         end
571 621
       end
@@ -574,34 +624,32 @@ def map_member_actions(map, resource)
574 624
         resource.member_methods.each do |method, actions|
575 625
           actions.each do |action|
576 626
             [method].flatten.each do |m|
577  
-              action_options = action_options_for(action, resource, m)
578  
-
579 627
               action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
580 628
               action_path ||= Base.resources_path_names[action] || action
581 629
 
582  
-              map_named_routes(map, "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action_path}", action_options)
  630
+              map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m)
583 631
             end
584 632
           end
585 633
         end
586 634
 
587  
-        show_action_options = action_options_for("show", resource)
588  
-        map_named_routes(map, "#{resource.shallow_name_prefix}#{resource.singular}", resource.member_path, show_action_options)
589  
-
590  
-        update_action_options = action_options_for("update", resource)
591  
-        map_unnamed_routes(map, resource.member_path, update_action_options)
592  
-
593  
-        destroy_action_options = action_options_for("destroy", resource)
594  
-        map_unnamed_routes(map, resource.member_path, destroy_action_options)
  635
+        map_resource_routes(map, resource, :show, resource.member_path, "#{resource.shallow_name_prefix}#{resource.singular}")
  636
+        map_resource_routes(map, resource, :update, resource.member_path)
  637
+        map_resource_routes(map, resource, :destroy, resource.member_path)
595 638
       end
596 639
 
597  
-      def map_unnamed_routes(map, path_without_format, options)
598  
-        map.connect(path_without_format, options)
599  
-        map.connect("#{path_without_format}.:format", options)
600  
-      end
601  
-
602  
-      def map_named_routes(map, name, path_without_format, options)
603  
-        map.named_route(name, path_without_format, options)
604  
-        map.named_route("formatted_#{name}", "#{path_without_format}.:format", options)
  640
+      def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil)
  641
+        if resource.has_action?(action)
  642
+          action_options = action_options_for(action, resource, method)
  643
+          formatted_route_path = "#{route_path}.:format"
  644
+
  645
+          if route_name
  646
+            map.named_route(route_name, route_path, action_options)
  647
+            map.named_route("formatted_#{route_name}", formatted_route_path, action_options)
  648
+          else
  649
+            map.connect(route_path, action_options)
  650
+            map.connect(formatted_route_path, action_options)
  651
+          end
  652
+        end
605 653
       end
606 654
 
607 655
       def add_conditions_for(conditions, method)
162  actionpack/test/controller/resources_test.rb
@@ -14,6 +14,8 @@ class LogosController < ResourcesController; end
14 14
 
15 15
 class AccountsController <  ResourcesController; end
16 16
 class AdminController   <  ResourcesController; end
  17
+class ProductsController < ResourcesController; end
  18
+class ImagesController < ResourcesController; end
17 19
 
18 20
 module Backoffice
19 21
   class ProductsController < ResourcesController; end
@@ -776,6 +778,121 @@ def test_with_path_segment_path_prefix_requirements
776 778
     end
777 779
   end
778 780
 
  781
+  def test_resource_has_only_show_action
  782
+    with_routing do |set|
  783
+      set.draw do |map|
  784
+        map.resources :products, :only => :show
  785
+      end
  786
+
  787
+      assert_resource_allowed_routes('products', {},                    { :id => '1' }, :show, [:index, :new, :create, :edit, :update, :destroy])
  788
+      assert_resource_allowed_routes('products', { :format => 'xml' },  { :id => '1' }, :show, [:index, :new, :create, :edit, :update, :destroy])
  789
+    end
  790
+  end
  791
+
  792
+  def test_singleton_resource_has_only_show_action
  793
+    with_routing do |set|
  794
+      set.draw do |map|
  795
+        map.resource :account, :only => :show
  796
+      end
  797
+
  798
+      assert_singleton_resource_allowed_routes('accounts', {},                    :show, [:index, :new, :create, :edit, :update, :destroy])
  799
+      assert_singleton_resource_allowed_routes('accounts', { :format => 'xml' },  :show, [:index, :new, :create, :edit, :update, :destroy])
  800
+    end
  801
+  end
  802
+
  803
+  def test_resource_does_not_have_destroy_action
  804
+    with_routing do |set|
  805
+      set.draw do |map|
  806
+        map.resources :products, :except => :destroy
  807
+      end
  808
+
  809
+      assert_resource_allowed_routes('products', {},                    { :id => '1' }, [:index, :new, :create, :show, :edit, :update], :destroy)
  810
+      assert_resource_allowed_routes('products', { :format => 'xml' },  { :id => '1' }, [:index, :new, :create, :show, :edit, :update], :destroy)
  811
+    end
  812
+  end
  813
+
  814
+  def test_singleton_resource_does_not_have_destroy_action
  815
+    with_routing do |set|
  816
+      set.draw do |map|
  817
+        map.resource :account, :except => :destroy
  818
+      end
  819
+
  820
+      assert_singleton_resource_allowed_routes('accounts', {},                    [:new, :create, :show, :edit, :update], :destroy)
  821
+      assert_singleton_resource_allowed_routes('accounts', { :format => 'xml' },  [:new, :create, :show, :edit, :update], :destroy)
  822
+    end
  823
+  end
  824
+
  825
+  def test_resource_has_only_collection_action
  826
+    with_routing do |set|
  827
+      set.draw do |map|
  828
+        map.resources :products, :except => :all, :collection => { :sale => :get }
  829
+      end
  830
+
  831
+      assert_resource_allowed_routes('products', {},                    { :id => '1' }, [], [:index, :new, :create, :show, :edit, :update, :destroy])
  832
+      assert_resource_allowed_routes('products', { :format => 'xml' },  { :id => '1' }, [], [:index, :new, :create, :show, :edit, :update, :destroy])
  833
+
  834
+      assert_recognizes({ :controller => 'products', :action => 'sale' },                   :path => 'products/sale',     :method => :get)
  835
+      assert_recognizes({ :controller => 'products', :action => 'sale', :format => 'xml' }, :path => 'products/sale.xml', :method => :get)
  836
+    end
  837
+  end
  838
+
  839
+  def test_resource_has_only_member_action
  840
+    with_routing do |set|
  841
+      set.draw do |map|
  842
+        map.resources :products, :except => :all, :member => { :preview => :get }
  843
+      end
  844
+
  845
+      assert_resource_allowed_routes('products', {},                    { :id => '1' }, [], [:index, :new, :create, :show, :edit, :update, :destroy])
  846
+      assert_resource_allowed_routes('products', { :format => 'xml' },  { :id => '1' }, [], [:index, :new, :create, :show, :edit, :update, :destroy])
  847
+
  848
+      assert_recognizes({ :controller => 'products', :action => 'preview', :id => '1' },                    :path => 'products/1/preview',      :method => :get)
  849
+      assert_recognizes({ :controller => 'products', :action => 'preview', :id => '1', :format => 'xml' },  :path => 'products/1/preview.xml',  :method => :get)
  850
+    end
  851
+  end
  852
+
  853
+  def test_singleton_resource_has_only_member_action
  854
+    with_routing do |set|
  855
+      set.draw do |map|
  856
+        map.resource :account, :except => :all, :member => { :signup => :get }
  857
+      end
  858
+
  859
+      assert_singleton_resource_allowed_routes('accounts', {},                    [], [:new, :create, :show, :edit, :update, :destroy])
  860
+      assert_singleton_resource_allowed_routes('accounts', { :format => 'xml' },  [], [:new, :create, :show, :edit, :update, :destroy])
  861
+
  862
+      assert_recognizes({ :controller => 'accounts', :action => 'signup' },                   :path => 'account/signup',      :method => :get)
  863
+      assert_recognizes({ :controller => 'accounts', :action => 'signup', :format => 'xml' }, :path => 'account/signup.xml',  :method => :get)
  864
+    end
  865
+  end
  866
+
  867
+  def test_nested_resource_inherits_only_show_action
  868
+    with_routing do |set|
  869
+      set.draw do |map|
  870
+        map.resources :products, :only => :show do |product|
  871
+          product.resources :images
  872
+        end
  873
+      end
  874
+
  875
+      assert_resource_allowed_routes('images', { :product_id => '1' },                    { :id => '2' }, :show, [:index, :new, :create, :edit, :update, :destroy], 'products/1/images')
  876
+      assert_resource_allowed_routes('images', { :product_id => '1', :format => 'xml' },  { :id => '2' }, :show, [:index, :new, :create, :edit, :update, :destroy], 'products/1/images')
  877
+    end
  878
+  end
  879
+
  880
+  def test_nested_resource_has_only_show_and_member_action
  881
+    with_routing do |set|
  882
+      set.draw do |map|
  883
+        map.resources :products, :only => [:index, :show] do |product|
  884
+          product.resources :images, :member => { :thumbnail => :get }, :only => :show
  885
+        end
  886
+      end
  887
+
  888
+      assert_resource_allowed_routes('images', { :product_id => '1' },                    { :id => '2' }, :show, [:index, :new, :create, :edit, :update, :destroy], 'products/1/images')
  889
+      assert_resource_allowed_routes('images', { :product_id => '1', :format => 'xml' },  { :id => '2' }, :show, [:index, :new, :create, :edit, :update, :destroy], 'products/1/images')
  890
+
  891
+      assert_recognizes({ :controller => 'images', :action => 'thumbnail', :product_id => '1', :id => '2' },                    :path => 'products/1/images/2/thumbnail', :method => :get)
  892
+      assert_recognizes({ :controller => 'images', :action => 'thumbnail', :product_id => '1', :id => '2', :format => 'jpg' },  :path => 'products/1/images/2/thumbnail.jpg', :method => :get)
  893
+    end
  894
+  end
  895
+
779 896
   protected
780 897
     def with_restful_routing(*args)
781 898
       with_routing do |set|
@@ -979,6 +1096,51 @@ def assert_resource_methods(expected, resource, action_method, method)
979 1096
       end
980 1097
     end
981 1098
 
  1099
+    def assert_resource_allowed_routes(controller, options, shallow_options, allowed, not_allowed, path = controller)
  1100
+      shallow_path = "#{path}/#{shallow_options[:id]}"
  1101
+      format = options[:format] && ".#{options[:format]}"
  1102
+      options.merge!(:controller => controller)
  1103
+      shallow_options.merge!(options)
  1104
+
  1105
+      assert_whether_allowed(allowed, not_allowed, options,         'index',    "#{path}#{format}",               :get)
  1106
+      assert_whether_allowed(allowed, not_allowed, options,         'new',      "#{path}/new#{format}",           :get)
  1107
+      assert_whether_allowed(allowed, not_allowed, options,         'create',   "#{path}#{format}",               :post)
  1108
+      assert_whether_allowed(allowed, not_allowed, shallow_options, 'show',     "#{shallow_path}#{format}",       :get)
  1109
+      assert_whether_allowed(allowed, not_allowed, shallow_options, 'edit',     "#{shallow_path}/edit#{format}",  :get)
  1110
+      assert_whether_allowed(allowed, not_allowed, shallow_options, 'update',   "#{shallow_path}#{format}",       :put)
  1111
+      assert_whether_allowed(allowed, not_allowed, shallow_options, 'destroy',  "#{shallow_path}#{format}",       :delete)
  1112
+    end
  1113
+
  1114
+    def assert_singleton_resource_allowed_routes(controller, options, allowed, not_allowed, path = controller.singularize)
  1115
+      format = options[:format] && ".#{options[:format]}"
  1116
+      options.merge!(:controller => controller)
  1117
+
  1118
+      assert_whether_allowed(allowed, not_allowed, options, 'new',      "#{path}/new#{format}",   :get)
  1119
+      assert_whether_allowed(allowed, not_allowed, options, 'create',   "#{path}#{format}",       :post)
  1120
+      assert_whether_allowed(allowed, not_allowed, options, 'show',     "#{path}#{format}",       :get)
  1121
+      assert_whether_allowed(allowed, not_allowed, options, 'edit',     "#{path}/edit#{format}",  :get)
  1122
+      assert_whether_allowed(allowed, not_allowed, options, 'update',   "#{path}#{format}",       :put)
  1123
+      assert_whether_allowed(allowed, not_allowed, options, 'destroy',  "#{path}#{format}",       :delete)
  1124
+    end
  1125
+
  1126
+    def assert_whether_allowed(allowed, not_allowed, options, action, path, method)
  1127
+      action = action.to_sym
  1128
+      options = options.merge(:action => action.to_s)
  1129
+      path_options = { :path => path, :method => method }
  1130
+
  1131
+      if Array(allowed).include?(action)
  1132
+        assert_recognizes options, path_options
  1133
+      elsif Array(not_allowed).include?(action)
  1134
+        assert_not_recognizes options, path_options
  1135
+      end
  1136
+    end
  1137
+
  1138
+    def assert_not_recognizes(expected_options, path)
  1139
+      assert_raise ActionController::RoutingError, ActionController::MethodNotAllowed, Test::Unit::AssertionFailedError do
  1140
+        assert_recognizes(expected_options, path)
  1141
+      end
  1142
+    end
  1143
+
982 1144
     def distinct_routes? (r1, r2)
983 1145
       if r1.conditions == r2.conditions and r1.requirements == r2.requirements then
984 1146
         if r1.segments.collect(&:to_s) == r2.segments.collect(&:to_s) then

16 notes on commit 44a3009

Amos King

woot. I was working on this, but now I can stop.

Sam Granieri

This is fantastic! I hope this makes it into 2.2

Joshua Clayton

I would love to see this included in 2.2. Totally awesome.

Amos King

+1 for 2.2 inclusion

Sean O'Brien

+1 for 2.2 inclusion as well. This is sweet.

Jonathan Goldman

+1 for 2.2 inclusion.

Michael Koziarski
Owner

Guys, this is still the 2.2 branch, it’s going in. ;)

Andreas Neuhaus

nice :)

Josh Susser

I’m all for the feature (been wanting it for ages), but I’d favor holding it to 2.2.1. Maintaining a feature freeze during a release cycle is important for the quality and stability of a release, and relaxing the discipline of a freeze for a shiny object is going to get you in trouble eventually.

Andrzej Sliwa

+1 for 2.2 inclusion, great job

Eloy Durán

I have to agree with joshsusser on this.

Michael Koziarski
Owner

We want it in for 2.2 final, if you think we should do another RC with it included, then we can do that. However it seems relatively harmless.

Pipe up on the ticket if you feel strongly about it. There’s an enhancement to come it seems

Josh Susser

There have probably been enough changes since RC1 to merit an RC2, especially since there have been some feature additions. And I notice there was an issue with removing index and show helpers, which is a good indication this change needs some time to bake.

S. Brent Faulkner

lol… apparently I was ahead of my time, I was shot down for suggesting this feature – which lead to me adding “shallow” nesting instead… oh well, now we have both

Michael Koziarski
Owner

@sbfaulkner: heh, yeah. Specific numbers on the memory usage made me change my mind.

@joshsusser: Indeed, this particular changeset is only intended for people looking to cut down on memory usage with thousands of routes. I just tested with my app and it was a tiny tiny difference.

We’ll do an RC2 and keep it ‘frozen’ barring surprises.

Pratik
Owner

Adding a blank space.

Please sign in to comment.
Something went wrong with that request. Please try again.