Blogging is hard work 4

Posted by Bob Silva Tue, 06 Jun 2006 03:55:00 GMT

Well, I lost control of my blog for a little while but I think I have it back to a sane usable state. Hopefully the new Typo engine will handle spam a little better than it's predecessor. Once I started getting traffic all the spammers started having a field day with my site and I didn't have the time to stay on top of it. We'll see how it goes now.


Unfortunately, I had to lose all the existing comments to fix it. Haven't looked into the exact method of how they are spamming it but I have a feeling that I will need to add some sort of verification system to Typo to maintain it.

Generating PDF Documents with Rails and PDFlib - Part II

Posted by Bob Silva Sat, 22 Apr 2006 21:09:00 GMT

Here is Part II of my series on PDF generation from Rails. Part I covered installing and configuring the PDFlib-Lite library from PDFlib.com. In this part, I will walk you through creating this from scratch.


Ready to begin?

First you need to download this zip file which contains a PDFlib wrapper which makes generating PDFs so much easier. It also contains the source code and images used in this tutorial. Extract the zip file and copy the pdf.rb into the lib directory of your Rails application. Then copy the rails.gif image somewhere on your filesystem. Doesn't matter where, we'll tell PDFlib where to find it later.

Open an existing controller or create a new one and addan action named pdftest as shown below:

  def pdftest
    send_data(render_to_string(:action => 'pdftest', :layout => false),
      :filename => "pdftest.pdf", 
      :type => "application/pdf", 
      :disposition => "inline")
  end
This method will render the PDF generated in the rhtml file to a string, and pass it through to the web browser using the send_data method in Rails.


Next, create a new pdftest.rhtml file in the appropriate app/views directory for the controller you just added the pdftest action to.


Lets start coding. Open the pdftest.rhtml in your favorite editor and follow me down the yellow brick road....

<%

# Create the PDF object and reverse the coordinate system. By
# default, PDFs cartesian coordinate system starts with (0,0)
# in the lower left corner. By setting @topdown => true, we flip
# the coordinate system upside to make the upper left (0,0).
# You can also set default margins and page sizes during initialization
p = PDF.new(:topdown => true)

# When working with PDFs, the typical structure is a document 
# with many pages. Calling open begins what PDFlib refers to as
# document scope.
p.open

  # Our PDF only needs one page to display the book cover
  # so we'll open a new page here, and close it at the end
  p.beginpage

    # When working with PDFs, setting a base font/size is required 
    # before writing any text to the page. We wont use this font for
    # this page, but thats OK
    p.font('Helvetica', 10, '#000000')

    # Remember when I said we would tell PDFlib where to find
    # our images? Here ya go. Stick in the path to where you
    # copied rails.gif to.
    p.set_parameter('SearchPath', '/path/to/images')


    # Let the fun begin. We'll start out with the book outline.
    # Just gonna draw a black outline and fill it with white.
    # Using the rect method, this is easy to do. By passing
    # the stroke and fill colors, we tell the method to both
    # stroke and fill the path created.
    # def rect(x, y, w, h, options={})
    p.rect(p.left_margin,
           p.top_margin,
           p.page_width-(p.left_margin+p.right_margin),
           p.page_height-(p.top_margin+p.bottom_margin),
           :fillcolor => '#ffffff',
           :strokecolor => '#000000')
    
    # Next up is the maroon tab at the top of the book with 
    # the text 'The Pragmatic Programmers'. To draw out this
    # odd shape, we'll create a path and pass an multi-dimensional
    # array of points for the path to follow. This time, by only
    # specifiying a fillcolor, we are telling PDFlib not to stroke
    # the path, but only fill it.
    p.path([[102,p.top_margin],
            [312, p.top_margin],
            [300, p.top_margin+63],
            [114, p.top_margin+63]],
            :fillcolor => '#741415')
    
    # Time to add the text in the pretty maroon box we just made.
    # Here, we set the font we want to use ahead of time. Later,
    # I'll show you a different way to do it inline.
    p.font('Times-Roman', 16, '#ffffff')
    p.print 'The', 160, 51
    p.print 'Pragmatic', 151, 66
    p.print 'Programmers', 172, 80
        
    # For the Title of the Book, we demonstrate how we can
    # use pass-through options for native PDFlib functionality
    # not provided for in the wrapper. We are able to adjust
    # the charspacing and horizscaling by passing those options
    # as Hash elements and the wrapper library sends them onto
    # the native PDFlib method. This also demonstrates how
    # to set the font/fontsize inline on a single call to print
    p.font('Times-Roman', 60, '#000000')
    p.print 'Agile', 112, 230 
    p.print 'Web', 254, 230, :charspacing => -2
    p.print 'Development', 162, 278, :horizscaling => 105
    p.print 'with', 248, 308, :fontsize => 38
    p.print 'Rails', 322, 324, :horizscaling => 110
    
    # Working with images in PDFs is easy as you can see below.
    # Pass the name and location and walla, an image in your PDF.
    # Images can be used as templates for forms as well. If you have
    # complex line layouts, its easier to save an image version of the
    # raw template, then insert it as a background layer and write on
    # top of it.
    p.image 'rails.gif', 140, 330
    
    # In these print calls, we give the :boxsize of the textfield 
    # and specify the :position to obtain right aligned text.
    p.font('Courier-BoldOblique', 14)
    p.print 'Dave Thomas', 300, 600, :boxsize => '{205 20}', :position => '{right bottom}'
    p.print 'David Heinemeier Hansson', 300, 618, :boxsize => '{205 20}', :position => '{right bottom}'
    
    # Just another rectangle here for the footer of the cover.
    p.rect(p.left_margin, 
           p.page_height-p.bottom_margin-35,
           p.page_width-p.left_margin-p.right_margin,
           35,
           :fillcolor => '#741415')
    
    # Here we right align the text, set the fillcolor for the font,
    # and set the font/fontsize inline on the print call
    p.print 'The Facets of Ruby Series', 260, 754, :boxsize => '{310 20}', :position => '{right center}', :fillcolor => '#ffffff', :font => 'Times-Roman', :fontsize => 24
    
  p.endpage
p.close

# This next line actually sends the in-memory PDF back to the 
# controller as a string so that it can be sent to the browser.
%>

<%= p.read -%>

If you PDF doesn't look like this, then you didn't follow me very well. As you can see, generating PDF's doesn't have to be difficult, but it can be time consuming as you get into more complex layouts. I'm done with my PDF work on my latest application, so this will conclude this series on Generating PDFs in Rails. Have fun!

ActiveRecord Calculations 1

Posted by Bob Silva Sat, 22 Apr 2006 14:59:00 GMT

Rick Olson's Calculations Plugin (which is now part of core) is great as long as you only need to perform a calculation on one column in your model. I had a need to sum two columns in a result set and after talking with Rick on IRC, started to modify his Calculations Plugin to accept a hash of {:calc => :column} pairs. Time was of the essence though so I found a different way to do it using AR. The examples below don't really have any wow factor, but they may be helpful to someone.


In the example below, it inner joins table (ModelB) and groups by a column (group_field) from that table, just to show how it might be done.

The magic is the :select option. It allows you to define what goes into the SQL statement (SELECT :select FROM ...). The :readonly attribute on the find call, is to prevent you from trying to modify the record since it is not a fully loaded AR record set.

@model_a = ModelA.find(:all,
  :select => 'sum(column_a) as column_a, sum(column_b) as column_b, model_b.group_field', 
  :joins => 'inner join model_b on model_a.id = model_b.model_a_id', 
  :group => 'model_b.group_field', 
  :conditions => ['column_a=?', column_a_value],
  :readonly => true)


Now that you have your recordset, you can use your calculated columns like any other AR attribute. In this case, @model_a.column_a. If you build more advaned reporting or charts into your applications, you will find yourself using this method a lot.

Hoping for snow 2

Posted by Bob Silva Sat, 22 Apr 2006 09:18:00 GMT

Even though this years snowmobile season has ended, I am already looking forward to the next one. After much bargaining and compromise with the wife, I was able to order a 2007 Polaris 600 H.O. with a 155" track.

I only started snowmobiling 5 years ago, but I feel that I'm at a level now where I can handle the extra power. Theres nothing like stomping on the gas of a powerful sled and zipping down the trails at 80MPH (conservatively).

Here's a pic of my new baby, can't wait till it arrives.



Just need to make sure I don't do this again! The result of hitting a tree while sidehilling at 25mph. It's amazing what the body in motion does when the transport mechanism stops instantly. I think I got a score of 9.5 from my friends for the front flip through the windshield onto my buttocks 10 feet away.

User Friendly Time Entry

Posted by Bob Silva Sat, 22 Apr 2006 08:49:00 GMT

Have a need to track time spent on something? Here's an easy way to allow your users to enter their time in a smart way (anyway they want). This example accepts fractional hours, overflowing minutes and converts and displays them as the user would expect. They are stored in your database as minutes (integer) and displayed as hours/minutes (regardless of how they were input).

Model Code (model.rb):

def set_travel_time(hours, minutes)
  self.travel_time = ((hours.to_f * 60) + minutes.to_i).to_i
end

def get_travel_time
  travel_time.to_i.divmod(60)
end


Controller Code (models_controller.rb):

def create
  @model.new(...)
  ...
  @model.set_travel_time(params[:hours], params[:minutes])
  ...
  if @model.save
  ...
end

def edit
  @model = Model.find(...)
  ...
  @hours, @minutes = @model.get_travel_time
  ...
end


View Code (_form.rhtml):

<%= text_field_tag 'hours', @hours -%> hours 
<%= text_field_tag 'minutes', @minutes -%> minutes 

Generating PDF Documents with Rails and PDFlib - Part I

Posted by Bob Silva Sun, 02 Apr 2006 14:16:00 GMT


Generating PDF documents programmatically is probably one of the worse programming tasks I've had the pleasure to work on. It's not terribly difficult, it's just tedious and time consuming. Some of my applications need to print catalogs or reports upwards of 300+ pages in real-time, so speed is of the essence, unfortunately, the pure Ruby PDF::Writer isn't up to the job. PDFlib-Lite, (read the license before using), is a fast library, which in the lastest 6.0.3 release contains Ruby Bindings (albeit horribly broken).

Installing PDFlib-Lite


Installing PDFlib is similar on Linux and MacOSX.

Download the lastest tarball

Extract the source

src> tar zxf PDFlib-Lite-6.0.3.tar.gz
src> cd PDFlib-Lite-6.0.3


If using 6.0.3, download this patch to the root of your extracted PDFlib-Lite source, apply the patch, and regenerate the configure script

PDFlib-Lite-6.0.3> patch -p0 < configure.in.diff
PDFlib-Lite-6.0.3> autoconf


Run the configure script. Use --help for additional options/language bindings. Your paths may be different, if you can't figure it out, switch to Windows.

On linux:
PDFlib-Lite-6.0.3> ./configure --with-ruby=/usr/bin/ruby --with-rubyincl=/usr/lib/ruby/1.8/i686-linux


On MacOSX (you built your own Ruby right?):
PDFlib-Lite-6.0.3> ./configure --with-ruby=/usr/local/bin/ruby --with-rubyincl=/usr/local/lib/ruby/1.8/i686-darwin8.5.2


If you followed these instructions, the configure script should tell you that the Ruby bindings are active.

Ruby language binding for PDFlib: yes


Build the library, (the 'make test' is required for these instructions to work)

PDFlib-Lite-6.0.3> make
PDFlib-Lite-6.0.3> make test
PDFlib-Lite-6.0.3> make install


All right, so now you'd think it would work since you've installed it. Remember when I said the Ruby bindings were broken, besides the patch to get PDFlib to realize it has Ruby bindings, you also have to manually copy the library to your Ruby's site_ruby/ directory. Optionally, you can symlink the real library found in your /usr/lib or /usr/local/lib directory.

PDFlib-Lite-6.0.3> cd bind/pdflib/ruby

On linux:
PDFlib-Lite-6.0.3/bind/pdflib/ruby> cp PDFlib.so /usr/lib/ruby/site_ruby/1.8


On MacOSX:
PDFlib-Lite-6.0.3/bind/pdflib/ruby> cp PDFlib.bundle /usr/local/lib/ruby/site_ruby/1.8



We'll cover usage of PDFlib in part II of this article which should be complete in a week or so.

Implement acts_as_threaded without a plugin 30

Posted by Bob Silva Fri, 31 Mar 2006 20:40:00 GMT

It's been awhile since I've posted so I thought I'd toss this out there. If you've used my acts_as_threaded plugin, you know its an off-shoot from the acts_as_nested set inside of AR itself. I've managed to create the same functionality without the use of a plugin using the native acts_as_nested_set feature of AR. The relevant code is below. The threading functionality has been moved to the model now, add the following code to your model and you are set.
  acts_as_nested_set :scope => :root
    
  def before_create
    # Update the child object with its parents attrs
    unless self[:parent_id].to_i.zero?
      self[:depth] = parent[:depth].to_i + 1
      self[:root_id] = parent[:root_id].to_i
    end
  end
  
  def after_create
    # Update the parent root_id with its id
    if self[:parent_id].to_i.zero?
      self[:root_id] = self[:id]
      self.save
    else
      parent.add_child self
    end
  end
  
  def parent
    @parent ||= self.class.find(self[:parent_id])
  end

Your database schema will need to have the following definition:
create_table "my_table_name", :force => true do |t|
  t.column "root_id", :integer
  t.column "parent_id", :integer
  t.column "lft", :integer
  t.column "rgt", :integer
  t.column "depth", :integer
end
The trick from this point, is that whenever you create a new thread, if it has a parent_id, then it will automatically be added as a child to that parent record. Otherwise, it will be set as a root thread. This version no longer requires that the fields have a default value of 0 relying on the fact that 'NilClass.to_i == 0'.

Hope you enjoy this, it's come in very handy for modeling structured content in some of my apps (like categories and multi-level organizations).

My Macbook Finally Arrived

Posted by Bob Silva Sun, 05 Mar 2006 09:06:00 GMT

One word, two meanings. It's HOT! and Ah crap, it is HOT!

Being my first experience with Mac, I am very pleased with it. I'm still coming up to speed with OSX and haven't had much time to play with it yet. The crowning moment came when I hooked it up to the 30" cinema. What a beautiful thing to have 6 full size browser windows visible on your desktop. Pic #1 Pic #2

I am a little disappointed in the amount of heat it generates though. I made the mistake of setting it on my lap after running for a few hours, and even though I had shorts on, it still got my attention rather quick. The little area just above the F keys gets a bit toasty as well. Since the majority of the heat comes from the battery, I am a little concerned that there may be other issues than just "Apple laptops have always run hot". But who knows? For now, I'm just wanting to finish up my current projects so I can have some time to play with it more.

More Advanced Rails Schema Generation 1

Posted by Bob Silva Sun, 05 Mar 2006 08:17:00 GMT

One of the problems I've seen with Rails as of late is that the database schemas for the unit tests get out of sync with each other. As new features get added, some db_definitions get updated and others missed. The core group is well aware of this issue and DHH left a challenge in a ticket I created to add missing table definitions from a previous changeset.


I decided I would take him up on his challenge and went to work. I printed off all the db schemas and started comparing them with each other, highlighting where they differed. This was my first indication as to the scope of this conversion.


Since the purpose of converting over to using schema.rb is to maintain one schema for all the tests, it would be helpful if all the existing definitions were at least similar to begin with. Most of the differences can be fixed without issue. Converting a varchar(3000) field to text, or a timestamp to datetime is no big deal as far as the unittests are concerned. The biggest road block is that some tables are defined using features that don't exist in AR::Schema. Features like foreign keys, arbitrary primary keys and sequences. User-defined column definitions is yet another example where AR::Schema isn't up to the job. For example, the Postgres tests use a table with geometric field types.


I had a pretty good idea how to implement everything except for sequences, so I started coding and within a few hours had the test suite working with MySQL using schema.rb. Before I dove much further into making all the adapters work with schema.rb, I decided I better get some feedback on whether these changes I was making to Rails schema generation would even be accepted into core. Unfortunately, I didn't get any feedback from the core developers.


I think the biggest issue they would raise is whether or not the SchemaDumper would be able dump these changes. The short answer is no, which coincidently, is probably the same answer my patch would get. So I scrapped my work on converting the unittests over but will consider making a plugin which adds this functionality (and much more) for more advanced schema generation/migrations. You just won't be able to dump your schema out into a schema.rb file.


Let me know if you think this would be useful and I'll put something together.

Handling Invalid Dates with ActiveRecord Date Helpers 3

Posted by Bob Silva Thu, 23 Feb 2006 03:42:00 GMT

If you've worked with the model based date helpers in Rails (date_select and datetime_select), then you may know that selecting an invalid date throws an exception deep within ActiveRecord. A lot of users assume they can handle this in the model validation, but due to the way ActiveRecord handles multi-parameter input values, it will raise an exception if an invalid date is input. Here's some sample code of how you can catch and handle this exception without affecting the user experience too much.
In controller.rb:

def create
  begin
    @client = Client.new(params[:client])
  # Catch the exception from AR::Base
  rescue ActiveRecord::MultiparameterAssignmentErrors => ex
    # Iterarate over the exceptions and remove the invalid field components from the input
    ex.errors.each { |err| params[:client].delete_if { |key, value| key =~ /^#{err.attribute}/ } }
    # Recreate the Model with the bad input fields removed
    @client = Client.new(params[:client])      
  end
    
  if @client.save
    flash[:notice] = 'Client was successfully created.'
    redirect_to :action => 'list'
  else
    render :action => 'new'
  end
end

In model.rb:

validates_presence_of :date_field_name, :message => 'must be a valid date'

Older posts: 1 2 3 4 5 6