Filed in
rails, ruby |
7 January, 2008
Apart from well known stuff like start_form_tag being deprecated, these things broke for us with Rails 2.0.2:
1. Super is no longer called in tests. Use setup_with_fixtures instead, which will work in rails 2.0.2 and future versions where the bug is fixed.
2. The paths to partials used by ActionMailer have changed - now we must use “controllername/partialname” as opposed to “../controllername/partialname”
3. The handling of plusses in urls has changed. It probably changed in this changeset. The result is that passing a + in a url path no longer gets translated to a space in the handling done by rails. So now we have correct handling according to RFC2396.
Filed in
rails, ruby |
26 December, 2007
We have a nice new server which runs 64 bit fedora linux. It’s quick and good. But alas FreeImage won’t compile on this architecture. So bang goes any chance of using Image science.
But really I am only interested in finding the dimensions of my images. And it shouldn’t be hard. I found this post about using the gd library to resize images. Well I don’t need to resize images, but I thought it was a good approach, so I hacked it to do what I wanted.
Here is my code (credit to Damien Tanner for the original code). Very short and simple, and you just need the gd library installed together with the header files. On my system ‘yum install gd-devel’ did the trick.
if !ENV['HOME']
ENV['INLINEDIR'] = RAILS_ROOT + “/tmp”
end
require ‘inline’
class ImageInfo
SUPPORTED_FORMATS = %w(jpg jpeg png gif)
def initialize(filename, type=nil)
@filename = filename
@type = SUPPORTED_FORMATS.index(type || @filename[/[^\.]*$/].downcase)
end
def height
unless @height
image_size
end
@height
end
def width
unless @width
image_size
end
@width
end
def fetch_image_size(filename, image_type); end
def image_size
if @type
fetch_image_size(@filename, @type)
else
raise “Unknown type of image”
end
end
inline do |builder|
builder.include ‘”gd.h”‘
builder.add_link_flags “-lgd”
builder.c <<-”END”
void fetch_image_size(char *filename, int image_type) {
gdImagePtr im_in;
FILE *in;
in = fopen(filename, “rb”);
/* Support diff image types: jpg jpeg png gif */
switch(image_type) {
case 0:
case 1: im_in = gdImageCreateFromJpeg(in);
break;
case 2: im_in = gdImageCreateFromPng(in);
break;
case 3: im_in = gdImageCreateFromGif(in);
break;
}
fclose(in);
if (im_in) {
rb_iv_set(self, “@width”, INT2FIX(im_in->sx));
rb_iv_set(self, “@height”, INT2FIX(im_in->sy));
}
}
END
end
end
To use it, do something like this:
begin
iinfo = ImageInfo.new(filepath)
my_image_width = iinfo.width
my_image_height = iinfo.height
rescue Exception=>e
# check errors
end
Filed in
rails, ruby |
4 December, 2007
Image Science is a small plugin that does just the stuff you need from RMagick, without all the weight that RMagick brings with it. It uses FreeImage to do the manipulations. (By the way, the MacPorts installation of that completely failed on my Leopard Mac, I advise you compile it by hand.)
I was getting these kind of errors from my dev site:
SystemExit (exit):
/usr/local/lib/ruby/gems/1.8/gems/RubyInline-3.6.5/lib/inline.rb:70:in `exit'
/usr/local/lib/ruby/gems/1.8/gems/RubyInline-3.6.5/lib/inline.rb:70:in `rootdir'
/usr/local/lib/ruby/gems/1.8/gems/RubyInline-3.6.5/lib/inline.rb:84:in `directory'
/usr/local/lib/ruby/gems/1.8/gems/RubyInline-3.6.5/lib/inline.rb:258:in `so_name'
/usr/local/lib/ruby/gems/1.8/gems/RubyInline-3.6.5/lib/inline.rb:294:in `load_cache'
/usr/local/lib/ruby/gems/1.8/gems/RubyInline-3.6.5/lib/inline.rb:678:in `inline'
/usr/local/lib/ruby/gems/1.8/gems/image_science-1.1.3/lib/image_science.rb:84
Not nice. Curiously everything worked fine on the production system. A bit of digging in the code was required. Image Science uses a gem called RubyInline to allow it to compile and install C extensions to Ruby on the fly. And RubyInline places these extensions in a directory called “.ruby_inline”. It chooses this directory from either the environment setting INLINEDIR, or if that isn’t set then HOME.
And there is the problem - my dev system was running as apache which does not have a home directory.
I added this kind of code:
if RAILS_ENV == "development"
ENV['INLINEDIR'] = RAILS_ROOT + “/tmp”
end
And I made sure that tmp in my rails dir was writeable by apache (but do not make it world writable, that won’t work either).
Filed in
IOCCC |
7 November, 2007
Here is my video of the IOCCC presentation at the Vintage Computer Festival at the Computer History Museum. Low quality I’m afraid - just recorded with the built in mic and camera on my Mac.
Filed in
OSX, rails |
28 October, 2007
Some small breakage in Leopard - it seems a couple of parts of X11 have been removed. Starting my rails setup, which has GraphicsMagick installed (read here for how), results in this:
dyld: NSLinkModule() error
dyld: Library not loaded: /usr/X11R6/lib/libdpstk.1.dylib
Referenced from: /usr/local/lib/ruby/gems/1.8/gems/rmagick-1.15.9/lib/RMagick.bundle
Reason: image not found
Trace/BPT trap
Those dylib files do not exist any more. But… I have a backup (I hope you do). I ended up doing this:
$ cd /Volumes/BackupDisk/usr/X11R6/lib
$ sudo cp libdps*1.0* /usr/X11R6/lib/
$ sudo ln -s /usr/X11R6/lib/libdpstk.1.0.dylib /usr/X11R6/lib/libdpstk.1.dylib
$ sudo ln -s /usr/X11R6/lib/libdps.1.0.dylib /usr/X11R6/lib/libdps.1.dylib
All fixed.
Without a backup you are going to need to find those two files from somewhere. I’m not sure I can legally post them here, but email me if you are stuck.
Filed in
rails, ruby |
25 October, 2007
Not only does exception notifier not work, you probably don’t know that it doesn’t work either. All your code has just become super-exception-free all of a sudden.
Ah, wishful thinking.
If you have installed Ruby 1.8.6p111 then you will want to take note of the comments here.
Just modify the file vendor/plugins/exception_notification/views/_environment.rhtml to say:
* <%= "%-*s: %s" % [max.length, key, @request.env[key].to_s.strip] %>
instead of
* <%= "%*-s: %s" % [max.length, key, @request.env[key].to_s.strip] %>
Filed in
rails |
19 October, 2007
We have a problem - we really do. Each time a user requests a page they have to make 50 http requests just to get back a “Not modified” message from the web server. Their browser is asking about every little image and css file and so on. Every one on the page. Those messages are small, but they add up. And as we know, “make fewer HTTP requests” is Steve Souder’s number one rule for speeding up your website.
What we need to do is to add an Expires header to our static content - one that gives a time a long way in the future so that the browser knows that the content should stays in the cache -and not be fetched again, or even checked (if it has been updated) from the server. But what if we want to change a css file or a js file? The user will get the old one from their own cache. No good.
Well Rails has a mechanism to prevent this - it adds a 10 digit number on the end of each url (as generated by image_tag, stylesheet_link_tag, or javascript_include_tag for instance) in the query string. That number is based on the file modification time - so when the file is updated then the URL will change. It’s like having a remote way to expire the item in the browser’s cache.
So it’s simple right? Just turn on mod_expires in Apache? Not so fast. What about those images and items that do not have the query string? We don’t want to send an expires header for those. Definitely not. (And you will likely have some - for instance images that are referenced from your css files.) If you do, the user is stuck with their cached version even if you change the file on the server.
So we need some way of selectively turning on the expires header in apache.
One way [Danny Burkes] (look at the update at the bottom) is to segregate by directory what you want to expire and what not. But this seems a bit clumsy to manage. (Also, side issue, I am not totally convinced that the munging of the urls provided by the plugin is needed - at least section 13.9 of the HTTP 1.1 spec seems to suggest that content with query strings will be cached fine if an explicit expires header is given.)
Actually you can do what you need with one symbolic link and some apache magic. The obvious magic won’t work - you can’t detect what’s in a query string using a LocationMatch or FilesMatch container. But we can get around this with a rewrite rule, and a directory container.
This is from my apache httpd.conf file:
# add something we can do a directory match on
RewriteCond %{QUERY_STRING} ^[0-9]{10}$
RewriteRule ^(.*)$ /add_expires_header%{REQUEST_URI} [QSA]
# the add_expires_header directory is just a symlink to public
<Directory “/path/to/rails_app/public/add_expires_header”>
ExpiresActive On
ExpiresDefault “access plus 10 years”
</Directory>
This detects those query strings (we assume you don’t use 10 digit query strings for anything else), and adds a directory on the front of the path.
We use this as something we can detect in a Directory container. And in there we turn on the expires header.
We need one more thing:
cd /path/to/rails_app/public
ln -s . add_expires_header
The symbolic link doesn’t go anywhere, and that’s just what we want. All the images and css files and whatnot will be found in their usual places. It’s pretty unobtrusive -you don’t need to change anything in your app to start to benefit from the expires header.
It’s a big benefit - we have literally gone from 50 HTTP requests per page to about 16. And with some tweaking we’ll get it down more - some of those are from references to images in css, but a few are due to us not using urls generated by rails for static content. And we can fix those.
Filed in
rails |
13 October, 2007
We have a reasonably complicated set of logic inside our controllers that is able to take an incoming url and serve the right content depending on 3 different parts of the url in various combinations and permutations. There are quite many DB queries required, and sometimes it goes slow.
I wanted to find where the system was spending its time, and the DB query logging provided by ActiveRecord was not enough to show me where the performance needed tuning.
The rails benchmark method to the rescue. And a little meta-programming. I added this to application.rb:
if ENV['EXTRA_BENCHMARKING']
@@added_methods = []
def self.method_added(id) # wrap a benchmark around all controller method calls
name = id.id2name
if name =~ /^old_(.*)$/
return if @@added_methods.include?($1)
else
return if @@added_methods.include?(name)
end
@@added_methods << name
alias_method “old_#{name}”.intern, name.intern
eval %{
def #{name}(*args, &block)
logger.info(”*=*=*=*=*=*=*=*=* #{name} START”)
self.class.benchmark(”*=*=*=*=*=*=*=*=* #{name}”, Logger::DEBUG, false) do
send(”old_#{name}”, *args, &block)
end
end
}
end
end
It’s important to add this pretty much at the top of application.rb, or at least before all the methods you want to time.
Method_added gets called every time a method is added to the class. As each method is added we make a note of it in our array @@added_methods, and then alias it with the prefix “old_”. Then we define a new method adding the logger calls and the benchmark call.
The output looks like this:
...
*=*=*=*=*=*=*=*=* save_url_history START
*=*=*=*=*=*=*=*=* save_url_history (0.00004)
*=*=*=*=*=*=*=*=* missing_page_catcher START
*=*=*=*=*=*=*=*=* get_categories START
*=*=*=*=*=*=*=*=* get_categories (0.00028)
*=*=*=*=*=*=*=*=* get_continents START
*=*=*=*=*=*=*=*=* get_continents (0.00004)
...
Also you will get all your regular debug level logging in between these new lines in your log.
Note that the @@added_methods array is important because both alias_method and the eval’d def result in a call to method_added - so we use the array to prevent infinite recursion.
Just run rails up with the EXTRA_BENCHMARKING environment variable set, and you are away:
EXTRA_BENCHMARKING=1 script/server -e production
I like to use production mode so I can see the effect of the fragment caching. But you’ll get valid results in development mode too.
Filed in
rails |
13 October, 2007
Curiously adding config.log_level = :debug to environment.rb appears to have no effect on the logging performed by ActiveRecord. To get to see the detailed logging and benchmarking of your database queries in production mode you need to place this after the Rails::Initializer.run block in environment.rb:
ActiveRecord::Base.logger.level = Logger::DEBUG
Filed in
rails |
9 October, 2007
Gotcha 1
Failing tests. We’d like them to pass of course. But there is something strange going on - the error is not a test error, it seems to come from rails itself:
1) Error:
test_searcher_email(OrderMailerTest):
NoMethodError: You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.[]
/usr/local/lib/ruby/gems/1.8/gems/activerecord-1.15.4/lib/active_record/fixtures.rb:498:in `orders’
./test/unit/order_mailer_test.rb:22:in `test_searcher_email’
1 tests, 0 assertions, 0 failures, 1 errors
What does it mean? The clue here is that the error comes from fixtures.rb. The other thing to note is that I have a setup in my test:
def setup
ActionMailer::Base.delivery_method = :test
ActionMailer::Base.perform_deliveries = true
ActionMailer::Base.deliveries = []
…
end
Which should be fine, but there is this nasty error. Let’s try something in the setup method - we’ll add a call to super there:
def setup
ActionMailer::Base.delivery_method = :test
ActionMailer::Base.perform_deliveries = true
ActionMailer::Base.deliveries = []
…
super
end
And the result:
Started
.
Finished in 0.160284 seconds.
1 tests, 1 assertions, 0 failures, 0 errors
Great, that’s fixed it. But why? Well it took me a while to find it. Have a look in /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.15.4/lib/active_record/fixtures.rb - there is your culprit at line 554:
alias_method :setup, :setup_with_fixtures
So the rule is, if you override setup you need to call super otherwise you don’t get your fixtures set up properly.
Gotcha 2
Ever tried to test your routes? You should. But if you have routes that depend on what’s in your database you’ve got a problem. Why? Because the routing is initialised before any fixtures are loaded. In fact none of the test related code is loaded at that point. So your tests just won’t work.
I don’t know a good way to fix this. In the end I opted for a fudge at the beginning of routes.rb:
if ENV['RAILS_ENV'] == “test” && Category.count == 0
Category.create!(:name=>”Families and Friends”, :alternative_name=>”Family”)
end
And I have a fixture that contains the same information. This isn’t really DRY, but I don’t really see a good way to do this. Maybe we could read the fixtures file ourselves and get the data from there.
But at least the tests run ok now.
« Previous Page — Next Page »