Task B: Catalog Display #1

Dave says:

The simplest solution is to add

<%= Time.now %>

somewhere in the sidebar div. However, it's better form to set an instance variable to the time in the controller's action, and then to use that instance variable in the view.

Q/ Why is it better to set a variable in the controller's action over a simple <%= Time.now %>?

A/ Because it would become easier to edit when it comes time to deal with localization.

Q/ But this is so ugly why not use <%= Time.now.strftime("%I:%M %p") %>?

A/ By coding this into the view, you've made a policy (business) decision in the view (because you've decided what it means to be the current time). Then the company says "All servers must run on GMT" and all the time display is messed up. Or maybe you want to display the time in the user's timezone. Whatever the potential issue, experience shows that it's generally better to derive the data to display outside the view.

Ken says:

To elaborate on Dave's 'better form' (and knowing that it hasn't been introduced yet but something to spark some investigation):

before_filter :prepare_time_for_display
def prepare_time_for_display
  @current_time = Time.now
end

Something similar to this should go in your ApplicationController? (application.rb) and then use @current_time in the view. This makes it so that its available for all layouts. If its not needed in other layouts then pull this down into the specific controller needed.

Donovan says:

Thanks for the guidance Ken. The 'before_filter' approach is greek at this stage for me. So I used the following. Does it violate any of Dave's guidelines?

In store_controller.rb:

  class StoreController < ApplicationController
    def index
      @products = Product.find_products_for_sale
      @current_time = Time.now
    end
  end

In store layout file (store.rhtml):

<div id="columns">
    <div id="side">
        <%= @current_time.strftime("%B %d %Y,") %>
        <%= @current_time.strftime("%I:%M %p") %><br /><br />            
        <a href="http://www....">Home</a><br />
        <a href="http://www..../faq">Questions</a><br />
        <a href="http://www..../news">News</a><br />
        <a href="http://www..../contact">Contact</a><br />
    </div>
    ...

Pete says:

After putting the data and time in the sidebar it was barely readable. So I added color: #fff; to the end of #side in depot.css. Now any plain text in the side bar will be white. Here's the whole style:

#side {
  float: left;
  padding-top: 1em;
  padding-left: 1em;
  padding-bottom: 1em;
  width: 14em;
  background: #141;
  color: #fff;
}

Nick says:

Instead of putting the formatting of the time into the view, you can place the formatting right into the controller. Unless it was your intent to have a specific date/time format in the sidebar.

For example in store_controller.rb:

  def index
    @products = Product.find_products_for_sale
    @current_time = Time.now.strftime("%Y-%m-%d %H:%M:%S")
  end

And in /store/index.rhtml

    <div id="side">
      <%= @current_time %><br /><br />    
      <a href="http://www....">Home</a><br />
      <a href="http://www..../faq">Questions</a><br />
      <a href="http://www..../news">News</a><br />
      <a href="http://www..../contact">Contact</a><br />
    </div>

GarryFre? says:

The above file name and location is incorrect, the code Nick refers to is actually at /depot/app/views/layouts/store.rhtml. I did not want to directly edit Nick's comments because for one thing, I'm a newbie at ruby, and I felt it his right to correct it not me. I would also like to add that the time code here, is NOT in the final code printed in the second edition of the book, as demonstrated by screen shot on page 104 and its absence in the final code. I would like to see some documentation about good ways to do many of the playtime stuff. So far there is some vagueness about how to best do an assignment and I know from experience, that just guessing around is only a way to create bad programming habits through ignorance.

Billy says:

Donovan, your code is flawed. It puts the definition of what time is into the view like Dave said (and the conventions say) not to. :)

Patrick says:

I did it the following way:

In store_controller.rb:

  class StoreController < ApplicationController
    def index
      @products = Product.find_products_for_sale
      @time = Time.now
    end
  end

In store layout file (store.rhtml):

<div id="columns">
    <div id="side">
        <a href="http://www....">Home</a>
        <br />
        <a href="http://www..../faq">Questions</a>
        <br />
        <a href="http://www..../news">News</a>
        <br />
        <a href="http://www..../contact">Contact</a>
        <br />
        <a href="http://www....">Reload for current time: <%= @time %></a>
        <br />
    </div>
</div>

That way I felt the user could click the time as a link that would just reload the page and display a new time and she would know what it was the time represented a bit more. (Plus I wasn't sure how else to get it in white! :) )

Does anyone know a more direct way of getting the page to refresh with a link like that? Could we just use link_to somehow? I tried putting the @time in the link_to directly but that really doesn't want to work:

<%= link_to 'Reload for current time: <%= @time %>', :action => 'index' %>

That really doesn't work but I feel there must be a way to do that. Any thoughts?

Martin says:

Try this:

<%= link_to "Reload for current time: #{@time}", :action => 'index' %>

Note:

I use double quotes (") instead of single quotes (') and enclosed the variable within "#{…}".
The "ruby-escape-tags" ('<%=' and '%>') have no special meaning within a string. Remember: these tags enclose ruby-code within html (→ rhtml). so you are already in ruby code.
since this is in a general template for the controller 'store' it is probably not a good idea to place a static argument :action ('index'). it should point to the current action, but i don't know (yet) what variable, method or function holds/returns that value.

Moritz says:

Ken's solution with the 'before_filter' thing definitely works best and, even if I'm a total Rails newbie, feels like it's the cleanest to me, because the view is free from logic or 'business decision' stuff but still I can get a nicely formatted date and time in all my controllers. And I asked myself already how to implement stuff into a Rails application which is available in more then one controller. Now I have an idea how to do this. Thanks Ken!

Steve says:

Following the most common answer (creating the time variable in the controller, and then calling @time in the view) works for the store page…

however, following into the next section of the book, when we are tasked with adding items to a cart (/store/add_to_cart/) the left hand portion of the screen doesnt retain the call to @time… any suggestions?

Tom says:

Take a look at Ken's solution - he puts the @current_time = Time.now in the application controller, rather than in the store_controller so that it is accessible by all views, not just by index.rhtml

Steve says:

Thanks Tom. Unfortuantely, I'm just not getting the subtle logic implied in Ken's example. I understand the addition to the application.rb file (and its impact on all subsequent pages), but the line directly above (before_filter :prepare_time_for_display)… well, i'm just missing the point, and how it is actually implemented.

Raul says:

In answer to Steves's question (that was also mine) about Ken's solution: the book has a good explanation although it requires to jump to p.447 with section "Before and After Filters". In summary:

When you have one or more methods that you want to be executed prior any action of some controllers, you usually write them in the ApplicationController? (for example, a method 'authenticate').
Then in the relevant Controllers (eg, in portions of the site where you want to authenticate users) you specify which methods you want to execute before any action (eg, before_filter :authenticate) of the controller.
There is a lot more about Filters; a good place to look is the code itself; in your gems path (eg /usr/local/lib/ruby/gems/1.8/gem), see file actionpack-1.13.3/lib/action_controller/filters.rb. The introductory comment is very interesting (eg, showing how filters can cascade through an inheritance chain, with an example of 'BankController?' and 'VaultController?').
To know 'how this is implemented', one needs to read the code, although it requires to know Ruby in some depth (lambdas, code blocks, and some metaprogramming); not inmediate for anyone coming from compiled languages.
The basic idea of Filters is however clear: add pre-processing and post-processing layers transparently to the rest of the application (using the metaprogramming tricks dear to Ruby).

Ken says:

I'm glad my comments sparked some people to do a little further digging.

I saw a couple of questions that I can probably answer (and thanks to some of those who already threw out answers).

As to why I would use a Filter: That allows the @current_time variable, which is used in the layout presumably, to be set no matter what controller you are working in. Basically it makes it so that you don't have to put @current_time = Time.now into every single method of every controller you create. That would be a huge waste of time and a nightmare to maintain. By doing it the way I suggested its what we call DRY.

As to Donovan's question about is his implementation wrong or right. Your answer is perfectly valid. However once you started to expand into other controllers I'm sure you would see that it become very cumbersome and be looking for a more elegant solution. Perfect time to refactor. See below for the progression of refactoring.

As to what exactly is a filter, Raul gave a good pile of information. It is a way to perform some work before and after a method in a controller is invoked. In my case my code was saying "Before you run this method in any controller, please set a variable called @current_time to the current time." That variable is then available in my view, just as if I had manually done it like Donovan suggested.

Progression of Refactoring:

First cut - Code @current_time = Time.now into the StoreController? index method.
Second cut - Realize you need that information througout the StoreController?, not just on the index method. Put the code into a helper method and call it with before_filter :prepare_time_for_display
Third cut - Realize that you are using a global layout (not just one for StoreController?) and that layout also needs the @current_time variable set. Move the those calls to the ApplicationController? and be done with it all.
cheers -ken

I wanted to add a different spin – using ajax to test this out so, in my index page I added:

<div id="time">
   <%= @current_time %>
</div>
<div id="ajax_update">
   <%= periodically_call_remote(:url =>{ :controller => "store", :action => "current_time"}, :frequency => 1) %>
</div>

and created an rjs for the controller with this:

page.replace_html "time", Time.now.strftime("%m-%d-%Y %H:%M:%S")

then added this in the store controller:

def current_time
 
end

the 'periodically_call_remote was just to see if I could do an ajax call without the user having to press a button. This is NOT the right way to do a clock (a javascript timer would be correct) but it was just to show you can do dynamic content update without the user's input – a really neat trick in Rails….

-Don

the @current time was using the 'application' controller approach.

Scott says:

Here's an example for 2.1:

config/environment.rb

# ...
  config.time_zone = 'Central Time (US & Canada)'

store_controller.rb

# ...
  def index
    @products = Product.find_products_for_sale
    @current_time = Time.zone.now.strftime("%c")
  end

store.html.erb

# ...
            <%= yield %>
        </div>
        <span id="timestamp"><%= @current_time %></span>
    </div>
</body>
</html>
#side a, #timestamp {
  color: #bfb;
  font-size: small;
}
 
#timestamp {
  float: left;
  padding-left: 1em;
  margin-top: -20px;
}

And here's a table for strftime.

Nick C says:

Since it's in the sidebar and part of the layout, not necessarily the action, is it better to put it in the initialize method, like so? That would ensure that other actions that use the same layout also retain the same sidebar.

def initialize
  super
  @time = Time.now  
end

Peetah says:

I've tried it several ways, just toying around. I came up with the following and although it's not pretty, it came out the way I want it.

<%= Time.now.to_s(:long) + Time.now.strftime(" %p") %>

Maybe someone could explain why or why not use this beside the use of Time.now twice.

PeterC says:

Peetah, I guess the best answer would be that it's not particularly DRY. You might just as well use

<%= Time.now.strftime("%b %d, %Y %H:%M %p") %>

Even this, however, might be considered problematic. For one thing, since you have 24 hour time through the %H parameter, there is no need to specify AM/PM with the %p parameter. Instead you can simplify your expression by taking out the %p, or else changing the %H to %I to give you a 12 hour clock.

Personally, I'd prefer taking the code out of the view entirely, as others have mentioned above - though I would rather create helper methods which could reside in helpers/application_helper.rb. You could create a set of various time/date helper methods with easy-to-remember names, which you then call wherever you need them. For example:

module ApplicationHelper
 
T = Time.now
 
def time_24_hour
  T.strftime("%H:%M")
end
 
def full_date
  Date.today.to_s(:long)
end
 
def simple_date_and_time
  T.to_s(:short)
end
 
def chatty_time_and_date
  "It's " + time_24_hour + " on " + full_date
end

Then you can insert an appropriate time-stamp in any of your views with a simple method call:

<%= chatty_time_and_date %>

and you'd have an output something like:

It's 17:04 on January 09, 2009
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License