Gravatar

Geoff Evason

Posts Tagged ‘rails’

Railscasts Does It Again : Site Wide Announcements

Tuesday, May 27th, 2008

A note to all rails developers, new and old.  If you’re not following Ryan Bates’ Railcasts, you should be.

I follow a variey of rails blogs and lean on a number of resources quite regularly, but the Railscasts are consistently the most useful.  There are now over 100 railscast, each one a roughly 5 minute screencast outlining the solution to some problem.

A recent cast showed how to create a site wide announcement that each user could mark as read individually.  This is a great, non-intrusive way to communicate notices with users.

The screencast details how to do it.  I was able to implement this on a site in a very short period of time.  I made some modifications which make it work better within my site.  I do have one suggestion to improve it overall.  To track whether a message had been read/should be shown Ryan uses the session.  Sessions expire in the near future, and if using a db store, should be wiped daily.  If your users don’t visit daily, you will want to create a message that hangs around for a week or 2.  In this case, a session variable won’t work.  Instead, you can store the info in a cookie and set a delayed expire time on it.  (By default, cookies expire with the session in rails).

Before reading how to store this info in a cookie you should watch the screencast.  Once you’ve implemented everything like Ryan’s demo, there are just 3 small changes to use cookies and hence have a longer memory.

1. In your controller, set the cookie:


def hide_announcement
  cookies[:announcement_hide_time] ={ :value => Time.now.to_s , :expires => 2.weeks.from_now }
end

2. In your helper method, read the value from the cookie.


def current_announcements
  @announcements ||= Announcement.current_announcements(cookies[:announcement_hide_time])
end

3. In the announcement controller you need to parse the time since it is stored as a string in the cookie


def self.current_announcements(hide_time)
  with_scope :find => { :conditions => "starts_at <= now() AND ends_at >= now()" } do
    if hide_time
      time = Time.parse(hide_time)
      find(:all, :conditions => ["updated_at >= ?", time])
    else
      find(:all)
    end
  end
end

Cross Site Reference Forgery

Monday, May 5th, 2008

I just read a great article on Cross Site Reference Forgery, specifically related to how Rails 2.0 handles it. I think it is a must read for all rails developers. It gives a very clear description of the potential vulnerability, which I think is important to understand

I recently upgraded some apps to Rails 2.0 from Rails 1.2.4 (more on that in another post) and this was one of the main reasons.

CSS For Sliding Door Input Buttons In Rails

Tuesday, February 26th, 2008

UPDATE: This article appears to get a reasonable amount of traffic from google so I figured I’d update/correct it. The solution I outline below ended up not working as nicely as I had hoped, so I scrapped it. Instead, I just created a single image and making all my button text fit within the length of that single image. Sliding doors isn’t worth the trouble (i.m.h.o), and what I wrote below isn’t a perfect solution anyway.

——

This is one of those annoying things that took me all day to figure out, and the stuff I was able to find on the internets wasn’t very helpful….

I wanted to create some fancy looking buttons with curved corners. But I wanted them to be real buttons (http:post) , not links (http:get) that just look like buttons.

I looked for a way to do it and found a way to do it using links and a way to do it using the <button> element. Neither of these did what I wanted. Rails uses <input type=”button” /> rather than <button>. After learning more about the difference between buttons and inputs I’m still not sure why, but I figured there must be a good reason. I did try <button> but IE was giving me headaches (I couldn’t click on the button) so I gave up. I tried adapting these methods and they weren’t working for me.

One of the methods linked above worked briefly, but it required floating one of the elements left, which makes layout a REAL pain!

So, I (perhaps by accident) found an easier way. I took the opposite approach from using the outer html element for the left image.

First, I made my own button helpers like so:


module ButtonHelper
  def fancy_button_to(button_text, action_text, button_class = "button")
    return '<form method="post" action="' + action_text + '">' + button_base(button_class, button_text) + '</form>'
  end

def button(button_text, button_class = "button")
    return button_base("button", button_text)
end

protected

  def button_base(button_class, button_text)
    return '<p class="button_wrapper"><span class="' + button_class + '"><input value="' + button_text + '" type="submit" /></span>'
  end

end

Then, the css looks like this:


span.button input {
 border:0;
 cursor:pointer;
 white-space:nowrap;
 color: #fff;
 text-align:center;
 font-weight: bold;
}

span.button {
 background: transparent url(/images/small_button_left.gif) no-repeat top left;
 padding: 6px 0px 6px 5px;
}

span.button input{
 background: transparent url(/images/small_button_right.png) no-repeat top right;
 padding-right: 5px;
 font-size: 15px;
 height: 25px;
}

You will need to create some sliding door images (see links at the beginning of this post). The values measured in pixels in the css will depend on those images.

Of course, there is some conditional css, but it’s pretty short. You just have to use different padding amounts for IE6 and IE7. So, I created a new css file call button_ie.css. I conditionally include it in the html file like so:


<!--[if lte IE 7]>
  <link href="/stylesheets/button_ie.css" media="screen" rel="Stylesheet" type="text/css"></link>
<![endif]-->

The button_ie.css looks like this:


span.button {
  padding: 0px 0px 0px 5px;
}

This isn’t perfect. Occasionally there is an error of 1 pixel in firefox 2, but IE6, IE7, & Safari seem to work fine.

Hopefully this will save someone else the frustration I faced today. Though to be honest, I’m actually thinking of moving to fix width buttons.

The (insane) Importance Of Database Indices

Thursday, December 20th, 2007

One of the great things about Rails (especially for beginners) is that you need to know very little about database.

One of the bad things about Rails (especially for beginners) is that you need to know very little about database.

Through all of the literature I read while learning rails, I only have a vague recollection of databases indexes being mentioned. I started learning more about them recently because the performance of one of my apps was degrading over time, and literally, in about 2 hours, I increased the performance of my backend processing by 5x!!!

Intrigued? First – some background:

Most tables in rails (anything that has a model really) has a field ‘id’. This is the primary key of the database, and an index is created on this. I am a database rookie, but I know that an index is just like an index in a book. It’s extra info that the database stores so that it can find things more quickly.

Indices are not a concern when you get started, nor should they be. When your table gets to 1000s of entries, then they CAN become more important. It largely depends on how your app works.

Lets take a simplified example. Lets say you have an app that has sites and pages:


create_table "sites", :force => true do |t|
  t.column "permalink", :string
end

create_table "pages", :force => true do |t|
  t.column "site_id", :integer
end

Someone can view a site, and each site has pages:


class Site < ActiveRecord::Base
  has_many :pages
end

Because you want pretty urls you want someone to be able to go to www.myapp.com/<site.name>/<page.id>. The first thing you need to do that is


@site = Site.find_by_permalink(params[:id])

Of course, you are also going to need to have navigation for that site, so while rendering you have something like this


<div id="nav">
  <% for page in @site.pages %>
     <%# (do something with the page here) %>
  <% end %>
</div>

So what happens, when your someone tries to view a site?

Step 1: find_by_permalink: The database has to open every single ’site’ and check if the permalink matches.

Step 2: @site.pages: The database has to open every single page and check if the event_id field matches the id of the event that it is looking for.

When you have small numbers of rows, this isn’t a big problem. When you get 1000s of rows, looking through every site and every page gets slow, quickly.

So, the solution is to add an index. You add indices in Rails using migrations. You can create a migration like this:


class AddIndices < ActiveRecord::Migration
  def self.up
    add_index :sites, :permalink
    add_index :pages, :site_id
  end

  def self.down
    remove_index :sites, :permalink
    remove_index :pages, :site_id
  end
end

When you add those 2 indices, the database can now much more quickly find and retrieve the single Site object by permalink, and find and retrieve only the required Page objects rather than having to search the entire database for a match.

Doing this got my a 5x improvement in the speed of some of my more common actions, and literally took 2 hours.

You do need to be careful. Adding indices can slow down your performance because every time you insert, update, or delete rows, the database must also alter the index.

Some useful links:

http://therailsway.com/2006/11/21/tracks-part-4

http://weblog.jamisbuck.org/2006/10/23/indexing-for-db-performance

http://www.websitedatabases.com/database-index.html

There are probably good ways of benchmarking this. After testing locally, I copied my live data to my staging area, added the indices, ran a few actions, and compared the logs from before/after the migration. In my case, some actions got a bitter slower, but those were uncommon ones. My most common actions got 5x faster!! Have a look at the following graph. The red lines show the % of time for an action after adding the indices. Above 100% means the action got slower. Below means it got faster. You an see most actions got a lot faster!

Effect of Database Index

A Quick Trick For Read Only Behaviour

Monday, November 26th, 2007

Sorry for the lack of posts recently. I haven’t really been doing any Rails stuff!

My focus as of late has been on design (css) and search engine optimization. I’ll write more about those too I suppose, but for now just a quick trick I cam up with today that I thougth was kind of neat.

MomentVille growth has been great, so we are upgrading hardware. This will cuase up to an hour of downtime as we migrate. But, that seems unacceptable to me. In reality, it’s only 5 minutes of downtime that is required for one specific thing. One VM is getting moved. It needs to be shut down and copied to new hardware. After it is copied, it can go back up on the original hardware, (5 min) and the rest of the time is getting the new hardware up and altering DNS settings. (55 min).

If we run the original after its’ been copied then any changes made will be lost, but that shouldn’t stop people from being able to READ from the db.

I put together a quick little fix to deal with this. In the main controller that deals with edits I created a protected method:

def check_for_maintenance_mode
respond_to do |format|
format.html {
redirect_to "/index_maintenance.html" and return
}
format.js {
render :update do |page|
page.alert "Nov 26, 2007: MomentVille.com is undergoing scheduled maintenance. " +
"It should last less than 1 hour. " +
"Any changes you try to make during this time will NOT be saved. " +
"We are sorry for any inconvenience."
end
return
}
end
end

I then call it in a before filter for any action that performs an edit:

before_filter :check_for_maintenance_mode, :except => [ :show ]

I save that new controller as: controller.maintenance.rb and wrote a little shell script to update the controller and restart the mongrel cluster. This will put me into ‘maintenance’ mode, and the outcome:

  • All parts of the website are viewable.
  • If someonetries to make a change that requires a database write they get:
    • redirected for an http request
    • an alert for an ajax request

YSlow On Rails

Wednesday, October 17th, 2007

I gave a talk recently on how to improve the front end performance of a rails app using YSlow as a tool to measure said performance.

Time Breakdown for Developing A Rails App: Postmortem

Thursday, August 30th, 2007

With v1.0 MomentVille now launched, I was doing a bit of a postmortem on the process. I hope that this will help other new app developers judge how much time they might need to deploy a new app… I’d love to hear what others find for their breakdown in terms of percentages.

1 Month: Learning: First, before even starting, I needed to learn Ruby on Rails to sufficiently start (with no web dev background besides HTML with table layouts). I would estimate this took about 1 month. I did start developing the wedding website builder before feeling really comfortable with rails, but most of the time in the first month was just getting comfortable with rails and web app development. I also got a book on CSS (highly recommended for anyone who still considers using tables) and that was included in my learning time.

These are rough estimates, but I would roughly categorize my time for this app as follows:

time.JPG

~25% Prototype Development: Writing the core of the application.

~ 25% Code Adjusments:

  • Code Refactoring: cleaning up hackish or silly things I did because I was just trying to get something working, or I didn’t know better.I think this should be down along the way, otherwise you end up with nasty bandaids everywhere.
  • My Code Adjustments: making adjustments to my code so things were a little more friendly. Eg: making the polls more user friendly,
  • Shared Code Adjustments: Adjusting shared code that I used to fit into my system. Eg: making the authentication code use email address as login id, update InPlaceRichEditor for undo edits when cancel is hit, etc…

~ 25% Cross Browser Testing

  • Gosh, this was painful. I hope it gets easier over time. A lot of the testing I did was manual, because I was looking for visual inconsistencies which I couldn’t find a good way to test in Selenium. I wanted to make sure that the cursors were correct based on mouse position, and state of the app, etc… Ugh. Start testing on IE6 early. This was probably made more difficult by the fact that I have a large number of themes (styles) over which to test. More on this in a later post…

~ 13% Styles

  • This is probably higher than most will be, since wedding websites on momentville can have any of a number of themes.

~ 12% Test Code

  • Test Writing: Writing unit & functional tests in Rails is easy. You should start early. Honestly. Just force yourself to learn how. It’ll take a day, and save many more….
  • Selenium Tests: Selenium is a great tool for UI testing. Rails testing is great for business logic, model verification etc, but you need to test what it is like to drive an app to, and Selenium is great for this.

Rails Development on Windows – Gotchas with File Uploading

Thursday, August 23rd, 2007

So, developing a Rails App on a PC is usually pretty straightforward. For the most part, my code on a test machine can be upload to a linux server and work fine. Further, most code examples found online also work.

There is only 2 gotchas I’ve found so far, and they both have to do with file uploads.

1. File would be corrupt on Windows

I was using some code I found online to write a file on the server. It was an image file uploaded to the rails server, but when the image displayed on the webpage, it was corrupted. So, how could I get rid of the corrupted image? Here’s the solution from http://wiki.rubyonrails.org/rails/pages/HowtoUploadFiles

Note for Windows: to avoid corrupting binary files, you must call File.open in binary mode. Change the “w” flag to “wb”, like this:

2. File would not always upload on a windows server.

I was raking my brain over this one, but fortunately someone else had already solved it (which so far has been a very common trend in the rails world):

http://www.railsweenie.com/forums/3/topics/1257

It turns out that the file handling in windows is a bit weird, so you just have to wait for it to catch up….

So, how to deal with these in the real world? After all, in development its fine to wait for a second for a file upload, but that is not okay on a production web server….

Well, the way I dealt with it is by using configurations. I have to environment.rb files. One is called environment.online.rb, and the other is environment.rb. This was suggested by my web host. Since it is a shared host, the way a rails app is set to production mode is the flag

ENV['RAILS_ENV'] ||= ‘production’

In environment.online.rb.

When I deploy an app, I just use the .online file by renaming it. This is slightly less than ideal, since it isn’t very DRY (don’t repeat yourself), but since I don’t control the web server on a shared host, I don’t have much choice… Since I have 2 files anyway, I set another flag for myself, in this case I just have the flag:

ON_WINDOWS = false //in envrionment.online.rb

ON_WINDOWS = true //in envrionment.rb

I’m sure that ruby has a way of checking the platform, but this was just easier and quicker…

Mac vs PC for Rails Development

Saturday, August 18th, 2007

Every time I go to a Rails meetup, I also get mac-envy… It seems I am one of the few rails develoeprs not using a Mac.

I thought about getting a Mac. In fact, my fiance has one, and I get to borrow it sometimes, but I’ve decided against getting a mac for myself. The reason? The end user experience…

Most of the big rails names use a mac, and most screencasts show textmate in use. Textmate looks very powerful. I thouight about this for a while, but decided to stay with a PC for development. Here’s the list I was weighing:

Benefits of Mac: The rails community seems more mac oriented, apples sense of design should taken on by most web developers, textmate.

Benefits of PC: Most end users of any web app I develop will be using a PC. I need to feel what they feel.

The PC won out. There are some good tools for PC rails development. As I said in my last post, InstantRails is amazing. For editors I’ve used both RoRad & RadRails – preferring RadRails by a fair bit. There are a few ‘gotchas’ to watch out for, which I’ll post in a very short while…

I Want to Learn Ruby On Rails, but Where Should I start?

Thursday, August 9th, 2007

Great, you’ve decided Rails is worth learning, but you are completely new to it. How should you go about it?
Well, I looked back at the path I took, left out the bits that weren’t useful, and re-ordered things the way I think they would have been most useful.
Step 1 – Feel the buzz. You need the wow factor. The rails community has a ‘Show don’t tell’ mentality, so check out these cool and quick screencasts: http://www.rubyonrails.org/screencasts
Excited yet?
<yoda>You will be… You will be…</yoda>
Step 2 –> Next up: The view from 30,000 feet:
- If you are reading this, you may already have this view. But, just in case, here is a good introduction to Ruby On Rails. http://www.onlamp.com/pub/a/onlamp/2005/10/13/what_is_rails.html good overview
Step 3 – Get Amongst It!

Time to make an actual app! You’ll need to set some things up first though. I’m on Windows, so if you are using another environment, this won’t be too much help. You’ll find that a large portion of Rails developers are Mac users, but I think the number of Windows Rails developers will continue to grow. I’ll post more on that later.

Anyway, let’s get moving. You can find a good first tutorial here: http://instantrails.rubyforge.org/tutorial/index.html It is a modified version of another great tutorial, but is set to fit Instant Rails. It is slightly out of date so see some notes below. Instant Rails (http://instantrails.rubyforge.org/wiki/wiki.pl ) is a super awesome one click and you’re done Ruby on rails environment, complete with MySql. Two Notes:

  • The tutorial mentions MySQL-Front (no longer available). Get HeidiSQL instead: http://www.heidisql.com/
  • The tutorial also talks about webrick server. Instead, mongrel will be running, but that shouldn’t affect the tutorial at all.

Step 4 – Editor

You may also an editor. I’ve tried RoRad, RadRails, and Aptana (which actually bought and integrated RadRails). Aptana is easily the best though it’s constant updates occasionally break things for me, and some of the configuration options aren’t intuitive. You can get Aptana here: http://www.aptana.com/download_rails_rdt.php (follow the instructions on the right)

Step 5 – More In Depth:

So now you should understand the high level view of rails, and have made your first app.  Make a more fully featured app by following the 4 Days on Rails tutorial here:

http://rails.homelinux.org/ A good second tutorial

The next step is to buy, read, and follow the book Agile Web Development On Rails http://www.pragmaticprogrammer.com/title/rails/ This is a great resource and definitely worth the money.

I’ll post more about other resources soon, but that should be enough to get people started.