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 # # /posts # # /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) # #

Posts for <%= link_to enclosing_resource_path, "#{enclosing_resource_name.humanize}: #{enclosing_resource.name}" %>

# # <%= 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: # #

# <%= post.name %> # <%= link_to 'edit', edit_resource_path(tag) %> # <%= link_to 'destroy', resource_path(tag), :method => :delete %> #

# # when viewed at /users/1/posts it will show # #

# Cool post # edit # delete #

# ... # # when viewd at /forums/1/posts it will show # #

# Other post # edit # delete #

# ... # # 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 , , <&block> # # ClassMethods#nested_in , , <&block> # # map_enclosing_resource , , <&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 , , <&block> # # ==== Options: # * :singleton: (default false) set this to true if the resource is a Singleton # * :find: (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 # * :in: specify the enclosing resources, by name. ClassMethods#nested_in can be used to # specify this more fully. # * :load_enclosing: (default true) loads enclosing resources automatically. # * :actions: (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. # * :only: only include the specified actions. # * :except: include all actions except the specified actions. # # ===== Options for unconvential use # (otherwise these are all inferred from the _name_) # * :route: 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. # * :source: a string or symbol (e.g. :users, or :user). This is used to find the class or association name # * :class: a Class. This is the class of the resource (if it can't be inferred from _name_ or :source) # * :segment: (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 # # eg # # include_actions ActionMixin, :only => :index # include_actions ActionMixin, :except => [:create, :new] def include_actions(mixin, options = {}) options.assert_valid_keys(:only, :except) raise ArgumentError, "you can only specify either :except or :only, not both" if options[:only] && options[:except] mixin = mixin.dup if only = options[:only] only = Array(options[:only]).collect(&:to_s) mixin.instance_methods.each {|m| mixin.send(:undef_method, m) unless only.include?(m)} elsif except = options[:except] except = Array(options[:except]).collect(&:to_s) mixin.instance_methods.each {|m| mixin.send(:undef_method, m) if except.include?(m)} end include mixin 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.class.name.underscore 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 # Has the resource been saved successfully? # If the record has not had validation attempted, it is saved. # Returns true if the record is not new, and there are no errors def resource_saved? resource.save unless resource.validation_attempted? resource.saved? end # DEPRECATED: just use resource.save def save_resource resource.save end deprecate :save_resource => 'Use resource.save' 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 [, ] 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, spec.name, :name_prefix => spec.name_prefix, :is_singleton => spec.singleton?, :as => spec.as ) end end def add_enclosing_resource(resource, name, options = {}) 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("@#{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