More tweaking

September 5, 2007

At the moment, i’m avoiding the nasty video conversion and deployment problems, and just focussing on improving Reeplies. Feels like shirking but hey (i’m also avoiding writing up my project). The next thing i’m going to do is to add a page for “Your draft Reeplies”, ie “stories where user_id = session[:user].id and complete = 0”. Ideally i’d do a tabbed page but this is unneccessary polish at this stage.

The drafts page is simply going to be a list of stories. To stay DRY i want to use the story_list partial, but there’s a problem – currently the story list partial defines what makes it into the list of stories, which won’t work for drafts as the criteria are different. I think it would be better if story_list works with a variable called ‘story’, and i define ‘for story in @some_stories’ before calling the partial.

Looking in my copy of ‘Agile web dev with RoR’, i see the perfect answer:  if you call a partial and pass a :collection value through, then the contents of the partial get called for every item in the collection.  In order for this to work, the name of the individual items in the partial should be the same as the name of the partial.  To avoid rewriting the partial, where i’m constantly referring to ‘story’, i’m going to rename the partial to be ‘story’.

So, the new _story partial is the same, but minus the for and end lines.  I now call i like this from article/show:

<%= render :partial => ‘story/story’, :collection => @article.complete_stories %>

and like this from story/list_drafts:

<%= render :partial => ‘story/story’, :collection = @drafts %>
I’ve done the same for the other partials that list stuff – i’ve moved the conditions for what gets listed outside of the partial, as above.

OK.  Next step is to show the drafts. First, i set up a new relation for User, ‘drafts’, which is all stories belonging to them where complete = false. Then, if i have @user, i can just say @user.drafts. In user.rb:

has_many :drafts,
:class_name => “Story”,
:conditions => [ ‘complete = ?’, false ],
:order => “added_at DESC”

Next, i add a link to the “logged in” partial, so you see

You are logged in as patton Log out Change password Your Draft Reeplies

at the top of the page. This is the link:

<%= link_to ‘Your Draft Reeplies’, :controller => “story”, :action => “list_drafts” %>

Next i need a story controller method to set up a couple of variables:

def list_drafts
@user = User.find(session[:user].id)
@drafts = @user.drafts

Then finally the view page for the list of drafts:

<%= render :partial => ‘shared/save_page’ %>
<%= render :partial => ‘user/login_manager’ %>

<span class=”page_header”>Your draft Reeplies</span>

<% @stories = @drafts%>
<%= render :partial => ‘story/story_list’ %>

Looks good, but there’s a problem: when you delete a draft, it takes you to the article page that the draft reeply was associated with. I want it to stay on the draft reeplies page.

I’m already saving the page with the save_page partial, which simply has this:

<% session[:return_to] = request.request_uri %></div>

So, it should simply be a case of making sure that the delete action goes to the saved page after doing it’s stuff. The story/delete action looks like this:

def delete
@story = Story.find(params[:id])
@article = @story.article
redirect_to :controller => “article”, :action => “show”, :id => @article

session[:return_to] is used by the application controller method redirect_to_stored. So, if i just change the last line to


then it should work fine. It does! Nice.

Did a little bit of tidying up with partials – the login_manager is now part of the standard layout, and has a link, which goes to the article list page. At some point it would be nice to replace this with a little logo.

Another thing i’ve been meaning to do for ages is to put a confirmation dialog on delete actions. This is simple enough: along with the parameters for a link_to, we pass a k-v pair with the key :confirm, and the value for whatever we want to be displayed for the confirmation question, eg “Delete this Reeply?”. One gotcha with this is that it goes in a different hash to the one that has :controller, :action, and :id, so we need to put those in { }.

(personally i think rails would be clearer if we always used the { }, but the convention is to omit them whenever possible. To me that highlights the problem with cases like this, where it breaks if you omit them).

Anyway, the link to delete now looks like this:

<span class=”option_link”><%= link_to “Delete \”#{story.title}\””,
{ :controller => “story”, :action => “delete”, :id => },
:confirm => “Delete Reeeply: are you sure?” %></span>

I added this option into the story show page, and of course it crashed because it was trying to go back to the show page for a story that doesn’t exist. So, when i call delete from story, i need to go to the article show page instead.

The way i’ve done this (which may not be the most elegant way) is to do a regexp check on session[:return_to] in the controller, and reroute to the article/show if it was going to go back to story/show:

if session[:return_to].match(/reeply\/show/)
redirect_to :controller => “article”, :action => “show”, :id => @article

Ah, wait – what if they came to story/show from the drafts page? Deleting the story should take you back to the drafts page. Hmm.

I wonder if i could handle this a bit more elegantly? Maybe on every page i could push the current url onto a stack, and if there’s a problem with the current page, go back down the stack until i find one that works? Someone must have thought of this stuff for rails already though…i think, instead of doing a massive stack, i’ll just have the previous and the page before that, using the same save_page system i’ve got at the moment. This is the new save_page partial:

<% session[:return_to_older] = session[:return_to]%>
<% session[:return_to] = request.request_uri %>

This is the new redirect_to_stored method:

  def redirect_to_stored(params = {})
if params[:older]
return_to = session[:return_to_older]
return_to = session[:return_to]
#test if we set return_to to something other than nil
if return_to
session[:return_to] = nil
#flash[:message] = “session[:return_to] was empty, returning to list page”
redirect_to :controller => ‘article’, :action => ‘list’

And this is where i call it, from the delete_story method:

 def delete
@story = Story.find(params[:id])
@article = @story.article
#before deleting, we need to check that we weren;t on the story/show page
#if we were, we can’t go back there after deleting: go to the previous instead
if session[:return_to].match(/reeply\/show/)
redirect_to_stored :older => “true”

This now works for deleting drafts – you go back to the “your drafts” page.  But it doesn’t work for deleting stories that you got to from the article/show page now – you go back TOO FAR!  agghh.  I think i’m going about this in a very clumsy way.   OK, tweaked it a bit – now the controller goes back to the draft_list if we came from their recently, otherwise it always goes back to article/show, for the article that owned the deleted reeply.

  def delete
@story = Story.find(params[:id])
@article = @story.article
    #after deleting a reeply, we either go to the ‘your drafts’ page or to
#the show page for the article that had that reeply

if session[:return_to].match(/reeply\/list_drafts/) ||
redirect_to :controller => “reeply”, :action => “list_drafts”
redirect_to :controller => “article”, :action => “show”, :id =>

OK, next thing – i think that links to ‘edit story’ or ‘delete_story’, etc, should be buttons, in keeping with their more significant actions.  There should also be buttons to ‘save draft’ and ‘delete’ on the ‘edit_story’ page.

Changing links to buttons couldn’t be simpler – just ‘button_to’ instead of ‘link_to’.  There seems to be a slight difference in that if you put two buttons next to each other, each inside a span element, they go on seperate lines.  So i put them in a little table if i need them next to one another.  So, that’s all easy.

A bit more complex is adding in the new buttons to the story/edit page: the way i did this, since all the buttons are in the middle of the form, is to have them all labelled as submit tags, and then, in the  create method of the story_controller, use a case statement to analyse :commit (which has the text from the button that was pressed) to decide what to do next.

Here’s my new list of buttons on the story/edit form:

<%= submit_tag “Submit Reeply” %>
<%= submit_tag “Save changes” %>
<%= submit_tag “Delete Reeply” %>

And here’s the two new cases  from story_controller/create:

  when “Save changes”
flash[:notice] = “Saved Reeply draft.”
flash[:notice] = “Sorry, your Reeply draft was not able to be saved.”
redirect_to :controller => “story”, :action => “edit”, :id => @story
when “Delete Reeply”
redirect_to :controller => “story”, :action => “delete”, :id => @story

So, they work.  Cool. Next though, i want to add confirmation dialogs to them, like i do with the other buttons to ‘Delete story’.  As these are “submit_tag”, rather than “button_to”, however, it doesn’t seem to be possible.

After a bit of forum help, of course it’s possible!  We just need to use a bit of javascript (of which i’m woefully ignorant).  Here’s how to do it:

  <%= submit_tag “Delete Reeply”,
:onclick => “return #{confirm_javascript_function(“Delete \”#{@story.title}\”?”)}”  %>

Note the complicated set of brackets and quotes at the end there!


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: