public
Description: resources_controller rails plugin: rc makes RESTful controllers fun
Homepage: http://plugins.ardes.com/doc/resources_controller
Clone URL: git://github.com/ianwhite/resources_controller.git
Click here to lend your support to: resources_controller and make a donation at www.pledgie.com !
resources_controller / lib / ardes / resources_controller.rb
100644 934 lines (855 sloc) 38.966 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
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
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
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
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
module Ardes#:nodoc:
  # With resources_controller (http://svn.ardes.com/rails_plugins/resources_controller) you can quickly add
  # an ActiveResource compliant controller for your your RESTful models.
  #
  # = Examples
  # Here are some examples - for more on how to use RC go to the Usage section at the bottom,
  # for syntax head to resources_controller_for
  #
  # ==== Example 1: Super simple usage
  # Here's a simple example of how it works with a Forums has many Posts model:
  #
  # class ForumsController < ApplicationController
  # resources_controller_for :forums
  # end
  #
  # Your controller will get the standard CRUD actions, @forum will be set in member actions, @forums in
  # index.
  #
  # ==== Example 2: Specifying enclosing resources
  # class PostsController < ApplicationController
  # resources_controller_for :posts, :in => :forum
  # end
  #
  # As above, but the controller will load @forum on every action, and use @forum to find and create @posts
  #
  # ==== Wildcard enclosing resources
  # All of the above examples will work for any routes that match what it specified
  #
  # PATH RESOURCES CONTROLLER WILL DO:
  #
  # Example 1 /forums @forums = Forum.find(:all)
  #
  # /users/2/forums @user = User.find(2)
  # @forums = @user.forums.find(:all)
  #
  # Example 2 /posts This won't work as the controller specified
  # that :posts are :in => :forum
  #
  # /forums/2/posts @forum = Forum.find(2)
  # @posts = @forum.posts.find(:all)
  #
  # /sites/4/forums/3/posts @site = Site.find(4)
  # @forum = @site.forums.find(3)
  # @posts = @forum.posts.find(:all)
  #
  # /users/2/posts/1 This won't work as the controller specified
  # that :posts are :in => :forum
  #
  #
  # It is up to you which routes to open to the controller (in config/routes.rb). When
  # you do, RC will use the route segments to drill down to the specified resource. This means
  # that if User 3 does not have Post 5, then /users/3/posts/5 will raise a RecordNotFound Error.
  # You dont' have to write any extra code to do this oft repeated controller pattern.
  #
  # With RC, your route specification flows through to the controller - no need to repeat yourself.
  #
  # If you don't want to have RC match wildcard resources just pass :load_enclosing => false
  #
  # resources_controller_for :posts, :in => :forum, :load_enclosing => false
  #
  # ==== Example 3: Singleton resource
  # Here's an example of a singleton, the account pattern that is so common.
  #
  # class AccountController < ApplicationController
  # resources_controller_for :account, :class => User, :singleton => true do
  # @current_user
  # end
  # end
  #
  # Your controller will use the block to find the resource. The @account will be assigned to @current_user
  #
  # ==== Example 4: Allowing PostsController to be used all over
  # First thing to do is remove :in => :forum
  #
  # class PostsController < ApplicationController
  # resources_controller_for :posts
  # end
  #
  # This will now work for /users/2/posts.
  #
  # ==== Example 4 and a bit: Mapping non standard resources
  # How about /account/posts? The account is found in a non standard way - RC won't be able
  # to figure out how tofind it if it appears in the route. So we give it some help.
  #
  # (in PostsController)
  #
  # map_enclosing_resource :account, :singleton => true, :class => User, :find => :current_user
  #
  # Now, if :account apears in any part of a route (for PostsController) it will be mapped to
  # (in this case) the current_user method of teh PostsController.
  #
  # To make the :account mapping available to all, just chuck it in ApplicationController
  #
  # This will work for any resource which can't be inferred from its route segment name
  #
  # map_enclosing_resource :users, :segment => :peeps, :key => 'peep_id'
  # map_enclosing_resource :posts, :class => OddlyNamedPostClass
  #
  # ==== Example 5: Singleton association
  # Here's another singleton example - one where it corresponds to a has_one or belongs_to association
  #
  # class ImageController < ApplicationController
  # resources_controller_for :image, :singleton => true
  # end
  #
  # When invoked with /users/3/image RC will find @user, and use @user.image to find the resource, and
  # @user.build_image, to create a new resource.
  #
  # ==== Example 6: :resource_path (equivalent resource path): aliasing a named route to a RESTful route
  #
  # You may have a named route that maps a url to a particular controller and action,
  # this causes resources_controller problems as it relies on the route to load the
  # resources. You can get around this by specifying :resource_path as a param in routes.rb
  #
  # map.root :controller => :forums, :action => :index, :resource_path => '/forums'
  #
  # When the controller is invoked via the '' url, rc will use :resource_path to recognize the
  # route.
  #
  # ==== Putting it all together
  #
  # An exmaple app
  #
  # config/routes.rb:
  #
  # map.resource :account do |account|
  # account.resource :image
  # account.resources :posts
  # end
  #
  # map.resources :users do |user|
  # user.resource :image
  # user.resources :posts
  # end
  #
  # map.resources :forums do |forum|
  # forum.resources :posts
  # forum.resource :image
  # end
  #
  # map.root :controller => :forums, :action => :index, :resource_path => '/forums'
  #
  # app/controllers:
  #
  # class ApplicationController < ActionController::Base
  # map_enclosing_resource :account, :singleton => true, :find => :current_user
  #
  # def current_user # get it from session or whatnot
  # end
  #
  # class ForumsController < AplicationController
  # resources_controller_for :forums
  # end
  #
  # class PostsController < AplicationController
  # resources_controller_for :posts
  # end
  #
  # class UsersController < AplicationController
  # resources_controller_for :users
  # end
  #
  # class ImageController < AplicationController
  # resources_controller_for :image, :singleton => true
  # end
  #
  # class AccountController < ApplicationController
  # resources_controller_for :account, :singleton => true, :find => :current_user
  # end
  #
  # This is how the app will handle the following routes:
  #
  # PATH CONTROLLER WHICH WILL DO:
  #
  # /forums forums @forums = Forum.find(:all)
  #
  # /forums/2/posts posts @forum = Forum.find(2)
  # @posts = @forum.forums.find(:all)
  #
  # /forums/2/image image @forum = Forum.find(2)
  # @image = @forum.image
  #
  # /image <no route>
  #
  # /posts <no route>
  #
  # /users/2/posts/3 posts @user = User.find(2)
  # @post = @user.posts.find(3)
  #
  # /users/2/image POST image @user = User.find(2)
  # @image = @user.build_image(params[:image])
  #
  # /account account @account = self.current_user
  #
  # /account/image image @account = self.current_user
  # @image = @account.image
  #
  # /account/posts/3 PUT posts @account = self.current_user
  # @post = @account.posts.find(3)
  # @post.update_attributes(params[:post])
  #
  # === Views
  #
  # Ok - so how do I write the views?
  #
  # For most cases, just in exactly the way you would expect to. RC sets the instance variables
  # to what they should be.
  #
  # But, in some cases, you are going to have different variables set - for example
  #
  # /users/1/posts => @user, @posts
  # /forums/2/posts => @forum, @posts
  #
  # Here are some options (all are appropriate for different circumstances):
  # * test for the existence of @user or @forum in the view, and display it differently
  # * have two different controllers UserPostsController and ForumPostsController, with different views
  # (and direct the routes to them in routes.rb)
  # * use enclosing_resource - which always refers to the... immediately enclosing resource.
  #
  # Using the last technique, you might write your posts index as follows
  # (here assuming that both Forum and User have .name)
  #
  # <h1>Posts for <%= link_to enclosing_resource_path, "#{enclosing_resource_name.humanize}: #{enclosing_resource.name}" %></h1>
  #
  # <%= render :partial => 'post', :collection => @posts %>
  #
  # Notice *enclosing_resource_name* - this will be something like 'user', or 'post'.
  # Also *enclosing_resource_path* - in RC you get all of the named route helpers relativised to the current resource
  # and enclosing_resource. See NamedRouteHelper for more details.
  #
  # This can useful when writing the _post partial:
  #
  # <p>
  # <%= post.name %>
  # <%= link_to 'edit', edit_resource_path(tag) %>
  # <%= link_to 'destroy', resource_path(tag), :method => :delete %>
  # </p>
  #
  # when viewed at /users/1/posts it will show
  #
  # <p>
  # Cool post
  # <a href="/users/1/posts/1/edit">edit</a>
  # <a href="js nightmare with /users/1/posts/1">delete</a>
  # </p>
  # ...
  #
  # when viewd at /forums/1/posts it will show
  #
  # <p>
  # Other post
  # <a href="/forums/1/posts/3/edit">edit</a>
  # <a href="js nightmare with /forums/1/posts/3">delete</a>
  # </p>
  # ...
  #
  # This is like polymorphic urls, except that RC will just use whatever enclosing resources are loaded to generate the urls/paths.
  #
  # = Usage
  # To use RC, there are just three class methods on controller to learn.
  #
  # resources_controller_for <name>, <options>, <&block>
  #
  # ClassMethods#nested_in <name>, <options>, <&block>
  #
  # map_enclosing_resource <name>, <options>, <&block>
  #
  # === Customising finding and creating
  # If you want to implement something like query params you can override *find_resources*. If you want to change the
  # way your new resources are created you can override *new_resource*.
  #
  # class PostsController < ApplicationController
  # resources_controller_for :posts
  #
  # def find_resources
  # resource_service.find :all, :order => params[:sort_by]
  # end
  #
  # def new_resource
  # returning resource_service.new(params[resource_name]) do |post|
  # post.ip_address = request.remote_ip
  # end
  # end
  # end
  #
  # In the same way, you can override *find_resource*.
  #
  # === Writing controller actions
  #
  # You can make use of RC internals to simplify your actions.
  #
  # Here's an example where you want to re-order an acts_as_list model. You define a class method
  # on the model (say *order_by_ids* which takes and array of ids). You can then make use of *resource_service*
  # (which makes use of awesome rails magic) to send correctly scoped messages to your models.
  #
  # Here's how to write an order action
  #
  # def order
  # resource_service.order_by_ids["things_order"]
  # end
  #
  # the route
  #
  # map.resources :things, :collection => {:order => :put}
  #
  # and the view can conatin a scriptaculous drag and drop with param name 'things_order'
  #
  # When this controller is invoked of /things the :order_by_ids message will be sent to the Thing class,
  # when it's invoked by /foos/1/things, then :order_by_ids message will be send to Foo.find(1).things association
  #
  # === using non standard ids
  #
  # Lets say you want to set to_param to login, and use find_by_login
  # for your users in your URLs, with routes as follows:
  #
  # map.reosurces :users do |user|
  # user.resources :addresses
  # end
  #
  # First, the users controller needs to find reosurces using find_by_login
  #
  # class UsersController < ApplicationController
  # resources_controller_for :users
  #
  # protected
  # def find_resource(id = params[:id])
  # resource_service.find_by_login(id)
  # end
  # end
  #
  # This controller will find users (for editing, showing, and destroying) as
  # directed. (this controller will work for any route where user is the
  # last resource, including the /users/dave route)
  #
  # Now you need to specify that the user as enclosing resource needs to be found
  # with find_by_login. For the addresses case above, you would do this:
  #
  # class AddressesController < ApplicationController
  # resources_controller_for :addresses
  # nested_in :user do
  # User.find_by_login(params[:user_id])
  # end
  # end
  #
  # If you wanted to open up more nested resources under user, you could repeat
  # this specification in all such controllers, alternatively, you could map the
  # resource in the ApplicationController, which would be usable by any controller
  #
  # If you know that user is never nested (i.e. /users/dave/addresses), then do this:
  #
  # class ApplicationController < ActionController::Base
  # map_enclosing_resource :user do
  # User.find(params[:user_id])
  # end
  # end
  #
  # or, if user is sometimes nested (i.e. /forums/1/users/dave/addresses), do this:
  #
  # map_enclosing_resource :user do
  # ((enclosing_resource && enclosing_resource.users) || User).find(params[:user_id])
  # end
  #
  # Your Addresses controller will now be the very simple one, and the resource map will
  # load user as specified when it is hit by a route /users/dave/addresses.
  #
  # class AddressesController < ApplicationController
  # resources_controller_for :addresses
  # end
  #
  module ResourcesController
    mattr_accessor :actions, :singleton_actions
    self.actions = Ardes::ResourcesController::Actions
    self.singleton_actions = Ardes::ResourcesController::SingletonActions
    
    def self.extended(base)
      base.class_eval do
        class_inheritable_reader :resource_specification_map
        write_inheritable_attribute(:resource_specification_map, {})
      end
    end
    
    # Specifies that this controller is a REST style controller for the named resource
    #
    # Enclosing resources are loaded automatically by default, you can turn this off with
    # :load_enclosing (see options below)
    #
    # resources_controller_for <name>, <options>, <&block>
    #
    # ==== Options:
    # * <tt>:singleton:</tt> (default false) set this to true if the resource is a Singleton
    # * <tt>:find:</tt> (default null) set this to a symbol or Proc to specify how to find the resource.
    # Use this if the resource is found in an unconventional way. Passing a block has the same effect as
    # setting :find => a Proc
    # * <tt>:in:</tt> specify the enclosing resources, by name. ClassMethods#nested_in can be used to
    # specify this more fully.
    # * <tt>:load_enclosing:</tt> (default true) loads enclosing resources automatically.
    # * <tt>:actions:</tt> (default nil) set this to false if you don't want the default RC actions. Set this
    # to a module to use that module for your own actions.
    # * <tt>:only:</tt> only include the specified actions.
    # * <tt>:except:</tt> include all actions except the specified actions.
    #
    # ===== Options for unconvential use
    # (otherwise these are all inferred from the _name_)
    # * <tt>:route:</tt> the route name (without name_prefix) if it can't be inferred from _name_.
    # For a collection resource this should be plural, for a singleton it should be singular.
    # * <tt>:source:</tt> a string or symbol (e.g. :users, or :user). This is used to find the class or association name
    # * <tt>:class:</tt> a Class. This is the class of the resource (if it can't be inferred from _name_ or :source)
    # * <tt>:segment:</tt> (e.g. 'users') the segment name in the route that is matched
    #
    # === The :in option
    # The default behavior is to set up before filters that load the enclosing resource, and to use associations on
    # that model to find and create the resources. See ClassMethods#nested_in for more details on this, and
    # customising the default behaviour.
    #
    # === load_enclosing_resources
    # By default, a before_filter is added by resources_controller called :load_enclosing_resources - which
    # does all the work of loading the enclosing resources. You can use ActionControllers standard filter
    # mechanisms to control when this filter is invoked. For example - you can choose not to load resources
    # on an action
    #
    # resources_controller_for :foos
    # skip_before_filter :load_enclosing_resources, :only => :static_page
    #
    # Or, you can change the order of when the filter is invoked by adding the filter call yourself (rc will
    # only add the filter if it doesn't exist)
    #
    # before_filter :do_something
    # prepend_before_filter :load_enclosing_resources
    # resources_controller_for :foos
    # before_filter :do_something_else # chain => [:load_enclosing_resources, :do_something, :do_something_else]
    #
    # === Default actions module
    # If you have your own actions module you prefer to use other than the standard resources_controller ones
    # you can set Ardes::ResourcesController.actions to that module to have this be included by default
    #
    # Ardes::ResourcesController.actions = MyAwesomeActions
    # Ardes::ResourcesController.singleton_actions = MyAweseomeSingletonActions
    #
    # class AwesomenessController < ApplicationController
    # resources_controller_for :awesomenesses # includes MyAwesomeActions by default
    # end
    def resources_controller_for(name, options = {}, &block)
      options.assert_valid_keys(:class, :source, :singleton, :actions, :in, :find, :load_enclosing, :route, :segment, :as, :only, :except)
      when_options = {:only => options.delete(:only), :except => options.delete(:except)}
      
      unless included_modules.include? ResourcesController::InstanceMethods
        class_inheritable_reader :specifications, :route_name
        hide_action :specifications, :route_name
        extend ResourcesController::ClassMethods
        helper ResourcesController::Helper
        include ResourcesController::InstanceMethods, ResourcesController::NamedRouteHelper
      end
 
      before_filter(:load_enclosing_resources, when_options) unless load_enclosing_resources_filter_exists?
      
      write_inheritable_attribute(:specifications, [])
      specifications << '*' unless options.delete(:load_enclosing) == false
      
      unless (actions = options.delete(:actions)) == false
        actions ||= options[:singleton] ? Ardes::ResourcesController.singleton_actions : Ardes::ResourcesController.actions
        include_actions actions, when_options
      end
      
      route = (options.delete(:route) || name).to_s
      name = options[:singleton] ? name.to_s : name.to_s.singularize
      write_inheritable_attribute :route_name, options[:singleton] ? route : route.singularize
      
      nested_in(*options.delete(:in)) if options[:in]
      
      write_inheritable_attribute(:resource_specification, Specification.new(name, options, &block))
    end
    
    # Creates a resource specification mapping. Use this to specify how to find an enclosing resource that
    # does not obey usual rails conventions. Most commonly this would be a singleton resource.
    #
    # See Specification#new for details of how to call this
    def map_enclosing_resource(name, options = {}, &block)
      spec = Specification.new(name, options, &block)
      resource_specification_map[spec.segment] = spec
    end
    
    # this will be deprecated soon as it's badly named - use map_enclosing_resource
    def map_resource(*args, &block)
      map_enclosing_resource(*args, &block)
    end
    
    # Include the specified module, optionally specifying which public methods to include, for example:
    # include_actions ActionMixin, :only => :index
    # include_actions ActionMixin, :except => [:create, :new]
    def include_actions(mixin, options = {})
      mixin.extend(IncludeActions) unless mixin.respond_to?(:include_actions)
      mixin.include_actions(self, options)
    end
  
  private
    def load_enclosing_resources_filter_exists?
      if respond_to?(:find_filter) # BC 2.0-stable branch
        find_filter(:load_enclosing_resources)
      else
        filter_chain.detect {|c| c.method == :load_enclosing_resources}
      end
    end
  
    module ClassMethods
      # Specifies that this controller has a particular enclosing resource.
      #
      # This can be called with an array of symbols (in which case options can't be specified) or
      # a symbol with options.
      #
      # See Specification#new for details of how to call this.
      def nested_in(*names, &block)
        options = names.extract_options!
        raise ArgumentError, "when giving more than one nesting, you may not specify options or a block" if names.length > 1 and (block_given? or options.length > 0)
        
        # convert :polymorphic option to '?'
        if options.delete(:polymorphic)
          raise ArgumentError, "when specifying :polymorphic => true, no block or other options may be given" if block_given? or options.length > 0
          names = ["?#{names.first}"]
        end
 
        # ignore first '*' if it has already been specified by :load_enclosing == true
        names.shift if specifications == ['*'] && names.first == '*'
        
        names.each do |name|
          ensure_sane_wildcard if name == '*'
          specifications << (name.to_s =~ /^(\*|\?(.*))$/ ? name.to_s : Specification.new(name, options, &block))
        end
      end
      
      # return the class resource_specification
      def resource_specification
        read_inheritable_attribute(:resource_specification)
      end
      
    private
      # ensure that specifications array is determinate w.r.t route matching
      def ensure_sane_wildcard
        idx = specifications.length
        while (idx -= 1) >= 0
          if specifications[idx] == '*'
            raise ArgumentError, "Can only specify one wildcard '*' in between resource specifications"
          elsif specifications[idx].is_a?(Specification)
            break
          end
        end
        true
      end
    end
    
    module InstanceMethods
      def self.included(base)
        base.class_eval do
        protected
          # we define the find|new_resource(s) methods only if they're not already defined
          # this allows abstract controllers to define the resource service methods
          unless instance_methods.include?('find_resources')
            # finds the collection of resources
            def find_resources
              resource_service.find :all
            end
          end
 
          unless instance_methods.include?('find_resource')
            # finds the resource, using the passed id
            def find_resource(id = params[:id])
              resource_service.find id
            end
          end
 
          unless instance_methods.include?('new_resource')
            # makes a new resource, optionally using the passed hash
            def new_resource(attributes = (params[resource_name] || {}))
              resource_service.new attributes
            end
          end
        end
        base.send :hide_action, *instance_methods
      end
      
      def resource_service=(service)
        @resource_service = service
      end
      
      def name_prefix
        @name_prefix ||= ''
      end
      
      # name of the singular resource
      def resource_name
        resource_specification.name
      end
      
      # name of the resource collection
      def resources_name
        @resources_name ||= resource_specification.name.pluralize
      end
      
      # returns the controller's resource class
      def resource_class
        resource_specification.klass
      end
      
      # returns the controller's current resource.
      def resource
        instance_variable_get("@#{resource_name}")
      end
      
      # sets the controller's current resource, and
      # decorates the object with a save hook, so we know if it's been saved
      def resource=(record)
        instance_variable_set("@#{resource_name}", record)
      end
  
      # returns the controller's current resources collection
      def resources
        instance_variable_get("@#{resources_name}")
      end
      
      # sets the controller's current resource collection
      def resources=(collection)
        instance_variable_set("@#{resources_name}", collection)
      end
      
      # returns the immediately enclosing resource
      def enclosing_resource
        enclosing_resources.last
      end
      
      # returns the name of the immediately enclosing resource
      def enclosing_resource_name
        @enclosing_resource_name
      end
      
      # returns the resource service for the controller - this will be lazilly created
      # to a ResourceService, or a SingletonResourceService (if :singleton => true)
      def resource_service
        @resource_service ||= resource_specification.singleton? ? SingletonResourceService.new(self) : ResourceService.new(self)
      end
      
      # returns the instance resource_specification
      def resource_specification
        self.class.resource_specification
      end
      
      # returns an array of the controller's enclosing (nested in) resources
      def enclosing_resources
        @enclosing_resources ||= []
      end
  
      # returns an array of the collection (non singleton) enclosing resources, this is used for generating routes.
      def enclosing_collection_resources
        @enclosing_collection_resources ||= []
      end
      
      # NOTE: This method is overly complicated and unecessary. It's much clearer just to keep
      # track of record saves yourself, this is here for BC. For an example of how it should be
      # done look at the actions module in http://github.com/ianwhite/response_for_rc
      #
      # Has the resource been saved successfully?, if no save has been attempted, save the
      # record and return the result
      #
      # This method uses the @resource_saved tracking var, or the model's state itself if
      # that is not available (which means if you do resource.update_attributes, then this
      # method will return the correct result)
      def resource_saved?
        save_resource if @resource_saved.nil? && !resource.validation_attempted?
        @resource_saved = resource.saved? if @resource_saved.nil?
        @resource_saved
      end
      
      # NOTE: it's clearer to just keep track of record saves yourself, this is here for BC
      # See the comment on #resource_saved?
      #
      # @resource_saved = resource.update_attributes(params[resource_name])
      #
      # Save the resource, and keep track of the result
      def save_resource
        @resource_saved = resource.save
      end
      
    private
      # returns the route that was used to invoke this controller and current action. The path is found first from params[:resource_path]
      # if it exists, and then from the request.path. Likewise the method is found from params[:resource_method]
      #
      # params[:erp] == params[:resource_path] for BC
      def recognized_route
        unless @recognized_route
          path = params[:resource_path] || params[:erp] || request.path
          environment = ::ActionController::Routing::Routes.extract_request_environment(request)
          environment.merge!(:method => params[:resource_method]) if params[:resource_method]
          @recognized_route ||= ::ActionController::Routing::Routes.routes_for_controller_and_action(controller_path, action_name).find do |route|
            route.recognize(path, environment)
          end or ResourcesController.raise_no_recognized_route(self)
        end
        @recognized_route
      end
      
      # returns the all route segments except for the ones corresponding to the current resource and action.
      # Also remove any route segments from the front which correspond to modules (namespaces)
      def enclosing_segments
        segments = remove_namespaces_from_segments(recognized_route.segments.dup)
        while segments.size > 0
          segment = segments.pop
          return segments if segment.is_a?(::ActionController::Routing::StaticSegment) && segment.value == resource_specification.segment
        end
        ResourcesController.raise_missing_segment(self)
      end
      
      # shift namespaces from segments, update the name_prefix accordingly for outgoing routes.
      def remove_namespaces_from_segments(segments)
        namespaces = controller_path.sub(controller_name,'').sub(/\/$/,'').split('/')
        while namespaces.size > 0
          if segments[0].is_a?(ActionController::Routing::DividerSegment) && segments[1].is_a?(ActionController::Routing::StaticSegment) && segments[1].value == namespaces.first
            segments.shift; segments.shift # shift the '/' & 'namespace' segments
            update_name_prefix("#{namespaces.shift}_")
          else
            break
          end
        end
        segments
      end
      
      # Returns an array of pairs [<name>, <singleton?>] e.g. [[users, false], [blog, true], [posts, false]]
      # corresponding to the enclosing resource route segments
      #
      # This is used to map resources and automatically load resources.
      def route_enclosing_names
        @route_enclosing_names ||= returning(Array.new) do |req|
          enclosing_segments.each do |segment|
            unless segment.is_optional or segment.is_a?(::ActionController::Routing::DividerSegment)
              req << [segment.value, true] if segment.is_a?(::ActionController::Routing::StaticSegment)
              req.last[1] = false if segment.is_a?(::ActionController::Routing::DynamicSegment)
            end
          end
        end
      end
      
      # this is the before_filter that loads all specified and wildcard resources
      def load_enclosing_resources
        specifications.each_with_index do |spec, idx|
          case spec
            when '*' then load_wildcards_from(idx)
            when /^\?(.*)/ then load_wildcard($1)
            else load_enclosing_resource_from_specification(spec)
          end
        end
      end
      
      # load a wildcard resource by either
      # * matching the segment to mapped resource specification, or
      # * creating one using the segment name
      # Optionally takes a variable name to set the instance variable as (for polymorphic use)
      def load_wildcard(as = nil)
        route_enclosing_names[enclosing_resources.size] or ResourcesController.raise_resource_mismatch(self)
        segment, singleton = *route_enclosing_names[enclosing_resources.size]
        if resource_specification_map[segment]
          spec = resource_specification_map[segment]
          spec = returning(spec.dup) {|s| s.as = as} if as
        else
          spec = Specification.new(singleton ? segment : segment.singularize, :singleton => singleton, :as => as)
        end
        load_enclosing_resource_from_specification(spec)
      end
      
      # loads a series of wildcard resources, from the specified specification idx
      #
      # To do this, we need to figure out where the next specified resource is
      # and how many single wildcards are prior to that. What is left over from
      # the current route enclosing names will be the number of wildcards we need to load
      def load_wildcards_from(start)
        specs = specifications.slice(start..-1)
        encls = route_enclosing_names.slice(enclosing_resources.size..-1)
        
        if spec = specs.find {|s| s.is_a?(Specification)}
          spec_seg = encls.index([spec.segment, spec.singleton?]) or ResourcesController.raise_resource_mismatch(self)
          number_of_wildcards = spec_seg - (specs.index(spec) -1)
        else
          number_of_wildcards = encls.length - (specs.length - 1)
        end
 
        number_of_wildcards.times { load_wildcard }
      end
         
      def load_enclosing_resource_from_specification(spec)
        spec.segment == route_enclosing_names[enclosing_resources.size].first or ResourcesController.raise_resource_mismatch(self)
        returning spec.find_from(self) do |resource|
          add_enclosing_resource(resource, :name => spec.name, :name_prefix => spec.name_prefix, :is_singleton => spec.singleton?, :as => spec.as)
        end
      end
      
      def add_enclosing_resource(resource, options = {})
        name = options[:name] || resource.class.name.underscore
        update_name_prefix(options[:name_prefix] || (options[:name_prefix] == false ? '' : "#{name}_"))
        enclosing_resources << resource
        enclosing_collection_resources << resource unless options[:is_singleton]
        instance_variable_set("@enclosing_resource_name", options[:name])
        instance_variable_set("@#{name}", resource)
        instance_variable_set("@#{options[:as]}", resource) if options[:as]
      end
      
      # The name prefix is used for forwarding urls and will be different depending on
      # which route the controller was invoked by. The resource specifications build
      # up the name prefix as the resources are loaded.
      def update_name_prefix(name_prefix)
        @name_prefix = "#{@name_prefix}#{name_prefix}"
      end
    end
    
    # Proxy class to provide a consistent API for resource_service. This is mostly
    # required for Singleton resources. Also allows decoration of the resource service with custom finders
    class ResourceService < Builder::BlankSlate
      attr_reader :controller
      delegate :resource_specification, :resource_class, :enclosing_resource, :to => :controller
      
      def initialize(controller)
        @controller = controller
      end
            
      def method_missing(*args, &block)
        service.send(*args, &block)
      end
      
      def find(*args, &block)
        resource_specification.find ? resource_specification.find_custom(controller) : super
      end
      
      def respond_to?(method)
        super || service.respond_to?(method)
      end
    
      def service
        @service ||= enclosing_resource ? enclosing_resource.send(resource_specification.source) : resource_class
      end
    end
    
    class SingletonResourceService < ResourceService
      def find(*args)
        if resource_specification.find
          resource_specification.find_custom(controller)
        elsif controller.enclosing_resources.size > 0
          enclosing_resource.send(resource_specification.source)
        else
          ResourcesController.raise_cant_find_singleton(controller.resource_name, controller.resource_class)
        end
      end
 
      # build association on the enclosing resource if there is one
      def new(*args)
        enclosing_resource ? enclosing_resource.send("build_#{resource_specification.source}", *args) : super
      end
 
      def service
        resource_class
      end
    end
    
    class CantFindSingleton < RuntimeError #:nodoc:
    end
 
    class MissingSegment < RuntimeError #:nodoc:
    end
 
    class NoRecognizedRoute < RuntimeError #:nodoc:
    end
    
    class ResourceMismatch < RuntimeError #:nodoc:
    end
 
    class << self
      def raise_cant_find_singleton(name, klass) #:nodoc:
        raise CantFindSingleton, <<-end_str
Can't get singleton resource from class #{klass.name}. You have have probably done something like:
 
nested_in :#{name}, :singleton => true # <= where this is the first nested_in
 
You should tell resources_controller how to find the singleton resource like this:
 
nested_in :#{name}, :singleton => true do
#{klass.name}.find(<.. your find args here ..>)
end
 
Or:
nested_in :#{name}, :singleton => true, :find => <.. method name or lambda ..>
 
Or, you may be relying on the route to load the resource, in which case you need to give RC some
help. Do this by mapping the route segment to a resource in the controller, or a parent or mixin
 
map_enclosing_resource :#{name}, :segment => ..., :singleton => true <.. as above ..>
end_str
      end
 
      def raise_missing_segment(controller) #:nodoc:
        raise MissingSegment, <<-end_str
Could not recognize segment '#{controller.resource_specification.segment}' in route:
#{controller.send(:recognized_route)}
 
Check that config/routes.rb defines a route named '#{controller.name_prefix}#{controller.resource_specification.singleton? ? controller.route_name : controller.route_name.pluralize}'
for controller: #{controller.controller_name.camelize}Controller"
end_str
      end
      
      def raise_no_recognized_route(controller) #:nodoc:
        raise NoRecognizedRoute, <<-end_str
resources_controller could not recognize a route that that the controller
was invoked with. This is probably being raised in a test.
 
The controller name is '#{controller.controller_name}'
The request.path is '#{controller.request.path}'
The route request environment is:
#{::ActionController::Routing::Routes.extract_request_environment(controller.request).inspect}
 
Possible reasons for this:
- routes have not been loaded
- the controller has been invoked with params that don't correspond to a
route (and so would never be invoked in a real app)
- the test can't figure out which route corresponds to the params, in this
case you may need to stub the recognized_route. (rspec example:)
@controller.stub!(:recognized_route).and_return(ActionController::Routing::Routes.named_routes[:the_route])
end_str
      end
      
      def raise_resource_mismatch(controller) #:nodoc:
        raise ResourceMismatch, <<-end_str
resources_controller can't match the route to the resource specification
route: #{controller.send(:recognized_route)}
specification: enclosing: [#{controller.specifications.collect{|s| s.is_a?(Specification) ? ":#{s.segment}" : s}.join(', ')}], resource :#{controller.resource_specification.segment}
the successfully loaded enclosing resources are: #{controller.enclosing_resources.join(', ')}
end_str
      end
    end
  end
end