Learning Ruby for Force.com Developers – Part 2

January 11th, 2011

This is part #2 from my adventures of learning Ruby. If you missed part #1 you might want to take a look at it just to get up to speed. Again, these are my goals for this series:

  • Learn Ruby
  • Develop an app locally using Ruby on Rails and the default SQLite database
  • Modify the app to use Database.com and the Force.com Toolkit for Ruby
  • Deploy the app to Heroku
  • Modify the app to use Database.com and the REST API

I’ve spent the last week or so digging into the Ruby language and have found Ruby Essentials, Ruby Programming and Ruby Tutorial to be especially useful. I’m not an expert, but I do have a feel for it. I’m not going to write a Ruby tutorial (see the links above for that) but I will try to incorporate as much as possible to demonstrate things that I found useful, different or cool.

Write an App in Ruby

The best way to really learn a language is to actually write an app. So that’s what I’m going to do. I expect that my approach and/or assumptions may need to be tweaked once I get further down the road but we’ll see. I’m going to rewrite my Dreamforce 2010 VMforce app in Ruby and Rails. In a nutshell this is a shopping cart app with a little twist. I do a lot of work for Medisend International, which is a non-profit that ships medical supplies to developing countries (among other things). They have an (old) international aid self-service portal that allows aid recipients (typically hospital administrators or local NGOs) to create a shipment and select medical supplies to be shipped to their country. If all goes well I may build the app out and put it into production on Heroku.

So let’s get started by looking at the domain objects for the app. We’ll need a Shipment object (of course) to manage our multiple shipments and an InventoryItem object which will represent the medical items that will be added to the shipment. These are modeled as Custom Object in my development org.

Before we start building out the objects, let’s step back and take a look at how Ruby works with objects. Apex isn’t exactly Java but it’s somewhat similar and a lot of Force.com developers come from a Java background. So it might help to compare Ruby and Java.

As with Java, you’ll find the following similar to Ruby:

  • Memory is managed for you via a garbage collector.
  • Objects are strongly typed.
  • There are public, private, and protected methods.
  • There are embedded doc tools (Ruby’s is called RDoc). The docs generated by rdoc look very similar to those generated by javadoc.

Unlike Java, you’ll find the following different in Ruby:

  • You don’t need to compile your code. You just run it directly.
  • There are different GUI toolkits. Ruby users can try WxRuby, FXRuby, Ruby-GNOME2, or the bundled-in Ruby Tk for example.
  • You use the end keyword after defining things like classes, instead of having to put braces around blocks of code.
  • You have require instead of import.
  • All member variables are private. From the outside, you access everything via methods.
  • Parentheses in method calls are usually optional and often omitted.
  • Everything is an object, including numbers like 2 and 3.14159.
  • There’s no static type checking.
  • Variable names are just labels. They don’t have a type associated with them.
  • There are no type declarations. You just assign to new variable names as-needed and they just “spring up” (i.e. a = [1,2,3] rather than int[] a = {1,2,3};).
  • There’s no casting. Just call the methods. Your unit tests should tell you before you even run the code if you’re going to see an exception.
  • It’s foo = Foo.new( “hi”) instead of Foo foo = new Foo( “hi” ).
  • The constructor is always named “initialize” instead of the name of the class.
  • You have “mixin’s” instead of interfaces.
  • YAML tends to be favored over XML.
  • It’s nil instead of null.
  • == and equals() are handled differently in Ruby. Use == when you want to test equivalence in Ruby (equals() is Java). Use equal?() when you want to know if two objects are the same (== in Java).

Shipment Class

The basic Shipment class looks like the following. It has two instance variables (name and country) and a getter and setter for each one.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Shipment
  def name
    @name
  end
 
  def name=(value)
    @name = value
  end
 
  def country
    @country
  end
 
  def country=(value)
    @country = value
  end
 
  def initialize()
    puts 'Hello Shipment!!'
  end
end

The following script creates a new instance of the Shipment class, populates the instance variables and display them.

1
2
3
4
5
6
require './Shipment' # similar to Java import
s = Shipment.new # create a new instance
s.name = 'ANG0903-COH1'
s.country = 'Angola'
puts s.name
puts s.country

Create a new directory called ‘RubyDemo’ (or whatever you’d like), switch to that new directory and create a file called Shipment.rb. Paste the contents of the Shipment class above into it. Create another file in that new directory called testshipment.rb and paste the test code above into it. Now open Terminal and switch to this new directory and run ruby testshipment.rb. You should see the following:

ruby-pt2-1.png

So now that we have our Shipment object we need to create our InventoryItem objects to add it. Here’s the class for our InventoryItem:

1
2
3
4
5
6
7
8
9
10
11
12
13
# base class for all items
class InventoryItem
  attr_accessor :name, :itemNumber, :category, :status, :type
  def initialize(name, itemNumber, category, type)
    @name, @itemNumber, @category, @type = name, itemNumber, category, type
    @status = 'Available'
  end
 
  # override the 'to string' method
  def to_s
    puts 'Item: ' + itemNumber + ', Name: ' + name + ', Category: ' + category
  end
end

You’ll notice that this class is a little different than the Shipment class. Instead of manually creating getter and setter methods, I used “attr_accessor” followed by the names of all of the instance variables for my class. With this command, Ruby will generate  basic getters and setters automatically for me. I could have used “attr_reader” to generate only the getter methods and “attr_writer” to generate the setters.

The initialize method is a standard Ruby class method and is the method which gets called first after an object based on this class has completed initialization. Four arguments are passed into this method and they are used to set instance variable for the object. You’ll also notice that @status is set to ‘Available’ by default each time a new object is created. The to_s method overrides the standard to_s (“to string”) method and displays some information about the item.

Ruby Inheritance

Unfortunately Medisend doesn’t ship just “inventory items”; they ship supplies, equipment and biomedical items. Each one of these types of items is similar but also slightly different. All items have a name, unique item number, category and status however supplies also have expiration dates and quantities of items in the box while equipment has a weight attribute. So instead of making classes for each type of InventoryItem with the same attributes we can use InventoryItem as a base class for each type of item and inherit the shared attributes and functionality from the this base class. (BTW… in salesforce.com there is a Custom Object called InventoryItem__c and Supply, Equipment and Biomed are the types of recordtypes.)

So here’s the SupplyItem that extends the InventoryItem class. The initialize method has 4 arguments; three of which are used to construct the InventoryItem class via the super() call and one that sets the instance variable @quantity. The method also sets a default expiration date just for fun.

1
2
3
4
5
6
7
8
9
10
11
require 'date'
require './InventoryItem'
 
class SupplyItem < InventoryItem
  attr_accessor :quantity, :condition, :expirationDate
  def initialize(name, itemNumber, category, quantity)
    super(name, itemNumber, category, 'Supply')
    @quantity = quantity
    @expirationDate = Date.new(2012, 01, 01)
  end
end

The EquipmentItem class also extends InventoryItem but has it’s own to_s method allowing for a more detailed representation of item.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
require './InventoryItem'
 
class EquipmentItem < InventoryItem
  attr_accessor :weight
  def initialize(name, itemNumber, category, weight)
    super(name, itemNumber, category, 'Equipment')
    @weight = weight
  end
 
  # provide a custom 'to string' method for equipment only
  def to_s
    puts 'Item: ' + itemNumber + ', Name: ' + name + ', Category: ' + category + ', Weight: ' + weight.to_s + ' lbs'
  end
end

The BiomedItem class is very similar to the SupplyItem class but adds its own weight and condition instance variables.

1
2
3
4
5
6
7
8
9
10
require './InventoryItem'
 
class BiomedItem < InventoryItem
  attr_accessor :weight, :condition
  def initialize(name, itemNumber, category, weight)
    super(name, itemNumber, category, 'Biomed')
    @weight = weight
    @condition = 'Unknown'
  end
end

Shipment Functionality

With all of the inventory items complete we need to turn our attention back to the Shipment class. The Shipment class should maintain a collection of InventoryItems that are assigned to it and also provide public methods to add and remove items. It should also give some kind of display of the shipment contents. Take a look at the code below along with the comments.

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
class Shipment
  # class (static) variable 
  @@totalShipmentItems = 0
  # constant
  MAX_ITEMS = 10
  # provides getters and setters for instance variables
  attr_accessor :name, :country, :type, :status, :items
 
  # init the object & set instance variables
  def initialize(name, country, type)
    @name, @country, @type = name, country, type
    @status = 'Not Shipped'
    @items = Hash.new
  end
 
  # adds an item to the Hash of shipment items
  def addItem(item)
    items[item.itemNumber] = item # item number is the key
    @@totalShipmentItems += 1 # increment total items
  end
 
  # deletes an item from the Hash of shipment items
  def deleteItem(itemNumber)
    items.delete(itemNumber) # deleted by key
    @@totalShipmentItems -= 1 # decrements total items
  end
 
  # displays item number and name of each item in shipment
  def displayItems()
    puts 'These are the items in the shipment:'
    items.each {|key, value| puts " #{key} -- #{value.name}" }
  end
 
  # displays item number of each item in shipment
  def displayItemNumbers()
    puts 'These are the item numbers in the shipment:'
    puts items.keys
  end
 
  # displays number of items for this plus all shipments
  def numberOfItems()
    puts 'Number of items in the shipment: ' + items.length.to_s
    puts 'Total items for all shipments: ' + @@totalShipmentItems.to_s
    puts 'Max items: ' + MAX_ITEMS.to_s
  end
end

Now for the fun part. Let’s write a script that utilizes all of our new classes. The following script will do the following:

  1. Prompt the user to type in the name of a new shipment
  2. Create and display a new supply item
  3. Create and display a new equipment item
  4. Create and display a new biomed item
  5. Create a new shipment with the name that the user entered
  6. Add the supply, equipment and biomed items to the shipment
  7. Display the shipment’s instance members as well as information about the items
  8. Remove the equipment item
  9. Display the information about the items

Save the following code in a new file called test.rb.

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
require './Shipment'
require './SupplyItem'
require './EquipmentItem'
require './BiomedItem'
 
puts "Enter a new shipment name: "  
STDOUT.flush  
shipmentName = gets.chomp  
 
s = SupplyItem.new('Surgical Gloves - Size 7', 'LP10001', 'Gloves', 500)
puts '=== Sample Supply Item =='
puts 'Name: ' + s.name
puts 'Item #: ' + s.itemNumber
puts 'Category: ' + s.category
puts 'Status: ' + s.status
puts 'Quantity: ' + s.quantity.to_s
puts 'Expiration Date: ' + s.expirationDate.to_s
puts s.to_s
 
e = EquipmentItem.new('Crutches', 'LP50879', 'Mobility', 2.5)
puts '=== Sample Equipment Item =='
puts 'Name: ' + e.name
puts 'Item #: ' + e.itemNumber
puts 'Category: ' + e.category
puts 'Status: ' + e.status
puts 'Weight: ' + e.weight.to_s
puts e.to_s
 
b = BiomedItem.new('Clinical Laboratory - Thermostat; w/ stirrer', 'LP25473', 'Clinical Laboratory', 15)
puts '=== Sample Biomed Item =='
puts 'Name: ' + b.name
puts 'Item #: ' + b.itemNumber
puts 'Category: ' + b.category
puts 'Status: ' + b.status
puts 'Weight: ' + b.weight.to_s
puts 'Condition: ' + b.condition
puts b.to_s
 
ship = Shipment.new(shipmentName,'Albania','40ft Container')
# add items to the shipment
ship.addItem(s)
ship.addItem(e)
ship.addItem(b)
puts '=== Sample Shipment =='
puts 'Name: ' + ship.name
puts 'Country: ' + ship.country
puts 'Type: ' + ship.type
puts 'Status: ' + ship.status
ship.numberOfItems()
ship.displayItems()
ship.displayItemNumbers()
puts '=== Deleting an Inventory Item =='
ship.deleteItem(e.itemNumber)
ship.numberOfItems()
ship.displayItems()
ship.displayItemNumbers()

Open Terminal again and type ruby tests.rb and you should see the following:

ruby-pt2-2.png

OK, so that’s a good overview of what we’ll be building. The next step is getting Rails up and running and beginning work on the web aspect. Any and all comments are welcome!

VN:F [1.9.22_1171]
Rating: 0.0/10 (0 votes cast)
VN:F [1.9.22_1171]
Rating: +1 (from 1 vote)

Categories: Code Sample, Ruby, Salesforce

Leave a comment

Comments Feed6 Comments

  1. Cory Cowgill

    Jeff,

    Another nice blog entry. I’ve been following your series as I’m doing the same exact thing. I’m looking forward to your entry when you get to the Force.com Integration. I’ve got my app running on Heroku but not integrated with Force.com (yet).

    I’m curious what IDE you are using (if any)?

    I couldn’t find a good Eclipse Plug-In that wasn’t out of date, so I’m using Netbeans. Any thoughts on that?

    VA:F [1.9.22_1171]
    Rating: 0.0/5 (0 votes cast)
    VA:F [1.9.22_1171]
    Rating: 0 (from 0 votes)
  2. Jeff Douglas

    @Cory, I’m actually using Textmate on a Mac. It has a really slick plugin but there is not code completion or anything. You might want to look at RadRails which is a free Eclipse plugin from Aptana. It’s pretty slick!!

    VN:F [1.9.22_1171]
    Rating: 0.0/5 (0 votes cast)
    VN:F [1.9.22_1171]
    Rating: 0 (from 0 votes)
  3. A Week with Ruby: Tips for Force.com Developers « d3developer

    [...] Wrote and deployed a Sinatra App to Heroku to test Jeff Douglas’s code. It works! [...]

  4. Links for January 2011 « Cloud Clout – Computing in the cloud

    [...] and is deployed on Heroku. Part1 provides an introduction to the Ruby language with resources and Part2 goes into the Ruby coding details with some comparison to [...]

  5. Ajay

    Jeff,
    These are great posts and extremely useful for the newbies. One question. When I run the test.rb I get the output pretty much the same, except that I get a ‘nil’ after every ‘puts X.to_s’

    Any thoughts?

    VA:F [1.9.22_1171]
    Rating: 0.0/5 (0 votes cast)
    VA:F [1.9.22_1171]
    Rating: -1 (from 1 vote)
  6. sentana

    Hi Geff,
    That is a great article.
    I am a newbie to rails and I wonder if you can help me in this regard. I want to do the following:- import xml file to my rails application and edit some values of the xml file from web form (jquery) and generate the new xml file.
    Thank you so much in advance

    VA:F [1.9.22_1171]
    Rating: 0.0/5 (0 votes cast)
    VA:F [1.9.22_1171]
    Rating: 0 (from 0 votes)

Leave a comment

Feed

http://blog.jeffdouglas.com / Learning Ruby for Force.com Developers – Part 2