Rorcraft Blog

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

http://ankoder.googlecode.com

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 =&gt; "hello_advance.pdf", :type =&gt; "application/pdf" 

Reference:
1: fpdf manual

2: How to Generate PDFs

3: PDF::Writer VS Ruby FPDF

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
  • Posted on August 28, 2007
  • Tagged Home, Rails

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, &amp;block)
  html = Builder::XmlMarkup.new
  html.ul(:id =&gt; "category-tree", :class =&gt; "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 =&gt; "#{'active' if c.id == open_cat}"
        end
        li += find_all_subcategories(c, open_cat, &amp;block) if c.children.size &gt; 0
        li += "</li>"
        html &lt;&lt; li

      end
    end
  end
end

def find_all_subcategories(category, open_cat = nil, &amp;block)
  if category.children.size &gt; 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 =&gt; "#{'selected' if c.id == open_cat }"
      end
      li += find_all_subcategories(c, &amp;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, &amp;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, &amp;block)  : "") ,
         :class=&gt;"#{'liOpen' if open_cat_ancestors &amp;&amp; open_cat_ancestors.include?(c) }"
       )
     end,
    :id =&gt; "category-tree", :class =&gt; "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

Categories