Uploading large files to Rails with Merb
As you know Rails does bad on handling file upload, a large file will block your Rails app a long while, make it busy on receiving the file and can’t give response to other visitors, make them upset and leave you alone.
One solution is using merb to handle file upload for rails. The latest Merb that build on Rack(a cool framework who help you dealing with all kinds of http servers) does a really good job on uploading.
First, install merb:
—- rubysudo gem install merb
1 2 3 4 5 6 7 |
Second, create a merb app in your rails dir:
--- ruby
merb-gen app uploader
cd uploader
|
You can ignore all other files except config/rack.rb, this is the only file we need to modify.
Currently there’s only one line in the file:
1 |
run Merb::Rack::Application.new |
It will ask merb to handle the http request come from rack.
Let us change this file to:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
require 'cgi' class File def to_s path end end # build a new handler to handler rack's request class Uploader def call(env) # leverage merb's utility to parse the request. # Merb will save the file to a tempfile and save the tempfile's path in request's param request = Merb::Request.new(env) params = request.params # pass the params directly to the real (rails) app result = post("http://someplace.com/api", hash_to_params(params)).split("\n")[-1] # processing result or just ignore it ... end private def post(url, params="") curl_cmd = "curl -H \"Content-type: application/x-www-form-urlencoded\" #{url} -d \"#{params}\"" puts "curl_cmd = #{curl_cmd}" f = IO.popen(curl_cmd +" 2>&1") result = f.read f.close result end def hash_to_params(hash) hash.map do |k, v| if v.kind_of? Hash h = {} v.each { |kk, vv| h["#{k}[#{kk}]"] = vv } hash_to_params h else "#{k}=#{CGI.escape v.to_s}" end end.join("&") end end run Uploader.new |
Run merb by
merb p 1234 -c 1 -e production -d-
-
Remember to config your apache or your favorite webserver to redirect all request from /uploader to port 1234 (Your merb uploader is listening here!).
Pretty easy, isn’t it?
RoRCraft 中文 and Zendesk 中文 庆祝北京奥运
为了庆祝今天08北京奥运会开幕,我们非常兴奋地宣布我们将与Zendesk的合作开发helpdesk 2.0并在chinese.zendesk.com推出中文版的禅宗服务台。禅宗服务台(Zendesk)是一个web2.0风格的在线桌面帮助方案。我 们之前曾帮助他们发行iphone接口,现在我们已经实现了禅宗服务台中文市场的本地化。如须协助建立汉化版禅宗客户服务台,请电邮致 rex@rorcraft.com 与我們联络。
同时我们非常高兴地宣布我们终于建立了自己的中文站点 – http://www.rorcraft.com/zh
We are very excited to announce our cooperation with Zendesk – helpdesk 2.0 and the launch of a Chinese version of Zendesk at chinese.zendesk.com to celebrate the start of Beijing Olympics 08. Zendesk is a online helpdesk solution in web2.0 style. We’ve previously helped them release their iphone interface and now we have localized it for the Chinese speaking market.
For assistance in setting up your own chinese localized version of Zendesk please contact us at rex@rorcraft.com.
We are also very happy to announce that we’ve finally localized our own site – http://www.rorcraft.com/zh/

熱烈慶祝2008北京奧運
Using XSendFile to prevent memory leak in mongrel
When we working on the lastest project which allow user to download video that may be upto a few hundred MB, we see that the memory usage of mongrel start from 5MB in the beginning , and jump to to 300MB in a single night. After trace for a while, we found that the source of the leaking pointing to sendfile function that we currently use. So we looking for a solution to fix the memory leak , and that is the XSendFile which we are going to talk about.
Most of the people would use mongrel as the application server to deploy the rails , while using apache as the load balance proxy to forward the traffic to the servers that running mongrel. By using XSendFile, instead of mongrel load the file , and pass the file stream to apache,
Ankoder API ruby library released
Do you need to build a mini youtube for your client?
Do you want to include video functionalities to your site?
We’ve released the ruby library that we are already using on http://free.ankoder.com both in rails plugin form as well as ruby gem form.
Install it and have a look at the rdoc
Rails Plugin
1 |
script/plugin install http://ankoder.googlecode.com/svn/trunk/trunk/ankoder_on_rails
|
Ruby Gem
http://rubyforge.org/projects/ankoder/
1 2 3 4 5 6 |
$ sudo gem install ankoder Password: Bulk updating Gem source index for: http://gems.rubyforge.org Successfully installed ankoder-0.0.2- Installing ri documentation for ankoder-0.0.2-... Installing RDoc documentation for ankoder-0.0.2-... |
Send us an email if you’re keen to try it.
We’re working on our payment model and cleaning up the UI so you can manage your services.
Generate PDFs in Rails with RFPDF
1, Install railsrfpdf plugin
ruby script/plugin install http://cnruby.googlecode.com/svn/trunk/rails-projects/infoq_rfpdf/vendor/plugins/railsrfpdf/
2, add new Mime type to environment.rb:
Mime::Type.register “application/pdf”, :pdf
3, in the controller:
—- rubyrespond_to do |format|
format.html # show.rhtml
format.xml { render :xml => @page.to_xml }
format.pdf do
pdf = FPDF.new
pdf.AddPage
pdf.SetFont(“Arial”,‘B’,18)
pdf.Cell(100, 20, “Hello World”)
send_data pdf.Output, :filename => “hello.pdf”, :type => “application/pdf”
end
end
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
<img src="http://devblog.rorcraft.com/assets/2007/12/19/hello.png" />
<strong>Useful method in RPDF</strong>
SetFont(string family [, string style [, float size]])
SetMargins(float left, float top [, float right])
SetTextColor(int r [, int g, int b])
SetXY(float x, float y)
AddFont(string family [, string style [, string file]])
Cell(float w [, float h [, string txt [, mixed border [, int ln [, string align [, int fill [, mixed link]]]]]]])
Image(string file, float x, float y [, float w [, float h [, string type [, mixed link]]]])
Line(float x1, float y1, float x2, float y2)
MultiCell(float w, float h, string txt [, mixed border [, string align [, int fill]]])
Rect(float x, float y, float w, float h [, string style])
<strong>A case in real world</strong>
<macro:code lang="ruby">
pdf = FPDF.new('portrait', 'pt', [800, 1380])
pdf.SetAutoPageBreak(false)
pdf.AddPage
# draw a rectangle
pdf.SetFillColor(240, 240, 240)
pdf.Rect(0, 0, 800, 895, 'F') pdf.Image("#{RAILS_ROOT}/public/images/a.jpg", 0, 0, 800, 600)
# add four images
pdf.Image("#{RAILS_ROOT}/public/images/b.jpg", 0, 700, 260)
pdf.Image("#{RAILS_ROOT}/public/images/c.jpg", 270, 700, 260)
pdf.Image("#{RAILS_ROOT}/public/images/d.jpg", 540, 700, 260)
# write text
txt = 'Agile Web Development'
pdf.SetFont("Arial", 'B', 38)
pdf.SetXY(0, 600)
pdf.Cell(800, 100, txt, 10, 1, 'C')
pdf.SetTextColor(10, 10, 10)
txt = 'What is Ruby FPDF?'
pdf.SetFont("Arial", 'B', 30)
pdf.SetXY(20, 920)
pdf.Cell(300, 30, txt, 10, 1)
txt = "Ruby FPDF is a Ruby port of Olivier Plathey's excellent PDF generator, /
FPDF. FPDF is written in PHP,
and as such can only be used From PHP scripts. /
Ruby FPDF, as the name suggests, is written in Ruby and /
can be used From Ruby scripts. Ruby FPDF was ported and is maintained by Brian Ollenberger. /
If you need to contact me, see the contact link abovenYou can download Ruby FPDF below. /
It is released under a permissive license, similar to that of FPDF. I only ask that you retain the /
copyright notice at the top of the source file. You may make modifications to FPDF, but if you /
redistribute those modifications, make it clear in a comment immediately before or after the /
copyright notice that you have modified it. I also wouldn't mind if you sent patches back to me, /
but you are not strictly required to do so."
pdf.SetFont("Arial", '', 18)
pdf.SetXY(20, 960)
pdf.MultiCell(500, 20, txt)
txt = 'Ruby | On | Rails'
pdf.SetFont("Arial", '', 24)
pdf.SetXY(540, 920)
pdf.Cell(260, 30, txt, 10, 1, 'C')
#draw rectangle
pdf.SetFillColor(240, 240, 240)
pdf.Rect(550, 960, 230, 300, 'F')
#write text
txt = "* Get Excitednn* Get Startedn* Get BetternnContactnnEnjoy Railsnenjoyrails@gmail.com"
pdf.SetFont("Arial", 'B', 18)
pdf.SetXY(560, 970)
pdf.MultiCell(220, 36, txt)
pdf.SetFillColor(170, 0, 1)
pdf.Rect(0, 1310, 800, 70, 'F')
txt = 'View this page at http://devblog.rorcraft.com/'
pdf.SetTextColor(255, 255, 255)
pdf.SetFont("Arial", '', 28)
pdf.SetXY(0, 1330)
pdf.Cell(800, 24, txt, 0, 0, 'C')
send_data pdf.Output, :filename => "hello_advance.pdf", :type => "application/pdf"
|

Reference:
1: fpdf manual
4: Iconv Rdoc
5: fpdf website
Building Ferret queries in models and ferret pagination
With the help of acts_as_ferret plugin, it is very easy to add ferret search functionalities to active records. However, it doesn’t come with pagination out of the box. Following the good practise of fat models and skinny controllers, I’ll show you how I structure my search methods with ferret pagination.
First – get the pagination find plugin. Ilya released a very useful script to combine with paginationfind, however it is not using exactly the same syntax as paginationfind and there’s a tiny issue with the results count when active record :conditions are included.
I’ve created my own patched version here, it accepts the same :page => { :size => 10, :current => 1 } hash.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
def paginating_ferret_search(query,options = {}, find_options = {}) page_options = find_options.delete(:page) || options.delete(:page) || {} current = page_options[:current] && page_options[:current].to_i > 0 ? page_options[:current] : 1 first = page_options[:first] || 1 auto = page_options[:auto] || false # Total size is either AR find result count or Search result total_hits or from limit param, whichever is less limit = options.delete(:limit) total_hits = if find_options[:group] || find_options[:conditions] || find_options[:select] ids = [] raw_hits = find_id_by_contents(query, {:limit => :all}) {|type, id, score, data_hash| ids << id } conditions_array = combine_conditions([ "#{self.table_name}.#{self.primary_key} in (?)", ids ], find_options[:conditions]) count :all, find_options.update({:conditions => conditions_array}) #find_by_contents(query, {:limit => :all},find_options).total_hits # <- bad way of doing it else find_by_contents(query, {:lazy => true}).total_hits end total_size = limit ? [limit, total_hits].min : total_hits # If :size isn't specified, then use the lesser of the total_size # and the default page size page_size = page_options[:size] || [total_size, DEFAULT_PAGE_SIZE].min PagingEnumerator.new(page_size, total_size, auto, current, first) do |page| # Set appropriate :offset and :limit options for this page offset = {:offset => (page - 1) * page_size } limit = {:limit => (page_size) < total_size ? page_size : total_size} # set paging settings on AR results if AR find option exists. # otherwise set paging settings on Ferret results. if find_options[:group] || find_options[:conditions] || find_options[:select] find_options.merge!(offset).merge!(limit) options[:limit] = :all else options.merge!(offset).merge!(limit) end find_by_contents(query, options, find_options) end end |
Next in my product.rb model , I have this class method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
class Product < ActiveRecord::Base def self.search(raw_query, *args) options = args.extract_options! brand_query = options.delete(:brand) query = Ferret::Search::BooleanQuery.new() query.add_query Ferret::Search::FuzzyQuery.new("*", raw_query) query.add_query Ferret::Search::TermQuery.new(:brand_name, brand_query), :must unless brand_query.blank? query.add_query Ferret::Search::TermQuery.new(:hidden, "N"), :must if options.delete(:public) == true puts query.to_s self.search_by_ferret_query(query.to_s, options) end def self.search_by_ferret_query(query,options) if options[:page] self.paginating_ferret_search(query, options) else self.find_by_contents(query, options) end end end |
Shrinking our view code
View, helper is a good way to modulise your view code.
We found these two functions from dzone for our acts_as_tree categories.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
def display_categories(categories, open_cat = nil, &block) html = Builder::XmlMarkup.new html.ul(:id => "category-tree", :class => "tree") do categories.each do |c| if c.parent_id == 0 li = "<li class="liOpen">" li += if block_given? yield(c) else link_to c.name, admin_edit_category_path(c) # :class => "#{'active' if c.id == open_cat}" end li += find_all_subcategories(c, open_cat, &block) if c.children.size > 0 li += "</li>" html << li end end end end def find_all_subcategories(category, open_cat = nil, &block) if category.children.size > 0 ret = '<ul>' category.children.each { |c| li = "<li class="liOpen">" li += if block_given? yield(c) else link_to c.name, admin_edit_category_path(c) ,:class => "#{'selected' if c.id == open_cat }" end li += find_all_subcategories(c, &block) li += "</li>" ret += li } ret += '</ul>' else '' end end |
Thanks to Ben from #ror_au irc chatroom today, I was able to cut that helper into 15 lines with added features.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
def display_categories(categories, open_cat = nil, &block) open_cat_ancestors = Category.find(open_cat).ancestors if open_cat content_tag(:ul, categories.inject("") do |list_items, c| list_items + content_tag(:li, (if block_given? yield(c,open_cat) else link_to c.name, admin_edit_category_path(c) end) + (c.children.any? ? display_categories(c.children, open_cat, &block) : "") , :class=>"#{'liOpen' if open_cat_ancestors && open_cat_ancestors.include?(c) }" ) end, :id => "category-tree", :class => "tree" ) end |
To use it just call it by
1 |
<%= display_categories(Category.find_level1, params[:category_id]) %>
|
Or you can customise theblock by
1 2 3 |
display_categories(Category.find_level1, params[:category_id]) do |cat, open_cat|
link_to h(cat.name), admin_products_path(:category_id => cat),:class => "#{'selected' if cat.id == open_cat.to_i }"
end
|
A plugin for installing plugins
We are just starting a new project and want to come up with a easy way install a list of common plugins that we’ve been using. What’s better than writing a plugin to install other plugins?
With the help of the highline gem, I wrote a simple rake task that read in a list of plugins from YAML file.
Install:
—- html./script/plugin install http://plugin-install.googlecode.com/svn/trunk/plugin_install
1 2 |
Usage: --- htmlrake plugin_install |
It’ll read the “plugins.yml” file copied to your /config directory and lets you choose Yes/No to install each one.
plugins.yml example:
1 2 3 4 5 6 7 8 9 10 11 12 |
plugins:
acts_as_ferret:
url: "svn://projects.jkraemer.net/acts_as_ferret/tags/stable/acts_as_ferret"
restful_authentication:
url: "http://svn.techno-weenie.net/projects/plugins/restful_authentication/"
rspec:
url: "svn://rubyforge.org/var/svn/rspec/tags/REL_0_9_4/rspec"
rspec_on_rails:
url: "svn://rubyforge.org/var/svn/rspec/tags/REL_0_9_4/rspec_on_rails"
.
.
.
|
Note: Also get the “”http://rapt.rubyforge.org/“>rapt” gem for discovering rails plugins.
Contact
We love to hear about your web projects.
Email:
Sydney: +61 421 591 943
Hong Kong:+852 6901 2682
