- We design and build extraordinary applications for companies looking to make the next great idea a reality.
- learn more
Hide, Seek, and Stay Dry, part two: Controllers
![]()
When last we discussed this, I was adding some hide-and-show functionality all over a Rails web site. In Part 1, I worked over the view code, trying to transform the HTML into a series of Rails helpers that allowed any block of code in view to have hide-and-show functionality just by surrounding the block with a helper call.
This time around, we'll cover the controller side, and when we're done -- SPOILER ALERT -- any controller can become a hide-and-show server with as little as a single line of code.
In order for a controller to manage the hide and show, it needs two method. One of them will obviously be called hide, the other can't be called show, as that's already reserved by Rails and will cause nasty name crashes if you try it. I've gone with reveal in this code (I didn't think of seek until after I was done with the code, and in any case, seek has other connotations in a computer context...)
My first stab at these controller methods was for a controller where the blocks being controlled were inside a loop and each had a specific object associated with it. All the hide method needs to do is blank out the container part of the hide seek and redraw the header to use the other version of the icon. The header redraw is managed using the same helper methods defined in part one of this post. Like so:
def hide
@project = Project.find(params[:id])
render :update do |page|
page.replace_html dom_id(@project, :container), ""
page.replace_html dom_id(@project, :display),
collapse_expand_header(false, @project, "#{h @project.longname} Project")
end
end
Couple notes: I realize that the caption was moved to a header in the view part of this post... bear with me, I'll get there. Also, you could make the only updating in the header be the image itself, rather than the image and text -- I included the text to allow for the possibility that the text might change based in the visual status of the block. You could probably also do this by making the block invisible via JavaScript.
The reveal method is similar, but is responsible for gathering any data needed to draw the code again, and also for rendering the right partial. Hmm, on reflection, changing a JavaScript property might be cleaner... Anyway, here's the reveal method, with some of the details of gathering the data elided.
def reveal
@project = Project.find(params[:id])
@things = @project.things
render :update do |page|
page.replace_html dom_id(@project, :container),
:partial => 'one_project',
:locals => {:project => @project, :things => @things}
page.replace_html dom_id(@project, :display),
collapse_expand_header(true, @project, "#{h @project.longname} Project")
end
end
In trying to convert this to a general feature, the two questions are What parts are specific to each individual application, and How best to account for those differences?
In this case, trying to adapt this code to the next controller revealed the following points of freedom.
-
The class of the object passed as the id, which can be an ActiveRecord or a String in this case.
-
The name of the partial view to be rendered
-
The data passed to that partial view in its
localshash.
After writing the nearly identical methods in the second controller, I was ready to refactor. I decided to use some metaprogramming to build the methods. I added the following class method to ApplicationController. It builds the hide and reveal method, given one argument: the class of the expected object being passed in the id parameter.
def self.hide_and_reveal(klass = nil) define_method(:hide) do obj_from_id(klass) render :update do |page| page.replace_html smart_dom_id(@obj, :container), "" page.replace_html smart_dom_id(@obj, :display), collapse_expand_content(false, @obj) end end define_method(:reveal) do obj_from_id(klass) locals = reveal_locals(@obj) partial = reveal_partial(@obj) render :update do |page| page.replace_html smart_dom_id(@obj, :container), :partial => partial, :locals => locals page.replace_html smart_dom_id(@obj, :display), collapse_expand_content(true, @obj) end end end
Both hide and reveal are built inside define_method blocks. The hide part is almost identical, except that the object initialization is how handled by the obj_from_id method, which takes the class into account. If there's no class, it assumes the id is a string and returns it directly. Also, the smart_dom_id method defined in the previous blog post is used to allow for string objects:
def obj_from_id(klass = nil) @id = params[:id] @obj = if klass then klass.find(@id) else @id end end
In the reveal method block, the name of the partial and the definition of the locals are offloaded to other methods. Defaults are provided, but the expectation is that a participating controller will override one or the other method:
def reveal_locals(obj)
{}
end
def reveal_partial(obj)
'reveal_partial'
end
There are various other possibilities for this involving having the locals and partials passed as arguments or a block to hide_and_reveal, but in the end I decided this version was the most readable.
One thing to notice, though, is that the locals and partial name need to be assigned outside the render block. I believe that instance_eval or a similar trick is used to change the context inside the render block, with the upshot that controller methods aren't available to self inside the block. Again, that probably could have been worked around with some other tricks, but the version as written seemed clearest.
With that refactoring, the initial hide and reveal methods are replaced by the following:
hide_and_reveal(Project)
private
def reveal_locals(obj)
@things = obj.things
{:project => obj, :things => @things}
end
def reveal_partial(obj)
'one_project'
end
I like that. The hide_and_reveal method isn't completely general, but I was able to add it to the remainder of my controllers and views without further changes to either the view helpers or the controller method (although one case where I had to pass an ID and a type was a bit of a hack).
If you liked this, you might also enjoy my book Professional Ruby on Rails.
Topics: Ruby on Rails
Leave a comment
About Pathfinder
Recent
- Dealing With A Legacy
- Big Changes Underway at LinkedIn for Groups
- Four blatant iPhone usability blunders (and one constant annoyance)
- Flash/Flex physics engines and examples
- A Rails Story, Or An Engine That Really Could
- Data visualization and the art of conveying information
- What’s In Your Junk Drawer?
- Selling Git on the Business End
- IE8 Beta 2 Released
- Faster JavaScript for Firefox 3.1 Thru JIT
Archives
- September 2008
- August 2008
- July 2008
- June 2008
- May 2008
- April 2008
- March 2008
- February 2008
- January 2008
- December 2007
- November 2007
- October 2007
- September 2007
- August 2007
- July 2007
- June 2007
- May 2007
- April 2007
- March 2007
- February 2007
- January 2007
- December 2006
- November 2006
- October 2006
- September 2006
- August 2006
- July 2006
- June 2006
- May 2006
- April 2006
- March 2006

