Appirio's Tech Blog

Friday, September 30, 2011

Make Any Salesforce sObject REST-enabled


One of our #awesome guys, George Acker, created this nifty process to REST-enable any sObject in salesforce.com.

It's a little hard to watch in this small window. You might want to view it full screen here.

Unable to display content. Adobe Flash is required.

Friday, June 3, 2011

Demo App - Ruby, Rails & Force.com REST API on Heroku

So continuing with my learning Ruby series, I finally finished my sample app using the Force.com REST API. I ran into a few issues and fortunately Quinton Wall and Heroku support came to my rescue. Apparently require 'Accounts' and require 'accounts' aren't the same when running on Heroku. Go figure.

This is a demo Rails app running on Ruby 1.9.2 and Rails 3.0.5 hosted on Heroku. It uses OAuth2 via OmniAuth to authorize access to a salesforce.com org. It uses the Force.com REST API to query for records, retreive records to display, create new records and update existing ones. It should be good sample app to get noobs (like me) up and running.

I forked Quinton Wall's (excellent) omniauth-rails3-forcedotcom project to get started. One of the things you have to do when using the Force.com REST API is to configure your server to run under SSL using WEBrick. I found some excellent instruction for OS X here.

You can run the app for yourself here.

All of the code for this app is hosted at github so feel free to fork it. I've pulled out some of the more important parts of the app for discussion after the video.


app/controllers/accounts_controller.rb

The account controller delegates authority to accounts.rb for integration with Force.com and then packages up the returns for the views.

require 'accounts'

class AccountsController < ApplicationController
  def index
  end
  
  def search
    @json = Accounts.search(params[:accountName])
  end
  
  def show
    @account = Accounts.retrieve(params[:id])
    @opportunities = Accounts.opportunities(params[:id])    
  end
  
  def create
     @account = Accounts.create
  end
  
  def edit
     @account = Accounts.retrieve(params[:id])
  end
  
  def save
    Accounts.save(params)
    redirect_to :action => :show, :id => params[:id]
  end  
  
  def new_opp 
    @account = Accounts.retrieve(params[:id])
  end
  
  def save_opp
    Accounts.create_opp(params)
    redirect_to :action => :show, :id => params[:id]
  end

end

lib/accounts.rb

Accounts.rb does most of the heavy lifting for the app. It prepares the requests to Force.com with the correct headers and makes the actual calls to Force.com with the REST API.

require 'rubygems'
require 'httparty'

class Accounts
  include HTTParty
  #doesn't seem to pick up env variable correctly if I set it here
  #headers 'Authorization' => "OAuth #{ENV['sfdc_token']}"
  format :json
  # debug_output $stderr

  def self.set_headers
    headers 'Authorization' => "OAuth #{ENV['sfdc_token']}"
  end

  def self.root_url
    @root_url = ENV['sfdc_instance_url']+"/services/data/v"+ENV['sfdc_api_version']
  end
  
  def self.search(keyword)
    Accounts.set_headers
    soql = "SELECT Id, Name, BillingCity, BillingState, Phone from Account Where Name = \'#{keyword}\'"
    get(Accounts.root_url+"/query/?q=#{CGI::escape(soql)}")
  end
  
  def self.create()
    Accounts.set_headers
    headers 'Content-Type' => "application/json"
    
    options = {
      :body => {
          :Name => "1234"
      }.to_json
    }
    response = post(Accounts.root_url+"/sobjects/Account/", options)
    # puts response.body, response.code, response.message
  end
  
  def self.save(params)
    Accounts.set_headers
    headers 'Content-Type' => "application/json"
    
    options = {
      :body => {
          :billingcity => params[:BillingCity]
      }.to_json
    }
    p options
    response = post(Accounts.root_url+"/sobjects/Account/#{params[:id]}?_HttpMethod=PATCH", options)
    # 201 response.body equals success
    # puts response.body, response.code, response.message
  end
  
  def self.retrieve(id)
    Accounts.set_headers
    get(Accounts.root_url+"/sobjects/Account/#{id}?fields=Id,Name,BillingCity,BillingState,Phone,Website") 
  end
  
  def self.opportunities(accountId)
    Accounts.set_headers
    soql = "SELECT Id, Name, Amount, StageName, Probability, CloseDate from Opportunity where AccountId = \'#{accountId}\'"
    get(Accounts.root_url+"/query/?q=#{CGI::escape(soql)}")
  end
  
  def self.create_opp(params)
    Accounts.set_headers
    headers 'Content-Type' => "application/json"
    
    options = {
      :body => {
          :name => params[:name],
          :amount => params[:amount],
          :accountId => params[:id],
          :amount => params[:amount],
          :closeDate => params[:closeDate],
          :stageName => params[:stageName]
      }.to_json
    }
    response = post(Accounts.root_url+"/sobjects/Opportunity/", options)
    # 201 response.body equals success
    # puts response.body, response.code, response.message
  end
 
end

Tuesday, May 31, 2011

Our Favorite Salesforce.com Summer ’11 Features

The salesforce.com Summer ’11 release is due to hit an org near you in the next couple of weeks. Are you ready? As usual, I’ve scoured through all 71 pages of the release notes and pulled out my favorite features. I didn’t hit all of the items in the release notes so make sure you pull up the PDF and check out all of #superfrickinawesomeness in this release. Also remember to visit the Summer ’11 Force.com Platform Release Preview page for any last minute info.

Jigsaw is GA!
  • Everyone using supported Salesforce editions (Developer, Enterprise, Professional, and Unlimited) can search for Jigsaw CRM records. The Free version of Jigsaw provides Jigsaw data to all users, but you need to implement the paid version of Jigsaw to get complete contact and lead data.
  • Free version users can't see phone and email information for contacts, and they can't add contact or lead records to Salesforce.
  • Jigsaw has it's own Licenses & Limits, so check the docs for more info.
Chatter Enhancements
  • Save feed search or topics as a "Chatter favorite" for easy access.
  • You can now view Chatter feeds on standard and custom object list views.
  • Use Global Search to find people who are mentioned in Chatter.
  • When sharing a file with a person or a group, you can choose to give them viewer or collaborator permissions.
  • Enhanced filters to help you find and view specific groups of files more quickly and easily on the files tab and "select from salesforce" search.
  • A new Chatter Mobile App for Android (beta) and updates to Chatter Mobile apps for BlackBerry and iOS devices
  • Organizations with login IP restrictions can now allow specific users and groups to use the Chatter mobile app outside of the login IP range.
  • Chatter will automatically recommend people in your organization that you might want to follow based on similar interests.
  • Each new org that enables Chatter will have a special user named "Chatty" that offers tips, tricks, and sample postings to help users get started with the default Chatter groups. Uh.... does this sound like Clippy?
Sales & Service Cloud Enhancements
  • Salesforce CRM Content Workspaces are now called "Libraries".
  • Chatter files are now included in Salesforce CRM Content Searches
  • Granular permissions for Salesforce Knowledge including "create", "read", "edit" and "delete" object permissions for article types.
  • Workflow rules and approval processes are now available for Salesforce Knowledge article types.
  • Custom console components allow you to customize, extend, or integrate the sidebars of the Service Cloud console using Visualforce.
  • You are now able to customize the page layout for milestones.
Analytics
  • Some operations with the Report Builder load asynchronously, so you can continue to make changes to your report while the preview loads. Asynchronous loading isn't available for matrix reports.
  • In Report Builder, you can now select multiple fields at once to add, remove, or reorder.
  • In addition to posting snapshots to dashboard and user feeds, you can now post snapshots them to Chatter group feeds! A snapshot is a static image of a dashboard component at a specific point in time.
  • No more "lost" dynamic dashboards! You can no longer save dynamic dashboards to a personal folder.
Security Enhancements
  • The identity confirmation process has been improved and made consistent across devices. Salesforce now sends a numeric verification code to users instead of a URL for identity confirmation.
  • Just-In-Time Provisioning with SSO uses SAML to create users on the fly the first time they try to log in with SSO.
Chatter Connect RESTful API
  • The new REST API makes it much easier to fetch chatter data!! No more need to traverse the byzantine Chatter data model. Relationships between objects can easily be traversed without having to resolve foreign keys!
  • The API provides access to Chatter feeds and social data such as users, groups, followers, and files.
  • Returned information is automatically localized to the user's time zone and language and feed items are structured in a way that makes it easy to render.
  • Uses OAuth2 and a Salesforce remote access application
Force.com Enhancements
  • The sidebar in Setup includes a search box for browsing and quickly finding setup tools (not records), a la the Greasemonkey Setup Enhancer. Finally!
  • The Global Search box now appears in the header of all Salesforce setup pages.
  • A new enhanced profile user interface to easily navigate, search, and modify settings for a profile.
  • Deferring Sharing Calculations allows you to suspend automatic group membership and sharing rule calculation until your system has the bandwidth and processing power.
  • A new System.URL Class which seems to be the favorite new feature for most developers. You can use the methods of the System.URL class to create links to objects in your organization. Objects can be images, logos, or records that you want to include in external emails, in activities, or in Chatter posts.
  • New limit for @Future method invocations. Per the release notes, "The basis for the computation of the limit on the number of future method invocations changed. In addition to full Salesforce user licenses, Salesforce Platform user licenses are now included in the computation of the limit and Chatter Only, Guest User, Customer Portal User, and Partner Portal User licenses are excluded. The limit is 200 method calls per full Salesforce user license and Salesforce Platform user license, per 24 hours. This is an organization-wide limit. For example, suppose your organization has three full Salesforce licenses, two Salesforce Platform licenses, and 100 Customer Portal User licenses. Your entire organization is limited to only 1,000 method calls every 24 hours ((3+2) * 200, not 105.)".
  • Dependent lookups are now GA.
  • The limit for the maximum number of fields on a custom object has been raised from 500 to 800 fields per object in Unlimited Edition.
  • The total number of sharing rules per object increases from 100 to 300 in Professional, Enterprise, Unlimited, Developer and Database.com Editions.
  • There are no longer limits to the number of rich text area and long text area fields that an object can contain, although your Edition's limit (all editions except Database.com) for the total number of custom fields allowed on an object, regardless of field type, still applies. Now, each object can contain a total of 1.6 million characters across long text area and rich text area fields. The default character limit for long text area and rich text area fields is 32,000 characters. A long text area or rich text area field needs to contain at least 256 characters.
Visualforce Enhancements
  • Javascript Remoting for Apex Controllers is now GA
  • Visualforce now allows some methods in Apex controllers to be called via Javascript using the @RemoteAction annotation.
  • Inline editing on Visualforce pages now supports rich text area fields bound to the component.
Dynamic Visualforce Components - Pilot
Dynamic Visualforce components offer a way to create Visualforce pages that render according to a variety of states, such as a user's permissions or actions. Rather than using standard markup, dynamic Visualforce components are designed in Apex.

Enhanced Field Sets Editor - Beta
With the Enhanced Field Sets Editor you can now create field sets with a drag and drop WYSIWYG interface. The enhanced editor lets you quickly customize, create or edit field sets for your organization, or edit any installed field set with an improved drag and drop design.

Mobile Phone Verification – Pilot
As part of the pilot, if you get an identity challenge when logging into Salesforce, you are sent an email message with a security code. You must enter that code to confirm your identity.

Friday, April 15, 2011

Salesforce Content Primer for Implementers

By Nitin Jain

Salesforce Content is a structured library of your organization’s business documents. This includes sales presentations, product data sheets, policy documents, proposal templates, and best practices. Salesforce Content supports the storage of many common document formats and can display a preview of most of the content without downloading it.

This Primer comes from hands-on experience implementing Salesforce Content, the challenges faced and the solutions devised during the implementation. Hopefully, implementers will save some time by learning from the tips, tricks and “gotchas” mentioned in here.

Terminology and Concepts
Here is an overview of some basic terminology and concepts in Salesforce Content:
  • Content Document: This is a wrapper object for a Content version object. It stores things like Owner and LatestPublishedVersionId.
  • Content Version: (API name is ContentVersion) This is the main object in Salesforce Content. ContentVersion is the holder of the actual content by version. The content data is stored in base64 encoded form in the VersionData field. Content Version also has many other system defined fields like Title, FileType, and Description.
  • Content Fields: In addition to system defined fields for Content, additional custom attributes (fields) can be added to the Content object.
  • Content Types: Content Types defines a business document type. This includes Training Material, Case Study, Marketing Material, etc. Content Types allows for the grouping of Content Fields using Page Layouts. Additionally, for each Content Type, a corresponding record type is also created.
  • Page Layout for Content Type: When a user selects a Content Type, the associated page layout is loaded for the user to fill-in fields defined in that layout. Click on “Edit” against the Content Types to edit the Page Layout, then drag and drop fields to the desired location on the page.
  • Picklist Customization: Content Type can also be used to customize picklists. After clicking the “Picklist” link, a list of Content Types is displayed. Select the Content Type for which you would like to customize the picklist.
  • The next page displays a list of content picklist fields. Simply select the picklist you would like to customize.
  • Workspaces: A Workspace represents an area where users can collaborate on content. Each Workspace can be shared across users.
  • Workspace Permissions: This allows you to add, edit and delete Workspace permissions.
  • Assigning Permissions to a Workspace: You can assign Workspaces to individual Salesforce CRM Content users or public groups that contain Salesforce CRM Content users.
  • Restricting Content Type for a Workspace: You can also restrict the type of content that can be delivered to a Workspace.

  • Defining Public Group for All Internal Content Users in the Org: You can build a public group to simplify the Workspace assignment process. An example use case could be; all users in the organization should have authoring and publishing permission to a “Pictures” Workspace. To do this you need to enable “All Internal Users” public group by enabling Customer Portal. Once the “All Internal Users” public group is enabled, use it to define an “All Internal Content User” public group. This new custom defined group can be then used to assign permission to the Workspace.

  • Bulk Loading Content: In order to bulk load content, follow the steps listed below:
1. Create a .csv file in the following format:
Column Name = Content Field [explanation]:

File Name = PathOnClient
[Used to determine file type e.g. Power Point, Word doc, jpg image. This is based on extension of the file name]

Document Location = VersionData
[Represents location of the actual file. Used to load the file data in the VersionData system field]

Workspace Id = FirstPublishLocationId
[This is the Workspace where the content would be initially published. Depending on the permission settings of the Workspace, the content can be shared with other Workspaces after the initial load]

2. Use Apex Data Loader to load the content using the load file created above.
- Object Name: Content Version
- Click on “Show More” to see the Content Version object.
- Use latest version of Apex Data Loader - older versions do not support Content objects.

3. Use Auto Matching for field mapping, then use the mapping above to map file name, document location and Workspace Id. Remember to save mapping for future loads.
Tips, Tricks and “Gotchas”

1. Displaying image from Content in a Portlet: Image data is stored as base64 encoded string.

Controller code:
public transient String imageData {get; set;}

public void loadImageData() {
List content = [select id, versiondata, pathonclient 
      from ContentVersion where Id=:contentid];

imageData = EncodingUtil.base64Encode(content[0].versiondata);
}

Visualforce page code:

  

Note: This technique works in Firefox and other non IE browsers. IE7 does not support a base64 image display. It may be supported in future versions of IE.

2. Do not deploy Content Types (i.e. Content Layouts) using Change Sets or the ANT migration tool. This will mess up the Content Types in an irrecoverable way. The only solution at that point is to request salesforce.com run dbscript.

3. Subscribed Content, Workspace, Author and Tags can not be queried via API.

4. The Subscribe feature is going to be replaced by a “Follow” feature.

5. Currently you can only follow Content. Author and Tags. Workspaces can not be followed, but will most likely be supported in upcoming releases.

6. Following an Author can be achieved by following People.

7. You cannot control the order of Content filters nor the showing/hiding of certain filters.

8. Related Content attached to an object cannot be queried via SOQL or API.

9. There is no support to deploy Workspaces and Workspace permissions from a sandbox org to production or to other sandbox orgs.

10. Content Filter does not have Content Type as filter criteria [Filter by Content Type in Salesforce Content]. A possible workaround can be found below:
a. Add a custom field on Content of datatype : Text (255)
b. Write a batch Apex Class to populate custom content type field from RecordType.Name.
Batch Apex Class code:
global class ContentBatch implements Schedulable, Database.Batchable  {
    
    global List failedContent = new List();
    
    global void execute(SchedulableContext SC) {
     ContentBatch contentBatch = new ContentBatch();
     ID batchprocessid = Database.executeBatch(contentBatch);
 }
    
    global Database.QueryLocator start(Database.BatchableContext BC) {
     return Database.getQueryLocator([SELECT id, recordtype.name
                                      FROM ContentVersion
                                      WHERE isLatest = true
                                      AND recordtype.name != ''
                                      AND content_type__c = '']);
 }
    
    global void execute(Database.BatchableContext BC, List batch) {

     List contentList = (List)batch;
   
 Set recordConnectionIds = new Set();
     for (ContentVersion contentRecord : contentList) {
         contentRecord.content_type__c = contentRecord.recordtype.name;         
     }
    
 Database.SaveResult[] dbResults = Database.update(contentList,false);

 // send failed updates via email to job submitter.
     String emailSubject = 'Content Batch Errors';
     String emailBody = '';
     Boolean sendEmail = false;
     
     for (Database.SaveResult dbResult : dbResults){
         if (!dbResult.isSuccess()) {
             Database.Error err = dbResult.getErrors()[0];
             emailBody += '\n' + 'id=' + dbResult.getId() + ' Error: ' + err.getMessage();
             sendEmail = true;
         }
     }
     
     if (sendEmail) {
      
      AsyncApexJob apexJob = [Select Id, Status, NumberOfErrors, JobItemsProcessed,
                            TotalJobItems, CreatedBy.Email
                           from AsyncApexJob where Id =:BC.getJobId()];
      // send an email out with results.
         Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
         String[] toAddresses = new String[] {apexJob.CreatedBy.Email};
         mail.setToAddresses(toAddresses);
         mail.setSubject(emailSubject);
         mail.setPlainTextBody(emailBody);
         Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
      
     }
 }
    
 global void finish(Database.BatchableContext BC) {
 }
    
 @isTest
 public static void testBatch() {
    ContentBatch cbatch = new ContentBatch();
     Database.executeBatch(cbatch);
       
  // Schedule the batch for hourly run
 String sch = '0 0 * * * ?';
     system.schedule('Content Hourly batch',sch,cbatch);
     
 }
}
c. Schedule the above batch apex class to run every hour or whatever frequency is appropriate for your implementation.
For scheduling , Run following command in “execute anonymous” block.
ContentBatch cbatch = new ContentBatch();
String sch = '0 0 * * * ?';
system.schedule('Content Hourly batch',sch,cbatch);

For running batch on demand, Run following command in “execute anonymous” block.
ContentBatch cbatch = new ContentBatch();
Database.executeBatch(cbatch);

To view Scheduled Jobs
Setup > Administration Setup > Monitoring > Scheduled Jobs

To Monitor Apex Batch Jobs
Setup > Administration Setup > Monitoring > Apex Jobs

11. It appears that Salesforce Content is moving towards the concept of Files. The current Content tab will be replaced by a Files tab and Content Workspaces will be re-named Content Libraries. The Files tab would show files from Content Libraries as well as files posted to the Chatter feed.

About the Author:
Nitin Jain is a Senior Technical Consultant at Appirio, where he designs and develops business applications in salesforce.com’s Sales and Service Cloud and custom applications on the Force.com Custom Cloud.

Thursday, February 24, 2011

Installing Ruby 1.8 and & 2.3.8 for ActiveSalesforce

I've been working for the past week or two on my Ruby for Force.com Developers series but have run into a few snags. First of all the ActiveSalesforce adapter doesn't work with Rails 3.0.3. There's a post on the message boards where Quinton Wall mentions that he is working with Heroku and is looking to write a new Ruby toolkit. Any new toolkit will be REST-based and move away from the active-record model but no ETA as of now.

So my other alternative was to go the REST route since I've already done it in Java. Pat Patterson has a great Cookbook Recipe but it uses the Sinatra framework and has confusing instruction on setting up na SSL reverse proxy with different HTTP servers. Setting up SSL with Rails 3.0.3 is a breeze and since my Ruby series is already headed down that path, I decided to stick with Rails. I quickly spun up a new Rails app and started working on the OAuth dance. I had no problems authorizing my app with salesforce.com and returning the code, however, when trying to POST to salesforce.com to get my token, I received a mysterious "end of file" error. Based upon the Ruby message boards, a number of people are having struggles with OAuth so I enlisted Quinton to see if he can help me. Hopefully we'll have a Rails 3 solution soon and I can add that to my developer series.

Note: We had an Appirio AppDev CoE call with Ben Scofield (Heroku's developer advocate) last week and he suggested rolling a solution with OmniAuth. I think we are going to post a CloudSpokes contest to build this out for everyone.

So I decided to downgrade to an earlier version of Ruby and Rails so that I could use ActiveSalesforce for my developer series. I ended up (finally!) installing Ruby 1.8.7 and Rails 2.3.8 on a Ubuntu 10.10 VM. I think I could have used Ruby 1.9.2 but was caught in version hell. There are a couple of conflicting articles on how to setup your Rails environment (this one seems to be the best) but I finally got it working with a little help from the ActiveSalesforce Google Group.

Here are the steps that I went through on a fresh install of Ubuntu:

apt-get install ruby-full

wget production.cf.rubygems.org/rubygems/rubygems-1.3.7.tgz

tar -xvf rubygems-1.3.7.tgz

cd rubygems-1.3.7/

ruby setup.rb

ln -s /usr/bin/gem1.8 /usr/bin/gem

gem install rdoc

# install the 2.3.8 version of rails
gem install --version=2.3.8 rails --include-dependencies

# install sqlite dev lib
apt-get install libsqlite3-dev

# install rubygem
apt-get install rubygems

# install bundler
gem install Bundler

# install sqlite3
gem install sqlite3-ruby

# install rake
gem install rake

# install rforcedotcom
gem install rforcedotcom

# install the older version of facets for toolkit
gem install -v=2.8.4 facets

# install hpricot
gem install hpricot

# install the soap adapter
gem install asf-soap-adapter

Sunday, February 20, 2011

Learning Ruby for Force.com Developers – Part 3

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:



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.htm

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.


Welcome to MediSend's MediAid!

MediAid is an international aid, self-service portal for inventory selection, processing and information centralization.

The following shipments are available for your aid case:

<% @shipments.each do |shipment| %> <% end %>
Shipment Type Status
<%= link_to shipment.name, shipment %> <%= shipment.shipmentType %> <%= shipment.status %>

The following options are also available:

  • <%= link_to "Maintain Shipments", shipments_path %>
  • <%= link_to "Maintain Inventory Items", inventory_items_path %>

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.


<%= notice %>

Shipment <%= @shipment.name %>

Status: <%= @shipment.status %> Type: <%= @shipment.shipmentType %> Country: <%= @shipment.country %> Items: <%= @shipment.items %> Selected: <%= @shipment.selected %> Reserved: <%= @shipment.reserved %>

Available options for this shipment:

  1. <%= link_to 'View items in this shipment', items_shipment_path(@shipment) %>
  2. <%= link_to 'Add items by product category to this shipment', additems_shipment_path(@shipment) %>
  3. Mark all items as "reserved" for this shipment
  4. Remove all items from this shipment
  5. <%= link_to 'View this shipment\'s manifest', manifest_shipment_path(@shipment) %>

<%= 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.


Add Inventory by Category

Category Available Add All
Surgical Gloves <%= @gloves %> <% if @gloves > 0 %>Add All<% else %>Add All<% end %>
Nebulizer Accessory Kits <%= @nebulizer %> <% if @nebulizer > 0 %>Add All<% else %>Add All<% end %>
Hyperinflation Systems <%= @hyperinflation %> <% if @hyperinflation > 0 %>Add All<% else %>Add All<% end %>
Breathing Circuits <%= @circuit %> <% if @circuit > 0 %>Add All<% else %>Add All<% end %>
Armboards <%= @armboard %> <% if @armboard > 0 %>Add All<% else %>Add All<% end %>
<%= link_to 'Back', shipment_path(@shipment) %>

View Shipment Inventory Items

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


Items for Shipment <%= @shipment.name %>

<% @items.each do |item| %> <% end %>
Item Name Category Status
<%= item.itemNumber %> <%= item.name %> <%= item.category %> <%= item.status %>
<%= 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.



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.



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:

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.

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:

Tuesday, February 8, 2011

Upcoming Salesforce.com Spring '11 Features

The salesforce.com Spring '11 release is set to start rolling onto orgs in the next few days. Are you ready? This release is packed with all kinds of platform #superfrickinawesome-ness!! Once again, I've scoured through all 88 pages of the release notes and pulled out my favorite features. I didn’t hit all of the items in the release notes so make sure you pull up the PDF and check out all of the goodies for this release. Also check out the Spring ’11 Force.com Platform Release Preview page for any last minute info. I've tried to link the features below to the appropriate section in the preview site for more info.

There are a number of new enhancements across the platform but developers really come out ahead with this release. Last fall I named the Winter '11 release, "OMG! We Love Outlook!" due to the heavy emphasis on Salesforce for Outlook. I've aptly named the Spring '11 release "Developers! Developers! Developers!". You'll see why as the article progresses.

Google Chrome Support
  • Hurrah! Salesforce now supports Google Chrome version 8.0.x and Google Chrome Frame plug-in for Microsoft® Internet Explorer.
  • Now there's no reason why you shouldn't be using my kick-ass Chrome plugin, the Force.com Utility Belt.
Chatter Enhancements
  • Like - Finally.. I was getting so sick of typing "Like" into my Chatter comments! You can now Like and unLike posts but not comments.
  • @Mentions - You can now mention a person's name to ensure that the person sees the update.
  • Posting Comments via Email Replies - You can now reply to Chatter posts and comments by simply replying to the email notification. #Awesome!
  • Trending Topics - The Chatter tab now includes Trending Topics to show what people in your company are talking about.
  • Follow Dashboard Components - Only metrics and gauges with conditional highlighting are eligible.
  • Live Updates - You can now click You have new updates at the top of a feed to see updates added to your Chatter feed since your last refresh.
  • Private File Sharing - Privately share a file with specific Chatter users. There are a bunch of settings around this such as email notifications after sharing, see where a file is shared, view and search for files shared by me, upload a new version, file sharing settings and permission. Check out the docs for more details.
  • Private Group Requests by Email - You can now click a button to automatically send an email to a group's owner and managers requesting permission to join a Chatter group.
  • Recommend People and Records - Chatter now recommends records for you to follow as well as people based on a shared interest in records.
  • Searchable Chatter Fields - The Global Search now includes Chatter feed results.
  • Chatter Desktop - New version of Chatter Desktop for Adobe Air
  • Chatter Mobile - Available for iPhone, iPad and BlackBerry devices. The Chatter mobile app does not have all of the functionality of Chatter in Salesforce.
  • Chatter Triggers - You can now write triggers for the FeedComment and FeedItem objects. More details here. Thanks Sandeep!
Sales Cloud
  • The Cloud Scheduler is now enabled by default for all organizations.
  • The Attachments related list is now automatically added to task and event records for new orgs. You'll need to add it manually for existing orgs.
  • Attachments are now searchable for tasks and events.
  • Attachments on emails that are sent using Email to Salesforce or Salesforce for Outlook can now be saved with the email's task record in Salesforce.
  • Salesforce for Outlook now uses OAuth-based authentication to securely store user login information.
Service Cloud
  • Salesforce Knowledge now supports multiple languages
  • Salesforce Knowledge Sidebar for the Service Cloud Console automatically searches and returns articles from your knowledge base that matches any of the words in the Subject of a case.
  • Global Search is the sole search tool for the Service Cloud Console
Report Builder Enhancements
  • New chart type for reports - scatter charts
  • Report builder is now available to users with Force.com and Salesforce Platform licenses
  • Group and Professional Edition orgs can now use report builder
  • All profiles get access to the report builder by default
  • More info here.
Dashboard Enhancements
  • Dashboard builder is automatically enabled for all users
  • More Dynamic Dashboards per edition: EE-up to five per org, UE-up to 10 per org, DE-up to three per org. I've also heard that there might be a black tab to increase these numbers.
  • Post a snapshot of a dashboard component to a user's Chatter feeds
  • You can show Chatter user and group photos in horizontal bar charts and tables in dashboards
Revised Apex Governor Limits
  • There is now a single context for all governor limits! Be still my beating heart!! All governor limits have the same amount of resources allocated to them, regardless if you're calling it from trigger, an anonymous block, a test, and so on.
  • The total number of DML statements issued for a process has gone from 20 for triggers, and 100 for everything else, to 150 for all contexts.
  • Total number of records retrieved by SOQL queries has gone from 10K to 50K
  • More info here.
REST API - Develop mobile and external apps in Java, Ruby, Python, PHP etc. using the REST API as an alternative to the SOAP-basedAPI. The REST API is a simplified approach for developers, using code that is much less verbose and easy to write. More info here.

Criteria-Based Sharing Rules - Instead of using Triggers to create and delete sharing rules, you can now declaratively create sharing rules based upon some criteria of the record (i.e. when BillingState = 'NY'). You can create criteria-based sharing rules for accounts, opportunities, cases, contacts, and custom objects. There is a limit of 10 criteria-based sharing rules per object. More info here.

Visualforce Inline Editing - A single component, <apex:inlineEditingSupport>, provides inline editing support in your Visualforce details pages. Now users will have the same editing experience in your Visualforce page that they get in standard page layouts. Inline editing is supported by 8 components so check out the docs for more details. Inline editing is not supported for rich text areas or dependent picklists. More info here.

Visualforce Dynamic Bindings - This feature allows you to write generic Visualforce pages that display information about records without necessarily knowing which fields to show. Administrators/developers can create fields sets that determine at runtime the files to be displayed. Therefore, when at you need to modify the fields on a Visualforce page you simply modify the associated field set instead of modifying the Apex controller and Visualforce page. More info here along with a blog post.

Change Sets - This admin features allows you to package up a number of configuration changes in one org and send them to another org to be deployed. Essentially the same as deploying with the Force.com IDE but you can now give administrators (non-developers) access to install modifications from sandbox. More info here.

Force.com Flow - Formerly Visual Process Manager, this is a client-side tool that allows you to design complex, robust scripting processes that you can embed in your Visualforce pages. You can also embed Flows in Visualforce pages to be used with Force.com Sites, the Customer Portal and the partner portal. This is gonna be big!! More info here.

System Log Console Execution Summary - The system log contains a new tab to highlight the duration and composition of a request so you can identify and isolate execution bottlenecks. There is also a new section that details the value of variables during a request. More info here.

Daily Limit on Workflow Alert Emails - To prevent abuse of email limits, the daily limit for emails sent from workflow and approval-related email alerts is 1,000 per standard Salesforce license per organization. The overall organization limit is 2 million.

Web Service Connector - The WSC replaces Apache Axis 1.3 as the preferred SOAP Java client framework. WSC is an open-source project and contains a command utility that generates Java source and bytecode files from WSDL files.

New & Modified API Objects
  • LoginHistory - Represents the login history for all successful and failed login attempts for organizations and enabled portals.
  • ContentDocumentLink - Represents the link between a Salesforce CRM Content document or Chatter file and where it's shared.
  • FeedLike - Indicates that a user has liked a feed item.
  • CollaborationGroupMemberRequest - Represents a request to join a private Chatter group.
  • DashboardComponent - Represents a dashboard component (read-only)
  • DashboardComponentFeed - Represents a single feed item in the feed displayed on a dashboard component.
  • A number of Chatter and Metadata API objects have been changed in API version 21.0 so check out the docs for details.
Pilot and Developer Previews

Javascript Remoting for Apex Controllers - Developer Preview - This new feature allows you to integrate Apex and Visualforce pages with JavaScript libraries. It provides support for some methods in Apex controllers to be called via Javascript using the @RemoteAction annotation. More info here.

Apex Test Framework - Pilot - Run just one, a set, or all the tests in your organization. Tests are run asynchronously: start them, then go work on other things. You can then monitor the tests, add more tests to the ones that are running, or abort running tests. Once a test finishes running, you can see additional information about that test run.

ReadOnly Annotation - Pilot - The @ReadOnly annotation provides developers the ability to perform unrestricted queries against the Force.com database. The annotation removes the limit of the number of returned rows for a request but also blocks any DML statements within the request. The @ReadOnly annotation is only available for Web services and the schedulable interface.

Chatter for Mobile Browsers - Pilot - In addition to the Chatter mobile app for Apple and BlackBerry devices, salesforce.com provides a mobilized Web version of Chatter that you can access from a mobile device browser without installing an app.
 
2006-2012 Appirio Inc. All rights reserved.
Appirio.com | Support | Resource Center | Contact | Careers | Privacy Policy