resource_controller
resource_controller copied to clipboard
Rails RESTful controller abstraction plugin. This fork adds Rails 3 support and nothing else.
= Resource Controller
resource_controller makes RESTful controllers easier, more maintainable, and super readable. With the RESTful controller pattern hidden away, you can focus on what makes your controller special.
== About this fork
- This fork is not maintained, and we're not accepting pull requests. Please simply fork.
- This fork adds Rails 3 compatibility to the currently unmaintained {resource_controller}[http://github.com/jamesgolick/resource_controller]. You should use the original resource_controller for Rails 2 applications.
- This fork adds no new features.
- This fork is available on RubyGems as +makandra_resource_controller+ for convenience.
== Installation
First install the gem:
sudo gem install makandra_resource_controller
Then add this to your +Gemfile+:
gem 'makandra_resource_controller'
Finally lock the gem version:
bundle install
= Usage
Creating a basic RESTful controller is as easy as...
class PostsController < ResourceController::Base end
...or if you prefer, you can use the method-call syntax. If you need to inherit from some other class, this syntax is definitely for you:
class PostsController < ApplicationController resource_controller end
Both syntaxes are identical in their behavior. Just make sure you call resource_controller before you use any other r_c functionality in your controller.
Nobody just uses the default RESTful controller, though. resource_controller provides a simple API for customizations.
== Action Lifecycle
It's really easy to make changes to the lifecycle of your actions.
Note: We had to call the new accessor "new_action", since new is somewhat reserved in ruby.
=== Before and After
class ProjectsController < ResourceController::Base
new_action.before do
3.times { object.tasks.build }
end
create.after do
object.creator = current_user
end
end
=== Flash
class ProjectsController < ResourceController::Base create.flash "Can you believe how easy it is to use resource_controller? Neither could I!" end
=== respond_to
You can add to what's already there...
class ProjectsController < ResourceController::Base
create.wants.js { render :template => "show.rjs" }
end
Or you can create a whole new block. This syntax destroys everything that's there, and starts again...
class ProjectsController < ResourceController::Base
create.response do |wants|
wants.html
wants.js { render :template => "show.rjs" }
end
end
If you have a nested resource and want to redirect to the parent after create/update and destroy you can do this in the object controller
class CommentsController < ResourceController::Base belongs_to :post
create.wants.html { redirect_to smart_url(parent_url_options) }
update.wants.html { redirect_to smart_url(parent_url_options) }
destroy.wants.html { redirect_to smart_url(parent_url_options) }
end
=== Scoping
Because sometimes you want to make a bunch of customizations at once, most of the helpers accept blocks that make grouping calls really easy. Is it a DSL? Maybe; maybe not. But, it's definitely awesome.
With actions that can fail, the scoping defaults to success. That means that create.flash == create.success.flash.
class ProjectsController < ResourceController::Base
create do
flash "Object successfully created!"
wants.js { render :template => "show.rjs" }
failure.wants.js { render :template => "display_errors.rjs" }
end
destroy do
flash "You destroyed your project. Good work."
failure do
flash "You cannot destroy that project. Stop trying!"
wants.js { render :template => "display_errors.rjs" }
end
end
end
== Singleton Resource
If you want to create a singleton RESTful controller inherit from ResourceController::Singleton.
class AccountsController < ResourceController::Singleton end
...or if need to inherit from some other class:
class AccountsController < ApplicationController resource_controller :singleton end
Note: This type of controllers handle a single resource only so the index action and all the collection helpers (collection_url, collection_path...) are not available for them.
Loading objects in singletons is similar to plural controllers with one exception. For non-nested singleton controllers you should override the object method as it defaults to nil for them.
class AccountsController < ResourceController::Singleton private def object @object ||= Account.find(session[:account_id]) end end
In other cases you can use the default logic and override it only if you use permalinks or anything special.
Singleton nesting with both :has_many and :has_one associations is provided...
map.resource :account, :has_many => :options # /account/options, account is a singleton parent map.resources :users, :has_one => :image # /users/1/image, image is a singleton child
If you have the :has_many association with a singleton parent remember to override parent_object for your :has_many controller as it returns nil by default in this case.
class OptionsController < ResourceController::Base belongs_to :account
protected
def parent_object
Account.find(session[:account_id])
end
end
== Helpers (ResourceController::Helpers)
=== Loading objects
You want to add something like pagination to your controller...
class PostsController < ResourceController::Base private def collection @collection ||= end_of_association_chain.find(:all, :page => {:size => 10, :current => params[:page]}) end end
Or maybe you used a permalink...
class PostsController < ResourceController::Base private def object @object ||= end_of_association_chain.find_by_permalink(param) end end
=== Building objects
Maybe you have some alternative way of building objects...
class PostsController < ResourceController::Base private def build_object @object ||= end_of_association_chain.build_my_object_some_funky_way object_params end end
...and there are tons more helpers in the ResourceController::Helpers
== Nested Resources
Nested controllers can be a pain, especially if routing is such that you may or may not have a parent. Not so with Resource Controller.
class CommentsController < ResourceController::Base belongs_to :post end
All of the finding, and creation, and everything will be done at the scope of the post automatically.
== Namespaced Resources
...are handled automatically, and any namespaces are always available, symbolized, in array form @ ResourceController::Helpers#namespaces
== Polymorphic Resources
Everything, including url generation is handled completely automatically. Take this example...
comment.rb
class Comment belongs_to :commentable, :polymorphic => true end
comments_controller.rb
class CommentsController < ResourceController::Base belongs_to :post, :product, :user end Note: Your model doesn't have to be polymorphic in the ActiveRecord sense. It can be associated in whichever way you want.
routes.rb
map.resources :posts, :has_many => :comments map.resources :products, :has_many => :comments map.resources :users, :has_many => :comments
All you have to do is that, and r_c will infer whichever relationship is present, and perform all the actions at the scope of the parent object.
=== Parent Helpers
You also get some helpers for reflecting on your parent.
parent? # => true/false is there a parent present? parent_type # => :post parent_model # => Post parent_object # => @post
=== Non-standard resource names
resource_controller supports overrides for every non-standard configuration of resources.
The most common example is where the resource has a different name than the associated model. Simply overriding the model_name helper will get resource_controller working with your model.
map.resources :tags ... class PhotoTag < ActiveRecord::Base ... class TagsController < ResourceController::Base private def model_name 'photo_tag' end end
In the above example, the variable, and params will be set to @tag, @tags, and params[:tag]. If you'd like to change that, override object_name.
def object_name 'photo_tag' end
If you're using a non-standard controller name, but everything else is standard, overriding resource_name will propagate through all of the other helpers.
map.resources :tags, :controller => "somethings" ... class Tag < ActiveRecord::Base ... class SomethingsController < ResourceController::Base private def resource_name 'tag' end end
Finally, the route_name helper is used by Urligence to determine which url helper to call, so if you have non-standard route names, override it.
map.resources :tags, :controller => "taggings" ... class Taggings < ActiveRecord::Base ... class TaggingsController < ResourceController::Base private def route_name 'tag' end end
== Url Helpers
Thanks to Urligence, you also get some free url helpers.
No matter what your controller looks like...
[edit_|new_]object_url # is the equivalent of saying [edit_|new_]post_url(@post) [edit_|new_]object_url(some_other_object) # allows you to specify an object, but still maintain any paths or namespaces that are present
collection_url # is like saying posts_url
Url helpers are especially useful when working with polymorphic controllers.
/posts/1/comments
object_url # => /posts/1/comments/#{@comment.to_param} object_url(comment) # => /posts/1/comments/#{comment.to_param} edit_object_url # => /posts/1/comments/#{@comment.to_param}/edit collection_url # => /posts/1/comments
/products/1/comments
object_url # => /products/1/comments/#{@comment.to_param} object_url(comment) # => /products/1/comments/#{comment.to_param} edit_object_url # => /products/1/comments/#{@comment.to_param}/edit collection_url # => /products/1/comments
/comments
object_url # => /comments/#{@comment.to_param} object_url(comment) # => /comments/#{comment.to_param} edit_object_url # => /comments/#{@comment.to_param}/edit collection_url # => /comments
Or with namespaced, nested controllers...
/admin/products/1/options
object_url # => /admin/products/1/options/#{@option.to_param} object_url(option) # => /admin/products/1/options/#{option.to_param} edit_object_url # => /admin/products/1/options/#{@option.to_param}/edit collection_url # => /admin/products/1/options
You get the idea. Everything is automagical! All parameters are inferred.
== Credits
- resource_controller was created by {James Golick}[http://jamesgolick.com]
- It was made compatible with Rails 3 by Brian Quinn, Derek Kastner and Sean Schofield in the {BDQ fork}[http://github.com/BDQ/resource_controller]
- Changes in this makandra fork by {Henning Koch}[http://makandra.com/]
== License
resource_controller is available under the {MIT License}[http://en.wikipedia.org/wiki/MIT_License]