function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
undeesundees 

Ruby, SOAP, and "concrete entity type" error

Hi. I'm using the Ruby SOAP4r library with the Enterprise portal to try to add a new Opportunity. I create a new Opportunity object and add all the fields that SalesForce appears to require:


opp = Opportunity.new
class << opp
attr_accessor :accountId, :amount, :name, :closeDate, :stageName
end

opp.accountId = the_account
opp.amount = "3.14"
opp.name = "Test from script"
opp.closeDate = "2005-8-25"
opp.stageName = "Closed Won"

binding.create(Create.new([opp]))


But I consistently see the error message "common.exception.ApiException: Must send a concrete entity type. (SOAP::FaultError)". I don't get it. What could be more concrete than an Opportunity? How can I find out what piece of information SForce is missing? Should I add more fields? Which ones?

Before you ask, yes, I've regenerated my WSDL files and rerun wsdl2ruby.
wrexwrex
You're ahead of me -- how did you set the sessionId in the SOAP header with soap4r?

How are you using wsdl2 ruby? I was trying the following with soap4r version 1.5.4:


require 'soap/wsdlDriver'
WSDL_URL = "enterprise.wsdl"
soap = SOAP::WSDLDriverFactory.new(WSDL_URL).create_rpc_driver

#soap.wiredump_dev = STDERR

userpw = { :username => "mylogin@sforce", :password => "mypassword" }

login_response = soap.login( userpw ).result

puts ---
Session ID: #{login_response.sessionId}
Server URL: #{login_response.serverUrl}
---

EOF

soap.endpoint_url=(login_response.serverUrl)

# Now need to set the session ID in the soap header -- how?!!

# The following fails
nogo = soap.describeGlobal(login_response.sessionId)


VERY happy to see someone else trying to use ruby with sforce.

Any tips will be very much appreciated.
undeesundees
wsdl2ruby works like this: first of all, don't call the one buried deep in the "lib" folder. Instead, call the one in the "bin" directory of the "soap4r" source distribution (this didn't even get installed on my system). Call it like this:

ruby wsdl2ruby.rb --type client --wsdl enterprise.wsdl


Now, for the session header -- I saw some sample code somewhere that looked like this:


class HeaderHandler < SOAP::Header::SimpleHandler
def initialize(tag, value)
super(XSD::QName.new(nil, tag))
@tag = tag
@value = value
end

def on_simple_outbound
@value
end
end


So here's how I fit that fragment into a semi-working program:


#Fill these in
salesForceURL = 'http://...'
user = 'yourusername'
pass = 'yourpassword'

binding = Soap.new salesForceURL
response = LoginResponse.new(binding.login(Login.new(user, pass)))
sessionId = response.result.result.sessionId

header = SessionHeader.new($i)
binding.headerhandler << HeaderHandler.new('SessionHeader', "<sessionId>#{sessionId}</sessionId>")


Notice that I cheated a little. The SOAP binding just refused to fill in the body of the SessionHeader, no matter how many different permutations I tried of passing in the header (string, SessionHeader, SObject, etc.). So I just generated a little XML fragment myself. This is a terrible strategy, by the way -- the real solution is to fix soap4r.
SuperfellSuperfell
The must send concrete type means that the request generated by the ruby code is missing the xsi:type="Opportunity" attribute, that indicates the sojbect type.
wrexwrex
Thank you!

You've made my day. :-)
talanbtalanb
Any hints on getting openssl configured? I'm getting

at depth 1 - 20: unable to get local issuer certificate
/usr/lib/ruby/site_ruby/1.8/http-access2.rb:970:in `connect': certificate verify failed (OpenSSL::SSL::SSLError)

Thanks!

Todd Breiholz
undeesundees
Hi, Todd.

I've never seen that error -- I'm not familiar with it. Looks like it's happening down in the https libraries. The only thing I can think of is to post to Ruby-talk and see if they've heard of anything like this.

Anyway, stay tuned -- a SalesForce-specific Ruby binding is coming soon on RubyForge.org.

--undees
undeesundees
Thanks for the pointer -- the Ruby SOAP binding wasn't supplying the correct parameter. I ended up using the information you supplied to write a more lightweight binding specifically tailored to sforce.com.
wrexwrex
The author of soap4r has put up a nice little sample of how to set the session id
cleanly (without directly munging the xml).

I'm still experimenting, but hope to post some basic "from scratch" cookbook information
for using sforce with ruby soon.
wrexwrex
I got the same error until I installed http-access2 and installed
the ca.pem certificate and soap/property file as shown in Nakamura-san's sample

Then ssl worked fine for me.
wrexwrex
Okay, here are my

Notes on using sforce with ruby

First download the sforce API documentation from http://www.sforce.com/us/docs/sforce60/sforce_API.pdf

Next download latest version of http-access2 from http://raa.ruby-lang.org/project/http-access2.

Install http-access2 with:

    $ cd ~/work
    $ tar xzvf ~/downloads/http-access-2_0_5.tar.gz
    $ cd http-access_2_0_5
    $ sudo ruby install.rb

Download the latest version of soap4r from http://raa.ruby-lang.org/project/soap4r

Install soap4r with:

    $ cd ~/work
    $ tar xzvf ~/downloads/http-access-2_0_5.tar.gz
    $ cd http-access_2_0_5
    $ sudo ruby install.rb

Copy the wsdl2ruby.rb and xsd2ruby.rb scripts someplace useful:

    $ sudp cp bin/*.rb /usr/local/bin

Make a working directory for your project:

    $ mkdir ~/work/myproj

Download the enterprise wsdl from salesforce.com. I think you need to login to salesforce as an administrator (alternately get a developer login on the sforce site and download the partner wsdl file). After logging in, go to "Setup" (upper right hand corner) then to "Integrate" (left-hand navigation) then to "WSDL Generator". Save the resulting file as ~/work/myproj/enterprise.wsdl.xml.

Create the ruby bindings with:

    $ /usr/local/bin/wsdl2ruby.rb --type client --wsdl enterprise.wsdl.xml

This creates the following files:

    default.rb                  # Class definition file
    defaultDriver.rb            # Driver file (includes default.rb)
    SforceServiceClient.rb      # Sample client (includes defaultDriver.rb)

The sample client isn't terribly useful as is, because the sforce api requires you to change the default endpoint and rewrite the session id in the SOAP header before you can use the API.

I created a new driver file called "sforceDriver.rb" that contained the following:

    require 'defaultDriver.rb'
    require 'soap/header/simplehandler'

    class SessionHeaderHandler < SOAP::Header::SimpleHandler

      HeaderName = XSD::QName.new('urn:partner.soap.sforce.com', 'SessionHeader')

      attr_accessor :sessionid

      def initialize
        super(HeaderName)
        @sessionid = nil
      end

      def on_simple_outbound
        if @sessionid
          {'sessionId' => @sessionid}
        else
          nil       # no header
        end
      end
    end

    class CallOptionsHandler < SOAP::Header::SimpleHandler

      HeaderName = XSD::QName.new('urn:partner.soap.sforce.com', 'CallOptions')

      attr_accessor :client

      def initialize
        super(HeaderName)
        @client = nil
      end

      def on_simple_outbound
        if @client
          {'client' => @client}
        else
          nil       # no header
        end
      end
    end

Now you need to create a certificate file and tell soap to use it.

    $ mkdir soap
    $ cat > soap/property
    client.protocol.http.ssl_config.ca_file = ca.pem
    ^D
    $ cat > ca.pem
    -----BEGIN CERTIFICATE-----
    MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG
    A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
    cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
    MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
    BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
    YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
    ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
    BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
    I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
    CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do
    lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc
    AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k
    -----END CERTIFICATE-----
    -----BEGIN CERTIFICATE-----
    MIIDgzCCAuygAwIBAgIQJUuKhThCzONY+MXdriJupDANBgkqhkiG9w0BAQUFADBf
    MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsT
    LkNsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw
    HhcNOTcwNDE3MDAwMDAwWhcNMTExMDI0MjM1OTU5WjCBujEfMB0GA1UEChMWVmVy
    aVNpZ24gVHJ1c3QgTmV0d29yazEXMBUGA1UECxMOVmVyaVNpZ24sIEluYy4xMzAx
    BgNVBAsTKlZlcmlTaWduIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gQ2xhc3Mg
    MzFJMEcGA1UECxNAd3d3LnZlcmlzaWduLmNvbS9DUFMgSW5jb3JwLmJ5IFJlZi4g
    TElBQklMSVRZIExURC4oYyk5NyBWZXJpU2lnbjCBnzANBgkqhkiG9w0BAQEFAAOB
    jQAwgYkCgYEA2IKA6NYZAn0fhRg5JaJlK+G/1AXTvOY2O6rwTGxbtueqPHNFVbLx
    veqXQu2aNAoV1Klc9UAl3dkHwTKydWzEyruj/lYncUOqY/UwPpMo5frxCTvzt01O
    OfdcSVq4wR3Tsor+cDCVQsv+K1GLWjw6+SJPkLICp1OcTzTnqwSye28CAwEAAaOB
    4zCB4DAPBgNVHRMECDAGAQH/AgEAMEQGA1UdIAQ9MDswOQYLYIZIAYb4RQEHAQEw
    KjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL0NQUzA0BgNV
    HSUELTArBggrBgEFBQcDAQYIKwYBBQUHAwIGCWCGSAGG+EIEAQYKYIZIAYb4RQEI
    ATALBgNVHQ8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgEGMDEGA1UdHwQqMCgwJqAk
    oCKGIGh0dHA6Ly9jcmwudmVyaXNpZ24uY29tL3BjYTMuY3JsMA0GCSqGSIb3DQEB
    BQUAA4GBAAgB7ORolANC8XPxI6I63unx2sZUxCM+hurPajozq+qcBBQHNgYL+Yhv
    1RPuKSvD5HKNRO3RrCAJLeH24RkFOLA9D59/+J4C3IYChmFOJl9en5IeDCSk9dBw
    E88mw0M9SR2egi5SX7w+xmYpAY5Okiy8RnUDgqxz6dl+C2fvVFIa
    -----END CERTIFICATE-----
    ^D

Now create your client application file. The following sample generates a list of accounts for your enterprise:

    #!/usr/bin/ruby

    require 'pp'
    require 'sforceDriver.rb'

    sessionid_handler = SessionHeaderHandler.new
    calloptions_handler = CallOptionsHandler.new

    endpoint_url = ARGV.shift
    obj = Soap.new(endpoint_url)
    obj.headerhandler << sessionid_handler
    obj.headerhandler << calloptions_handler

    # Turn on wiredumps
    obj.wiredump_dev = STDERR

    # Dunno what this accomplishes
    calloptions_handler.client = 'client'

    parameters = Login.new('mylogin@mydomain.com', 'mypassword')
    login_result = obj.login(parameters).result

    sessionid_handler.sessionid = login_result.sessionId

    # Get accounts (defaults to no more than 2000 at a time, use querymore
    # to get the rest)

    my_query = XSD::XSDString.new("select Name, Id from Account")

    accounts = obj.query(my_query)

    #pp accounts
    accounts.result.records.each { |obj| puts "#{obj.id[0]}\t#{obj.name}" }

Running this program generates output like:

    $ chmod +x client.rb
    $ ./client.rb
    00130000000wgugAAA      Spacely Sprockets
    00130000000wguhAAA      Cogswell Cogs
    ...

I was using "https://na1-api.salesforce.com/services/Soap/c/6.0" as my endpoint_url.

The API says to use the endpoint URL that is returned after the call to login using the default url. This was the na1-api URL above. Unfortunately, however, there doesn't appear to be any way to change the endpoint URL after calling Soap.new (at least none that I can figure out).

Note, too, that it seems you can't call query with a simple string. This was a surprise to me.

I'm also not sure what the calloptions stuff is for, either, but it was in Nakamura-san's example so I left it.

I'm rapidly becoming allergic to SOAP. All these extraneous calls to a "result" method seems overly verbose to put it mildly. The last line in client.rb just cries for yet another wrapper method -- it would be nice if wsdl2ruby.rb just did all this for you. It feels like the "2ruby" part is only halfway there -- I want to use RUBY strings as parameters to query() and I want to have accessors that return normal RUBY objects.

To add insult to injury, you can't even run the debugger on the client program above, even though it runs fine outside of the debugger:

    $ ruby -r debug client.rb
    Debug.rb
    Emacs support available.

    client.rb:3:require 'pp'
    (rdb:1) b 33
    Set breakpoint 1 at client.rb:33
    (rdb:1) c
    Wire dump:

    = Request

    ! CONNECTION ESTABLISHED


    = Response



    Wire dump:

    = Request



    = Response



    /usr/lib/ruby/1.8/soap/rpc/proxy.rb:156: `org.xml.sax.SAXException:
    SimpleDeserializer encountered a child element, which is NOT expected,
    in something it was trying to deserialize.' (SOAP::FaultError)
            from /usr/lib/ruby/1.8/soap/rpc/driver.rb:177:in `call'
            from /usr/lib/ruby/1.8/soap/rpc/driver.rb:231:in `query'
            from /usr/lib/ruby/1.8/soap/rpc/driver.rb:226:in `query'
            from client.rb:30
    /usr/lib/ruby/1.8/soap/rpc/proxy.rb:156:      raise
    SOAP::FaultError.new(body.fault)

Bah!

wrexwrex
Whoops. Forgot I used the enterprise urn, not the partner one from Nakamura-san's sample. Here are my corrected

Notes on using sforce with ruby and soap4r

First download the sforce API documentation from http://www.sforce.com/us/docs/sforce60/sforce_API.pdf

Next download latest version of http-access2 from http://raa.ruby-lang.org/project/http-access2.

Install http-access2 with:

    $ cd ~/work
    $ tar xzvf ~/downloads/http-access-2_0_5.tar.gz
    $ cd http-access_2_0_5
    $ sudo ruby install.rb

Download the latest version of soap4r from http://raa.ruby-lang.org/project/soap4r.

Install soap4r with:

    $ cd ~/work
    $ tar xzvf ~/downloads/http-access-2_0_5.tar.gz
    $ cd http-access_2_0_5
    $ sudo ruby install.rb

Copy the wsdl2ruby.rb and xsd2ruby.rb scripts someplace useful:

    $ sudp cp bin/*.rb /usr/local/bin

Make a working directory for your project:

    $ mkdir ~/work/myproj

Download the enterprise wsdl from salesforce.com. I think you need to login to salesforce as an administrator (alternately get a developer login on the sforce site and download the partner wsdl file). After logging in, go to "Setup" (upper right hand corner) then to "Integrate" (left-hand navigation) then to "WSDL Generator". Save the resulting file as ~/work/myproj/enterprise.wsdl.xml.

Create the ruby bindings with:

    $ /usr/local/bin/wsdl2ruby.rb --type client --wsdl enterprise.wsdl.xml

This creates the following files:

    default.rb                  # Class definition file
    defaultDriver.rb            # Driver file (includes default.rb)
    SforceServiceClient.rb      # Sample client (includes defaultDriver.rb)

The sample client isn't terribly useful as is, because the sforce api requires you to change the default endpoint and rewrite the session id in the SOAP header before you can use the API.

I created a new driver file called "sforceDriver.rb" that contained the following:

    require 'defaultDriver.rb'
    require 'soap/header/simplehandler'

    class SessionHeaderHandler < SOAP::Header::SimpleHandler

      HeaderName = XSD::QName.new('urn:enterprise.soap.sforce.com', 'SessionHeader')

      attr_accessor :sessionid

      def initialize
        super(HeaderName)
        @sessionid = nil
      end

      def on_simple_outbound
        if @sessionid
          {'sessionId' => @sessionid}
        else
          nil       # no header
        end
      end
    end

    class CallOptionsHandler < SOAP::Header::SimpleHandler

      HeaderName = XSD::QName.new('urn:enterprise.soap.sforce.com', 'CallOptions')

      attr_accessor :client

      def initialize
        super(HeaderName)
        @client = nil
      end

      def on_simple_outbound
        if @client
          {'client' => @client}
        else
          nil       # no header
        end
      end
    end

Now you need to create a certificate file and tell soap to use it.

    $ mkdir soap
    $ cat > soap/property
    client.protocol.http.ssl_config.ca_file = ca.pem
    ^D
    $ cat > ca.pem
    -----BEGIN CERTIFICATE-----
    MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG
    A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
    cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
    MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
    BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
    YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
    ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
    BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
    I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
    CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do
    lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc
    AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k
    -----END CERTIFICATE-----
    -----BEGIN CERTIFICATE-----
    MIIDgzCCAuygAwIBAgIQJUuKhThCzONY+MXdriJupDANBgkqhkiG9w0BAQUFADBf
    MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsT
    LkNsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw
    HhcNOTcwNDE3MDAwMDAwWhcNMTExMDI0MjM1OTU5WjCBujEfMB0GA1UEChMWVmVy
    aVNpZ24gVHJ1c3QgTmV0d29yazEXMBUGA1UECxMOVmVyaVNpZ24sIEluYy4xMzAx
    BgNVBAsTKlZlcmlTaWduIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gQ2xhc3Mg
    MzFJMEcGA1UECxNAd3d3LnZlcmlzaWduLmNvbS9DUFMgSW5jb3JwLmJ5IFJlZi4g
    TElBQklMSVRZIExURC4oYyk5NyBWZXJpU2lnbjCBnzANBgkqhkiG9w0BAQEFAAOB
    jQAwgYkCgYEA2IKA6NYZAn0fhRg5JaJlK+G/1AXTvOY2O6rwTGxbtueqPHNFVbLx
    veqXQu2aNAoV1Klc9UAl3dkHwTKydWzEyruj/lYncUOqY/UwPpMo5frxCTvzt01O
    OfdcSVq4wR3Tsor+cDCVQsv+K1GLWjw6+SJPkLICp1OcTzTnqwSye28CAwEAAaOB
    4zCB4DAPBgNVHRMECDAGAQH/AgEAMEQGA1UdIAQ9MDswOQYLYIZIAYb4RQEHAQEw
    KjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL0NQUzA0BgNV
    HSUELTArBggrBgEFBQcDAQYIKwYBBQUHAwIGCWCGSAGG+EIEAQYKYIZIAYb4RQEI
    ATALBgNVHQ8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgEGMDEGA1UdHwQqMCgwJqAk
    oCKGIGh0dHA6Ly9jcmwudmVyaXNpZ24uY29tL3BjYTMuY3JsMA0GCSqGSIb3DQEB
    BQUAA4GBAAgB7ORolANC8XPxI6I63unx2sZUxCM+hurPajozq+qcBBQHNgYL+Yhv
    1RPuKSvD5HKNRO3RrCAJLeH24RkFOLA9D59/+J4C3IYChmFOJl9en5IeDCSk9dBw
    E88mw0M9SR2egi5SX7w+xmYpAY5Okiy8RnUDgqxz6dl+C2fvVFIa
    -----END CERTIFICATE-----
    ^D

Now create your client application file. The following sample generates a list of accounts for your enterprise:

    #!/usr/bin/ruby

    require 'pp'
    require 'sforceDriver.rb'

    sessionid_handler = SessionHeaderHandler.new
    calloptions_handler = CallOptionsHandler.new

    endpoint_url = ARGV.shift
    obj = Soap.new(endpoint_url)
    obj.headerhandler << sessionid_handler
    obj.headerhandler << calloptions_handler

    # Turn on wiredumps
    obj.wiredump_dev = STDERR

    # Dunno what this accomplishes
    calloptions_handler.client = 'client'

    parameters = Login.new('mylogin@mydomain.com', 'mypassword')
    login_result = obj.login(parameters).result

    sessionid_handler.sessionid = login_result.sessionId

    # Get accounts (defaults to no more than 2000 at a time, use querymore
    # to get the rest)

    my_query = XSD::XSDString.new("select Name, Id from Account")

    accounts = obj.query(my_query)

    #pp accounts
    accounts.result.records.each { |obj| puts "#{obj.id[0]}\t#{obj.name}" }

Running this program generates output like:

    $ chmod +x client.rb
    $ ./client.rb
    00130000000wgugAAA      Spacely Sprockets
    00130000000wguhAAA      Cogswell Cogs
    ...

I was using "https://na1-api.salesforce.com/services/Soap/c/6.0" as my endpoint_url.

The API says to use the endpoint URL that is returned after the call to login using the default url. This was the na1-api URL above. Unfortunately, however, there doesn't appear to be any way to change the endpoint URL after calling Soap.new (at least none that I can figure out).

Note, too, that it seems you can't call query with a simple string. This was a surprise to me.

I'm also not sure what the calloptions stuff is for, either, but it was in Nakamura-san's example so I left it.

I'm rapidly becoming allergic to SOAP. All these extraneous calls to a "result" method seems overly verbose to put it mildly. The last line in client.rb just cries for yet another wrapper method -- it would be nice if wsdl2ruby.rb just did all this for you. It feels like the "2ruby" part is only halfway there -- I want to use RUBY strings as parameters to query() and I want to have accessors that return normal RUBY objects.

To add insult to injury, you can't even run the debugger on the client program above, even though it runs fine outside of the debugger:

    $ ruby -r debug client.rb
    Debug.rb
    Emacs support available.

    client.rb:3:require 'pp'
    (rdb:1) b 33
    Set breakpoint 1 at client.rb:33
    (rdb:1) c
    Wire dump:

    = Request

    ! CONNECTION ESTABLISHED


    = Response



    Wire dump:

    = Request



    = Response



    /usr/lib/ruby/1.8/soap/rpc/proxy.rb:156: `org.xml.sax.SAXException:
    SimpleDeserializer encountered a child element, which is NOT expected,
    in something it was trying to deserialize.' (SOAP::FaultError)
            from /usr/lib/ruby/1.8/soap/rpc/driver.rb:177:in `call'
            from /usr/lib/ruby/1.8/soap/rpc/driver.rb:231:in `query'
            from /usr/lib/ruby/1.8/soap/rpc/driver.rb:226:in `query'
            from client.rb:30
    /usr/lib/ruby/1.8/soap/rpc/proxy.rb:156:      raise
    SOAP::FaultError.new(body.fault)

Bah!

undeesundees
Fantastic job, wrex. The session ID stuff in the headers is a much nicer touch than my original rough cut.

Have you been able to create / update objects yet?
wrexwrex

undees wrote:
Fantastic job, wrex. The session ID stuff in the headers is a much nicer touch than my original rough cut.

Have you been able to create / update objects yet?



Heh. Thanks, but I didn't do anything smart -- I just pieced together all the bits of information that were strewn about here (from you!) and on the soap4r site by NaHi (Nakamura-san). I thought it would be useful to put together a single document (to prevent anyone else from having to go through the same amount of pain for the same "baby step" result).

I called it a night after getting a simple query working, but I'll try to get an update working today.

Anyway, I think I'll join your rubyforge project and communicate further there.
wrexwrex
I'm stumped trying to create objects in sforce with soap4r. I think the problem is a bug in soap4r (no support for extensions in complexContent containers). Hopefully NaHi will fix this in soap4r 1.5.5. Other notes in case anyone is actually following this saga. In my previous instructions,

    my_query = XSD::XSDString.new("select Name, Id from Account")
would be better written as:

    my_query = Query.new("select Name, Id from Account")
Also, except when testing, you should probably use the default endpoint for the initial login and then use the endpoint_url= method to change it later. I'm now using a class like the following to connect to sforce:

class RForce

  attr_reader :soap

  def initialize(user, password, debug=false, endpoint=nil)

    sessionid_handler = SessionHeaderHandler.new
    calloptions_handler = CallOptionsHandler.new

    @soap = Soap.new(endpoint)

    @soap.headerhandler << sessionid_handler
    @soap.headerhandler << calloptions_handler
    @soap.wiredump_dev = STDERR if debug

    params = Login.new(user, password)
    login_result = @soap.login(params).result

    sessionid_handler.sessionid = login_result.sessionId
    calloptions_handler.client = 'client'

    # reset the endpoint URL unless it is supplied explicitly
    @soap.endpoint_url = login_result.serverUrl unless endpoint

  end
end
Call it like

drv = RForce.new('mylogin@mydomain', 'mypasswd')
or, if you want to explicitly set the endpoint and view the over-the-wire transfer:

drv = RForce.new('mylogin@mydomain', 'mypasswd', true, "http://na1-api.salesforce.com/services/Soap/c/6.0")
I still can't write anything, but at least I can query all of my objects in salesforce.
wrexwrex
Ian, NaHi, and I are getting closer to getting the soap4r bindings to work with sforce, but creates are still eluding us.

Our latest wire trace follows -- we're still getting the INVALID_TYPE "Must send a concrete entity type" error. I must be missing something, but the type stuff in the wiretrace looks pretty much identical to the sample at http://www.sforce.com/us/resources/soap/sforce60/sforce_API_messages_create.html

Where are we messing up?


= Request

! CONNECTION ESTABLISHED
POST /services/Soap/c/6.0 HTTP/1.1
SOAPAction: ""
Content-Type: text/xml; charset=utf-8
User-Agent: SOAP4R/1.5.5 (/92, ruby 1.8.2 (2004-12-25) [powerpc-darwin8.0])
Date: Fri Sep 23 20:54:42 PDT 2005
Content-Length: 941
Host: na1-api.salesforce.com

<?xml version="1.0" encoding="utf-8" ?>
<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<env:Header>
<SessionHeader env:mustUnderstand="0"
xmlns="urn:partner.soap.sforce.com">
<sessionId>4r1Oy_foo_bar_baz_blat=</sessionId>
</SessionHeader>
<CallOptions env:mustUnderstand="0"
xmlns="urn:partner.soap.sforce.com">
<client>client</client>
</CallOptions>
</env:Header>
<env:Body>
<create xmlns="urn:partner.soap.sforce.com">
<sObjects>
<type xmlns="urn:sobject.partner.soap.sforce.com">Contact</type>
<any>
<LastName>Spaceley</LastName>
</any>
</sObjects>
</create>
</env:Body>
</env:Envelope>

= Response

HTTP/1.1 500 Internal Server Error
Server: sfdc
Cache-Control: private
Content-Type: text/xml; charset=utf-8
Connection: close
Transfer-Encoding: chunked
Date: Sat, 24 Sep 2005 03:55:15 GMT

0303
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<soapenv:Fault>
<faultcode>soapenv:Server</faultcode>
<faultstring>common.exception.ApiException: Must send a concrete entity type.</faultstring>
<detail>
<sf:fault xsi:type="sf:InvalidSObjectFault" xmlns:sf="urn:fault.enterprise.soap.sforce.com">
<sf:exceptionCode>INVALID_TYPE</sf:exceptionCode>
<sf:exceptionMessage>Must send a concrete entity type.</sf:exceptionMessage>
<sf:row>-1</sf:row>
<sf:column>-1</sf:column>
</sf:fault>
</detail>
</soapenv:Fault>
</soapenv:Body>
</soapenv:Envelope>
SuperfellSuperfell
You're sending a partner formatted message to the enterprise API endpoint, you need to change the URL, check the URL in the partner WSDL.
wrexwrex
Aaargh. Foolish me.

Immediate and perfect response as usual. Don't you ever sleep?!!

Many thanks!
portrmanportrman
I'm using Ruby 1.8.2 with all the other items installed as listed above. I had to do a bit more work to get this going.

The librarys that come with 1.8.2 don't detect the characterset that SF sends it's soap messages in. I do not know if this is fixed in the current 1.8.4 release, but just put this near the top of your sforceDriver.rb file.
XSD::Charset.encoding = 'UTF8'

Also, had to apply a fix to the soap4r library:
http://dev.ctor.org/soap4r/ticket/152

And while most might get this(I'm extremely new to ruby), the ca.pem file does NOT go into the soap directory.

I believe that is all I did to get things working and now all QUERYS well.
portrmanportrman
I looks like the bug you mentioned (http://dev.ctor.org/soap4r/ticket/72) is now fixed but targeted for 1.5.6 which isn't out yet. Does anyone know what needs to be changed in the 1.5.5 release to make create/update work?

I can't do:

myAccount = Account.new
myAccount.Name = "My Company Name"

because .Name doens't exist. And I can't pass anything to .new either.

Any suggestions or weird hacks are acceptable. Once the ActiveSalesforce project has a demo I'll probably move to that, but in the mean time...

Thanks!

Message Edited by portrman on 02-07-2006 03:33 PM

dchasmandchasman

ActiveSalesforce (ASF) is up and ready to to try out (I am about to move the project into Beta). You do not need to use all of Rails (its very cool though) to take advantage of ASF as a powerful interface into salesforce. We are in the process of developing some demos - more for marketing than for developer education at this point because if you know Rails you know most of what you need to know to use ASF. We will also have live demos up and running from the ASF hosted site at activesalesforce.textdriven.com.

The new home page just went up over the weekend (http://activesalesforce.textdriven.com/) that includes the simple instructions to get you up and running quickly. ASF is gem installable - in fact you can start with a naked Ruby w/ gem box and with just:

gem install activesalesforce --include-dependencies

you will get everything you need (including rails itself).

I would love to get more info/details from you on what type of information you need to be successful using ASF!

BTW - Initially during development of ActiveSalesforce I ran into all of the same issues with soap4r - and different sets of issues from one release of Ruby to the next. So much so that I ended up trying out and loving rforce (see http://rubyforge.org/projects/rforce/) and have added some mods to rforce (still to be committed). In a nice bit of symmetry the author of rforce is one of the early adopters of ASF.

vanpeltvanpelt
I'm extremly excited to see active development on a solution to simplify the pain of Salesforce in Rails. I tryed SOAP4r for a while and was completly depressed... I went to rforce, and was much happier, but still had some problems. I can't wait to try activesalesforce. On that note when can we expect some example code? I can't find any examples of using the extension.

Chris
portrmanportrman
Hi dchaasman,

The biggest thing I need is a working example RoR application. I started using RoR and Ruby for the first time last week, so I don't even know how to use ActiveSalesforce in RoR. My experience with the SF api is in .NET so I have an idea of what examples I would be interested in.

Creating/Updating/Deleting Accounts, Contacts, etc
Any special handling of DateTime's ? .NET doesn't allow DateTime to be nullable so there was a _cSpecified property for those.

Querying .. Ruby way of using queryMore(). Apparently C# benefits from being asynchronous when using queryMore.

My needs are actually very basic at the moment, however so are my Ruby and Rails skills.

One other note. I'm using the SF 7.0 api. But I don't think this is a problem as I didn't see anything in your setup example about providing a wsdl file.

I've very excited about ActiveSalesforce as well. Hopefully SF/sforce will add a section for Ruby soon.
dchasmandchasman
No example code has been posted yet (I do plan to change that very soon) because using ASF is as close to using any other backend (e.g. mysql) - easier in fact because releationships are automatically added to your ActiveRecord::Base subclasses and if your model refers to something that does not already have an AR model one is created dynamically for the duration of your session.

The development process is essentially indistinguishable from building a rails app using any other backend (some optional features like Transactions are not supported because the sforce api does not support transactions).

Basically:

- use:

rails my_app

to generate the skelton for development of my_app (feel free to use the name 'depot' if you are following the sample app in Agile Development Using Rails)

- follow the directions on http://activesalesforce.textdriven.com/ on how to configure (involves modifying my_app/config/environment.rb and my_app/config/database.yml)

that's the end of the ASF specific bits. Then

- use:

ruby scripts/generate scaffold Account

to generate the scaffolding for your salesforce.com Account object

- ruby scripts/server

point you browser at http://localhost:3000/accounts and you should see the list of accounts for your org.

edit, show and create should all be fully functional also.
dchasmandchasman
please see my response to vanpelt for some of my response. I do plan to post an example very soon to showcase things like SID-based authentication support, how to build an scontrol backend using RoR and ASF, etc).

The rails approach to things is quite a bit different (and very pleasant) from the classic
jdbc/soap/odbc way of doing things. For example, you do not deal with wdsl to work with salesforce at all - in fact for the most part you can do quite a bit w/out ever seeing any SOQL and you will definitely not interact with soap bits like wdsl. All metadata is dynamically handled (things like describeSObject do happen under the covers but that is part of ASF). Fields are automatically added at runtime to your model (ActiveRecord::Base subclasses). Relationships are also automatically added to your model (this is an enhancement over most RoR connectors that typically stop at exposing fields automatically).

http://www.pragmaticprogrammer.com/titles/rails/index.html will get you started fast (in just a couple of chapters).

I have asked to have a new board just for Ruby & RoR created asap - we do need our own space.

Nothing special should be needed for various data types like datetimes (if you run into any please log a bug at http://rubyforge.org/tracker/?group_id=1201).
portrmanportrman
I got everything working and it is awsome, I can't say enough. Getting VS.NET to work was easy but this is even easier. I'm able to do everything with the objects without problems.

Now comes the next step. I realize most of problems come from a lack of knowledge about RoR but I'm working, not just leaching for answers, promise :)

So far I've been working in a seperate RoR application. Now I need to take what I've learned and integrate SF integration into our app that is written using Rails. Because Rails really likes it's naming conventions, I can't use just "Contact" or "Account" like SF does because our web app already uses those names. So instead I created a Sfcontact (note: ruby wanted that casing, personally I prefer SFContact). Here are the steps I ended up using.

Add SF database connection under "development:" in database.yml
Removed the connection to my MySQL db.
ruby script/generate contact (it connects to SF (because I did the switch in the database.yml file) and generates the controller, helper, views, etc)
Added mysql db connection info back
Placed SF connection under "sf_development" and "sf_production"
Repaced "Contact" with "Sfcontact" everywhere required by Rails
Add set_table_name "contact" to model
Add establish_connection "sf_development" to model

When I try /sfcontact/list I get a "uninitialized constant Contact" from dependencies:200 when trying to load contact.rb. My guess is, is that Rails is trying to include the model "contact.rb" so it can create a new instance of that model.

So what am I missing? I will need to do this for several of the others like "Account" and then would like to maintain the naming convention of SfAccount, SfAsset, Sfcontact if possible.

Thanks. This really is awsome work!
matt.homatt.ho
This is great stuff!  I had some issues running ASF outside of rails and thought I would post the workaround.  The main issue is that ASF depends on some of the functionality provided by rails.  To use ASF outside rails, we have to stub in that functionality.  Here's a tiny example that uses ASF to dump out the contents of the leads table.

require 'rubygems'
require_gem 'activesalesforce'

# begin stubbed in functionality
class Logger
    def initialize
    end

    def debug message
        puts message
    end
end

module ActiveRecord
    Base.logger = Logger.new
    module ConnectionAdapters
        class SalesforceAdapter
            def log sql, name
                yield sql, name
            end
        end
    end
end
# end stubbed in functionality

# now for the real script
ActiveRecord::Base.establish_connection(
  :adapter  => "activesalesforce",
  :username => "INSERT-YOUR-USERNAME-HERE",
  :password => "INSERT-YOUR-PASSWORD-HERE"
)

# Define the class we want to introspect from.  Ruby does a
# tremendous amount of introspection so we're relieved of the
# burden of creating setters and getters.
class Lead < ActiveRecord::Base
end

# Here's a very simple query to get all our leads from Salesforce
all_my_leads = Lead.find(:all)

# Loop through each of my leads and ask it for the contents so i
# can see what's inside.
all_my_leads.each do |lead|
    puts lead.inspect
end


Message Edited by matt.ho on 03-12-2006 05:08 PM

Message Edited by matt.ho on 03-12-2006 05:09 PM

dchasmandchasman
Interesting Matt - but why don't you just use only the parts of Rails you need (activerecord and activesupport)? As we develop more functionality in ASF it will become more and more reliant on Rails bits and it seems like you will be forever adjusting to keep pace. I am mostly curious about what it is about the problems you are trying to solve that have lead you to this approach (maybe we can help you solve them in a different way that would be easier to support?) - thx Doug
matt.homatt.ho
There's a lot to be said for maintaining ASF's ability to run as a pure ActiveRecord.  Already, ASF makes it extremely easy to write simple administrative scripts that don't have to be deployed.  We're currently using it as part of our watir unit tests (http://wtr.rubyforge.org/) to verify the state of Salesforce after our tests have run.   I'd love to see the ASF ActiveRecord adapter go the path of the mysql and postgresql ActiveRecord adapters.  Both are seamlessly integrated into rails and yet can be just as easily invoked from outside rails.