Learning Ruby for Force.com Developers – Part 3

February 3rd, 2011

This is part #3 of my adventures of learning Ruby for Force.com developers. If you missed parts #1 and #2 you might want to take a look at those 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 (this is where we are at right now)
  • Modify the app to use Database.com and the Force.com Toolkit for Ruby instead of the SQLite database
  • Deploy the app to Heroku
  • Modify the app to use Database.com and the REST API

In this post we’ll get started building a web app using Ruby on Rails and SQLite. In a nutshell we’ll be building 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. We’ll be building a replacement with Ruby on Rails.

Before you get started you might want to go through Get Started with Rails which has a lot of great stuff. I’m only using a subset of the functionality outlined in this guide so you’ll definitely want to go through this entire article. I’ll zip up all of the code for this part so you can download it and pick it apart.

The first thing we’ll want to do is install our software needed for Rails. I had some issues during some of the installations but unfortunately I can’t help out much so hopefully things go well for you. First, open Terminal and run the following lines:

sudo gem update –system
sudo gem install rails
sudo bundle install
sudo gem update rake
sudo gem update sqlite3-ruby

Now that all of your software is (hopefully) installed let’s start building the app using SQLite to store data. Open Terminal and change to the directory where you want to store your files (~/Documents/Programming/Ruby in my case) and run the following command to create the application:

rails new mediaid-sqlite

This will create a Rails application called MediaidSqlite in a directory called mediaid-sqlite. Now switch to this new directory:

cd mediaid-sqlite

Rails created an entire directory structure for us with all of the files we need to begin building out the app. Feel free to take a look. We’ll mainly be working in the app directory. Since we’ll be using SQLite, run the following command to create an empty database:

rake db:create

This will create both a development and test SQLite databases inside the db/ folder. You now have a fully functional Rails application. To see it in action, fire up the web server on your local development machine by running:

rails server

If all goes well, when you point your browser to http://localhost:3000 you should see the following:

ruby-part3-welcome.png.png

To stop the web server, simply hit Ctrl+C in the same Terminal window. I typically have at least two Terminal tabs open; one for the server and one for running commands. When running in development mode, Rails does not generally require you to bounce the server when changes are made; changes and files will be automatically picked up by the server.

For the required “Hello World” for the home page, you need to create at minimum a controller and a view. Fortunately, you can do that in a single command. Run the following command in Terminal:

rails generate controller home index

Now we need to delete the default page from your application so Rails will not load it by default. We need to do this as Rails will deliver any static file in the public directory in preference to any dynamic contact we generate from the controllers:

rm public/index.html

Now, you have to tell Rails where the new home page is located. Open the file config/routes.rb in Textmate or your favorite editor. This is your application’s routing file which holds entries in a special DSL that tells Rails how to route incoming requests to your controllers and actions. This file contains many commented out sample routes, and one of them actually shows you how to connect the root of your site to a specific controller and action. Find the line beginning with :root to towards the end of the file, uncomment it so that is looks like the following:

root :to => “home#index”

One of the cool thing about Rails is the scaffolding. Rails scaffolding is a quick and easy way to generate some of the major pieces of an application such as models, views, and controllers for a new resource. It provides the basic functionality and UI to CRUD records. We can create the scaffolding for Shipment and InventoryItem with just a few easy commands. The command below creates the scaffolding for the Shipment resource and specifies the fields for the model:

rails generate scaffold Shipment name:string country:string shipmentType:string status:string shipDate:date items:integer selected:integer reserved:integer

One of the outputs of the rails generate scaffold command is a database migration script. Migrations are Ruby classes that are designed to make it simple to create and modify database tables. Rails uses rake commands to run migrations, and it’s possible to undo a migration after it’s been applied to your database. Migration filenames include a timestamp to ensure that they’re processed in the order that they were created. Now use the following rake command to run the migration:

rake db:migrate

Now we need the InventoryItems to add to our Shipments. Run the following rails generated scaffold command to create the InventoryItems scaffolding:

rails generate scaffold InventoryItem name:string itemNumber:string category:string status:string shipment:integer

Now run the rake command to perform the database migration for InventoryItems:

rake db:migrate

So now we have our database setup and the basic functionality generated for us by Rails. Some people don’t like the scaffolding and prefer to code from scratch but I’m going to simply modify the code generated by the scaffolding. The application consists of essentially 6 view and one controller. We’ll look at the views first and then dig into the controller.

Home Page

The home page displays a summary of the available shipments and allows the user to select a shipment to process. There are also links at the bottom to access the auto-generated UI to CRUD records for both shipments and inventory items.

Ruby3 1

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
<h3>Welcome to MediSend's MediAid!</h3>
<p>MediAid is an international aid, self-service portal for 
  inventory selection, processing and information centralization.</p>
 
<p>The following shipments are available for your aid case:</p>
 
<table width="75%" cellpadding="2" cellspacing="2">
<tr>
  <th align="left">Shipment</th>
  <th align="left">Type</th>
  <th align="left">Status</th>
</tr>
<% @shipments.each do |shipment| %>
<tr>
  <td><%= link_to shipment.name, shipment %></td>
  <td><%= shipment.shipmentType %></td>
  <td><%= shipment.status %></td>
</tr>
<% end %>
</table>
 
<p><br/>The following options are also available:</p>
 
<ul>
  <li><%= link_to "Maintain Shipments", shipments_path %></li>
  <li><%= link_to "Maintain Inventory Items", inventory_items_path %></li>
</ul>

Shipment Display

This page is where most of the work is done for a shipment. It provides the relevant info on the shipment and allows the users to manage the shipment’s contents.

Ruby3 2

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
<p id="notice"><%= notice %></p>
 
<script type="text/javascript">
function confirmDelete() {
  var answer = confirm("Are you sure you want to remove all items from this shipment?")
  if (answer) window.location = "/shipments/<%= @shipment.id %>/remove";
}
 
function confirmReserve() {
  var answer = confirm("Are you sure you want to update all of the items in this shipment as reserved?")
  if (answer) window.location = "/shipments/<%= @shipment.id %>/reserve";
}
</script>
 
<h3>Shipment <%= @shipment.name %></h3>
 
<p>
Status: <%= @shipment.status %><br/>
Type: <%= @shipment.shipmentType %><br/>
Country: <%= @shipment.country %><br/>
Items: <%= @shipment.items %><br/>
Selected: <%= @shipment.selected %><br/>
Reserved: <%= @shipment.reserved %>
</p>
 
<p>Available options for this shipment:
<ol>
  <li><%= link_to 'View items in this shipment', items_shipment_path(@shipment) %></li>
  <li><%= link_to 'Add items by product category to this shipment', additems_shipment_path(@shipment) %></li>
  <li><a href="#" onclick="return confirmReserve();">Mark all items as "reserved" for this shipment</a></li>
  <li><a href="#" onclick="return confirmDelete();">Remove all items from this shipment</a></li>
  <li><%= link_to 'View this shipment\'s manifest', manifest_shipment_path(@shipment) %></li>
</ol>
</p>
 
<%= link_to 'Edit', edit_shipment_path(@shipment) %> |
<%= link_to 'Back', shipments_path %>

Add Inventory Items

The add items pages displays the number of available inventory items by category and if there is at least one available, provides the user with a link to add all of the available items. Clicking the link runs the addAll route to add the items to the shipment and then redirect the user back to the shipment display page.

Ruby3 3

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
<h3>Add Inventory by Category</h3>
 
<table width="100%" cellpadding="2" cellspacing="2">
<tr>
  <th align="left">Category</th>
  <th align="left">Available</th>
  <th align="left">Add All</th>
</tr>
<tr>
  <td>Surgical Gloves</td>
  <td><%= @gloves %></td>
  <td><% if @gloves > 0 %><a href="/shipments/<%= @shipment.id %>/addAll?category=Gloves">Add All</a><% else %>Add All<% end %></td>
</tr>
<tr>
  <td>Nebulizer Accessory Kits</td>
  <td><%= @nebulizer %></td>
  <td><% if @nebulizer > 0 %><a href="/shipments/<%= @shipment.id %>/addAll?category=Nebulizer">Add All</a><% else %>Add All<% end %></td>
</tr>
<tr>
  <td>Hyperinflation Systems</td>
  <td><%= @hyperinflation %></td>
  <td><% if @hyperinflation > 0 %><a href="/shipments/<%= @shipment.id %>/addAll?category=Hyperinflation">Add All</a><% else %>Add All<% end %></td>
</tr>
<tr>
  <td>Breathing Circuits</td>
  <td><%= @circuit %></td>
  <td><% if @circuit > 0 %><a href="/shipments/<%= @shipment.id %>/addAll?category=Circuits">Add All</a><% else %>Add All<% end %></td>
</tr>
<tr>
  <td>Armboards</td>
  <td><%= @armboard %></td>
  <td><% if @armboard > 0 %><a href="/shipments/<%= @shipment.id %>/addAll?category=Armboards">Add All</a><% else %>Add All<% end %></td>
</tr>
</table>
 
<br/><%= link_to 'Back', shipment_path(@shipment) %>

View Shipment Inventory Items

The page simply displays the inventory items currently assigned to this shipment.

Ruby3 4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<h3>Items for Shipment <%= @shipment.name %></h3>
 
<table width="100%" cellpadding="2" cellspacing="2">
<tr>
  <th align="left">Item</th>
  <th align="left">Name</th>
  <th align="left">Category</th>
  <th align="left">Status</th>
</tr>
<% @items.each do |item| %>
<tr>
  <td><%= item.itemNumber %></td>
  <td><%= item.name %></td>
  <td><%= item.category %></td>
  <td><%= item.status %></td>
</tr>
<% end %>
</table>
 
<br/><%= link_to 'Back', shipment_path(@shipment) %>

Mark Items as Reserved

Items that have been added to the shipment need to be marked as reserved so that they can be processed for shipping. Clicking “OK” runs the reserve route to mark the items in the shipment as reserved and then redirect the user back to the shipment display page.

Ruby3 5

Remove Items from Shipment

Users may want to remove all of the items from their shipment and begin the process anew. Clicking “OK” runs the remove route which removes all of the items from the shipment, making them available again, and then redirects the user back to the shipment display page.

Ruby3 6

Routes

To process the flow of our application we need to modify app/routes.rb to include our new pages. Here’s a snippet from the beginning of the file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
resources :inventory_items
 
resources :shipments do
  member do
    get 'additems' # /shipments/1/additems
    get 'addAll'   # /shipments/1/addAll
    get 'items'    # /shipments/1/items
    get 'remove'   # /shipments/1/remove
    get 'reserve'  # /shipments/1/reserve
    get 'manifest' # /shipments/1/manifest
  end
end  
 
get "home/index"

ShipmentController

Last but not least is the ShipmentController. Controllers provide the “glue” between models and views. In Rails, controllers are responsible for processing the incoming requests from the web browser, interrogating the models for data, and passing that data on to the views for presentation. Take a look at the following code which should explain quite a bit. Since the controller contains code both generated by Rails and added by me, I’ve annotated it for your viewing ease.

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
class ShipmentsController < ApplicationController
  # GET /shipments
  # GET /shipments.xml
  def index
    @shipments = Shipment.all
 
    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render :xml => @shipments }
    end
  end
 
  # GET /shipments/1
  # GET /shipments/1.xml
  def show
    @shipment = Shipment.find(params[:id])
 
    respond_to do |format|
      format.html # show.html.erb
      format.xml  { render :xml => @shipment }
    end
  end
 
  # GET /shipments/new
  # GET /shipments/new.xml
  def new
    @shipment = Shipment.new
 
    respond_to do |format|
      format.html # new.html.erb
      format.xml  { render :xml => @shipment }
    end
  end
 
  # GET /shipments/1/edit
  def edit
    @shipment = Shipment.find(params[:id])
  end
 
  # CUSTOM
  # GET /shipments/1/additems
  def additems
    @shipment = Shipment.find(params[:id])
 
    # get a a count of the available inventory by category
    @gloves = InventoryItem.where(:category => 'Gloves', :status => 'Available').count
    @nebulizer = InventoryItem.where(:category => 'Nebulizer', :status => 'Available').count
    @hyperinflation = InventoryItem.where(:category => 'Hyperinflation', :status => 'Available').count
    @circuit = InventoryItem.where(:category => 'Circuits', :status => 'Available').count
    @armboard = InventoryItem.where(:category => 'Armboards', :status => 'Available').count
  end
 
  # CUSTOM
  # GET /shipments/1/addAll
  def addAll
    @shipment = Shipment.find(params[:id])
 
    # add all of the avaiilable items for the category to the shipment as 'selected'
    InventoryItem.where(:category => params[:category], :status => 'Available').each do |item| 
      # assign the item to the shipment
      item.shipment = @shipment.id
      # set the status as 'selected'
      item.status = 'Selected'
      item.save
    end
    # update the shipment with total number of items on it
    @shipment.items = InventoryItem.where(:shipment => @shipment.id).count
    # update the shipment with the total number of 'selected' items
    @shipment.selected = InventoryItem.where(:shipment => @shipment.id, :status => 'Selected').count
    @shipment.save
    respond_to do |format|
      format.html { redirect_to(@shipment, :notice => 'Items have been successfully added.') }
      format.xml  { head :ok }
    end
  end
 
  # CUSTOM
  # GET /shipments/1/items
  def items
    @shipment = Shipment.find(params[:id])
 
    # fetch all of the items on the shipment regardless of status
    @items = InventoryItem.where(:shipment => @shipment.id).order(:name)
  end
 
  # CUSTOM
  # GET /shipments/1/manifest
  def manifest
    @shipment = Shipment.find(params[:id])
 
    # fetch all of the items on the shipment regardless of status
    @items = InventoryItem.where(:shipment => @shipment.id).order(:name)
  end    
 
  # CUSTOM
  # GET /shipments/1/remove
  def remove
    @shipment = Shipment.find(params[:id])
 
    # remove all items on the shipment
    InventoryItem.where(:shipment => @shipment.id).each do |item| 
      # remove it from the shipment
      item.shipment = nil
      # mark the item as available
      item.status = 'Available'
      item.save
    end
 
    # update the shipment with the correct counts
    @shipment.items = 0
    @shipment.reserved = 0
    @shipment.selected = 0
    @shipment.save
    respond_to do |format|
      format.html { redirect_to(@shipment, :notice => 'All items removed from the shipment.') }
      format.xml  { head :ok }
    end
  end
 
  # CUSTOM
  # GET /shipments/1/reserve
  def reserve
    @shipment = Shipment.find(params[:id])
 
    # mark all items on the shipment as reserved
    InventoryItem.where(:shipment => @shipment.id).each do |item| 
      item.status = 'Reserved'
      item.save
    end
 
    # get a count of the number of reserved items on the shipment
    @shipment.reserved = InventoryItem.where(:shipment => @shipment.id).count
    @shipment.save
    respond_to do |format|
      format.html { redirect_to(@shipment, :notice => 'All items were successfully marked as reserved.') }
      format.xml  { head :ok }
    end
  end
 
  # POST /shipments
  # POST /shipments.xml
  def create
    @shipment = Shipment.new(params[:shipment])
 
    respond_to do |format|
      if @shipment.save
        format.html { redirect_to(@shipment, :notice => 'Shipment was successfully created.') }
        format.xml  { render :xml => @shipment, :status => :created, :location => @shipment }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @shipment.errors, :status => :unprocessable_entity }
      end
    end
  end
 
  # PUT /shipments/1
  # PUT /shipments/1.xml
  def update
    @shipment = Shipment.find(params[:id])
 
    respond_to do |format|
      if @shipment.update_attributes(params[:shipment])
        format.html { redirect_to(@shipment, :notice => 'Shipment was successfully updated.') }
        format.xml  { head :ok }
      else
        format.html { render :action => "edit" }
        format.xml  { render :xml => @shipment.errors, :status => :unprocessable_entity }
      end
    end
  end
 
  # DELETE /shipments/1
  # DELETE /shipments/1.xml
  def destroy
    @shipment = Shipment.find(params[:id])
    @shipment.destroy
 
    respond_to do |format|
      format.html { redirect_to(shipments_url) }
      format.xml  { head :ok }
    end
  end
end

Summary

That’s our application in a nutshell. You can download a zip of the entire project from here. In the next part of the series we’ll dive into the Force.com Toolkit for Ruby and really start to integrate with the platform.

Related Posts:

VN:F [1.9.22_1171]
Rating: 9.5/10 (4 votes cast)
VN:F [1.9.22_1171]
Rating: +3 (from 3 votes)
Learning Ruby for Force.com Developers – Part 3, 9.5 out of 10 based on 4 ratings

Categories: Code Sample, Ruby, Salesforce

Leave a comment

Comments Feed8 Comments

  1. Ajay

    Can you explain what ‘items_shipment_path’ or ‘additems_shipment_path’ are?

    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. Scott Lowe

    Not sure if this comment will make it through the anlge-bracket sanitizer, but:

    You don’t need that extra JavaScript in your links. Rails will take care of it for you if you fill out a :confirm parameter on link_to.

    “Are you sure you want to update all of the items in this shipment as reserved?” %>

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

    One more go at posting some sanitized code:

    <%= link_to “Mark all items as ‘reserved’ for this shipment”,
    “/shipments/#{@shipment.id}/reserve”,
    :confirm => “Are you sure you want to update all of the items in this shipment as reserved?” %>

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

    @Ajay, The link_to method is one of Rails’ built-in view helpers. It creates a hyperlink based on text to display and where to go – in this case, to the path for shipment items.

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

    Hi Jeff,
    I am looking to learn Salesforce.
    but i have one query should i learn apex or ruby?

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

    Rahul, I would definitely start with Apex. You might want to pick up a copy of our Salesforce Handbook to get you started.

    VN:F [1.9.22_1171]
    Rating: 0.0/5 (0 votes cast)
    VN:F [1.9.22_1171]
    Rating: 0 (from 0 votes)
  7. Andy Schwartz

    Jeff, Thanks for this tutorial. From a clean install on a Mac, I seemed to need the following in the gem install section at the top of this article:
    “gem install bundler”.

    I needed it before “bundle install” would work.

    Also, it was not mentioned anywhere that one takes the downloaded zip and expands it into the root of the mediaid-sqlite directory. I did that to run your app. Perhaps there was some other step I missed.

    Still working on understanding how to add items to Inventory…

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

    Glad it worked for you. BTW… I’ve been watching the tutorials on http://ruby.railstutorial.org. They are excellent!!

    VN:F [1.9.22_1171]
    Rating: 0.0/5 (0 votes cast)
    VN: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 3