I’ve always been annoyed at the lack of maintainability that comes with using multiple resource controllers in my Rails apps. Each generated resource controller clocks in at 85 lines, and most of mine only differ from each other by a line or two – an added before_filter or a change in the url that the users is redirected to after the creation of a new Widget. Not very DRY. When coming back to each one of these controllers to add or adjust features, it takes me entirely too much time to sift through the stock 85 lines and find my application-specific behavior.
Enter resource_this
git clone git://github.com/jnewland/resource_this.gitresource_this aims to solve this maintainability problem by making your stock resource controllers look like this:
class PostsController < ActionController::Base
resource_this
endBehind the scenes, this code is generated:
class PostsController < ActionController::Base
before_filter :load_post, :only => [ :show, :edit, :update, :destroy ]
before_filter :load_posts, :only => [ :index ]
before_filter :new_post, :only => [ :new ]
before_filter :create_post, :only => [ :create ]
before_filter :update_post, :only => [ :update ]
before_filter :destroy_post, :only => [ :destroy ]
protected
def load_post
@post = Post.find(params[:id])
end
def new_post
@post = Post.new
end
def create_post
@post = Post.new(params[:post])
@created = @post.save
end
def update_post
@updated = @post.update_attributes(params[:post])
end
def destroy_post
@post = @post.destroy
end
def load_posts
@posts = Post.find(:all)
end
public
def index
respond_to do |format|
format.html
format.xml { render :xml => @posts }
format.js
end
end
def show
respond_to do |format|
format.html
format.xml { render :xml => @post }
format.js
end
end
def new
respond_to do |format|
format.html { render :action => :edit }
format.xml { render :xml => @post }
format.js
end
end
def create
respond_to do |format|
if @created
flash[:notice] = 'Post was successfully created.'
format.html { redirect_to @post }
format.xml { render :xml => @post, :status => :created, :location => @post }
format.js
else
format.html { render :action => :new }
format.xml { render :xml => @post.errors, :status => :unprocessable_entity }
format.js
end
end
end
def edit
respond_to do |format|
format.html
format.js
end
end
def update
respond_to do |format|
if @updated
flash[:notice] = 'Post was successfully updated.'
format.html { redirect_to @post }
format.xml { head :ok }
format.js
else
format.html { render :action => :edit }
format.xml { render :xml => @post.errors, :status => :unprocessable_entity }
format.js
end
end
end
def destroy
respond_to do |format|
format.html { redirect_to :action => posts_url }
format.xml { head :ok }
format.js
end
end
endNested resources like so:
class CommentsController < ActionController::Base
resource_this :nested => [:posts]
endThis generates a very similar controller to the one above with adjusted redirects and one additional before_filter / loader method pair to grab the parent resource. In this case:
before_filter :load_post
def load_post
@post = Post.find(params[:post_id])
endThe separation of logic – DB operations in before_filters, rendering in the standard resource controller methods – makes this approach ridiculously easy to customize. Need to load an additional object for the :show action? Slap another before_filter on it. Need to change the path that the :update action redirects to? Override the :update action with your new rendering behavior. And this customized behavior sticks out like a sore thumb – making it infinitely easier to maintain.
Oh, there’s also a generator:
./script/generate resource_this FooKlass [title:string body:text]This works just like the resource generator, with the addition of the resource_this line to your controller and a functional test. No views are generated, so the test focuses on the XML behavior of this controller.
Contributing
resource_this is hosted on GitHub, so feel free to fork it and send a pull request with your changes.

Comments
Leave a response
This looks great! I would only suggest that you add some flash hooks so that I can use which ever internationalization code I want for the “Post was successfully updated.” bits.
How does this compare with Caitlin’s make resourceful?
The issue I had with make resourceful was that it added an extra layer of complexity on top of the controllers, so you had to know something extra on top of controller logic. It worked well when a project was young, but the controllers soon filled up with exceptions to the plugin as the project matured. Debugging was also difficult as too much of the logic was hidden.
I am aware of make_resourceful. My plugin was designed with two main goals:
To me, overriding the rendering behavior by redefining the
:createor:updatemethods is easier and more maintainable than using a custom syntax. Similarly,before_filter/after_filteris already supported in action controller – why create another confusing syntax for it? I’ve just rolled this plugin out in 2 apps w/ several people working on them, and it’s going well so far.Looks like the only feature I’m missing from make_resourceful is the flash customization. That’ll be coming soon.
I guess the only thing I wonder about is all the filters. I’m thinking I heard that using a lot of before filters was slow. Can’t remember where though so maybe it was a lie. :)
Other than that, it looks nice. I’ll try it out on my next app.
Meh, skip the flash customization bit from m_r. It was an ill-conceived feature and is slated for removal.
Nice one.
If I want to the new method for html formats to do action => :new (instead of :edit as it does now by default), what would be the way to do that?
Another question: shouldn’t you return true in create_post and update_post? Suppose @created is set to false, processing stops, and no controller action is run.
Jimmy – I’ve fixed the
create_postandupdate_postbehavior. Good catch.svn upfor the fix.Here’s an example of overriding the
:newaction:http://pastie.caboo.se/99379
Personally, I prefer the
:newmethod to render the:editaction and useActiveRecord::Base#new_record?to display/hide certain chunks of the form.If you prefer rendering the ‘edit’ template for action => new, why do you then render the ‘new’ template when a save fails in action => create?
That doesn’t seem consistent.
Surely you either redirect to the new action, or render the edit template.
Neil – fixed. Good catch.
Thanks
One other thing that popped up while I was using this today.
the ‘render xml => @post’ trick only works in edge rails. 1.2.3 still needs ’@post.to_xml’ or it fails with a horrible error.
Could you add ‘to_xml’ for maximum compatibility?
Any plans to have resource namespace support (map.namespace :admin and Admin::UsersController)?
Try this for namespaced controllers:
resource_this :class_name => "Entry", :path_prefix => "admin_"Just path prefix is enough, I just missed the underscore at the end of it.
Jesse, great work here! The only thing I could suggest right now is a few more opportunities to override the default behavior without having to re-write the actions so often.
Some methods like create_success_message and create_failure_message (or similar) would allow flash message customization pretty easily. Same with URLs for redirection.
Something else that comes to mind is that I may not want to always respond to xml or js. Is there anything in the API for restricting the number of formats?
Sure, I get it, I can override the method and do whatever I want, but most of the time I’d only want to change a fraction of what’s there.
Is anyone else trying to use this plugin on a nested resource that also acts_as_list?
In the create#{singular_name} method, I get an SQL exception on save because the parent ID isn’t set prior to saving.
If I change to code (resource_this.rb line #69) from:
@#{singular_name} = #{finder_base}.new(params[:#{singular_name}])
to:
@#{singular_name} = #{finder_base}.build(params[:#{singular_name}])
then it works fine. Obviously, now this won’t work for non-nested resources. So I set a variable, new_method, to be either “new” or “build” depending on whether the :nested property is set and change the create code to:
@#{singular_name} = #{finder_base}.#{new_method}(params[:#{singular_name}])
and everything seems to be working fine.