Also appeared live in studio at KEXP a few days ago.
Also appeared live in studio at KEXP a few days ago.
I’d like to share another handy little rake task that helps you fill up your Rails application with realistic ‘fake’ data. Here it is
Using the very excellent Faker gem by Benjamin Curtis this task can be configured to populate your models with random amounts of fake information. This can a be a real time-saver for load testing, preparing demos/screencasts, or just filling up your pages with realistic data so you can get to work on your views.

The classic 'comb-over', a traditional fake.
The task includes the following features;
Simply configure it (see code comments for help on this), drop it into your Rails lib/tasks folder and run like so;
sudo gem install faker # if you haven't got the gem already
rake fakeout:small # or tiny / medium / large
Cleaning all ...
Faking it ... (small)
* Users: 53
* Questions: 53
* Answers: 38
* Tags: 38
* Taggings: 160
Done, I Faked it!
Along with this subdomain script from last month, I have pushed this code to github as a gist, so you can track it and grab updates to it whenever. The example there shows the task configured for a small Question & Answer app I’ve been working on.
And yes, I have started drawing a bit again, maybe it’ll make these posts more interesting to look at until I properly build this blog out.
Update Blarp! – Github is down, heres the raw script on pastie
The 1951 Mies van der Rohe designed Farnsworth House, an icon of 20th century modern architecture in Plano, Illinois.
If you follow my tweets, you might have noticed I occasionally use the excellent Cyclemeter (iPhone app) for collecting stats. What you might not have noticed is that you can actually shout support (or more likely abuse) at me while I’m cycling in and out of work!
The app uses text-to-speech to convert your @hiddenloop replies (pushed to the iphone through the app) and plays them over whatever music I’m listening to. Clever stuff! So next time you see a tweet like this holla back!
If you’re big into tracking exercise stats, I would recommend Cyclemeter over the other apps out there right now. I’ve tried MapMyRide, MapMyRun (and some others) and none work as well or are as simple to use. CycleMeter can also be used for running, but I prefer to use a Nike+ kit with an iPod nano (rather than lugging the iPhone around with me)
I’m working on a new feed & push importer service (with web-hooks) for Bugle that will allow any valid feed data to be displayed/updated within your site or blog. First examples of this will probably be put to use here, with my tweets, run data, cycle stats etc.
After watching the excellent Rails Best Practices presentation by Wen-Tien Chang, I took the opportunity to perform this optimization on my apps. Basically removing the default Rails route mappings from routes.rb. If your Rails app is RESTful then you should have all your resource endpoints defined in routes; and be using the Rails url/path view helpers in your templates. If this is the case you can remove these default mappings at the base of your routes file;
# these can go!
map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id.:format'
Doing this on my smaller Rails apps caused no problems, but in Bugle (a bigger app) I came across the following gotchas;
Look for urls or paths in your code that are defined with { :controller => :foo, :action => :bar } options, these should be replaced for the equivalent Rails helper for the controller – rake routes is your friend in figuring out what to use; eg.
url_for(:controller => 'blogs', :action => 'show' :id => @blog.id}
# should simply change to
blog_url(@blog)
# or
blog_path(@blog)
Calls to link_to should again use the same urls/path helpers as above – but in some instances this became a “Route not found problem”;
link_to 'comment website', @comment.website
# where @comment.website is a String e.g. http://hallo.com
# instead ditch link_to and use simple HTML tags
Happily I was able to delete a large amount of unnecessary routing specs, that were essentially testing the behavior of the 7 standard RESTFul Rails routes. Rails 3 has a completely revamped routing system (see here for details), so making optimizations and cleaning up routes.rb prior to it’s release is advisable.
A time saving rake task for adding or updating your local /etc/hosts file. I created this for Bugle, allowing me to quickly configure my development machine hosts file with subdomains used in the app. See the inline comments for an explanation.
Has the added feature of an array of default hosts to always add when you run it. Work is done on a tmp file then, then sudo copied on top of /etc/hosts. It exists as a gist on github so i’ll be sure to post any updates to it there.
# subdomains.rake (in /lib/tasks)
namespace :subdomains do
desc "adds the necessary hosts to your /etc/hosts file from current subdomains in your application"
task :setup => :environment do
# NOTE: default_hosts is used as a locator for the line to update in /etc/hosts
tmp_file, changed = '/tmp/etc_hosts_copy', false
default_hosts, hosts = %w(blog.local emptyblog.blog.local), []
# find all the subdomains used in your app (push to hosts array) - modify this to suit your app
Blog.find(:all).each { |blog| hosts << "#{blog.subdomain}.blog.local" unless blog.subdomain.blank? }
# build hosts line to add/edit
host_line = "127.0.0.1 " + hosts.sort.unshift(default_hosts).join(' ')
# work with a copied hosts file in tmp
%x[cp /etc/hosts #{tmp_file}]
file = File.new(tmp_file)
lines = file.readlines
lines.each do |line|
changed = true if line.gsub!(/^127.0.0.1 #{Regexp.escape(default_hosts.join(' '))}.+$/, host_line)
end
# add line, if no line found for update
lines += ["\n", host_line, "\n"] unless changed
file = File.new(tmp_file,'w')
lines.each { |line| file.write(line) }
file.close
# copy hosts file from tmp - may ask for sudo password
%x[sudo -p "Password:" cp #{tmp_file} /etc/hosts]
# explain what happened
puts "\nAdded the following domains:"
hosts.each { |host| puts "* http://#{host}" }
puts "\nAlso added defaults:"
default_hosts.each { |default| puts "* http://#{default}" }
puts "\n"
end
end
To run simply type
rake subdomains:setup
Over the Christmas holidays I started looking at integrating the nginx upload module into Bugle.
The nginx upload module has been around for a while, but I couldn’t find anything to explain exactly what went on with the params and the best way to integrate it with the Paperclip gem in Rails (which Bugle uses for all upload handling). As I worked with it I found a few caveats along the way.
Without the module, your Rails app will receive the raw uploaded data, parsing its entire contents before it can be used. For large uploads this can be quite slow, since Ruby is the work horse throughout.
With this module, parsing the file happens in C through nginx and before your Ruby application gets it. The module puts the parsed file into a tmp directory and strips all the multipart params out of the POST body, replacing it with params you can use (in Rails) to get the name and location of the file on disk. So by the time the request hits your application, all the expensive parsing has been done and the file is ready to be used by your app. Basically hard work is moved from Ruby to C.
To install the module you need to build nginx from source and pass it the upload module source directory as an argument. Since I run Bugle on a live production machine I wanted to work with things locally first. I began by setting up my local (OSX) box to match the production stack. I currently use nginx with Passenger and Ruby Enterprise Edition.
First download and untar both the nginx and upload module sources. Then build using the following commands (these worked on both OSX and my Ubuntu production server)
sudo /opt/ruby-enterprise-1.8.7-20090928/bin/passenger-install-nginx-module --nginx-source-dir=<path to nginx sources> --extra-configure-flags=--add-module='<path to upload module sources>'
Or if you’re just building nginx from source (without using the handy passenger installer) go with this;
cd <path to nginx sources>
./configure --add-module=<path to upload module sources>
make
make install
Don’t worry about any existing nginx.conf files you have or nginx vhosts etc. They will be unaffected after recompiling.
The next step is to configure nginx to use the module. In Bugle uploads are normally sent via a POST request to the uploads controller using a restful URL that can be one of;
POST /admin/blogs/:blog_id/uploads
or
POST /admin/themes/:theme_id/uploads
These hit the ‘uploads’ controller, ‘create’ action. I wanted to keep the same restful URLs so I tried the following regex in the nginx config.
location ~* admin\/(themes|blogs)\/([0-9]+)\/uploads { }
While this did work in recognising the URL, the upload module wouldn’t work with it. In the end I opted to use a new defined url for faster uploads, here is the route for it in Rails. You may have a simpler upload URL controller action making this unnecessary.
map.connect 'admin/uploads/fast_upload', :controller => 'admin/uploads',
:action => 'create',
:conditions => { :method => :post }
So the modified nginx server config becomes;
server {
listen 80;
server_name bugleblogs.com *.bugleblogs.com;
# ...
# somewhere inside your server block
# ...
# Match this location for the upload module
location /admin/uploads/fast_upload {
# pass request body to here
upload_pass @fast_upload_endpoint;
# Store files to this directory
# The directory is hashed, subdirectories 0 1 2 3 4 5 6 7 8 9 should exist
# i.e. make sure to create /u/apps/bugle/shared/uploads_tmp/0 /u/apps/bugle/shared/uploads_tmp/1 etc.
upload_store /u/apps/bugle/shared/uploads_tmp 1;
# set permissions on the uploaded files
upload_store_access user:rw group:rw all:r;
# Set specified fields in request body
# this puts the original filename, new path+filename and content type in the requests params
upload_set_form_field upload[fast_asset][original_name] "$upload_file_name";
upload_set_form_field upload[fast_asset][content_type] "$upload_content_type";
upload_set_form_field upload[fast_asset][filepath] "$upload_tmp_path";
upload_pass_form_field "^theme_id$|^blog_id$|^authenticity_token$|^format$";
upload_cleanup 400 404 499 500-505;
}
location @fast_upload_endpoint {
passenger_enabled on; # or this could be your mongrel/thin backend
}
}
At this point its worth testing the app. Perform an upload and check that nginx is sending these params to your controller action. For more info here is a complete guide to all the module directives.
Finally I needed to modify Rails to make use of these new params. In Bugle the upload module has_attached_file :asset using Paperclip. One problem is that new file in the tmp/ directory exists with a hashed meaningless filename, so simply passing this file to self.asset will not work for Paperclip processing. It needs to have the original filename and content_type. Fortunately we have those in the new params too. So the new fast_asset= method shifts and renames the file into a sub tmp directory (which gets cleaned on the after_create filter). All this seems a little convoluted, but I couldn’t see any other way to do this, without perhaps modifying the Paperclip internals. If anyone has any suggestions around this let me know in the comments.
class Upload < ActiveRecord::Base
has_attached_file :asset, :styles => {:thumb => ["64x64#", :jpg]},
:url => ":base_url/:resource/:styles_folder:basename:style_filename.:extension",
:path => "public/u/:resource/:styles_folder:basename:style_filename.:extension"
attr_accessor :tmp_upload_dir
after_create :clean_tmp_upload_dir
# handle new param
def fast_asset=(file)
if file && file.respond_to?('[]')
self.tmp_upload_dir = "#{data['filepath']}_1"
tmp_file_path = "#{self.tmp_upload_dir}/#{data['original_name']}"
FileUtils.mkdir_p(self.tmp_upload_dir)
FileUtils.mv(data['filepath'], tmp_file_path)
self.asset = File.new(tmp_file_path)
end
end
private
# clean tmp directory used in handling new param
def clean_tmp_upload_dir
FileUtils.rm_r(tmp_upload_dir) if self.tmp_upload_dir && File.directory?(self.tmp_upload_dir)
end
end
For completeness here is the regular controller action;
def create
# ...
@upload = @resource.uploads.build(params[:upload])
if @upload.save
# ...
end
Thats it, you should now have much faster uploads through nginx! To see the improvement try uploading a 50Mb+ file with/without the module. In a future series of posts (and with this project on github) – I will be conducting a complete walkthrough of the uploader I have built for Bugle. End to end from the browser, to Rails and the actual server configuration.