Topic: Site-wide Announcements on Rails 2.1

Hi All.

Note: As a newer Rails developer I feel the desperate need to contribute to the community that (as of now) I am taking so much from.  I feel that this community should be about give and take and that I must do some giving for all my taking.

###

A few weeks ago Railscast #103 came out showing us how to build a Site-wide Announcements system.  I followed the tutorial and was happy to have it working!  When I upgraded to Rails 2.1 and all its time zones and named_scope glory, the code broke.  Today I debugged it and updated it for Rails 2.1.  I'd like to teach everyone how I fixed and improved it all. 

Obviously a lot of credit goes to Ryan for first teaching us how to solve the problem in the first place.  Before I begin showing you how I improved the code, if you haven't yet watched Railscast #103, do so.  Ryan introduces the theory behind the code.

Note: I have renamed some of the methods to my own liking.

Start off, like in the Railscast and generate a scaffold for your Announcements and a Javascripts  controller:

script/generate scaffold announcement message:text starts_at:datetime ends_at:datetime
script/generate controller javascripts
rake db:migrate

Now create a partial for displaying the announcements (_annoucements.html.erb).  Put it wherever in your views folder you would like.  I put all my layout partials in their own directory (layout_partials) to keep my views folders tidy.

Add the following code to the layout files you want to display the announcements:

<%= render :partial => "layout_partials/announcements" unless announcements_for_display.empty? -%>

Since this partial will be rendered by different controllers, be sure to include the folder before the partial name. 

The unless announcements_for_display.empty? calls a helper method... I will get to that soon.

Now lets write the code for the partial.

<div id="announcement" class="container">
    <%- for announcement in announcements_for_display -%>
      <p><%=h announcement.message -%></p>
    <%- end -%>
      <%= link_to_remote announcements_link_label, :url => "/javascripts/hide_announcement.js" %>
    </div>

The link_to_remote calls a helper method named announcements_link_label, I will explain that later too.

Now the announcements.rb model:

class Announcement < ActiveRecord::Base
   
  named_scope :active, lambda { { :conditions => ['starts_at <= ? AND ends_at >= ?', Time.now.utc, Time.now.utc] } }
  named_scope :since, lambda { |hide_time| { :conditions => (hide_time ? ['updated_at > ? OR starts_at > ?', hide_time.utc, hide_time.utc] : nil) } }
 
  def self.display(hide_time)
    active.since(hide_time)
  end
 
end

It was in the model that a lot of the Rails 2.1 upgrades occurred.  The first big error that broke the code had to do with the way the current time was input into the search.  In the original code, the SQL variable/function/constant now() was used.  This variable didn't even work on my SQLite3 implementation.  The show comments provided a fix: now() is not SQL standard, but current_timestamp is.  In Rails 2.1, however, because dates are converted by ActiveRecord into UTC, and because the database (well, SQLite) doesn't have a current_time equivalent for UTC, the searches were wrong: the searches were using local time while the data was in UTC.

To fix this, I had to figure out a way for Rails use utc in the search.  While messing around with it, I revisited the show comments where Arron talked about improving the code with named_scope.  I added named_scope to the model, but the searches were still broken.

Then I learned that by chaining the class method "utc" to a time object, it would covert the time into utc.  I then made both conditions lambda blocks and appended the time objects with the "utc" method.  Now the searches worked!

To get the javascript working, just copy Ryan's code...


#javascripts_controller.rb
class JavascriptsController < ApplicationController
 
  def hide_announcement
    session[:announcement_hide_time] = Time.now
  end
 
end

#views/javascripts/hide_announcement.js.rjs
page[:announcement].hide


And finally lets talk about the helper methods.

I moved all of my helper methods into the announcements_helper.rb unlike Ryan who put them in the application helper.  Be sure you add the line "helper: announcement" to your application_controller so it will work.  I am a fan of tidy code, and I didn't like the announcements helpers in the application_helper file.

The first helper is Ryan's code exactly, except for the renamed methods.

The second helper is my own code.  I foresaw the possibility of having two site-wide announcements at once and wanted the link text to be plural if there were more than one.  So I wrote that method, which is called in the link_to_remote line in the announcements partial.  I toyed with the idea of using the inflector, but why when it would require more code and more system processing time? 

module AnnouncementsHelper

  def announcements_for_display
    @announcements_for_display ||= Announcement.display(session[:announcement_hide_time])
  end
 
  def announcements_link_label
    return "Hide " + (@announcements_for_display.size == 1 ? "announcement" : "announcements") + "."
  end 

end


And that is it!  Hope I have helped out all of you who are running edge and learning

Last edited by avarhirion (2008-05-28 03:20:40)

Re: Site-wide Announcements on Rails 2.1

Hi!

Thanks for that hint using hide_time.utc in the query. I tried to save the utc time in the session but that doesn't work.

session[:announcement_hide_time] = Time.now.utc

When I store this time in the session everything is fine. But when i reload the page the time in the session is converted to my local time zone.

Does anyone know if this is a bug in rails?

Re: Site-wide Announcements on Rails 2.1

This is great, thanks avarhirion for making this work on 2.1.

I found another route for pluralizing the announcement;

  def announcements_link_label
    return "Hide " + pluralize(@announcements_for_display.size, 'announcement')   
  end

Does anyone have fall-back to html for when the client has js disabled? Also, is anyone using this in production (or any other Railcasts snippets, for that matter)? Unless I'm missing it all, we rarely get to hear back from people actually using all these great tutorials on live sites. Would be great to see some in action.

Re: Site-wide Announcements on Rails 2.1

What needs to be updated to use cookies? I need it to work so that people don't have to close it every time they come back if they aren't logged in... What are the advantages to using sessions over cookies?

Re: Site-wide Announcements on Rails 2.1

I just blogged with a solution that gracefully degrades if JavaScript is disabled.  Additionally, it uses cookies as well as the session so users won't see the same announcement over and over again.

More here:
http://davidwparker.com/2008/09/17/site … ry-jgrowl/

Re: Site-wide Announcements on Rails 2.1

Tried doing http://davidwparker.com/2008/09/17/site … ry-jgrowl/

But doesn't seem to work for me.  I am really bad with JS so I think that's where I am messing up.

my application.js looks like

$(document).ready(function() {
    $.jGrowl.defaults.closer = true;
      $(