views:

671

answers:

3

I'm using a hideous pattern in my code and I know there must be a better way to do this. Help me rethink what I'm doing.

My website is a kind of discussion forum. All replies to discussions are done on the DiscussionsController#show page, inline.

Some replies are invalid, though - for example, if you try and post a reply that has no text in it, it returns you to DiscussionsController#show with an error message.

Here's a brief outline of how I've implemented this workflow:

  1. User goes to DiscussionsController#show. This template has a reply form on it. There is no explicit RepliesController#new action.
  2. User submits reply form, which is POSTed to replies_path and handled in RepliesController#create.
  3. RepliesController#create is unable to save the reply because it's invalid (validates_length_of in Reply invalidates the object).
  4. RepliesController#create puts the reply object in session[:new_reply] and redirects to the discussion_path the user came from.
  5. DiscussionsController#show handles the session object...

Like so:

if session[:new_reply]
  @new_reply = session[:new_reply]
  session.delete(:new_reply)
end

And now show.html.erb has a newly-regenerated @new_reply object to inspect for errors.

There's something obviously wrong with this - you shouldn't store entire objects inside of the session. But since the Reply object we attempted to save in RepliesController#create is never saved, how do I persist it between controller action calls?

Or if there's a larger design solution, feel free to share it. This is so ugly it's hurting me. Thanks.

+2  A: 

How about instead of redirecting, do render :action => "discussions/show" instead. I was going to say you'd have to set up the @discussion variable too, but this should be done since replies are nested resources inside of discussions, right?

Ryan Bigg
I'm doing that kind of things that way. Just be sure you have @discussion and @reply loaded.
klew
I thought of doing that, but there are two reasons I dislike the render approach:1. It changes the URL; I'd like to keep the illusion that you're still on the same page (because you are).2. I really don't like the fact that I'd have to duplicate the logic in DiscussionsController#show - making sure the same filters are applied, making sure that @discussion and other instance variables are chosen the same way (custom finds with conditions, etc.) - doesn't seem very DRY.
Raphomet
A: 

Instead of the session, I would store the reply in the flash, which is good for passing messages/objects across an action.

The controller would simply store the reply in the flash if it wasn't valid and redirect:

if @reply.save
   ...
else
   flash[:reply] = @reply
end

And your form logic on the discussions/show page would need to be a bit smarter and grab the reply in the flash or the new one (which I assume you are creating in the discussions/show action):

form_for(flash[:reply] || @reply) do
  ...
end
ry
I was disinclined to use this - Obie Fernandez, author of The Rails Way, says to use flash for messages and the session to pass values between actions. But the Rails documentation (http://api.rubyonrails.org/classes/ActionController/Flash.html) seems to imply that the flash is an appropriate place to store any kind of object temporarily. I think I'll try this for now and learn from it if it comes back to burn me. :)
Raphomet
A: 

I would say that the way you're doing it is reasonable. Not the cleanest but it is RESTful.

Radar's suggestion is great but in the current version of Rails, using render :action => "discussions/show" doesn't create the correct behavior, at least in my attempts, which have been without nested resources routing. Using render :controller => "discussions", :action => "show" yields another behavior that isn't what the poster is looking for.

Alex Kahn