Thursday, November 20, 2008

Prawnto, Generating PDF in Rails Applications

Rails wiki lists a number of libraries that can be used to generate PDF files in ruby. Prawnto was very suitable for my need (generating PDF files that contain text with differentfont-size and adding some formatting). Prawn is able to generate more complex PDF documents including adding photos. You can check the installation steps online

Prawnto requires you to add code to your controller and to a .prawn layout file. There are several demos available online and can be used as guidance.

Below is an example for generating a PDF format of an article. The article has a title and an array of blocks, each block has title and content. The controller code is:

def show
   @title="My Article"
   @blocks= [
         {:title=>'Block One', :content=>'content one'},
         {:title=>'Block Two', :content=>'content two'},
         {:title=>'Block Three', :content=>'content three'},
         {:title=>'Block Four', :content=>'content four'}]
   prawnto :prawn => {
      :page_size => 'A4',
      :left_margin => 50,
      :right_margin => 50,
      :top_margin => 24,
      :bottom_margin => 24},
      :filename=>"#{@title.gsub(' ','_')}.pdf"
   render :layout=>false
end

In the view file of a prawnto action, we should use the pdf object. that has several basic methods to plot our PDF file. those include:

  • pdf.text("text to be written", :size=>15) #writing some text with a specific size
  • pdf.font("Helvetica") #setting font
  • pdf.stroke{line([x1,y1], [x2,y2])} #drawing a line
  • pdf.table(data_array, options) # to draw a table containing array of string arrays (data)

I used instance_eval that i described in my earlier post to create a higher level methods to increase the readibility of the view file.

pdf.font "Helvetica"
pdf.instance_eval do
   def font_size; {:title=>20, :subtitle=>16, :text=>10} end
   def write(some_text, style=:text)
      text some_text, :size=>font_size[style]
   end
   def separator
      write " ", :text
      stroke {y=@y-25; line [1,y], [bounds.width,y]}
      write " ", :text
   end
end

pdf.write "#{@title}", :title
@blocks.each do |block|
   pdf.separator
   pdf.write block[:title], :subtitle
   pdf.write block[:content], :text
end

Creating the higher level methods made writing the real data easier. Now you just call separator to draw a horizontal line, instead of worrying about the corresponding coordinates.

Monday, November 17, 2008

Writing Prettier Ruby Code with instance_eval

instance_eval is a method of ruby Object class that lets you pass a string of ruby code or a block to be evaluation in the scope of that object.

Through instance_eval you can actually add new methods to a particular object. This can help you write more pretty and readable code. for example, we can define a new average method for an array of numbers

>> a = [1, 2, 3, 4, 5, 6, 7, 8]
=> [1, 2, 3, 4, 5, 6, 7, 8]
>> a.sum
=> 36
>> a.size
=> 8
>> a.average
NoMethodError: undefined method `average' for [1, 2, 3, 4, 5, 6, 7, 8]:Array
    from (irb):14
>> a.sum / a.size
=> 4
>>

With instance_eval we can add that new method here


a.instance_eval do
    def average ; sum/size end
end
>> a.average
=> 4

In a lot of cases this approach can be useful.

Thursday, November 13, 2008

REXML Error when running rcov

Rcov is one of the tools we use at eSpace to measure automated tests coverage for our rails applications.

By rcov through gem install rcov, I had rcov(0.8.1.2.0) installed.

After upgrading to the latest ubuntu 8.10, I realized that the installed rcov is not compatible with ruby 1.8.7 that i have. Rcov failed at generating the html reports with this stacktrace

/usr/lib/ruby/1.8/rexml/formatters/pretty.rb:131:in `[]': no implicit conversion from nil to integer (TypeError)
    from /usr/lib/ruby/1.8/rexml/formatters/pretty.rb:131:in `wrap'
    from /usr/lib/ruby/1.8/rexml/formatters/pretty.rb:131:in `wrap'
    from /usr/lib/ruby/1.8/rexml/formatters/pretty.rb:90:in `write_text'
    from /usr/lib/ruby/1.8/rexml/formatters/default.rb:50:in `write'
    from /usr/lib/ruby/1.8/rexml/formatters/pretty.rb:75:in `write_element'
    from /usr/lib/ruby/1.8/rexml/formatters/pretty.rb:73:in `each'
    from /usr/lib/ruby/1.8/rexml/formatters/pretty.rb:73:in `write_element'
    from /usr/lib/ruby/1.8/rexml/formatters/default.rb:31:in `write'
    from /usr/lib/ruby/1.8/rexml/formatters/pretty.rb:75:in `write_element'
    from /usr/lib/ruby/1.8/rexml/formatters/pretty.rb:73:in `each'
    from /usr/lib/ruby/1.8/rexml/formatters/pretty.rb:73:in `write_element'
    from /usr/lib/ruby/1.8/rexml/formatters/default.rb:31:in `write'
    from /usr/lib/ruby/1.8/rexml/formatters/pretty.rb:117:in `write_document'
    from /usr/lib/ruby/1.8/rexml/formatters/pretty.rb:111:in `each'
    from /usr/lib/ruby/1.8/rexml/formatters/pretty.rb:111:in `write_document'
    from /usr/lib/ruby/1.8/rexml/formatters/default.rb:28:in `write'
    from /usr/lib/ruby/1.8/rexml/document.rb:197:in `write'
    from (eval):93:in `pretty'
    from /usr/lib/ruby/gems/1.8/gems/rcov-0.8.1.2.0/lib/rcov/report.rb:1003:in `create_file'
    from /usr/lib/ruby/gems/1.8/gems/rcov-0.8.1.2.0/lib/rcov/report.rb:708:in `execute'
    from /usr/lib/ruby/gems/1.8/gems/rcov-0.8.1.2.0/lib/rcov/report.rb:125:in `each'
    from /usr/lib/ruby/gems/1.8/gems/rcov-0.8.1.2.0/lib/rcov/report.rb:125:in `each_file_pair_sorted'
    from /usr/lib/ruby/gems/1.8/gems/rcov-0.8.1.2.0/lib/rcov/report.rb:707:in `execute'
    from /usr/lib/ruby/gems/1.8/gems/rcov-0.8.1.2.0/lib/rcov.rb:640:in `dump_coverage_info'
    from /usr/lib/ruby/gems/1.8/gems/rcov-0.8.1.2.0/lib/rcov.rb:640:in `each'
    from /usr/lib/ruby/gems/1.8/gems/rcov-0.8.1.2.0/lib/rcov.rb:640:in `dump_coverage_info'
    from /usr/lib/ruby/gems/1.8/gems/rcov-0.8.1.2.0/bin/rcov:421
    from /usr/lib/ruby/1.8/test/unit.rb:278

Luckily Mauricio Fernandez has provided a new version of rcov to solve this issue. It can be downloaded from his github repo or simply:

git clone git://github.com/spicycode/rcov.git
ruby setup.rb

This will install the latest rcov 0.8.1.5 that will work fine

Thursday, November 06, 2008

Creating Functional Tests for Restful Authencation based websites

At eSpace we stress on having automated tests to cover our code.

If you are using Restful Authentication plugin in your rails application, You will have to bypass the authentication filter that is processed before your controller actions.Restful authentication helps you does that through the login_as() method of theAuthenticatedTestHelper module. Let me show you how.

In your functional test, login before you send the request.

   class PublicationsControllerTest < ActionController::TestCase
       fixtures :users

       def test_method
          login_as (:user_one)
          Your test code...
       end
   end

That user is loaded from the users fixtures and will act as the current logged in user. The fixtures must have something like:

user_one:
    id: 1
    login: test
    email: test@espace.com.eg
    salt: 7e3041ebc2fc05a40c60028e2c4901a81035d3cd
    crypted_password: 00742970dc9e6319f8019fd54864d3ea740f04b1 # test

The login_as method is defined in the AuthenticatedTestHelper module, so you need to make sure you are including that module (preferably in the test helper)

Now you can create functional tests for controllers that requires login.