#103 Site-Wide Announcements (revised)
Here I show how to add an announcement message at the top of every page in the application and allow the user to permanently hide it. This is all done test-first and even includes testing the JavaScript behavior.
- Download:
- source codeProject Files in Zip (60.5 KB)
- mp4Full Size H.264 Video (24.2 MB)
- m4vSmaller H.264 Video (13.2 MB)
- webmFull Size VP8 Video (15.5 MB)
- ogvFull Size Theora Video (30.7 MB)
Resources
terminal
rails g integration_test announcement rails g model announcement message:text starts_at:datetime ends_at:datetime rails g controller announcements rake spec
views/layouts/application.html.erb
<% Announcement.current(cookies.signed[:hidden_announcement_ids]).each do |announcement| %> <%= div_for announcement do %> <%= announcement.message %> <%= link_to "hide announcement", hide_announcement_path(announcement), remote: true %> <% end %> <% end %>
config/routes.rb
match 'announcements/:id/hide', to: 'announcements#hide', as: 'hide_announcement'
announcements_controller.rb
def hide ids = [params[:id], *cookies.signed[:hidden_announcement_ids]] cookies.permanent.signed[:hidden_announcement_ids] = ids respond_to do |format| format.html { redirect_to :back } format.js end end
views/announcements/hide.js.erb
$("#announcement_<%= j params[:id] %>").remove();
models/announcement.rb
def self.current(hidden_ids = nil) result = where("starts_at <= :now and ends_at >= :now", now: Time.zone.now) result = result.where("id not in (?)", hidden_ids) if hidden_ids.present? result end
app/assets/stylesheets/announcements.css.scss
.announcement { width: 100%; background-color: #F1F094; border-bottom: solid 1px black; padding: 10px 0; text-align: center; a { font-size: 12px; margin-left: 5px; color: #6E5910; } }
Gemfile
gem 'rspec-rails', groups: [:test, :development] group :test do gem 'capybara' gem 'launchy' gem 'poltergeist' end
spec_helper.rb
config.filter_run focus: true config.run_all_when_everything_filtered = true
spec/requests/announcements_spec.rb
describe "Announcements" do it "displays active announcements" do Announcement.create! message: "Hello World", starts_at: 1.hour.ago, ends_at: 1.hour.from_now Announcement.create! message: "Upcoming", starts_at: 10.minutes.from_now, ends_at: 1.hour.from_now visit root_path page.should have_content("Hello World") page.should_not have_content("Upcoming") click_on "hide announcement" page.should_not have_content("Hello World") end it "stays on page when hiding announcement with javascript", js: true do Announcement.create! message: "Hello World", starts_at: 1.hour.ago, ends_at: 1.hour.from_now visit root_path page.should have_content("Hello World") expect { click_on "hide announcement" }.to_not change { page.response_headers } page.should_not have_content("Hello World") end end
spec/models/announcement_spec.rb
describe Announcement do it "has current scope" do passed = Announcement.create! starts_at: 1.day.ago, ends_at: 1.hour.ago current = Announcement.create! starts_at: 1.hour.ago, ends_at: 1.day.from_now upcoming = Announcement.create! starts_at: 1.hour.from_now, ends_at: 1.day.from_now Announcement.current.should eq([current]) end it "does not include ids passed in to current" do current1 = Announcement.create! starts_at: 1.hour.ago, ends_at: 1.day.from_now current2 = Announcement.create! starts_at: 1.hour.ago, ends_at: 1.day.from_now Announcement.current([current2.id]).should eq([current1]) end it "includes current when nil is passed in" do current = Announcement.create! starts_at: 1.hour.ago, ends_at: 1.day.from_now Announcement.current(nil).should eq([current]) end end