Rails + SugarCRM – An alternative approach

After slogging through connecting Rails and Sugar via SOAP, I was tired and frustrated. The API is slow, and doing anything meaningful took a long time (ok, it took 30 seconds, but that seems slow to me; aren’t computers supposed to be fast?!). So, I came up with an alternative approach.

I know that Active Record (or whatever it’s called) in Rails is really just a fancy wrapper for the database. So, I created a second database connection, directly to the Sugar database!

You just have to setup new models and controllers in Rails. But, that’s not too hard. See this solution (http://pragdave.pragprog.com/pragdave/2006/01/sharing_externa.html). Use option 3. It worked well. Once you get the mapping figured out, you can even use the join tables, relationships, etc. It all works transparently.

I use this to synchronize various objects and fields.

Of course, you have to have access to the database. And, of course, I know I’m majorly in danger of screwing something up. And you have to figure out the inner workings of the Sugar database. And, of course, this might not work on the next version, etc., etc.

But, heck it is fast! And very easy to use!

If you want to know more, just let me know.

Rails + SugarCRM + SOAP – Get a list of Sugar accounts into Rails as an array

I have long wanted to make sure my CRM system (SugarCRM) and my project management system synchonized certain data, mainly company names. I hate having to sync stuff like that manually. So, I’ve been working on integrating the data using a SOAP client on the Rails side. It took all day to get this working. I don’t know why this took forever to find out, or why there aren’t many good references on the Web. (Maybe I’m just dense!)

I had to patch together a bunch of code, re-code some PHP examples, and do quite a bit of experimenting, to get this all working. Here is a list of some of the sites I used:

http://www.sugarcrm.com/forums/showthread.php?t=17954&highlight=get_entry_list
Good PHP examples about how certain SOAP calls are structured.
http://www.ruby-doc.org/stdlib/libdoc/soap/rdoc/index.html
Basics on how the SOAP objects work. Nothing really useful here, but it helped a little.
http://www.beanizer.org/site/content/view/2/29/lang,en/
Another good PHP reference.
http://kousenit.wordpress.com/2006/08/08/ruby-web-service-clients/
Has some general SOAP info, but not on SugarCRM
http://www.sugarcrm.com/wiki/index.php?title=SOAP_in_RUBY
Sugar’s own starter directions. This is the backbone of the code below.
http://www.sugarcrm.com/wiki/index.php?title=SOAP_Documentation
Sugar’s SOAP documentation. I have to admit that this doesn’t make a lot of sense to me.
http://martyhaught.com/articles/2006/08/04/mapping-soap-response-without-wsdl/
Good example of how confusing this can be. Note that Marty’s situation is exactly where I got stuck. It turns out that these “special” attributes in the SOAP Mapping Object response (__xmlattr, __xmele, etc.) are not really important to the programmer. You can access the data with special attributes that refer to the attributes returned from the server. At least that’s my theory. So, in my example below, you access the results you want using the “result.entry_list” attribute, because the “get_entry_list” command returns that variable. Note that when you examine the results object, you don’t see these special attributes. I’m sure this is some special Ruby thing that I don’t understand.

Anyway, here’s the code for getting a list of all companies, called “Accounts” in SugarCRM, into Rails in an array.

 def list_accounts
    require 'soap/wsdlDriver'
    require 'digest/md5'
    u = "username"
    p = Digest::MD5.hexdigest("password")
    ua = {"user_name" => u,"password" => p}
    wsdl = "http://your-sugar-web-site.com/soap.php?wsdl"

    #create soap
    s = SOAP::WSDLDriverFactory.new(wsdl).create_rpc_driver

    #uncomment this line for debugging. saves xml packets to files
    #s.wiredump_file_base = "soapresult"

    #create session
    ss = s.login(ua,nil)

    #check for login errors
    if ss.error.number.to_i != 0 

    	#status message
    	logger.debug "failed to login - #{ss.error.description}"

    	#exit program
    	exit

    else

    	#get id
    	sid = ss['id']

    	#get current user id
    	uid = s.get_user_id(sid)

    	#status message
    	logger.debug "logged in to session #{sid} as #{u} (#{uid})"

         #the part below is general. you can use it to get any type of data you want. just change the "module_name"

         module_name = "Accounts"

         query = "" # gets all the acounts, you can also use SQL like "accounts.name like '%company%'"
         order_by = "" # in default order. you can also use SQL like "accounts.name"
         offset = 0 # I guess this is like the SQL offset
         select_fields = ['name','industry'] # this can't be an empty array, my testing showed
         max_results = "1000000" # if set to 0 or "", this doesn't return all the results, like you'd expect
         deleted = 0 # whether you want to retrieve deleted records, too

         result = s.get_entry_list(sid,module_name,query,order_by,offset,select_fields,max_results,deleted)

         #below is where we build the array of names. note that everything gets returns in a name-value pairs hash (name_value_list), using the field list from the request

         @output = []
         for entry in result.entry_list
            item = {}
            for name_value in entry.name_value_list
              item[name_value.name]=name_value.value
            end
           @output << item
         end
        
    	#logout
    	s.logout(sid)

    	#status message
    	logger.debug "logged out"
    	    	
    end

Place Custom Rails Routes First

This is another one that should have been obvious. But, I was getting it wrong. Maybe it had something to do with upgrading to Rails 1.2.6.

Anyway, I was getting an error with a custom action. I was sending a form to “projects/do_something”. But, I kept getting the error of “Can’t find project with ID=do_something”.

This was happening because Rails thought it was supposed to be looking in the route for the show action. But, checking the routes file, I could see that I had defined my custom route. What, what was up?

It turns out, the custom route has to come before the standard “map.resources :projects” line.

Once I fixed that, all is well again. (I still think this acted differently in 1.2.3. Hmm).

Ruby on Rails: Using a different controller with in_place_editor_field

I don’t know why this took a while to figure out, but it did. If you are using the stock Rails in_place_editor_field, you know it looks like this in the controller:

in_place_edit_for :user, :name

And like this in the view:

<%= in_place_editor_field :user, :name %>

This works fine so long as you’re rendering from the users controller. But, what if this view is a partial inside a different controller’s view? In that case, what gets called is not “/users/set_user_name” but “/othercontroller/set_user_name”. And, of course, it fails because there is no method (dynamic or otherwise) like that there.

The solution is easy, but the documentation isn’t helpful. You need to alter the view to be like this:

<%= in_place_editor_field :user, :name, {}, :url=>{:controller=>'users', :action=>'set_user_name', :id=>user.id}} %>

Rails: Parsing iCal calendar on a password-protected WebDAV server

It took me a little while, but I finally got this working. You’ll need the iCalendar plugin.

require 'icalendar' 
def view_ical
    request = Net::HTTP::Get.new('/calendars/calendar.ics') 
   response = Net::HTTP.start('webdav.site.com') {|http| 
     request.basic_auth 'username', 'password' 
     response = http.request(request) 
   } 
   calendar_text = response.body
   calendars = Icalendar.parse(calendar_text) 
   calendar = calendars.first
end

Nested resources in Ruby on Rails: why bother?

Nested resources in Ruby on Rails are sort of neat, but they are a pain to implement. What’s more, I have to ask myself, why bother?

If a resource has a unique identifier id, then why would you need to call its parent resource to call it? The unique identifier is enough. And what resource doesn’t have a unique id these days?

Ruby on Rails Calendar Helper doesn’t quite work for me

Geoffrey Grosenbach’s Ruby on Rails plugin calendar_helper is simple and easy to use. Maybe I’m just picky, but one part of it just wasn’t working right for me.

Originally, it looks like this on line 96:

cal << %(<caption class="#{options[:month_name_class]}"></caption><thead><tr><th colspan="7">#{Date::MONTHNAMES[options[:month]]}</th></tr><tr class="#{options[:day_name_class]}">)

It doesn’t really make sense for the month name to be in a TH tag and the caption to be empty. So, I put the month name in the caption and eliminated the extra table row. In the end I changed it to this:

    cal << %(<caption class="#{options[:month_name_class]}">#{Date::MONTHNAMES[options[:month]]}</caption><thead><tr>)

This works well for me. If I knew anything about how to contribute to an open source project, I might propose a patch.

Getting a remote form to submit within a partial collection when any select changes in Ruby on Rails

This one is harder than it seems. But, I figured out a way. The trick and breakthrough came from Teflon Ted.

With a regular form, you could do this in your select statement:

:onChange=>"this.form.submit();"

This won’t work with a remote form, because the submission is not handled with the submit method but rather within the JavaScript callback in onsubmit. So, with a remote form, you have to change it to this:

:onChange=>"this.form.onsubmit();"

So, here is my code.

<%- remote_form_for
  :user,
  user,
  :url=>{:action=>'update_remote', :id=>user.id},
  :html=>{:id=>'form_'+user.id.to_s},
  :loading=>"Element.show('spinner_"+user.id.to_s+"'); Form.disable('form_"+user.id.to_s+"')"
  do |f|
-%>

<%= f.select
  :project_id,
  Project.find(:all).collect{|p| [p.name,p.id]},
  {:include_blank=>false, :selected=>user.project_id},
  {:onChange=>"this.form.onsubmit();", :id=>'user_project_id_'+user.id.to_s}
%>

<%- end -%>


Note that this is in a partial that gets iterated over a collection. So, I have to give everything a unique id in the HTML. Also, note that this includes the user of a spinner to show activity is taking place. I also like to disable the form temporarily to make sure nothing else gets selected. I don’t have to hide the spinner or reactivate the form because when the table row gets regenerated via RJS, it goes back to the default condition.

In_place_editor with a collection in a partial in Ruby on Rails

It seems like it would take a lot of work to get the in_place_editor to work in a partial on a collection, but it does. (It took me a lot of time to figure this out, but maybe I’m just more than average dense.) The best post to-date on this is at we eat bricks.

Just add the usual in the controller (user_controller.rb):

in_place_edit_for :user, :name

And, of course, the method:

def edit
  @users = User.find(:all)
end

Then, in the main view (edit.rhtml):

<%= render :partial=>'user', :collection=>@users %>

Then, in the partial (_users.rhtml):

<%= in_place_editor_field :model, :column %>

At first, this won’t work. You will get an error that says “Called id for nil, which would mistakenly be 4 — if you really wanted the id of nil, use object_id” on the line containing the “in_place_editor_field”.

It turns out this is a bug, and there is a workaround. Just add this at the top of your partial:

<%- @user = user -%>

I hope this helps someone save some time.

What to use for old permalink structure when implementing Dean’s Permalinks Migration Plugin for WordPress

If you want to move your WordPress permalinks via 301, you can try to do it by hand using rewrites in your .htaccess file. Much easier is Dean’s Permalinks Migration Plugin for WordPress.

On my work blog (http://www.synaxisworks.com/blog/), I used to use the default “?p=post-number” format, and I wanted to change to a more SEO-friendly format. I setup Dean’s plugin. The first problem is that I didn’t know what to put for the “old permalink structure”. I tried a million combinations of text, regex, and WordPress codes to try to get it to match the default format.

It turns out, according to this post, that the default permalinks never redirect and always work. So, the upshot is that I can’t redirect the default links and that I don’t really need Dean’s plugin at all. At least my new permalinks will be recorded in Google with the new format.