articles tagged with rails

FAQtory update

no comments yet, post one now

Its been a while since I mentioned anything about the FAQtory app. Rest assured that, along with Bugle it’s been keeping me busy in my spare time!

To re-cap quickly, the FAQtory (pronounced Factory) is a simplified stackoverflow-like question and answer app. It allows you to Ask and Answer questions on any topic you like. The app is entirely content-focused and deliberately simple. A simple user voting mechanism ensures the most popular questions and most correct answers filter to the top.

Looking at the logs, it was last August when I kicked off the very first commit. After almost a year of on-again, off-again work and long periods of inactivity, last week finally saw it launch! In its current form the FAQtory is deployed for a single account, (pmfaqtory.com) – you can take a look here. Over time PMfaqtory intend to use FAQtory to build a resource of project management questions and answers.

This is a big milestone for me and with all features locked down for launch, I can concentrate on preparing for a general release. This will involve adding an account model, pricing options and payment gateway integration. I’ll also be moving the app to a new server stack and cloud based host.

Over the coming weeks I hope to talk more about some the design and technology decisions I have made so far. For a bit of fun, here is a replay of the Git log using the latest Gource visualisation engine (now with added ‘Bloom’ effect!)

music is 'Penguin' by the Books

Gource is now available on Mac Ports which is a much easier install than it used to be (again, compiling from source stills give errors on FTGL)

July 24, 2010 19:08

ActsAsTextcaptcha

no comments yet, post one now

Announcing ActsAsTextcaptcha! It’s the first gem to be extracted from Bugle.

ActAsTextcaptcha

ActsAsTextcaptcha - pretending to be human just got tougher!

ActsAsTextcaptcha provides spam protection for your Rails models using logic questions from the excellent Text CAPTCHA web service (by Rob Tuley of Openknot).

To get started, grab an API key for your website and follow along with the instructions in the README.

You can also play around with a working demo on heroku.

The gem can be configured with your very own logic questions (to fall back on if the textcaptcha service is down) or as a replacement for the service. It also makes use of bcrypt encryption when storing the answers in your session (recommended if you’re using the default Rails CookieStore)

About the code

The gem contains two parts, a module for your ActiveRecord models, and a tiny helper method (spamify).

A call to spamify(@model) in your controller will query the Text CAPTCHA web service. A restful GET request is made with Net::HTTP and parsed using the standard XML::Parser. A spam_question is assigned to the model, and an array of possible answers are encrypted in the session.

validate_spam_answer() is called on @model.validate() and checks that the @model.spam_answer matches one of those possible answers in the session. This validation is only carried out on new records, i.e. never on edit, only on create. User’s attempted spam answers are not case-sensitive and have trailing/leading white-space removed.

BCrypt encryption is used to securely store the possible answers in your session. You must specify a valid bcrypt-salt and (computational) cost in your options. Without these options possible answers will be MD5-hashed only.

allowed?() and perform_spam_check?() are utility methods (that can be overridden in your model) They basically act as flags allowing you to control creation of new records, or whether the spam check should be carried out at all.

If an error occurs in loading or parsing the web service XML, ActsAsTextcaptcha will fall back to choose a random logic question defined in your options. Additionally, if you’d prefer not to use the service at all, you can omit the api_key from your options entirely.

If the web service fails or no-api key is specified AND no alternate questions are configured, the @model will not require spam checking and will pass as valid.

For more details on the code please check the documentation.

About the logic questions

Text CAPTCHA’s logic questions are aimed at a child’s age of 7, so they can be easily solved by all but the most cognitively impaired users. As they involve human logic, such questions cannot be solved by a robot. There are both advantages and disadvantages for using logic questions rather than image based captchas, find out more at Text CAPTCHA.Rob Tuley of Openknot

Finally, since things have changed so much over the years, i’ll be doing a refresher post on the state of play for creating/testing and releasing a Rails gem/plugin from scratch.

Rake Tasks

If you choose to install as a plugin, or are interested in the code, the following rake tasks are available;

  • rake spec (run the tests)
  • rake rcov (run tests showing coverage)
  • rake rdoc (generate docs)

Links

Who’s who?

Rails 3 preparation

no comments yet, post one now

Here’s a few links on Rails 3 I’ve collected in preparation for its release. I haven’t read these all through yet, or started using Rails 3 in any way, but I have a couple of production apps primed and waiting to be upgraded as soon as it launches.

Start with this one from RubyInside, 36 links to get you going

And finally some official release notes;

April 15, 2010 14:27

Bugle, FAQtory and other things

no comments yet, post one now

Just a quick update to explain what I’ve been working on lately.

Some years ago (lets say 3) I was approached by a long-time business pal. He had the idea of creating a very simple FAQ gathering web application. It would offer no other features, only questions and answers, and would provide a rich knowledge base for those working in the field of project management. Sound familiar ?

As the years passed and other projects, distractions got in the way, the FAQtory (as it was named) was put on hold. Then in September 2008, stackoverflow launched and soon after their 3rd party offering, stackexchange and following that an entire barnyard of clones; stacked, cnprog, soclone to name a few.

This was all a bit dis-heartening, given we had the idea some years ago and never jumped on it. Around Christmas last year, I picked up the FAQtory again, dusted it off and got to work in my spare time building it. Right now I am at the final stages of styling the UI and launching the first version as a sub site at pmfaqtory.com.

some notebook sketches made while designing The FAQtory

some notebook sketches made while designing the FAQtory

So with my attention on the FAQtory, work on Bugle has been put on hold. After the FAQtory launches however, i’ll be back developing on it again. Being a one-man show its hard to maintain a focus on more than one app at a time. Im encouraged by Rework and DHH’s concept of a little italian restaurant on the web

I’ve learnt a great deal from building these two apps and in many cases I’ve applied learning gained from experiences in one, to the other. I also have some valuable by-products I hope to share. I’ve already mentioned some on this blog and on github. So stay tuned, in future posts I’ll try to explain more of the decisions I’ve made while building each app, and hopefully get around to open sourcing more code.

March 18, 2010 18:09

Fake it till you make it!

no comments yet, post one now

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 classic 'comb-over', a traditional fake.

The task includes the following features;

  • Assign relationships between models with a random object picker
  • A random tag picker, for populating tag lists
  • Fake out your app at different different sizes; tiny, small, medium and large
  • Clean task, to clear stuff out before you start your faking
  • Disables mail sending before faking out, since before/after and other code hooks in your app might trigger sending mails
  • Helper to fake out a randomly picked time object within the last year
  • Prompts to ask before deleting any data on cleaning task.
  • Summary report after faking out, explaining exactly how many objects were created

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 OR 
sudo gem install ffaker # use the faster faker gem from Emmanuel Oga 
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 – I have amended the task definitions to take a single argument; no_prompt. This turns off the confirm prompt and is useful for non-interactive running, e.g. with heroku rake commands for instance.

Update – This rake task is also fully compatible with the faster ffaker gem by Emmanuel Oga

February 07, 2010 15:31

Cleaning your Rails routes

no comments yet, post one now

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.

Related Links

January 21, 2010 12:18

A timesaving subdomain rake task

no comments yet, post one now

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
January 14, 2010 16:53

NGINX upload module with Paperclip (on Rails)

11 comments

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.

Why bother?

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.

Compiling Nginx to include the module

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.

Configuring Nginx

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
 } 
}
  • After processing, the upload module puts the file in one of 10 tmp directories, these should be already created and accessible by your Rails app, since I am using Capistrano, I’ve chosen the shared/ folder to house the tmp files (you’d risk loosing a file mid-deploy if you chose somewhere in the RAILS_APP current/ directory)
  • The upload_pass_form_field directive preserves any params that match the regex (we don’t want the module to strip the following params: format, blog_id, theme_id or authenticity_token)
  • The upload_pass directive sets what should handle the request after the upload module has finished with the file (we want to handle it using Rails, through passenger, so a @fast_upload_endpoint location is defined)
  • The upload_set_form_field directives are used to specify the params that Rails will now receive, this will give;
    • params[‘upload’][‘fast_asset’][‘original_name’]
    • params[‘upload’][‘fast_asset’][‘content_type’]
    • params[‘upload’][‘fast_asset’][‘filepath’]

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.

Working with Paperclip and Rails

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 = "#{file['filepath']}_1"
      tmp_file_path = "#{self.tmp_upload_dir}/#{file['original_name']}"
      FileUtils.mkdir_p(self.tmp_upload_dir)
      FileUtils.mv(file['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

Go Go Uploads!

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 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.

Related Links

Handy Aliases

4 comments

Working on the rails day in and out now, I’ve found the following aliases to come in handy;

# General Commands
alias ls='ls -al'

# TextMate, mate all of current dir and crucial rails folders only
alias et='mate . &'
alias ett='mate app config lib db public test vendor/plugins &'

# RAILS,  (run these from your rails folder)

# rails scripts
alias ss='./script/server'
alias sc='./script/console'
alias sg='./script/generate'
alias sp='./script/plugin'
alias mr='mongrel_rails start'

# rails testing 
alias att='autotest'
alias tu='rake test:units'
alias tf='rake test:functionals'    

# tail logs
alias tl='tail -f ./log/development.log'
alias tt='tail -f ./log/test.log'      

# clean the logs
alias ctl='cp /dev/null ./log/test.log'
alias cdl='cp /dev/null ./log/development.log'

I should credit Peep Code for the idea. To use, (e.g. in OSX) place the above in a ~/.bash_aliases file and in ~/.bash_profile, load it in with this command;

if [ -f ~/.bash_aliases ]; then . ~/.bash_aliases ; fi

Also, (and before I forget it myself) – here’s a quick session cleaner command to put in your cron, (for day old, mysql session clearage action); rather than build a rake task, or extra controller to clean them out.

#!/bin/bash

cd /u/apps/matthewhutchinson.net/current
echo "<== CRON TASK ==> clear day old sessions data on matthewhutchinson.net"
ruby script/runner -e production "ActiveRecord::Base.connection.delete(\"DELETE FROM sessions WHERE updated_at < NOW() - INTERVAL 1 DAY\")"

Rails Royal Rumble

1 comment

  • #cabal
  • [12:37] @davidjrice: here, before you do that! Rails Rumble http://railsrumble.com/
  • [12:37] @davidjrice: we needs ta register today cause there have been 101 of 150 applications
  • [12:38] @matt: I have no time for the rumble, unless there is wrestling involved)
  • [12:38] @matt: like, a royal rumble
  • [12:38] @matt:* matt dons cape, mask and goes to summon his Eagle Powers – [link]
August 31, 2007 14:02
← (k) prev | next (j) →