Thursday 21 January 2010

Search and replace in SQL

Have a situation where had to change a bunch of values on a one-off in a db. Could have written a script to do it, but found this really useful tip

UPDATE table_name SET field_name = REPLACE(field_name, 'old_string','new_string')

works to change:
/OLD/PATH/TO/SOMEDATA/A_FILES

to:
/NEW/PATH/TO/A_LOT/MORE/NEW_DATA/A_FILES

Wednesday 20 January 2010

Getting the id of the object when inside form_for or fields_for

This was a post I put on StackOverflow - and then found the answer about 20 mins later ... doh

I have the following code that is generating fields for an invoice

THis is in the edit.html.erb for the invoice class

<% f.fields_for(:invoice_items) do |f| %>
<%= render :partial => 'invoice_items/fields', :locals => {:f => f} %>
<% end %>


and I generate the invoice_items as part of the invoice object

@invoice = Invoice.find(params[:id], :include => :invoice_items, :order =>"invoice_items.display_order")

It works just fine, but I need to wrap each one in a div, and assign that object's id to the div. (div id=i_2345 - that kind of thing) so I can use jQuery wizardry.

Where I am stumbling like a new-born foal is how do I access the the id of the invoice_item that is being called?

And the answer is ...

f.object.id

Tuesday 19 January 2010

jQuery readonly on checkboxes

Well here is an annoying thing.
You can't set the readonly attribute to checkboxes or select. And if you set disabled="disabled" the values aren't passed in the submit.
Bum.

Fields_for, symbols and instance variables

Had a problem with fields_for and accepts_nested_attributes_for

The invoice class has many invoice items, but they have to be displayed in a certain order. Originally I had an @invoice_items variable - but the form wasn't looking at this.
However, a genius on teh Ruby forum posted the following:
@invoice = Invoice.find(params[:id], :include => :invoice_items, :order "invoice_items.display_order")

When the invoice calls the invoice_items they are already in the object and in the right place. Now he has pointed it out to me, I get what it is - but don't think I would have come up with that on my own.

Thursday 14 January 2010

Transactions

Used them for the first time - and love them.
I have a situation where a job can have many editions. An edition has many pages. If you create a new page for a job, you must create it for every edition. Likewise if you delete a page from a job, you must delete it from every edition. You must never end up in a situation where one page is deleted from one edition, but for some reason it fails on the next one.
Enter transactions.

This is my destroy action (handled by jQuery rather than the inbuilt rails method)
def destroy
@page = Page.find(params[:id])
@pages = Page.find_all_by_job_id_and_page_name_or_no(@job.id, @page.page_name_or_no)
invalid = false
Page.transaction do
@pages.each do |page|
begin
page.destroy
rescue ActiveRecord::StatementInvalid
invalid = true
end
end
end
if invalid
respond_to do |format|
format.html { render :action => "new" }
format.js {render :text => "-1" }
format.xml { render :xml => @page.errors, :status => :unprocessable_entity }
end
else
respond_to do |format|
format.html { redirect_to(pages_url) }
format.js {render :json => @page.id.to_json }
format.xml { head :ok }
end
end
end


And it all works.

Monday 11 January 2010

Didn't know this one

pages = Page.find_all_by_job_id(18511).collect(&:page_name_or_no)

gives me an array with just that field. Sweet.

Saturday 9 January 2010

more on iText

Have to be able to merge pages - so playing more with iText and the following worked (eventually)

>> require 'rjb'
=> ["RjbConf"]
>> Rjb::load('/var/www/html/renaissance/lib/iText.jar')
=> nil
>> FileOutputStream = Rjb::import('java.io.FileOutputStream')
=> #
>> Color = Rjb::import('java.awt.Color')
=> #
>> Element = Rjb::import('com.itextpdf.text.Element')
=> #
>> Document = Rjb::import('com.itextpdf.text.Document')
=> #
>> Font = Rjb::import('com.itextpdf.text.Font')
=> #
>> FontFactory = Rjb::import('com.itextpdf.text.FontFactory')
=> #
>> PageSize = Rjb::import('com.itextpdf.text.PageSize')
=> #
>> Paragraph = Rjb::import('com.itextpdf.text.Paragraph')
=> #
>> Phrase = Rjb::import('com.itextpdf.text.Phrase')
=> #
>> BaseFont = Rjb::import('com.itextpdf.text.pdf.BaseFont')
=> #
>> ColumnText = Rjb::import('com.itextpdf.text.pdf.ColumnText')
=> #
>> PdfPageEvent = Rjb::import('com.itextpdf.text.pdf.PdfPageEvent')
=> #
>> PdfPCell = Rjb::import('com.itextpdf.text.pdf.PdfPCell')
=> #
>> PdfContentByte = Rjb::import('com.itextpdf.text.pdf.PdfContentByte')
=> #
>> PdfPTable = Rjb::import('com.itextpdf.text.pdf.PdfPTable')
=> #
>> PdfWriter = Rjb::import('com.itextpdf.text.pdf.PdfWriter')
(irb):18: warning: already initialized constant NAME
=> #
>> PdfReader = Rjb::import('com.itextpdf.text.pdf.PdfReader')
=> #
>> pdfTest = PdfReader.new('/tmp/test.pdf')
=> #<#:0x2b0282c08e38>
>> pdfTest2 = PdfReader.new('/tmp/test2.pdf')
=> #<#:0x2b0282c03ff0>
>> document = Document.new
=> #<#:0x2b0282c00f80>

>> pdf_writer = PdfWriter.getInstance(document, FileOutputStream.new("/tmp/merge.pdf"))
=> #<#:0x2b0282bf55b8>
>> document.open
=> nil
>> cb = pdf_writer.getDirectContent()
=> #<#:0x2b0282bef898>
>> document.newPage
=> true
>> page1 = pdf_writer.getImportedPage(pdfTest, 1)
=> #<#:0x2b0282be2b20>
>> cb.addTemplate(page1, 0, 0)
=> nil
>> document.newPage
=> true
>> page2 = pdf_writer.getImportedPage(pdfTest2, 1)
=> #<#:0x2b0282bd7a18>
>> cb.addTemplate(page2, 0, 0)
=> nil
>> document.close
=> nil
>>


NOw have a two page pdf - hurrah (again)

Friday 8 January 2010

Playing with iText

hurrah for Hello World

In the app - I need to create a whole bunch of pdfs and then glue them all together. Having installed iText and RJB seems sensible to try and get it to work.
Playing in the console - the following actually worked

>> require 'rjb'
=> ["RjbConf"]
>> Rjb::load('/var/www/html/renaissance/lib/iText.jar')
=> nil
>> filestream = Rjb::import('java.io.FileOutputStream')
=> #
>> pdfreader = Rjb::import('com.itextpdf.text.pdf.PdfReader')
=> #
>> pdfwriter = Rjb::import('com.itextpdf.text.pdf.PdfWriter')
(irb):5: warning: already initialized constant NAME
=> #
>> pdfdocument = Rjb::import('com.itextpdf.text.Document')
=> #
>> document = pdfdocument.new
=> #<#:0x2b49dc62af10>
>> pdfwriter.getInstance(document, filestream.new('/tmp/test.pdf'))
=> #<#:0x2b49dc624430>
>> document.open
=> nil
>> pdfParagraph = Rjb::import('com.itextpdf.text.Paragraph')
=> #
>> paragraph = pdfParagraph.new("hello world")
=> #<#:0x2b49dc60cb78>
>> document.add(paragraph)
=> true
>> document.close
=> nil
>>


It opens a for real pdf with the iconic words "Hello World". Hurrah

leading zeros

This was handy

"%05d" % int

if int = 123 it will give you the string "00123"

Wednesday 6 January 2010

iText, rjb and Rails

Got that all to work, but with one important 'gotcha'.
The class names in iText have changed - they are now called things like com.itextpdf.text.factories.PK - rather than com.lowagie.text.factories.PK
That caused me a few problems - kept getting

NoClassDefFoundError: com/lowagie/text/Document

until I found out that the names had changed.

Accessing helpers in models

Have a couple of v simple helpers which I needed access to in my model for the PDF.
Simply trying to call them fails because application_helper.rb is only available to views and controllers.
Bit of googling and tip o' the hat to hJLHPBv2 and to So far, it’s RoR for some helpful pointers.
Hived off the helper methods into their own Module and put it in the ./lib folder.

module DisplayHelper


def img_list(index = nil)
img_array = [["JPEG", 1], ["TIFF", 2], ["EPS", 3], ["Lo-res", 4], ["WN", 5], ["CMYK",6]]
if (index)
img_array[index-1][0]
else
img_array
end
end

def supply_on_list(index = nil)
supply_array = [["CD", 1], ["DVD", 2], ["CD&DVD", 3], ["email", 4], ["WN", 5], ["Zip",6], ["Multiple",7]]
if (index)
supply_array[index-1][0]
else
supply_array
end
end

def scan_or_photo(index = nil)
scan_array = [["Scanning", 1], ["Photography", 2], ["Scanning & Photography", 3]]
if (index)
if (index >0)
scan_array[index-1][0]
else
return ''
end
else
scan_array
end
end

def batch_image_list(index = nil)
batch_image_array = [["Not received", 1], ["Booked in", 2], ["Shot", 3], ["Cut out", 4], ["Retouched", 5], ["Checked",6], ["Approved",7], ["Out",8]]
if (index)
batch_image_array[index-1][0]
else
batch_image_array
end
end
end


Then explicitly called that helper in the model

include DisplayHelper

And ta-da. All works.
I don't think I am offending the MVC gods as there is no mark-up going on

Tuesday 5 January 2010

PDFs on RoR

To make sense of the earlier post.
The legacy system produces big old A3 job tickets. These exist as PDFs into which the system drops some values - and users write by hand other instructions. It works well so want to replicate it.
My first thought was to use Prawn and Prawnto. Very simple to set up - but building an A3 page in Prawn was a non starter - you have to specify every line, ever text box. Unfortunately you can't open an existing PDF with it.
Plan B was to use an old fave - ImageMagick. It handles PDFs - but unfortunately it rasterizes the image and it looks like crap when you print them out. Not a very good ad for the new system since peopel look at these things every day.
So now looking at iText and using rjb or jRuby to act as the bridge. RJB attracts me as it appears to be simpler.
We will see ...

Installing RJB to allow iText to work to allow PDFs to be produced

Tried - and failed - at home on my Ubuntu box. So tried - and succeeded (eventually) on the RHEL5 box at work.
First thing to do was to install and switch over to the Sun JDK from the one that comes as standard in RHEL5 - easier said than done.
However, following - to the letter - the advice at http://www.ja-sig.org/wiki/display/CASUM/HOWTO+Switch+to+Sun+JVM+in+RHEL eventually got that working.
Essentially this means installing the supplementary channel for the RHEL5 subscription. Follow this will tell you how

Then it is just a question of
yum install java-1.6.0-sun-devel

Next problem - running
gem install rjb

resulted in the following:
ERROR: Error installing rjb:
ERROR: Failed to build gem native extension.

/usr/bin/ruby extconf.rb
*** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of
necessary libraries and/or headers. Check the mkmf.log file for more
details. You may need configuration options.

Provided configuration options:
--with-opt-dir
--without-opt-dir
--with-opt-include
--without-opt-include=${opt-dir}/include
--with-opt-lib
--without-opt-lib=${opt-dir}/lib
--with-make-prog
--without-make-prog
--srcdir=.
--curdir
--ruby=/usr/bin/ruby
extconf.rb:48: JAVA_HOME is not set. (RuntimeError)


Some extensive googling and eventually you find how to set JAVA_HOME - which is not that hard (export command), but ... what the hell is it?
After a lot of blundering around I get a new error message

extconf.rb:40:in `open': No such file or directory - /usr/java/jdk1.6.0_17/jre/Home/include (Errno::ENOENT) from extconf.rb:40

Well, at least that is progress. Looking at line 40 you find the following:
inc = p.include(javahome, 'include')
inc = p.include(javahome, 'Home/include') unless File.exists?(inc)


Aha - so all we have to do is to find a directory that has either an include or Home/include subdirectory and is vaguely related to java - and there is a good chance it will work.
On a RHEL x-86_64 box the following allowed me to install the RJB gem:
export JAVA_HOME=/usr/lib/jvm/java-1.6.0-sun-1.6.0.17.x86_64/
gem install rjb

and the very satisfying ...
Successfully installed rjb-1.2.0
So now - all I have to do is to get it to talk to iText - which rather worryingly is described as "a bit tricky" , esp when gem install RJB was described as "the easy bit".