Archive for the 'rails' Category

Ruby SOAP – the story continues..

As written before, I've been struggling with Ruby and SOAP. Apart from the fact that I really don't understand why the world likes to use SOAP, I've ran into a couple of issues I'd like to share for future generations:

  • soap4r and ActionWebService don't play nicely. The SOAP implementation that is included in the standard Ruby distribution isn't very nice at all (issues with validating WSDLs), so I tried using soap4r to make SOAP requests to remote services. Single scripts worked eventually, but when including this in a Rails project that also acts as a SOAP server (using AWS), things broke majorly. Not spending too much time, I decided to make SOAP requests from external scripts that I call with exec().
  • soap4r doesn't set the xsi:nil attribute to elements that allow this when the content is nil, but the element is a ComplexType. The solution here was to manually construct the SOAP elements (using SOAP::SOAPElement).
  • On my development machine (OSX with Ruby 1.8.6) things finally worked fine, but when deploying to a production environment (Linux with Ruby 1.8.7), things broke when calling "id" on a SOAP result object with the message "warning: Object#id will be deprecated; use Object#object_id" and instead of the id in the SOAP result, I got the object_id (which is an internal id for the object and utterly useless for my purposes). The solution here was to not call methods on the result object, but treating it as a Hash. So result.id becomes result['id'].

Rails and SOAP part II

As written in a previous last post, I've been busy with Rails and SOAP. I've been figuring out how to pass hashes to my remote calls instead of an argument list.

The default API requires something like:

api_method :get_my_thingy, :expects => [:id => :int, :name => :string], :returns => [Thingy.struct]

When calling this (e.g. with a ruby soap thingy) it looks something like:

soap.getMyThingy(2, "foo")

But I would like to do:

soap.getMyThingy(:id => 2, :name => "foo")

For this to happen, I introduced an OptionStruct:

module ActionWebService
 class OptionStruct < ActionWebService::Struct
 member :id, :int
 member :name, :string
 end
end

After this, the API should be like:

api_method :get_my_thingy, :expects => [OptionStruct], :returns => [Thingy.struct]

See my previous post for the Thingy.struct

Rails and SOAP

For a project, I'm currently implementing a SOAP API in Rails. The Rails application will function as a SOAP server and I'm using the datanoise activewebservice plugin for this. Implementing a SOAP API is pretty easy with this, however, I ran into some problems with returning complete model objects.

I have a model called Customer that I want to expose via my API. After setting up the API controller, I specified the following in /app/services/customer_api.rb:

class CustomerAPI < ActionWebService::API::Base
  api_method :get, :expects => [:int], :returns => [Customer]
end

Implemented with:

def get(id)
  return Customer.find(id)
end

Requesting the WSDL nicely gives the definition of Customer, but when making a request for a specific customer errored with the message "Cannot map Customer to SOAP/OM.". It appears that boolean values (in Ruby presented as true/false) and emtpy values (presented in Ruby as nil), were the problem. SOAP wants them as "true", "false" and "".

I solved this by extending my models with a to_struct() method that returns all attributes in a Struct. I extended both ActiveRecord::Base instances to get the instance method to_struct() inside my models and I extended ActiveRecord::Base class so it would return a Struct. The latter is needed, since the SOAP plugin wants to know the attributes and types of the Struct, without actually creating one. Here's what I did:

To extend ActiveRecord::Base with class methods create a module and put the following in your /config/environment.rb:

require 'model_extensions'
ActiveRecord::Base.send(:extend, ModelExtensions)

Then in the module:

module ModelExtensions
  def struct
    # Check if the DataStruct is already defined
    begin
      eval("#{self}::DataStruct")
    rescue
      # Define the DataStruct class and fill it with
      # members that resemble the model
      class_eval <<-EOF
        class DataStruct < ActionWebService::Struct
          #{self}.columns.map{|c| member c.name.to_s, c.type.to_s}
        end
      EOF
    end
    return eval("#{self}::DataStruct")
  end
end

Now to create a DataStruct in a model, we define the following:

module ActiveRecord
  class Base
    def to_struct
      # Get a DataStruct and instantiate
      struct = self.class.struct.new
      # Set the attributes after clean-up
      self.attributes.each do |k,v|
        v = (v.nil?) ? "" : ((v == true) ? "true" : (v == false) ? "false" : v)
        struct.send("#{k}=", v)
      end
      return struct
    end
  end
end

To finish off, we change the API implementation a bit to the following:

class CustomerAPI < ActionWebService::API::Base
  api_method :get, :expects => [:int], :returns => [Customer.struct]
end

Implemented with:

def get(id)
  return Customer.find(id).to_struct
end

Rails will_paginate plugin

I know, it has been a long time since I've blogged and actually people have complained about the fact that I didn't do this for a long time. Well, in short, I'm fine. A lot has happened since the last time I've blogged, but I'll write about that when I make the time somewhere in the upcoming weeks. Since I've been coding a lot with Rails lately, I just wanted to share some geek stuff with the rest of the world today.

In one of my projects, I'm using the will_paginate plug-in, which is pretty cool. Without a lot of stuff, it will give you pagination in your views. It also works for generic Arrays. Anyway, there is one problem. The project I'm working with can contain a lot of records per table. Since will_paginate doesn't act like a named scope (or at least not in this case, since we're not directly calling paginate() on an AssociationProxy, but on an Array) and requires the whole set returned. This is, because it needs the size of the array as well, so it will calculate the total amount of pages and records in the collection. Obviously, this can be quite expensive if you have a lot of records in your table. It will retrieve all records, create objects in the Array and finally cut of just a very small part of it to display. Because of this, we came up with the following solution:

# The amount of objects per page we want to show
limit = $OBJECTS_PER_PAGE

# If the page was set to 0 (shouldn't happen), or nil (yeah, nil.to_i == 0),
# set the offset to 0
offset_page = (page.to_i > 0) ? page.to_i - 1 : 0
offset = limit * offset_page

# Create args hash with which we count
count_args = args.dup

# Add the limit and the offset to the arguments hash
args[:limit] = limit unless args[:limit]
args[:offset] = offset unless args[:offset]

# Count all object that would be there
total_count = objects.count(:all, count_args)

# Find all objects (this is limited)
objects = find(:all, args)

# Create an array filled with nil values, the size of the total collection
objects_array = Array.new(total_count, nil)

# Insert the found objects at the right place in the array
objects_array[(offset)..(offset + limit - 1)] = objects

# Call paginate() on the array with limit and page   
return objects_array.paginate(:per_page => limit, :page => page)

What this does is only getting the required records, using :limit and :o ffset in find() and an additional count() without :limit and :o ffset. To make sure that will_paginate gets a collection the size of the total collection, we create an Array filled with nil values, the size of count(). Then we insert the found records in this array.

The only problem is that the whole operation isn't atomic. The count could differ from the real objects array.

I haven't run any benchmarks, but the Rails logs tell me that only a very small subset of the complete table is selected.

Update: Pointed out by Habbie, using SQL OFFSET is very slow. Back to the drawing board.

IntelliJ IDEA 7 with Ruby (on Rails)

Only today, I noticed that IntelliJ IDEA 7 was released some weeks ago and I saw that it comes with Ruby and Rails support. I decided to check it out and give it a go. For Rails development, I currently use Aptana RadRails as an Eclipse plugin. Even though I like RadRails, it still lacks some features and coherency in my opinion. Also, setting up RadRails as an Eclipse plugin can be a pain in the ass when dealing with the latest beta versions or nightly builds.

So, I downloaded IntelliJ to my Mac, dragged it to my Applications folder and started it. After installing the Ruby plugin (2 clicks and a restart), I was good to go. I played with it for an hour or so and I must say that I'm quite impressed. The interface is clean (better then NetBeans) and stuff is where I look for it. Also code completion is working (in oposite to RadRails) very well. Hopping through code (declarations, views, models, etc) is nice.

I'll be playing with it for the next couple of days and trying out some other stuff and see if it's perhaps worth buying a license.

BackgroundRb and blocking method calls

At work, we use BackgroundRb on a server to kick off long running tasks. This runs on a backend server. I wrote some code that can start, stop and manage workers, but noticed that when a task is running, calling a self defined method on the worker object is possible. However, on long running and intensive tasks, the call to this method blocks. The strange thing is that the method is executed, eventhough the task is still running, but the timing of the call seems unpredictable. I haven't looked at the backgroundrb code yet, but in my case using results[] was a better option.

Ruby en Rails lui gezocht!

Sinds een paar weken zit ik bij CareerNetwork/NoXa, beetje Ruby, beetje Rails, beetje Linux, allemaal erg tof. Zo tof dat er mensen bij moeten; meer toffe mensen. Nu weet ik dat er wel wat geeks mijn blog lezen en daarom dus (wederom) een shameless plug. Dus, om even de recruiter in mij naar boven te halen: "Ben jij die sexy Rails programmeur, creatief en houd je van hipness? Dan zoeken we jou!!" (voor meer info, stuur me even een mailtje op railsgast@evenflow.nl)

Ruby en Rails 2007

Afgelopen donderdag was het Ruby en Rails dag 2007 in Amsterdam. Het was een leuke dag, met een hoop interessante sprekers, waaronder Dr. Nic en Geoffrey Grosenbach, twee bekende namen in de Rails-wereld. De sfeer was erg goed, Rails is hip, Rails is sexy en zo'n soort vibe hing er ook. Het leuke aan Rails in Nederland is dat het er een hoop kleine bedrijfjes met Rails bezig zijn. De grote namen (Cap Gemini, Logica CMG, etc) doen nog niet mee, waardoor de hele business nog lekker informeel en zonder hoop marketing blabla is. Een paar bedrijven hadden kleine standjes, maar dat was eigenlijk ondergeschikt aan het hele evenement. Uiteraard was er het een en ander te winnen en een van de bedrijven die er stonden gaf een Nintendo Wii weg. Wat je moest doen was 5 vragen over Ruby en Rails beantwoorden en dan zo snel mogelijk. De hele dag had er een kerel met 60 seconden bovenaan gestaan, totdat ik langs kwam. 5 minuten voor de prijsuitreiking scoorde ik 53 seconden op 5 vragen goed en kaapte zo de hoofdprijs weg :)

Robijn op de Rails

rails_logo_remix.gif Een tijdje terug was ik in gesprek met iemand over scripttalen. Het ging over mijn voorkeur voor Perl en hoe ik al menig keer iets in PHP had gemaakt, maar na wat frustraties de boel weg gemikt had en opnieuw in Perl ben begonnen.  

Degene waar ik dit tegen vertelde zei me dat ik eens een keer naar Ruby moest kijken. Nu heb ik eigenlijk maar weinig met OO scripttalen gedaan (ik vond Python maar irritant), maar op de een of andere manier ben ik toch eens wat gaan lezen. Na wat over Ruby zelf gelezen te hebben, stuitte ik op Ruby on Rails, een web framework. Ruby is een erg grappig taaltje, met een hoop leuke en snelle truckjes. Het is compleet OO, dus zelfs een integer is een instantie van een klasse. Ruby on Rails combineert Ruby met een erg uitgebreid en
flexibel web framework.

Na het zien van de intro video (te vinden op de homepage van Ruby on Rails) heb ik meteen 2 boeken over Ruby en Rails gekocht en ben gaan coden. Het is mij in een uur of 3 (zonder echte kennis van Ruby) gelukt om een dynamische website te maken. Dit had me in Perl of PHP waarschijnlijk wel langer gekost. Met name het maken van formuliertjes en het afhandelen hiervan kost in PHP/Perl vaak wel wat klopwerk (formuliertje uit elkaar trekken, sql statement maken, uitvoeren, error handling). Naast al dit fraais heeft Rails ook standaard AJAX ondersteuning (hip asynchroon javascript voor de web-iliterate onder ons), maar dit moet ik nog eens gaan proberen.

Mijn eerste gepruts met Ruby en Rails is te vinden op http://rails.xinit.cx en voor mensen die wel into webdevelopment zijn: check de video.