.png and .jpg are the two of the most popular formats for graphics on the web. Knowing when to use each can dramatically improve your page's load-time and can mean the difference between an amateur and professional finish.
Quality Vs. Compression
The crux of this argument is really quality vs. compression. As png is a lossless compression format, it compresses images without losing quality. Image quality can often make a big difference in how professional your website looks, especially when it comes to photographs and your logo.
In my opinion, photographs and other important graphics on your page should be png format or, if you must use jpg, in a high compression format.
Although png offers higher quality, the trade-off is that the file-sizes are much larger (sometimes 3 or 4 times larger). This will obviously have a negative effect on page load times. The longer your visitors have to wait on your web pages to load, the more likely it is that they will leave your site.
With this in mind, jpg is the more logical solution for graphics which don't need to be super-high quality. Things like thumbnails, background images, user-avatars, button images etc. can all be served up as jpg without damaging the overall appearance of your website and can reduce the time your pages take to load.
Other Considerations
If quality is a must, there are other things we can do to improve page load speed without scrimping on image quality. Here are a few suggestions:
- Utility graphics such as buttons and page elements can be grouped together in one image called a sprite. See SpriteMe for more information.
- Resize images so they are exactly the same height and width as they appear on the page. Resizing an image to be smaller using HTML or CSS is a waste of bandwidth.
- Trim any unnecessary white space/background content out of your images so only what's required is kept.
- Consider using CSS and HTML5 canvases to replace images (but create the same effect) where appropriate.
- Use ImageMagick to resize images and change the format when required.
Conclusion
In conclusion, png offers higher quality than jpg but this results in higher file size and longer load times. In instances where quality, is essential, use png. Otherwise, jpg is better!
In any case, always look for alternatives to using large graphic files wherever possible.
If you're using bundler to manage your ruby gems, and capistrano to deploy your application, this handy deploy task can save you some time.
Add the following to deploy.rb
# ...lots of other code
namespace :bundle do
desc "run bundle install and ensure all gem requirements are met"
task :install do
run "cd #{current_path} && bundle install --without=test --no-update-sources"
end
end
before "deploy:restart", "bundle:install"
After the code on the server has been updated, but before the application has been restarted, this task will run bundle install to ensure all gem requirements are met.
Note: "--without=test" and "--no-update-source" are optional. "--without=test" ignores any gems that are in the test group within your Gemfile and "--no-update-source" speeds things up a little by skipping the "Fetching source index for http://rubygems.org/..."
A quick ruby tip for calling one rake task from another rake task:
namespace :mytasks do
desc "Task number one"
task :one do
# do something here
end
desc "Task number two"
task :two do
Rake::Task["mytasks:one"].execute
end
end
Note: Even although task one and task two are within the same namespace, you still have to include the full name (namespace and task name) in the method call.
For Mac users, TextMate is powerful tool for writing code. But having a great tool is pretty pointless unless you know how to use it. By spending a few hours learning about TextMate’s features you can almost half the time it takes you to write code. This tutorial will walk you through creating a simple Rails app using some of TextMate’s more advanced features.
Before you start, I’d recommend you get the latest version of the Ruby on Rails TextMate bundle and install it. Also – I realise the application from this tutorial is far from perfect but I figured this would be a good way to introduce the features and show examples of how they can be used. If you have any suggestions please leave a comment below.
Lets create a new rails app, in your terminal type the following:
Once the app has been created:
cd text_mate_demo && mate .
to open the full application in TextMate.
In true Rails fashion, this demo is going to create a simple blog site. To create our first model type the following in your terminal window:
rails g model Post title:string body:text
rails g model Comment name:string body:text post:references
Next, open comment.rb and press ctrl + | (or ctrl + shift + ) and 2 to run the migration.
Next, still in TextMate, type ctrl + alt + cmd + D to hide the project drawer – we won’t be using it in this tutorial. Instead we’ll navigate by pressing cmd + T and typing (part of) the name of the file you want to open. Lets go to post_test.rb and write some unit tests for our Post model. Type cmd + T followed by “poste” and hit enter.
For our unit tests we’ll create a setup method. Here’s a good chance to introduce tab triggers. Type “def” followed by the tab key. This should create a new empty method with the name highlighted. Name this method “setup” and hit tab again to jump down a line.
To setup our unit tests lets create a new Post instance and run validation checks on it.
require 'test_helper'
class PostTest < ActiveSupport::TestCase
def setup
@post = Post.new
@post.valid?
end
end
On a new line type “test” and hit tab to create a new test. Lets call it “should not be valid without title”. As a shortcut to writing “assert_equal”, you can write “ase” and hit tab, then tab for the expected value and tab again for the actual value.
I’ve also added another two tests to meet our requirements:
require 'test_helper'
class PostTest < ActiveSupport::TestCase
def setup
@post = Post.new
@post.valid?
end
test "should not be valid without title" do
assert_equal @post.errors.on(:title), "can't be blank"
end
test "should not be valid without a body" do
assert_equal @post.errors.on(:body), "can't be blank"
end
test "should have many comments" do
@post = posts(:one)
2.times { @post.comments.create! :name => "John Smith", :body => "This is a comment" }
end
end
Save this page and then press ctrl + followed by 7 to test units. The three tests should fail. Now press alt + cmd + down to jump to post.rb.
Our unit tests specify that our posts should not be valid without a title or a body. To quickly add a “validates_presence_of” validation, type “vp” and then press tab. Now type “title” and then press tab and delete to clear the extra options (which we don’t require here).
For our second validation we’re going to try something different. On a new line, type “va” and then press esc. The escape key can be used to auto-complete any method, constant or variable name that’s already present on that document. This is a great way to save time retyping long method/variable names.
To add our has_many association, type “hm” and hit tab. Type “comment” and then tab, delete again.
These validations could do with being DRYed up but we’ll leave that for now. In the meantime, press cmd + / to start a comment and leave yourself a TODO note “TODO – DRY this up”.
class Post < ActiveRecord::Base
# TODO: - DRY this up
validates_presence_of :title
validates_presence_of :body
has_many :comments
end
Now we need to create a Comment model so our third test passes.
rails g model Comment name:string body:text post:references
rake db:migrate
Quick note – jump to comments.yml and populate the “post” attribute like so:
one:
name: MyString
email: MyString
post: one
body: MyText
two:
name: MyString
email: MyString
post: two
body: MyText
Now jump to comment_test.rb, again using cmd + T and add some tests like so:
require 'test_helper'
class CommentTest < ActiveSupport::TestCase
def setup
@comment = Comment.new
@comment.valid?
end
test "should belong to post" do
assert_equal( posts(:one), comments(:one).post )
end
test "should not be valid without name" do
assert_equal(@comment.errors.on(:name), "can't be blank")
end
test "should not be valid without body" do
assert_equal(@comment.errors.on(:body), "can't be blank")
end
end
Again, run the tests, see the failures and then write the model:
class Comment < ActiveRecord::Base
belongs_to :post
validates_presence_of :name, :body
end
The tests should now pass.
There’re a whole bunch of shortcuts for commonly used methods. Most of them are used by writing a few letters and then hitting tab. To browse through them go to Bundles > Ruby on Rails.
This may be a good time to DRY up the Post model. Press ctrl + shift + T to have a look at the annotations you’ve left in your code. You should see 1 TODO in post.rb. Click on the link to jump to this comment and remove the duplication:
class Post < ActiveRecord::Base
validates_presence_of :title, :body
has_many :comments
end
Now we need a controller for our posts. By pressing shift + alt + cmd + down you should see the goto menu. Press 1 for controller. Since there isn’t already a posts controller TextMate will ask if you’d like to create one. Click “create”. (From this point let’s just imagine we’ve written tests first).
Now we need an index action to display our posts. Again, using our “def” then tab shortcut, create an index action and try to add some code here to find all of the posts and order them by “created_at DESC”. But crap! We’ve forgotten the different options available for the find method. Here comes the coolest trick TextMate has to offer…
Highlight the find method and press ctrl + H. A window will pop up with either 2 or 3 options. If you only see 2, then select “Documentation for Selection” and select “ActiveRecord::Base::find”. If you do have the 3rd option “Documentation for Word” then select it. This gives you a chance to browse the Rails documentation for any method and see examples of how to use it. This is a really powerful tool! By pressing cmd + H you can see the documentation for practically any gem you’ve installed. It also works for other languages like HTML and CSS so it’s a great way to learn as you go.
You quickly read up on find and see the options available. Now you can add the required code to the index action:
class PostsController < ApplicationController
def index
@posts = Post.where(['created_at > ?', 3.months.ago]).order("created_at DESC").limit(5).eager_load(:comments)
end
end
To collapse this method and neaten your controller, press alt + cmd + 2. You can collapse foldings at different levels by using alt + cmd + 1,2,3 etc.
With the cursor still in the index action, press alt + cmd + down to create the view template for the index action.
To create rhtml tags press ctrl + >. By pressing this again you can cycle through the various erb tags available. To create new html tags press ctrl + <. Using this method, add the post title to our new heading tags like so:
<% @posts.each do |post| %>
<h3><%= post.title %></h3>
<%= post.body %>
<% post.comments.each do |comment| %>
<% div_for comment do %>
<p><%= comment.name %></p>
<p><%= comment.body %></p>
<% end %>
<% end %>
This tutorial could go on for quite some time but those are the commands that I feel are the most useful. You may have already noticed too that TextMate already has its own Rails tutorial which covers loads of the snippets etc. You can access this tutorial by pressing cmd + H and selecting “View Demo Help”.
If there’s anything you’d like to see here that I haven’t covered, feel free to leave a comment.
Raw dates and times in Ruby are not too user-friendly!
The default times and datetimes in rails are pretty unattractive and contain more information than we usually need to display.
For example:
@user.created_at # => Mon, 18 May 2009 19:51:51 +0100
A useful way around this is to use the strftime() method to specify the format of the date or time.
So instead, we can use:
@user.created_at.strftime("%d %b %y") # => "18 May 09"
This way we only display the information we want to.
Specifying time with strftime() throughout an entire app is not ideal though; apart from anything else it’s not DRY. Also, if your strftime formats are quite elaborate, the chances of you mistyping them and leaving inconsistencies throughout your app are pretty high.
Enter: to_formatted_s()
to_formatted_s() is a method available to classes Date, Time and DateTime which converts the time format to a more practical format.
@user.created_at.to_formatted_s(:time) # => "20:06"
The default format is :default but you can specify any of the other pre-set formats by simply passing the name of the format as a parameter.
Classes Time and DateTime share these formats:
DATE_FORMATS = {
:db => "%Y-%m-%d %H:%M:%S", # => "2009-05-18 20:03:48"
:number => "%Y%m%d%H%M%S", # => "20090518200545"
:time => "%H:%M", # => "20:06"
:short => "%d %b %H:%M", # => "18 May 20:06"
:long => "%B %d, %Y %H:%M", # => "May 18, 2009 20:07"
:long_ordinal => lambda { |time| time.strftime("%B #{time.day.ordinalize}, %Y %H:%M") }, # => "May 18th, 2009 20:07"
:rfc822 => "%a, %d %b %Y %H:%M:%S %z" # => "Mon, 18 May 2009 20:08:25 +0100"
}
and the Date class has these formats:
DATE_FORMATS = {
:short => "%e %b", #=> "18 May"
:long => "%B %e, %Y", # => "May 18, 2009"
:db => "%Y-%m-%d", # => "2009-05-18"
:number => "%Y%m%d", #=> "20090518"
:long_ordinal => lambda { |d| d.strftime("%B #{d.day.ordinalize}, %Y") }, # => "May 18th, 2009"
:rfc822 => "%e %b %Y" # => "18 May 2009"
}
(RFC822 is the standard for the format of ARPA internet text messages.)
As well as using these default time formats, you can also set your own formats that can be used throughout your entire app.
To do this, simply expand on the default DATE_FORMATS.
First, create a new file: config/initializers/datetime_formats.rb and add the following:
Date::DATE_FORMATS[:my_format] = lambda { |t| t.strftime("#{time.day.ordinalize} of %B, %Y") }
Time::DATE_FORMATS[:my_format] = lambda { |t| t.strftime("#{time.day.ordinalize} of %B, %Y") }
If you’re setting the same format(s) on both Date and Time then this can be further simplified to:
[Time, Date].map do |klass|
klass::DATE_FORMATS[:my_format] = lambda { |t| t.strftime("#{time.day.ordinalize} of %B, %Y") }
end
Now all you have to do is call
<%= @user.created_at.to_formatted_s(:my_format) %>
or even simpler:
<%= @user.created_at.to_s(:my_format) %>
Much DRYer!
Jo Hund has put together a great cheat-sheet for stfrtime formats