Tuesday, May 31, 2011

More work on Chorenivore

I'm still working on Chorenivore, my to-do list application. Nothing major to report, but I'm still wrestling with a way to update the Finished? status from the view.

What I've read suggests I should have two methods in the Tasks controller, called Finish and Unfinish. This makes sense, because then a user could toggle this status from the Tasks view with either a button_for or a link_to.

So here's the code I've got so far:

def finish
  { redirect_to(tasks_path, :notice => 'Task completed!') }
end

def unfinish
  { redirect_to(tasks_path, :notice => 'Did you forget something?') }
end


I haven't quite thought out HOW this method will be called, but I want a user to be redirected to the Tasks list once a Task has been marked as finished or unfinished. Am I thinking about this in the right way? What I want is to be able to mark something as finished, then have that change reflected in the view, and also to be able to change it back, in case you change your mind. Whoops! Is creating two methods the best way to do this?

I've also got this little number in the Tasks view:

<%= task.created_at.strftime("%b %d, %Y") %>


After some srs googling, I was able to find a way to display when a Task was created and format that information in a way I found acceptable. My only question is this: is this too much logic to load into a view? Couldn't I create another method in the Controller, then call it from the View?

7 comments:

  1. If you stick to a RESTful design with rails, you can just keep using the default update method in your controller, just making sure you pass along the value for finished in your form.

    Since you want to update the "Finished?" status from the show page, you can either use a form with a hidden input, and an image (or button) for to submit the form. Forms don't always have to be obviously forms.

    <%= form_for @task do |f| %>
    <% if @task.finished? %>
    <%= f.hidden_field :finished, false %>
    <%= image_submit_tag('checkmark.png') %>
    <% else %>
    <%= f.hidden_field :finished, true %>
    <%= image_submit_tag('gray-x.png') %>
    <% end %>
    <% end %>

    Another way you could do it is to hook up some AJAX and make it so clicking a link or something sends the post to update and update the webpage dynamically. You can get very fancy this way. Something along the lines of:

    <%= link_to( (@task.finished? ? 'Finished' : 'Not Finished'), '#', :id => 'update_task_link' ) %>

    // inside a script tag
    $('#update_task_link').observe('click', function(link){
    var status = (link.innerHTML == 'Finished') ? 'false' : 'true';
    new Ajax.Request('/tasks/<%= @task.id %>', {
    method: 'put',
    parameters: 'task[finished]='+status,
    onSuccess: function(){
    link.innerHTML = (status == 'true') ? 'Not Finished' : 'Finished';
    }
    }
    })

    Also, the Rails way to format dates is to define your date formats either in application.rb or an initializer like so:

    # add a hash of formats you want to use throughout your app
    Time::DATE_FORMATS.merge!(:basic => "%b %d %Y")
    # add to date formats too
    Date::DATE_FORMATS.merge!(Time::DATE_FORMATS)

    Then in your views:

    <%= task.created_at.to_s(:basic) %>

    ReplyDelete
  2. Some ideas:

    You could provide a single action and have it tell the task to update status as appropriate:

    # TaskController
    def toggle_completion
    @task = current_user.tasks.find_by_id(params[:id])
    @task.toggle_completion
    redirect_to @task
    end

    # Task
    def toggle_completion
    complete = complete? ? false : true
    save!
    end

    The view could show 'Mark Complete' or 'Mark Incomplete' depending on the task status.

    For the time display, you can pass a format to to_s. Either define a custom format (see http://apidock.com/rails/DateTime/to_formatted_s) or turn created_at into a date first, e.g. task.created_at.to_date.to_s(:long).

    ReplyDelete
  3. Since you're updating an existing resource, using an HTTP PUT is the way to go, but you'll need to specify this if using a link (defaults to GET) or form (defaults to POST).

    Define a Rails 3 member route off of the existing route declaration:

    resources :tasks do
    member do
    put 'toggle_completion'
    end
    end

    Also, if you're choosing to update attributes by passing params (as in Dave's comment above), remember to scope the task find by user so that people can't update others' tasks.

    ReplyDelete
  4. @David Thanks for the reply! I actually went and hung out with Eli and Matt after posting this last night and Eli showed me how to do exactly what you recommended. I didn't even know that hidden fields were a thing, or the amount of customization you could do with forms. I'm a bit too rigid in my thinking, but things are starting to make more sense.

    ReplyDelete
  5. @Matt Welcome to the party! Your comment about scoping the task find by user is a good tip, considering I'll eventually want to have account creation/login functionality.

    ReplyDelete
  6. Wow, David's code is eerily similar to what Tyler and I developed.

    ReplyDelete