Demo App – Ruby, Rails & Force.com REST API on Heroku
May 31st, 2011
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.
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 | 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.
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 | 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 |
app/views/search.html.erb
The search view allows the user to search for an account and display the results.
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 | <span class="title">Welcome to the ACME Telesales Application (RoR on Heroku)</span><p/> <p>This demo shows you how to query, retrieve, create and edit records in salesforce.com.</p> <p>Search for an Account to view all of its Opportunities or to create a new one.</p> <p/> <form method="post" action="search"> <span class="heading">Search by Account Name:</span><p/> <input type="text" name="accountName" value="<%= params[:accountName] %>" style="width: 300px"/> <input type="submit" value="Search"/> </form> <p/> <span class="heading"><%= @json["records"].length %> accounts matching your search criteria:</span> <p/> <table border="0" cellspacing="1" cellpadding="5" bgcolor="#CCCCCC" width="50%"> <tr bgcolor="#407BA8"> <td style="color: #ffffff; font-weight: bold;">Name</td> <td style="color: #ffffff; font-weight: bold;">City</td> <td style="color: #ffffff; font-weight: bold;">State</td> <td style="color: #ffffff; font-weight: bold;">Phone</td> </tr> <% @json["records"].each do |record| %> <tr style="background:#ffffff" onMouseOver="this.style.background='#eeeeee';" onMouseOut="this.style.background='#ffffff';"> <td><a href="/accounts/<%= record["Id"]%>"><%= record["Name"] %></a></td> <td><%= record["BillingCity"] %></td> <td><%= record["BillingState"] %></td> <td><%= record["Phone"] %></td> </tr> <% end %> </table> |
Categories: Code Sample, Heroku, Ruby, Salesforce













Thanks Jeff!
Awesome, i ‘ll try it in person. Keep adding Jeff