PageRenderTime 57ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 0ms

/merb-core/lib/merb-core/dispatch/router/resources.rb

https://github.com/mattetti/merb
Ruby | 329 lines | 147 code | 43 blank | 139 comment | 17 complexity | b53b6b2dedfda4d2b4b5ce26eb02e35d MD5 | raw file
Possible License(s): CC-BY-SA-3.0, GPL-2.0, LGPL-2.1
  1. module Merb
  2. class Router
  3. module Resources
  4. # Behavior#+resources+ is a route helper for defining a collection of
  5. # RESTful resources. It yields to a block for child routes.
  6. #
  7. # ==== Parameters
  8. # name<String, Symbol>:: The name of the resources
  9. # options<Hash>::
  10. # Ovverides and parameters to be associated with the route
  11. #
  12. # ==== Options (options)
  13. # :namespace<~to_s>: The namespace for this route.
  14. # :name_prefix<~to_s>:
  15. # A prefix for the named routes. If a namespace is passed and there
  16. # isn't a name prefix, the namespace will become the prefix.
  17. # :controller<~to_s>: The controller for this route
  18. # :collection<~to_s>: Special settings for the collections routes
  19. # :member<Hash>:
  20. # Special settings and resources related to a specific member of this
  21. # resource.
  22. # :identify<Symbol|Array>: The method(s) that should be called on the object
  23. # before inserting it into an URL.
  24. # :keys<Array>:
  25. # A list of the keys to be used instead of :id with the resource in the order of the url.
  26. # :singular<Symbol>
  27. #
  28. # ==== Block parameters
  29. # next_level<Behavior>:: The child behavior.
  30. #
  31. # ==== Returns
  32. # Array::
  33. # Routes which will define the specified RESTful collection of resources
  34. #
  35. # ==== Examples
  36. #
  37. # r.resources :posts # will result in the typical RESTful CRUD
  38. # # lists resources
  39. # # GET /posts/?(\.:format)? :action => "index"
  40. # # GET /posts/index(\.:format)? :action => "index"
  41. #
  42. # # shows new resource form
  43. # # GET /posts/new :action => "new"
  44. #
  45. # # creates resource
  46. # # POST /posts/?(\.:format)?, :action => "create"
  47. #
  48. # # shows resource
  49. # # GET /posts/:id(\.:format)? :action => "show"
  50. #
  51. # # shows edit form
  52. # # GET /posts/:id/edit :action => "edit"
  53. #
  54. # # updates resource
  55. # # PUT /posts/:id(\.:format)? :action => "update"
  56. #
  57. # # shows deletion confirmation page
  58. # # GET /posts/:id/delete :action => "delete"
  59. #
  60. # # destroys resources
  61. # # DELETE /posts/:id(\.:format)? :action => "destroy"
  62. #
  63. # # Nesting resources
  64. # r.resources :posts do |posts|
  65. # posts.resources :comments
  66. # end
  67. #
  68. # :api: public
  69. def resources(name, *args, &block)
  70. name = name.to_s
  71. options = extract_options_from_args!(args) || {}
  72. match_opts = options.except(*resource_options)
  73. options = options.only(*resource_options)
  74. singular = options[:singular] ? options[:singular].to_s : Extlib::Inflection.singularize(name)
  75. klass_name = args.first ? args.first.to_s : singular.to_const_string
  76. keys = options.delete(:keys) || options.delete(:key)
  77. params = { :controller => options.delete(:controller) || name }
  78. collection = options.delete(:collection) || {}
  79. member = { :edit => :get, :delete => :get }.merge(options.delete(:member) || {})
  80. # Use the identifier for the class as a default
  81. begin
  82. if klass = Object.full_const_get(klass_name)
  83. keys ||= options[:identify]
  84. keys ||= @identifiers[klass]
  85. elsif options[:identify]
  86. raise Error, "The constant #{klass_name} does not exist, please specify the constant for this resource"
  87. end
  88. rescue NameError => e
  89. Merb.logger.debug!("Could not find resource model #{klass_name}")
  90. end
  91. keys = [ keys || :id ].flatten
  92. # Try pulling :namespace out of options for backwards compatibility
  93. options[:name_prefix] ||= nil # Don't use a name_prefix if not needed
  94. options[:resource_prefix] ||= nil # Don't use a resource_prefix if not needed
  95. options[:controller_prefix] ||= options.delete(:namespace)
  96. context = options[:identify]
  97. context = klass && options[:identify] ? identify(klass => options.delete(:identify)) : self
  98. context.namespace(name, options).to(params) do |resource|
  99. root_keys = keys.map { |k| ":#{k}" }.join("/")
  100. # => index
  101. resource.match("(/index)(.:format)", :method => :get).to(:action => "index").
  102. name(name).register_resource(name)
  103. # => create
  104. resource.match("(.:format)", :method => :post).to(:action => "create")
  105. # => new
  106. resource.match("/new(.:format)", :method => :get).to(:action => "new").
  107. name("new", singular).register_resource(name, "new")
  108. # => user defined collection routes
  109. collection.each_pair do |action, method|
  110. action = action.to_s
  111. resource.match("/#{action}(.:format)", :method => method).to(:action => "#{action}").
  112. name(action, name).register_resource(name, action)
  113. end
  114. # => show
  115. resource.match("/#{root_keys}(.:format)", match_opts.merge(:method => :get)).to(:action => "show").
  116. name(singular).register_resource(klass_name, :identifiers => keys)
  117. # => user defined member routes
  118. member.each_pair do |action, method|
  119. action = action.to_s
  120. resource.match("/#{root_keys}/#{action}(.:format)", match_opts.merge(:method => method)).
  121. to(:action => "#{action}").name(action, singular).register_resource(klass_name, action, :identifiers => keys)
  122. end
  123. # => update
  124. resource.match("/#{root_keys}(.:format)", match_opts.merge(:method => :put)).
  125. to(:action => "update")
  126. # => destroy
  127. resource.match("/#{root_keys}(.:format)", match_opts.merge(:method => :delete)).
  128. to(:action => "destroy")
  129. if block_given?
  130. parent_keys = keys.map do |k|
  131. k == :id ? "#{singular}_id".to_sym : k
  132. end
  133. nested_keys = parent_keys.map { |k| ":#{k}" }.join("/")
  134. nested_match_opts = match_opts.except(:id)
  135. nested_match_opts["#{singular}_id".to_sym] = match_opts[:id] if match_opts[:id]
  136. # Procs for building the extra collection/member resource routes
  137. placeholder = Router.resource_routes[ [@options[:resource_prefix], klass_name].flatten.compact ]
  138. builders = {}
  139. builders[:collection] = lambda do |action, to, method|
  140. resource.before(placeholder).match("/#{action}(.:format)", match_opts.merge(:method => method)).
  141. to(:action => to).name(action, name).register_resource(name, action)
  142. end
  143. builders[:member] = lambda do |action, to, method|
  144. resource.match("/#{root_keys}/#{action}(.:format)", match_opts.merge(:method => method)).
  145. to(:action => to).name(action, singular).register_resource(klass_name, action, :identifiers => keys)
  146. end
  147. resource.options(:name_prefix => singular, :resource_prefix => klass_name, :parent_keys => parent_keys).
  148. match("/#{nested_keys}", nested_match_opts).resource_block(builders, &block)
  149. end
  150. end # namespace
  151. end # resources
  152. # Behavior#+resource+ is a route helper for defining a singular RESTful
  153. # resource. It yields to a block for child routes.
  154. #
  155. # ==== Parameters
  156. # name<String, Symbol>:: The name of the resource.
  157. # options<Hash>::
  158. # Overides and parameters to be associated with the route.
  159. #
  160. # ==== Options (options)
  161. # :namespace<~to_s>: The namespace for this route.
  162. # :name_prefix<~to_s>:
  163. # A prefix for the named routes. If a namespace is passed and there
  164. # isn't a name prefix, the namespace will become the prefix.
  165. # :controller<~to_s>: The controller for this route
  166. #
  167. # ==== Block parameters
  168. # next_level<Behavior>:: The child behavior.
  169. #
  170. # ==== Returns
  171. # Array:: Routes which define a RESTful single resource.
  172. #
  173. # ==== Examples
  174. #
  175. # r.resource :account # will result in the typical RESTful CRUD
  176. # # shows new resource form
  177. # # GET /account/new :action => "new"
  178. #
  179. # # creates resource
  180. # # POST /account/?(\.:format)?, :action => "create"
  181. #
  182. # # shows resource
  183. # # GET /account/(\.:format)? :action => "show"
  184. #
  185. # # shows edit form
  186. # # GET /account//edit :action => "edit"
  187. #
  188. # # updates resource
  189. # # PUT /account/(\.:format)? :action => "update"
  190. #
  191. # # shows deletion confirmation page
  192. # # GET /account//delete :action => "delete"
  193. #
  194. # # destroys resources
  195. # # DELETE /account/(\.:format)? :action => "destroy"
  196. #
  197. # You can optionally pass :namespace and :controller to refine the routing
  198. # or pass a block to nest resources.
  199. #
  200. # r.resource :account, :namespace => "admin" do |account|
  201. # account.resources :preferences, :controller => "settings"
  202. # end
  203. #
  204. # :api: public
  205. def resource(name, *args, &block)
  206. name = name.to_s
  207. options = extract_options_from_args!(args) || {}
  208. params = { :controller => options.delete(:controller) || name.pluralize }
  209. member = { :new => :get, :edit => :get, :delete => :get }.merge(options.delete(:member) || {})
  210. options[:name_prefix] ||= nil # Don't use a name_prefix if not needed
  211. options[:resource_prefix] ||= nil # Don't use a resource_prefix if not needed
  212. options[:controller_prefix] ||= options.delete(:namespace)
  213. self.namespace(name, options).to(params) do |resource|
  214. # => show
  215. resource.match("(.:format)", :method => :get).to(:action => "show").
  216. name(name).register_resource(name)
  217. # => create
  218. resource.match("(.:format)", :method => :post).to(:action => "create")
  219. # => update
  220. resource.match("(.:format)", :method => :put).to(:action => "update")
  221. # => destroy
  222. resource.match("(.:format)", :method => :delete).to(:action => "destroy")
  223. member.each_pair do |action, method|
  224. action = action.to_s
  225. resource.match("/#{action}(.:format)", :method => method).to(:action => action).
  226. name(action, name).register_resource(name, action)
  227. end
  228. if block_given?
  229. builders = {}
  230. builders[:member] = lambda do |action, to, method|
  231. resource.match("/#{action}(.:format)", :method => method).to(:action => to).
  232. name(action, name).register_resource(name, action)
  233. end
  234. resource.options(:name_prefix => name, :resource_prefix => name).
  235. resource_block(builders, &block)
  236. end
  237. end
  238. end
  239. protected
  240. # :api: private
  241. def register_resource(*key)
  242. options = extract_options_from_args!(key) || {}
  243. key = [ @options[:resource_prefix], key ].flatten.compact
  244. identifiers = [ @options[:parent_keys], options[:identifiers] ]
  245. @route.resource = key
  246. @route.resource_identifiers = identifiers.flatten.compact.map { |id| id.to_sym }
  247. self
  248. end
  249. # :api: private
  250. def resource_block(builders, &block)
  251. behavior = ResourceBehavior.new(builders, @proxy, @conditions, @params, @defaults, @identifiers, @options, @blocks)
  252. with_behavior_context(behavior, &block)
  253. end
  254. def resource_options
  255. [:singular, :keys, :key, :controller, :member, :collection, :identify,
  256. :name_prefix, :resource_prefix, :controller_prefix, :namespace, :path]
  257. end
  258. end # Resources
  259. class Behavior
  260. include Resources
  261. end
  262. # Adding the collection and member methods to behavior
  263. class ResourceBehavior < Behavior #:nodoc:
  264. # :api: private
  265. def initialize(builders, *args)
  266. super(*args)
  267. @collection = builders[:collection]
  268. @member = builders[:member]
  269. end
  270. # :api: private
  271. def collection(action, options = {})
  272. action = action.to_s
  273. method = options[:method]
  274. to = options[:to] || action
  275. @collection[action, to, method]
  276. end
  277. # :api: private
  278. def member(action, options = {})
  279. action = action.to_s
  280. method = options[:method]
  281. to = options[:to] || action
  282. @member[action, to, method]
  283. end
  284. end
  285. end
  286. end