README.rdoc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. === Deprecation notice
  2. Since Rails 3 came out, I have no longer used Inherited Resources. I have found that the
  3. responders abstraction and custom Rails generators offer the perfect balance between
  4. hiding and showing too much logic. That said, I suggest developers to make use of the
  5. responders gem (at https://github.com/plataformatec/responders) and no longer use Inherited
  6. Resources.
  7. == Inherited Resources
  8. Inherited Resources speeds up development by making your controllers inherit
  9. all restful actions so you just have to focus on what is important. It makes
  10. your controllers more powerful and cleaner at the same time.
  11. In addition to making your controllers follow a pattern, it helps you to write better
  12. code by following fat models and skinny controllers convention. There are
  13. two screencasts available besides this README:
  14. * http://railscasts.com/episodes/230-inherited-resources
  15. * http://akitaonrails.com/2009/09/01/screencast-real-thin-restful-controllers-with-inherited-resources
  16. == Installation
  17. === Rails 3
  18. You can let bundler install Inherited Resources by adding this line to your application's Gemfile:
  19. gem 'inherited_resources'
  20. And then execute:
  21. bundle install
  22. Or install it yourself with:
  23. gem install inherited_resources
  24. === Rails 2.3.x
  25. If you want to use the Rails 2.3.x version, you should install:
  26. gem install inherited_resources --version=1.0.6
  27. Or checkout from the v1.0 branch:
  28. http://github.com/josevalim/inherited_resources/tree/v1.0
  29. == HasScope
  30. Since Inherited Resources 1.0, has_scope is not part of its core anymore but
  31. a gem dependency. Be sure to check the documentation to see how you can use it:
  32. http://github.com/plataformatec/has_scope
  33. And it can be installed as:
  34. gem install has_scope
  35. == Responders
  36. Since Inherited Resources 1.0, responders are not part of its core anymore,
  37. but is set as Inherited Resources dependency and it's used by default by
  38. InheritedResources controllers. Be sure to check the documentation to see
  39. how it will change your application:
  40. http://github.com/plataformatec/responders
  41. And it can be installed with:
  42. gem install responders
  43. Using responders will set the flash message to :notice and :alert. You can change
  44. that through the following configuration value:
  45. InheritedResources.flash_keys = [ :success, :failure ]
  46. Notice the CollectionResponder won't work with InheritedResources, as InheritedResources hardcodes the redirect path based on the current scope (like belongs to, polymorphic associations, etc).
  47. == Basic Usage
  48. To use Inherited Resources you just have to inherit (duh) it:
  49. class ProjectsController < InheritedResources::Base
  50. end
  51. And all actions are defined and working, check it! Your projects collection
  52. (in the index action) is still available in the instance variable @projects
  53. and your project resource (all other actions) is available as @project.
  54. The next step is to define which mime types this controller provides:
  55. class ProjectsController < InheritedResources::Base
  56. respond_to :html, :xml, :json
  57. end
  58. You can also specify them per action:
  59. class ProjectsController < InheritedResources::Base
  60. respond_to :html, :xml, :json
  61. respond_to :js, :only => :create
  62. respond_to :iphone, :except => [ :edit, :update ]
  63. end
  64. For each request, it first checks if the "controller/action.format" file is
  65. available (for example "projects/create.xml") and if it's not, it checks if
  66. the resource respond to :to_format (in this case, :to_xml). Otherwise returns 404.
  67. Another option is to specify which actions the controller will inherit from
  68. the InheritedResources::Base:
  69. class ProjectsController < InheritedResources::Base
  70. actions :index, :show, :new, :create
  71. end
  72. Or:
  73. class ProjectsController < InheritedResources::Base
  74. actions :all, :except => [ :edit, :update, :destroy ]
  75. end
  76. In your views, you will get the following helpers:
  77. resource #=> @project
  78. collection #=> @projects
  79. resource_class #=> Project
  80. As you might expect, collection (@projects instance variable) is only available
  81. on index actions.
  82. If for some reason you cannot inherit from InheritedResources::Base, you can
  83. call inherit_resources in your controller class scope:
  84. class AccountsController < ApplicationController
  85. inherit_resources
  86. end
  87. == Overwriting defaults
  88. Whenever you inherit from InheritedResources, several defaults are assumed.
  89. For example you can have an AccountsController for account management while the
  90. resource is a User:
  91. class AccountsController < InheritedResources::Base
  92. defaults :resource_class => User, :collection_name => 'users', :instance_name => 'user'
  93. end
  94. In the case above, in your views you will have @users and @user variables, but
  95. the routes used will still be accounts_url and account_url. If you plan also to
  96. change the routes, you can use :route_collection_name and :route_instance_name.
  97. Namespaced controllers work out of the box, but if you need to specify a
  98. different route prefix you can do the following:
  99. class Administrators::PeopleController < InheritedResources::Base
  100. defaults :route_prefix => 'admin'
  101. end
  102. Then your named routes will be: 'admin_people_url', 'admin_person_url' instead
  103. of 'administrators_people_url' and 'administrators_person_url'.
  104. If you want to customize how resources are retrieved you can overwrite
  105. collection and resource methods. The first is called on index action and the
  106. second on all other actions. Let's suppose you want to add pagination to your
  107. projects collection:
  108. class ProjectsController < InheritedResources::Base
  109. protected
  110. def collection
  111. @projects ||= end_of_association_chain.paginate(:page => params[:page])
  112. end
  113. end
  114. The end_of_association_chain returns your resource after nesting all associations
  115. and scopes (more about this below).
  116. InheritedResources also introduces another method called begin_of_association_chain.
  117. It's mostly used when you want to create resources based on the @current_user and
  118. you have urls like "account/projects". In such cases you have to do
  119. @current_user.projects.find or @current_user.projects.build in your actions.
  120. You can deal with it just by doing:
  121. class ProjectsController < InheritedResources::Base
  122. protected
  123. def begin_of_association_chain
  124. @current_user
  125. end
  126. end
  127. == Overwriting actions
  128. Let's suppose that after destroying a project you want to redirect to your
  129. root url instead of redirecting to projects url. You just have to do:
  130. class ProjectsController < InheritedResources::Base
  131. def destroy
  132. super do |format|
  133. format.html { redirect_to root_url }
  134. end
  135. end
  136. end
  137. You are opening your action and giving the parent action a new behavior. On
  138. the other hand, I have to agree that calling super is not very readable. That's
  139. why all methods have aliases. So this is equivalent:
  140. class ProjectsController < InheritedResources::Base
  141. def destroy
  142. destroy! do |format|
  143. format.html { redirect_to root_url }
  144. end
  145. end
  146. end
  147. Since most of the time when you change a create, update or destroy
  148. action you do so because you want to to change its redirect url, a shortcut is
  149. provided. So you can do:
  150. class ProjectsController < InheritedResources::Base
  151. def destroy
  152. destroy!{ root_url }
  153. end
  154. end
  155. If you simply want to change the flash message for a particular action, you can
  156. pass the message to the parent action using the keys :notice and :alert (as you
  157. would with flash):
  158. class ProjectsController < InheritedResources::Base
  159. def create
  160. create!(:notice => "Dude! Nice job creating that project.")
  161. end
  162. end
  163. You can still pass the block to change the redirect, as mentioned above:
  164. class ProjectsController < InheritedResources::Base
  165. def create
  166. create!(:notice => "Dude! Nice job creating that project.") { root_url }
  167. end
  168. end
  169. Now let's suppose that before create a project you have to do something special
  170. but you don't want to create a before filter for it:
  171. class ProjectsController < InheritedResources::Base
  172. def create
  173. @project = Project.new(params[:project])
  174. @project.something_special!
  175. create!
  176. end
  177. end
  178. Yes, it's that simple! The nice part is since you already set the instance variable
  179. @project, it will not build a project again.
  180. Before we finish this topic, we should talk about one more thing: "success/failure
  181. blocks". Let's suppose that when we update our project, in case of failure, we
  182. want to redirect to the project url instead of re-rendering the edit template.
  183. Our first attempt to do this would be:
  184. class ProjectsController < InheritedResources::Base
  185. def update
  186. update! do |format|
  187. unless @project.errors.empty? # failure
  188. format.html { redirect_to project_url(@project) }
  189. end
  190. end
  191. end
  192. end
  193. Looks too verbose, right? We can actually do:
  194. class ProjectsController < InheritedResources::Base
  195. def update
  196. update! do |success, failure|
  197. failure.html { redirect_to project_url(@project) }
  198. end
  199. end
  200. end
  201. Much better! So explaining everything: when you give a block which expects one
  202. argument it will be executed in both scenarios: success and failure. But if you
  203. give a block that expects two arguments, the first will be executed only in
  204. success scenarios and the second in failure scenarios. You keep everything
  205. clean and organized inside the same action.
  206. == Smart redirects
  207. Although the syntax above is a nice shortcut, you won't need to do it frequently
  208. because (since version 1.2) Inherited Resources has smart redirects. Redirects
  209. in actions calculates depending on the existent controller methods.
  210. Redirects in create and update actions calculates in the following order resource_url,
  211. collection_url, parent_url (which we are going to see later), and root_url. Redirect
  212. in destroy action calculate in following order collection_url, parent_url, root_url.
  213. Example:
  214. class ButtonsConntroller < InheritedResources::Base
  215. belongs_to :window
  216. actions :all, :except => [:show, :index]
  217. end
  218. This controller redirect to parent window after all CUD actions.
  219. == Success and failure scenarios on destroy
  220. The destroy action can also fail, this usually happens when you have a
  221. before_destroy callback in your model which returns false. However, in
  222. order to tell InheritedResources that it really failed, you need to add
  223. errors to your model. So your before_destroy callback on the model should
  224. be something like this:
  225. def before_destroy
  226. if cant_be_destroyed?
  227. errors.add(:base, "not allowed")
  228. false
  229. end
  230. end
  231. == Belongs to
  232. Finally, our Projects are going to get some Tasks. Then you create a
  233. TasksController and do:
  234. class TasksController < InheritedResources::Base
  235. belongs_to :project
  236. end
  237. belongs_to accepts several options to be able to configure the association.
  238. For example, if you want urls like /projects/:project_title/tasks, you can
  239. customize how InheritedResources find your projects:
  240. class TasksController < InheritedResources::Base
  241. belongs_to :project, :finder => :find_by_title!, :param => :project_title
  242. end
  243. It also accepts :route_name, :parent_class and :instance_name as options.
  244. Check the lib/inherited_resources/class_methods.rb for more.
  245. == Nested belongs to
  246. Now, our Tasks get some Comments and you need to nest even deeper. Good
  247. practices says that you should never nest more than two resources, but sometimes
  248. you have to for security reasons. So this is an example of how you can do it:
  249. class CommentsController < InheritedResources::Base
  250. nested_belongs_to :project, :task
  251. end
  252. If you need to configure any of these belongs to, you can nest them using blocks:
  253. class CommentsController < InheritedResources::Base
  254. belongs_to :project, :finder => :find_by_title!, :param => :project_title do
  255. belongs_to :task
  256. end
  257. end
  258. Warning: calling several belongs_to is the same as nesting them:
  259. class CommentsConroller < InheritedResources::Base
  260. belongs_to :project
  261. belongs_to :task
  262. end
  263. In other words, the code above is the same as calling nested_belongs_to.
  264. == Polymorphic belongs to
  265. We can go even further. Let's suppose our Projects can now have Files, Messages
  266. and Tasks, and they are all commentable. In this case, the best solution is to
  267. use polymorphism:
  268. class CommentsController < InheritedResources::Base
  269. belongs_to :task, :file, :message, :polymorphic => true
  270. # polymorphic_belongs_to :task, :file, :message
  271. end
  272. You can even use it with nested resources:
  273. class CommentsController < InheritedResources::Base
  274. belongs_to :project do
  275. belongs_to :task, :file, :message, :polymorphic => true
  276. end
  277. end
  278. The url in such cases can be:
  279. /project/1/task/13/comments
  280. /project/1/file/11/comments
  281. /project/1/message/9/comments
  282. When using polymorphic associations, you get some free helpers:
  283. parent? #=> true
  284. parent_type #=> :task
  285. parent_class #=> Task
  286. parent #=> @task
  287. Right now, Inherited Resources is limited and does not allow you
  288. to have two polymorphic associations nested.
  289. == Optional belongs to
  290. Later you decide to create a view to show all comments, independent if they belong
  291. to a task, file or message. You can reuse your polymorphic controller just doing:
  292. class CommentsController < InheritedResources::Base
  293. belongs_to :task, :file, :message, :optional => true
  294. # optional_belongs_to :task, :file, :message
  295. end
  296. This will handle all those urls properly:
  297. /comment/1
  298. /tasks/2/comment/5
  299. /files/10/comment/3
  300. /messages/13/comment/11
  301. This is treated as a special type of polymorphic associations, thus all helpers
  302. are available. As you expect, when no parent is found, the helpers return:
  303. parent? #=> false
  304. parent_type #=> nil
  305. parent_class #=> nil
  306. parent #=> nil
  307. == Singletons
  308. Now we are going to add manager to projects. We say that Manager is a singleton
  309. resource because a Project has just one manager. You should declare it as
  310. has_one (or resource) in your routes.
  311. To declare an association as singleton, you just have to give the :singleton
  312. option.
  313. class ManagersController < InheritedResources::Base
  314. belongs_to :project, :singleton => true
  315. # singleton_belongs_to :project
  316. end
  317. It will deal with everything again and hide the action :index from you.
  318. == Namespaced Controllers
  319. Namespaced controllers works out the box.
  320. class Forum::PostsController < InheritedResources::Base
  321. end
  322. Inherited Resources prioritizes the default resource class for the namespaced controller in
  323. this order:
  324. Forum::Post
  325. ForumPost
  326. Post
  327. == URL Helpers
  328. When you use InheritedResources it creates some URL helpers.
  329. And they handle everything for you. :)
  330. # /posts/1/comments
  331. resource_url # => /posts/1/comments/#{@comment.to_param}
  332. resource_url(comment) # => /posts/1/comments/#{comment.to_param}
  333. new_resource_url # => /posts/1/comments/new
  334. edit_resource_url # => /posts/1/comments/#{@comment.to_param}/edit
  335. edit_resource_url(comment) #=> /posts/1/comments/#{comment.to_param}/edit
  336. collection_url # => /posts/1/comments
  337. parent_url # => /posts/1
  338. # /projects/1/tasks
  339. resource_url # => /projects/1/tasks/#{@task.to_param}
  340. resource_url(task) # => /projects/1/tasks/#{task.to_param}
  341. new_resource_url # => /projects/1/tasks/new
  342. edit_resource_url # => /projects/1/tasks/#{@task.to_param}/edit
  343. edit_resource_url(task) # => /projects/1/tasks/#{task.to_param}/edit
  344. collection_url # => /projects/1/tasks
  345. parent_url # => /projects/1
  346. # /users
  347. resource_url # => /users/#{@user.to_param}
  348. resource_url(user) # => /users/#{user.to_param}
  349. new_resource_url # => /users/new
  350. edit_resource_url # => /users/#{@user.to_param}/edit
  351. edit_resource_url(user) # => /users/#{user.to_param}/edit
  352. collection_url # => /users
  353. parent_url # => /
  354. Those urls helpers also accepts a hash as options, just as in named routes.
  355. # /projects/1/tasks
  356. collection_url(:page => 1, :limit => 10) #=> /projects/1/tasks?page=1&limit=10
  357. In polymorphic cases, you can also give the parent as parameter to collection_url.
  358. Another nice thing is that those urls are not guessed during runtime. They are
  359. all created when your application is loaded (except for polymorphic
  360. associations, that relies on Rails polymorphic_url).
  361. == Custom actions
  362. Since version 1.2, Inherited Resources allows you to define custom actions in controller:
  363. class ButtonsController < InheritedResources::Base
  364. custom_actions :resource => :delete, :collection => :search
  365. end
  366. This code creates delete and search actions in controller (they behaves like show and
  367. index actions accordingly). Also, it will produce delete_resource_{path,url} and
  368. search_resources_{path,url} url helpers.
  369. == What about views?
  370. Sometimes just DRYing up the controllers is not enough. If you need to DRY up your views,
  371. check this Wiki page:
  372. https://github.com/josevalim/inherited_resources/wiki/Views-Inheritance
  373. Notice that Rails 3.1 ships with view inheritance built-in.
  374. == Some DSL
  375. For those DSL lovers, InheritedResources won't leave you alone. You can overwrite
  376. your success/failure blocks straight from your class binding. For it, you just
  377. need to add a DSL module to your application controller:
  378. class ApplicationController < ActionController::Base
  379. include InheritedResources::DSL
  380. end
  381. And then you can rewrite the last example as:
  382. class ProjectsController < InheritedResources::Base
  383. update! do |success, failure|
  384. failure.html { redirect_to project_url(@project) }
  385. end
  386. end
  387. == Bugs and Feedback
  388. If you discover any bugs, please describe it in the issues tracker, including Rails and Inherited Resources versions.
  389. Questions are better handled on StackOverflow.
  390. Copyright (c) 2011 José Valim http://blog.plataformatec.com.br
  391. See the attached MIT License.