www.flickr.com
Michael Kovacs' photos More of Michael Kovacs' photos
Recommend Me Cable Car Software logo

Thursday, December 03, 2009

Rails realities part 29 - Salesforce and rails integration, the easy way

In honor of Dreamforce and RubyConf both being in San Francisco at the same time a couple of weeks ago today's post is about making the two of them work together.
If you do ever need to integrate Salesforce.com with your rails app there's really one game in town, (well really two but one depends on the other, you'll see shortly) and while it works great once you get things working I think the documentation around the process could use a little help so today we're going to have a little tutorial and go through the process of integrating Salesforce into an existing rails application.

So first off the assumptions:

  1. You have a rails 2.3.x app (I'm sure this works with older 2.x versions but for my app I'm running 2.3.4 to be exact)

  2. You'd like to integrate with Salesforce but the entirety of your app is not driven by Salesforce.


What are your options? Well there are currently two projects that seem to be the default and have the most momentum for connecting to SF:
  1. rForce - A simple 'low level" API access library to get at your SF data.

  2. ActiveSalesforce - An ActiveRecord extension that allows you to interact with your SF data using model objects. It uses rForce to make the API calls.


rForce is really simple to use and makes the assumption that you are familiar with the SF API. There's a great tutorial here on how to get that up and running:

http://oldfartdeveloper.blogspot.com/2009/09/rforcesalesforce-setup-for-dummies.html

That's the article that got me "unstuck" trying to figure out my way through the world of Salesforce integration. If you'd like to get up and running with rForce have a look at that tutorial. If you're interested in ActiveSalesforce read on....

So if you didn't read old fart's tutorial above and create a SF developer account then you're gonna wanna do that before we get started. The short of it is:

  1. Go to http://developer.force.com/ and sign up


  2. You will receive an email with your username (Your email address is your username) and a temporary password.


  3. Either click on the embedded link in the email to login or visit https://login.salesforce.com/ and login from there with your email and password. (Once logged in you'll want to change your password to something more memorable than the random string that was generated for you)


  4. Once you've logged in you're taken to the home page of your personal CRM sandbox. At the top of the page you'll want to click on the "Setup" link.




  5. Now we have to obtain your security token that's used to login to the API. To do that click on the "Setup" link at the top of the page.


  6. Once on your setup page you should see a section on the left side navigation titled "Personal Setup". In that section there's a node named "My Personal Information". Expand that node. You should see something like this:



  7. Click on the "Reset Security Token" button to have Salesforce send you an email with your security token.


Once that's completed we're ready to dig into some ruby and get our application talking with the API.

Let's get started!

  1. First let's add a configuration entry in environment.rb in our rails app for the activesalesforce gem. I'm using the johnreilly version on github.
        config.gem "johnreilly-activerecord-activesalesforce-adapter", 
    :lib => 'activerecord-activesalesforce-adapter', :source => 'http://gems.github.com'

  2. Once you have that entry in your environment.rb file you can install the gem by running the following rake command from the root of your application
    sudo rake gems:install

  3. OK so let's create our first SF related model, a base class that will be used for all SF model classes. So we generate:
    ruby script/generate model Salesforce::SfBase

    After that is generated we add some code to connect to SF and end up with something like the following:
    class Salesforce::SfBase < ActiveRecord::Base  
    self.abstract_class = true
    # create a connection to Salesforce for the given user
    def self.login(user, pass, token)
    params = {:adapter => "activesalesforce",
    :url => "https://login.salesforce.com/services/Soap/u/16.0",
    :username => user,
    :password => pass + token}
    self.establish_connection(params)
    end
    end


  4. Next let's generate an SF class that represents a user.
    ruby script/generate model Salesforce::User

    Modify that newly created user class to look as follows:
    class Salesforce::User < Salesforce::SfBase
    end


  5. Now we're ready to test our connection to SF and retrieve a user in their system via the API. Let's create a test in the Salesforce::UserTest as follows:
    class Salesforce::UserTest < ActiveSupport::TestCase
    def test_should_return_sf_user
    user = 'yourSFemail@yourdomain.com'
    password = 'yourSFpassword'
    key = 'your SF security token'
    Salesforce::SfBase.login(user, password, key)
    u = Salesforce::User.first
    assert_not_nil u
    end
    end

    If all went well you should see a successful test run and have a user from the SF system. The code simply logs into SF via the login method defined in our base class. This sets the connection for any ActiveRecord classes that derive from SfBase for the rest of the request. Then we simply use our SF User class the same way we would any ordinary ActiveRecord class.
    As a side not you'll probably want to store the SF credential information in a config file somewhere or if your SF support is on a per user basis somewhere in your users or accounts table.


  6. Extra credit: You can also use related objects in the standard ActiveRecord way as well. For example we could change our User class as follows:
    class Salesforce::User < Salesforce::SfBase
    has_many :contacts, :class_name => "Salesforce::Contact", :foreign_key => 'owner_id'
    has_many :cases, :class_name => 'Salesforce::Case', :foreign_key => 'owner_id'
    end

    Having these relations defined allows us to use relations in the normal rails way:
    user = Salesforce::User.first
    user.contacts
    user.cases

    Of course we'll need to generate a Salesforce::Contact and Salesforce::Case class but you get the idea. ActiveSalesforce will automatically match up any of the built in classes with your properly namespaced salesforce class. The key is that your classes do need to be namespaced at "Salesforce::" as far as I know. Perhaps you can override that behavior but I found it to be exactly what I'd want to do myself.


That should do it. Easy integration of Salesforce as another data source into your existing rails app. I should point out that this is not production tested code so it should be pointed out that things like ensuring the connection to Salesforce isn't causing any leaks should be investigated before deploying to production.

Enjoy!

Tuesday, November 10, 2009

Rails realities part 28 - Sinatra holdin' up the cache-money

For those of you looking to integrate the cache-money write through caching within your rails app and you're using Sinatra to create some metals beware.

It all started with this command on the rails console:
>> a = User.find(1)

Which resulted in this error message:


NoMethodError: You have a nil object when you didn't expect it!
The error occurred while evaluating nil.key?
from /Library/Ruby/Gems/1.8/gems/sinatra-0.9.4/lib/sinatra/base.rb:768:in 'route'
from /Library/Ruby/Gems/1.8/gems/sinatra-0.9.4/lib/sinatra/base.rb:754:in 'get'
from (__DELEGATE__):2:in 'send'
from (__DELEGATE__):2:in 'get'
from /Library/Ruby/Gems/1.8/gems/nkallen-cache-money-0.2.5/lib/cash/transactional.rb:27:in 'send'
from /Library/Ruby/Gems/1.8/gems/nkallen-cache-money-0.2.5/lib/cash/transactional.rb:27:in 'method_missing'
from /Library/Ruby/Gems/1.8/gems/nkallen-cache-money-0.2.5/lib/cash/accessor.rb:22:in 'fetch'
from /Library/Ruby/Gems/1.8/gems/nkallen-cache-money-0.2.5/lib/cash/accessor.rb:31:in 'get'
from /Library/Ruby/Gems/1.8/gems/nkallen-cache-money-0.2.5/lib/cash/query/abstract.rb:65:in 'hit_or_miss'
from /Library/Ruby/Gems/1.8/gems/nkallen-cache-money-0.2.5/lib/cash/query/abstract.rb:18:in 'perform'
from /Library/Ruby/Gems/1.8/gems/nkallen-cache-money-0.2.5/lib/cash/query/abstract.rb:7:in 'perform'
from /Library/Ruby/Gems/1.8/gems/nkallen-cache-money-0.2.5/lib/cash/finders.rb:24:in 'find_every'


Huh? Uhh, OK... WTF is the code doing in Sinatra? After some investigation I came across this very similar problem from someone using the ruby redis client https://sinatra.lighthouseapp.com/projects/9779/tickets/251-sinatra-collision-with-redis-client. Since that stack trace looked familiar I looked a little more closely. Turns out this bit of documentation is the key:


"The methods available to Sinatra::Base subclasses are exactly as those available via the top-level DSL. Most top-level apps can be converted to Sinatra::Base components with two modifications:

* Your file should require sinatra/base instead of sinatra;
otherwise, all of Sinatra's DSL methods are imported into the main namespace."

Read more here:
http://github.com/sinatra/sinatra/commit/536033334f33c6822acb64e18b6cdb4eee2f85fe#L0R515

Turns out the rails app I was working on which has some metals had included sinatra instead of sinatra/base. Simply changing that require fixed the problem with cache-money. Hopefully this saves someone else that same headache that cost me more time than it should've.

Labels:


Friday, October 30, 2009

Tivo your code

Lately I've been working on a project with some other developers and we were having discussions about development methodology choices and the role of testing in software development. I know that this topic has been covered to death but I personally always find it fun and rewarding to come up with new analogies for thinking about software, or anything for that matter, that helps educate and clarify the meaning or impact of a given concept, behavior, or pattern.

So while I'm not going to step back to the question of "What is meaning of life?", I am going to pose a high level question of "What is the process of writing software?" What is it you are actually doing when you're typing away in that text editor?

You're telling a story. A story of a software's journey. You're giving that story a plot line and characters and ideally it's going to go somewhere interesting. Not all software does, and there's a lot of reruns out there. Now you might be creating a really simple children's story like "The Little Iterator That Could" or something more involved like "The Passion of the Load Balancer: Timeouts Edition"

Whatever you're writing, every time you do so you're creating or building on an existing story. Your first "audience" for that story is your computer. Now computers are really patient but at the same time pretty intolerant listeners. They will sit there and listen to your story, no matter how good or bad, whenever you want. Like your mom they have an unending supply of patience and can endure the most boring of stories because, well, they have to. On the other hand just like many moms they're very active listeners and will critique your story until they understand every last detail, and it can get annoying because sometimes you just wish they'd implicitly know what you're trying to tell in your story. You can find yourself saying things like: "No, no, no... it doesn't go to 'find_the_dud', come on you know I meant 'find_the_dude'" So yeah it can be a bit annoying, but they really help keep your story straight for when you're ready to move on to the big time.

Speaking of the big time... it looks like your story was just optioned for a spot in the latest upcoming code repository! Oh joy! You can't wait to share your entire story with the world. You can't wait for the first episode to air with your new audience members, people. Your mom, err computer, loved the story and was able to understand it, surely your code is destined to be the next "Seinfeld" of the software world.

So it's opening night, your new episode is about to air, but then something strange happens. You continue telling your story and your audience is confused. They're not following your plot. They don't understand who the characters are, the roles they play, and generally what's going on and where things are going. Hmm... this is a problem. How is anyone going to love and adore your story if they aren't able to follow what's going on? And yeah, they're all coming into your story after it's begun. It might be episode #3 in your series of stories, or perhaps this is your Treehouse of Terror XX, the 20th installment in an entire series of stories.

So how the heck are you going to get your audience up to speed on your plot and characters so that they can follow your amazing story from here on out? And how are you going to ensure that they can always keep up to date on your series if they can't be there to watch/listen as you're creating this poetic genius?

You do what we all do when there's something we want to watch/follow but don't have time to do so "live", you Tivo it. Ahh yes Tivo. No longer do we don't have to have the show's writers sitting next to us on our couch, or more commonly, a friend explaining to us what happened on last week's episode of "As the Kernel Dumps". We can go get caught up at our leisure by simply watching the old episode on our trusty Tivo, and we'll be up to date with the latest plot when a new episode airs. Heck, we could even stand in for the writers and create our own next episodes of the show if we wanted to or had to in the event of a strike.

Fast forward a bit and now based on your smashing success of your last project you've just landed a role as a writer for a hot new Abobe Flex sitcom involving some charts, data, and other things that are sure to be instant hilarity, I mean what's not funny about charts and data? Hmm, but you still have to crank out new episodes of your other ongoing series. Guess you'll have to find some other writers to bring onboard to help with that. Hmm, luckily you've been Tivoing all your episodes so that this new writer can go back and watch all the old episodes to get up to speed and be able to create meaningful new stories that add to the existing plotline in a meaningful way. Oh and Tivoing these stories allows them to serve as a "sanity check" or "regression check" for any new plot lines this new writer comes up with. It's easy to go back and see if what they're coming up with fits within the historical boundaries of the series and if not fix the story accordingly.

Unfortunately not everyone out there has seen the light with regards to Tivoing their code. Throngs of software stories are out there being spoken into the ether with nary a chance at them truly being understood. Unless you were there from the firing of the opening brace or shortly thereafter you're not likely to understand what the heck is going on without some help from someone else that's been listening to the story or by talking to the authors themselves. The problem is that talking to the authors or even others in attendance doesn't really scale so well and it's kind of annoying to have to do for all parties. You as the story creator become the bottleneck. Imagine if every author out there had to tell their novels in person. We wouldn't be much further along than Shakespeare right now.

Of course if you haven't figured it out by now I'm talking of course about writing tests for your code and some of the advantages of doing so and the drawbacks of not. Without them you're delivering half a script as to what story the software is trying to tell. Tivoing your code is always a great idea, even if you're the only one that's ever going to be "listening" to these software stories. I promise you that in 6 months from now you'll come back to your series and you'll be plenty grateful that you have those old episodes Tivo'd to help you advance that storyline.

The world is waiting to hear your great stories! Now if you'll excuse me I have to go see "what's playing" in my git repository!


Tuesday, June 30, 2009

Mo simple sortable tables in rails


A long time ago I wrote a blog posting on a plugin I created that would allow you to create a sortable table for your rails models with minimal work. Well, 2.5 years later I'm taking the time to write yet another blog post on a revamped version of that plugin that does more with less and is about 10x easier to set up and use. If you refer back to my old posting you'll see that I set out to create a plugin that would allow you to create a non-ajax sortable and paginated table. It certainly worked but involved too much configuration and understanding by you the developer in order to use it. The goal here is the same and the motivation is largely to make it braindead easy to get a table up and running without having to know much of anything. I use this plugin in my apps in user facing pages as well as the starting point for all of my admin pages, completely replacing the ever fragile ActiveScaffold. (Sorry you guys, it totally rocks when it works but it just kept breaking too much for me and I didn't need all the bells and whistles) Over the past couple of years I've completely revamped how this plugin works so let's a have a look at "sortable reloaded" OK let's get started:



  1. First You either create or use an existing a rails app (2.2.2 is the version I'm using still, not positive but this should work with 2.3.x or any rails 2.x apps without a problem).

  2. Next we need to install the will_paginate gem. Assuming you're familiar with specifying gems for your app in your environment.rb file here's my entry for will_paginate:
      config.gem 'mislav-will_paginate',
    :lib => 'will_paginate',
    :source => 'http://gems.github.com',
    :version => '~> 2.3.6'

  3. Now that we've got the pagination gem in place it's time to install the plugin itself. We can do that with:
    ruby script/plugin install git://github.com/kovacs/sortable.git

  4. Next up I'm going to assume that you have a model for whose objects you'd like to create a paginated, sortable table. You're going to need a model as well as a controller with an entry in your routes.rb file. For example:
    in user.rb

    class User < ActiveRecord::Base
    end

    in users_controller.rb

    class UsersController < ApplicationController
    end

    in routes.rb

    map.resources :users

  5. Assuming you have that set up configured the simplest possible way to use sortable to create a sortable, paginated, searchable table of your objects is like this:

    class UsersController < ApplicationController
    sortable_table User
    end
    After you add that snippet to your controller go and hit: http://localhost:3000/users and watch the magic happen. Pretty cool for a one line declaration. The magic happening behind the scenes is that the declaration "sortable_table User" is generating an 'index' action in your UsersController and is using a default index.erb template file for the view of that action. Here's a look at that code: In sortable.rb (vendor/plugins/sortable/lib/sortable.rb)

    def index
    get_sorted_objects(params)
    render :template => 'sortable/index'
    end
    in the view for index (vendor/plugins/sortable/views/sortable/index.erb)

    <%= sortable_table %>
    Everything is customizable but this is just demonstrating the simplest table you can create with the plugin.

  6. By default you'll notice that the plugin uses all the attributes on your model when it builds the table. I can hear it already: "Dude, I don't want it to show XYZ attribute on my model" to which I'd say "Pfft... way ahead of you dawg". Specify what columns you want displayed in the table by default like this:

    class UsersController < ApplicationController
    sortable_table User, :display_columns => ['id', 'name', 'email', 'created_at', 'timezone', 'state']
    end

    Of course this example assumes that you have the attributes on your User model. Replace with your attributes as needed. What you'll end up seeing is this:


  7. Of course there's a ton of flexibility in the plugin that allows you to place a sortable table on any page you'd like as opposed to having it only show up on an index page that lists all your objects. You can also show a table that sorts, searches, and paginates over more than one object type. So if you wanted to show all your users and one or more attributes from a related object or objects you can easily do that with a few attributes in your sortable_table declaration. You can also easily customize the L&F of the table and turn off searching if you don't want/need it. See more details in sortable.rb and sortable_helper.rb where it outlines all of the options. If there's something that it doesn't do feel free to fork the code and add it yourself and send me a pull request.

  8. The basic mechanics on how to make use of the plugin in your app is that you want to call:

    get_sorted_objects(params, sortable_options)
    This call will populate a few variables for you to use:

    @objects # The collection of objects that you can display in a table
    @headings # The collection of table header objects that are used to build the header and links.
    Then in your view you'll just call:

    <%= sortable_table %>
    Wherever you'd like that sortable table to appear. It's really that simple. You can easily override the template that's being used to build the table with a parameter to the helper call. For example:

    <%= sortable_table :partial => 'loans/loan_table' %>
    Have a look at vendor/plugins/sortable/views/sortable/_table.html.erb for an idea of how to build a table for your objects if you want to have more flexibility than the standard template gives you.



If you find this plugin useful and especially if you use it on a site that's a project that you're intending to make money with please donate something as it did require time and effort to clean up, document, and package this plugin for general consumption. Your donation will go towards improving this plugin and inspire me to release other plugins that I know would be generally useful to other rails developers. You might want to note that unlike most open sores projects this plugin comes with example code, documented code, tests, and has 100% test coverage as well. It's also being used in production on a real site that charges real money for its products so it's not just someone's hobby project that's going to be abandoned. Think about that while you're busting out your credit card and making a suggested donation of $20 (or whatever you'd like) here:





If you aren't going to donate or are using it for a non-profit project I'd really appreciate a link to my company site: http://www.cablecarsoftware.com. Thanks everyone and hope you enjoy it and find it useful! Oh and finally, get the code here.

Tuesday, May 19, 2009

Production deployments from the friendly skies

This past weekend I flew to NY to attend my youngest sister's graduation from Hofstra. Once again I flew Virgin America like I always try to when I fly to NY. Power outlets at each seat mean that I can either work or play on my mac the entire time turning a 5 hour flight into nothing more than just another day at the office or a coffee shop :-) The only thing that's ever been missing of course is internet access. Well for $12.95 on Virgin you can now get high speed internet access. I forgot to test the speed but for browsing it was fine. I did notice lag when streaming a video but that's to be expected. Simply having it at all and having it usable is a huge win.

So there I was working on some fixes for LendingKarma and was ready to deploy. Then the realization that I was about to embark upon a production deployment from 30K feet hit me. What if there's a problem and the wifi goes out? I can't rollback thus leaving the site in whatever state caused by my deployment gone wrong. After posing the question to some fellow engineers via a twitter/facebook status update which of course resulted in "do it man!", because they're not the ones that have to deal with the fallout of a failure :-), I pushed ahead with my deployment. Wouldn't you know it, there was a problem and I immediately rolled back. Phew! Murphy's Law strikes again! I fixed the problem quickly and then redeployed successfully. Wow... I just did a production deployment from an airplane. Pretty damn cool :-)


Monday, May 04, 2009

And.... we're back

Phew! That was a long commercial break. I always said I wanted to take a year off in my 30s. I guess a year off from blogging is about the closest thing I've come to that year off :-) Quite a bit's happened in the year since I last wrote. I helped a client launch, http://www.newrelic.com, went on a vacation to Europe (Budapest, Prague, and Berlin) and most recently launched a new venture of mine, http://www.lendingkarma.com.

For the folks that still have me in their feed reader after nearly a year without a peep, I say thanks for being so lazy about removing me :-) Considering that the majority of folks that do read this blog are interested in ruby and rails development I do have a few things that I can talk about that might be of interest.

So as you could probably guess LendingKarma is built on rails, but it's also built using a base app that is comprised of numerous plugins that make it really easy to get a base application up and running. Some of the plugins are:



I also have a few plugins I created that aren't on Github at the moment but are part of my base app:

OrderSystem - Order system is a plugin for e-commerce that's focused on selling digital products. It makes it brain dead easy to add any of the ActiveMerchant gateways as well as making it trivial to setup and test paypal standard or web payments pro as your gateway. It even has support for subscriptions using web payments pro. Subscription support for non-Paypal web payments pro is coming but for LendingKarma we are using Paypal so that was done first. I'm thinking of releasing this plugin in the same fashion as the RailsKit software. I looked at that software and wasn't happy with the product so I ended up creating my own which I think is an easier fit for integrating into an existing rails app. Anyway, more to come on that. Let me know if you'd like more information on that plugin.

Tooltip - A plugin that makes it easy to declaratively add tooltips to your site.

Sortable - A plugin that makes it easy to create a sortable, paginated table over your objects that is also very flexible.

ListEditor - List Editor allows you to declaratively create CRUD lists that use a modal dialog and ajax for the CRUD operations.

More on these plugins in future posts.

The end result of this work is a rails app that out of the box comes with user reg/auth, e-commerce support, and a CMS to manage the site's static pages. A great first step to many projects out there. I'm also just thinking about bundling the entire app itself for release as a base starter app.

Stay tuned as I plan on posting more than once this year :-)

Thursday, May 08, 2008

JavaOne 2008 - Scala

Between working, waiting, wishing for my Wii to arrive (yay!) I've finally managed to make it to my first talk at JavaOne. Because my good friend David Pollak has been bugging me to check out lift (written in Scala) I decided to go and check out the guy behind the guy behind the guy (Martin Odersky) The creator of Scala. (Enough links in there for ya?)

What's scala? Well it's a fully JVM compliant language that integrates functional and object-oriented language features. Boy that sounds to me like it's gonna get complicated or bloated real fast. From Martin's presentation today:

Is Scala a kitchen sink language?
Roughly comparable to java, smaller than C++/#

Two design principles:

  1. focus on abstraction and composition so that users can implement their own specialized features

  2. have the same constructs work for small as well as large programs


Scala adds:

pure object system - everything is an object unlike Java and Ruby

operator overloading

closures - Rubyists take note

mixins - Rubyists take note

existential types

abstract types

pattern matching



Removes:

statics - it adds its own static of sorts with a singleton object (called a companion object) where you can define methods


class Sample(x: Int, val p: Int) {
def instMeth(y: Int) = x + y
}
object Sample { <-- companion object... contains statics
def staticMeth(x: Int, y: Int) = x * y
}

primitives

break,continue

special treatment of interfaces

wildcards

raw types

enums


There's a good beginner intro here.

It seems like the biggest thing that most people object to (myself included the first time I saw it) is the syntax of the language. Martin referred to it at today's talk:

x: Int instead of int x

why?

works better with type inference
works better for large type expressions
val x: HashMap[String, (String, List[Char])] = ...

instead of

public final HashMap>> x = ...

OK so this is all fine and good but what about the thing all Java devs care about... tool support. Well it turns out that there's some stuff in the works and out there for Scala:

scalac

shell: scala

testing:

sunit, scalacheck, specs

eclipse plugin v2 in beta

in the works:

intellij plugin

netbeans plugin (caoyuan)

OK so that's my nickel tour (hey you get what you pay for) of Scala. For those of you around SF after Friday checkout Scala lift off on Saturday morning at 9AM. Unfortunately I can't make it myself due to a trip I had planned but if I weren't out of town I'd definitely go check it out.

Saturday, April 26, 2008

So long TextMate?... Hello NetBeans? Really? Yeah, really.

I'll admit it, I'm one of many folks that used to treat NetBeans as a whipping boy. Questioning why Sun would bother dumping money into the horse that so obviously lost the race to Eclipse and IntelliJ to win the hearts and minds of Java developers around the world.

Over the years NetBeans has flirted with me by introducing various features that sounded great on the surface and taken by itself usually was a pretty good feature. Like the new debugger and profiler that was introduced a couple years back. Or the first to implement JEE 5.0 tools, or Matisse - the Swing GUI toolkit. The list goes on and on.

But each and every time I'd download NetBeans and try it out no matter how good that one feature might've been it was surrounded by basic editing and development features that were laughable. There's no way anyone could use this tool on a daily basis and be productive.

Well over the past year and a half or so the NetBeans teams has been cranking away on support for ruby and rails development. Every once in awhile I'd take a look and see what kind of progress they'd made and always conclude that it wasn't there yet. Well, the other day I had need to hunt for a good javascript editor that would do code completion for me and so knowing NetBeans has support for javascript editing and code completion I decided to see how far the ruby and rails support in NetBeans has come as well.

I grabbed a nightly build of 6.1 and installed it. Right away I noticed that they made a special build for ruby where they've stripped it down to where it is focused on supporting ruby and rails apps. They added a theme that looks similar to textmate to make me feel more at home. Many keybindings are similar to Textmate (though not all but they are easily configurable). You can easily debug your rails apps with the graphical debugger. The generators work. Code completion works okay at times. API docs are easily visible inline during code completion. Navigating around the project is now easy where before that was the single biggest outage that kept me from even considering it. You can easily jump to type definitions. Simple refactoring is supported. Basically it's welcome to 2001 for anyone in the ruby world that came from Java. Regardless it's great to have some of these high leverage tools while working in the ruby world.

I haven't completely ditched TextMate. If I know I have to do some quick edits and know that I'm not going to have to do any heavy duty debugging or dev work and don't have the project open in NetBeans I'll still use it.

Anyway, great job team NetBeans! Thanks so much for the great dev tool.

Thursday, March 20, 2008

Dave Chappelle was the man at Punchline tonight!

Wow! I was lucky enough to score a pair of tickets to Wednesday night's surprise performance at the Punchline here in SF and man what an amazing show! The show was supposed to start at 11 but nothing happened until 11:30. That's when Dave came on. For the next 4.5 hours, yeah that's right, 4.5 hours, he delighted the crowd with topics ranging from Eddie Veder's singing voice (a recurring theme all night), to a stripper in the audience, to a guy that was in Afghanistan who stumbled upon a cave with a bunch of hash, cocaine, and steroids and managed to get himself kicked out of the military for smoking too much. Hell, he even managed a Police Academy reference, mentioning the Blue Oyster when someone suggested an eatery in the Castro when asking for a late night restaurant after the show.

Dude opens and closes his show and is on stage for 4.5 hours straight! No breaks, nothing. Two packs of cigarettes and about 6 coffees later it ended and my throat is sore from laughing.

What a fantastic night! Thanks Dave for a memorable time. Hope to have the privilege of seeing you again sometime. Hope you enjoyed that Grubsteak or Mel's. Come back to SF soon.

Wednesday, February 20, 2008

Opensocial observations (Part 1 - Data types available in MySpace v0.6 and Orkut v0.7)

For anyone brave enough to dive into the world of opensocial you'll quickly find that there's a lot of stuff around to read but not much in the way of examples and demos showing the lay of the land.

I won't go into great detail here about how opensocial apps are architected but what I will say is that you have to define javascript in any opensocial app just to get the thing off the ground. On some containers you need to do an XML situp as well. I've only experimented on Orkut and MySpace thus far with varying degrees of success and confusion.

Basically if you're using v0.7 and on Orkut you create an XML file that contains your app definition. Here's an example:

<?xml version="1.0" encoding="UTF-8"?>
<Module>
<ModulePrefs title="Data type inspector">
<Require feature="opensocial-0.7"/>
</ModulePrefs>
<Content type="html">
<![CDATA[
<script src="http://www.pitchwire.com/javascripts/openSocialTestV0.7.js"></script>
<script>
gadgets.util.registerOnLoadHandler(init);
</script>
<div id='snoop'></div>
]]>
</Content>
</Module>


You need to put this XML file somewhere that Orkut can get to it and load it. For example: http://www.pitchwire.com/opensocialTestV0.7.xml In fact you can simply add an app in Orkut and when it asks for the URL simply point to this XML file I have hosted if you don't wish to create these files and host them yourself.

You can see that in the XML file an external javascript file is referenced. That file contains the code I whipped up today that tells you what data types are available in a given container.

This code works for Orkut and any v0.7 opensocial containers:
function inspectEnv() {
var env = opensocial.getEnvironment();
html = new Array();
camera1 = 'b3c7f1';
camera2 = 'fff';
last_camera = camera1;

html.push('<br/>Domain: ' + env.getDomain() );
html.push('<table style="border: 1px solid black;">');
html.push('<tr style="background-color: c8eaa9;"><td>Type</td><td>Field</td><td>Supported?</td></tr>');

var fieldType = opensocial.Environment.ObjectType.ACTIVITY;
var fields = [opensocial.Activity.Field.TITLE,
opensocial.Activity.Field.TITLE_ID,
opensocial.Activity.Field.BODY,
opensocial.Activity.Field.BODY_ID,
opensocial.Activity.Field.EXTERNAL_ID,
opensocial.Activity.Field.ID,
opensocial.Activity.Field.MEDIA_ITEMS,
opensocial.Activity.Field.POSTED_TIME,
opensocial.Activity.Field.PRIORITY,
opensocial.Activity.Field.STREAM_FAVICON_URL,
opensocial.Activity.Field.STREAM_TITLE,
opensocial.Activity.Field.STREAM_SOURCE_URL,
opensocial.Activity.Field.STREAM_URL,
opensocial.Activity.Field.TEMPLATE_PARAMS,
opensocial.Activity.Field.URL,
opensocial.Activity.Field.USER_ID,
opensocial.Activity.Field.APP_ID];
inspectType(env, fieldType, fields);

fieldType = opensocial.Environment.ObjectType.ACTIVITY_MEDIA_ITEM;
fields = [opensocial.Activity.MediaItem.Field.MIME_TYPE,
opensocial.Activity.MediaItem.Field.TYPE,
opensocial.Activity.MediaItem.Field.URL];
inspectType(env, fieldType, fields);

fieldType = opensocial.Environment.ObjectType.ADDRESS;
fields = [opensocial.Address.Field.COUNTRY,
opensocial.Address.Field.EXTENDED_ADDRESS,
opensocial.Address.Field.LATITUDE,
opensocial.Address.Field.LOCALITY,
opensocial.Address.Field.LONGITUDE,
opensocial.Address.Field.PO_BOX,
opensocial.Address.Field.POSTAL_CODE,
opensocial.Address.Field.REGION,
opensocial.Address.Field.STREET_ADDRESS,
opensocial.Address.Field.TYPE,
opensocial.Address.Field.UNSTRUCTURED_ADDRESS];
inspectType(env, fieldType, fields);

fieldType = opensocial.Environment.ObjectType.BODY_TYPE;
fields = [opensocial.BodyType.Field.BUILD,
opensocial.BodyType.Field.EYE_COLOR,
opensocial.BodyType.Field.HAIR_COLOR,
opensocial.BodyType.Field.HEIGHT,
opensocial.BodyType.Field.WEIGHT];
inspectType(env, fieldType, fields);

fieldType = opensocial.Environment.ObjectType.EMAIL;
fields = [opensocial.Email.Field.ADDRESS,
opensocial.Email.Field.TYPE];
inspectType(env, fieldType, fields);

fieldType = opensocial.Environment.ObjectType.FILTER_TYPE;
fields = [opensocial.DataRequest.FilterType.ALL,
opensocial.DataRequest.FilterType.HAS_APP];
inspectType(env, fieldType, fields);

fieldType = opensocial.Environment.ObjectType.MESSAGE;
fields = [opensocial.Message.Field.BODY,
opensocial.Message.Field.TITLE,
opensocial.Message.Field.TYPE];
inspectType(env, fieldType, fields);

fieldType = opensocial.Environment.ObjectType.MESSAGE_TYPE;
fields = [opensocial.Message.Type.EMAIL,
opensocial.Message.Type.NOTIFICATION,
opensocial.Message.Type.PRIVATE_MESSAGE,
opensocial.Message.Type.PUBLIC_MESSAGE];
inspectType(env, fieldType, fields);

fieldType = opensocial.Environment.ObjectType.NAME;
fields = [opensocial.Name.Field.ADDITIONAL_NAME,
opensocial.Name.Field.FAMILY_NAME,
opensocial.Name.Field.GIVEN_NAME,
opensocial.Name.Field.HONORIFIC_PREFIX,
opensocial.Name.Field.HONORIFIC_SUFFIX,
opensocial.Name.Field.UNSTRUCTURED];
inspectType(env, fieldType, fields);

fieldType = opensocial.Environment.ObjectType.ORGANIZATION;
fields = [opensocial.Organization.Field.ADDRESS,
opensocial.Organization.Field.DESCRIPTION,
opensocial.Organization.Field.END_DATE,
opensocial.Organization.Field.FIELD,
opensocial.Organization.Field.NAME,
opensocial.Organization.Field.SALARY,
opensocial.Organization.Field.START_DATE,
opensocial.Organization.Field.SUB_FIELD,
opensocial.Organization.Field.TITLE,
opensocial.Organization.Field.WEBPAGE];
inspectType(env, fieldType, fields);

fieldType = opensocial.Environment.ObjectType.PERSON;
fields = [opensocial.Person.Field.ABOUT_ME,
opensocial.Person.Field.ACTIVITIES,
opensocial.Person.Field.ADDRESSES,
opensocial.Person.Field.AGE,
opensocial.Person.Field.BODY_TYPE,
opensocial.Person.Field.BOOKS,
opensocial.Person.Field.CARS,
opensocial.Person.Field.CHILDREN,
opensocial.Person.Field.CURRENT_LOCATION,
opensocial.Person.Field.DATE_OF_BIRTH,
opensocial.Person.Field.DRINKER,
opensocial.Person.Field.EMAILS,
opensocial.Person.Field.ETHNICITY,
opensocial.Person.Field.FASHION,
opensocial.Person.Field.FOOD,
opensocial.Person.Field.GENDER,
opensocial.Person.Field.HAPPIEST_WHEN,
opensocial.Person.Field.HEROES,
opensocial.Person.Field.ID,
opensocial.Person.Field.INTERESTS,
opensocial.Person.Field.JOB_INTERESTS,
opensocial.Person.Field.JOBS,
opensocial.Person.Field.LANGUAGES_SPOKEN,
opensocial.Person.Field.LIVING_ARRANGEMENT,
opensocial.Person.Field.LOOKING_FOR,
opensocial.Person.Field.MOVIES,
opensocial.Person.Field.MUSIC,
opensocial.Person.Field.NAME,
opensocial.Person.Field.NICKNAME,
opensocial.Person.Field.PETS,
opensocial.Person.Field.PHONE_NUMBERS,
opensocial.Person.Field.POLITICAL_VIEWS,
opensocial.Person.Field.PROFILE_SONG,
opensocial.Person.Field.PROFILE_URL,
opensocial.Person.Field.PROFILE_VIDEO,
opensocial.Person.Field.QUOTES,
opensocial.Person.Field.RELATIONSHIP_STATUS,
opensocial.Person.Field.RELIGION,
opensocial.Person.Field.ROMANCE,
opensocial.Person.Field.SCARED_OF,
opensocial.Person.Field.SCHOOLS,
opensocial.Person.Field.SEXUAL_ORIENTATION,
opensocial.Person.Field.SMOKER,
opensocial.Person.Field.SPORTS,
opensocial.Person.Field.STATUS,
opensocial.Person.Field.TAGS,
opensocial.Person.Field.THUMBNAIL_URL,
opensocial.Person.Field.TIME_ZONE,
opensocial.Person.Field.TURN_OFFS,
opensocial.Person.Field.TURN_ONS,
opensocial.Person.Field.TV_SHOWS,
opensocial.Person.Field.URLS];
inspectType(env, fieldType, fields);

fieldType = opensocial.Environment.ObjectType.PHONE;
fields = [opensocial.Phone.Field.NUMBER,
opensocial.Phone.Field.TYPE];
inspectType(env, fieldType, fields);

fieldType = opensocial.Environment.ObjectType.SORT_ORDER;
fields = [opensocial.DataRequest.SortOrder.NAME,
opensocial.DataRequest.SortOrder.TOP_FRIENDS];
inspectType(env, fieldType, fields);

fieldType = opensocial.Environment.ObjectType.URL;
fields = [opensocial.Url.Field.ADDRESS,
opensocial.Url.Field.LINK_TEXT,
opensocial.Url.Field.TYPE];
inspectType(env, fieldType, fields);
html.push('</table>');
document.getElementById('snoop').innerHTML = html.join('');
}

function inspectType(env, fieldType, fields) {
for(j = 0; j < fields.length; j++) {
supports = env.supportsField(fieldType, fields[j]);
display = supports ? 'style="font-weight: bold;"' : ''
html.push('<tr style="background-color: ' + last_camera + '"><td>' + fieldType + '</td><td>' + fields[j] + '</td><td ' + display + '>' + supports + '</td></tr>');
last_camera = last_camera == camera1 ? camera2 : camera1
}
}

function init() {
inspectEnv();
}


Not so bad once you get your bearings and understand how things work. I will say that it hasn't been the smoothest development environment in the world but compared to MySpace it's a dream. Speaking of MySpace the current situation there is so awful at the moment it's almost as if they don't want people to write apps for their platform.

The current state of affairs there is that they expect you to paste your HTML and javascript code into a textarea on their developer site. They too suffer from some of the same confusion that Orkut does where it's not clear what the oder of operations are and where key links are missing when trying to navigate around the sandbox. Also on MySpace your app has a MySpace profile as if it's another user. It can have friends, etc. Yeah.. that seems about right for MySpace :-)

Because I'm apparently a glutton for punishment I decided to try and run my container introspection code on MySpace. Turns out V0.6 is much smaller in terms of data types than V0.7 with only 3 defined.

Here's the code that you can paste into your canvas textarea once you've created an app on MySpace:

<div id='snoop' style='height: 500px; overflow: scroll;'></div>
<script type="text/javascript" language="javascript">
function init() {
inspectEnv();
}

function inspectEnv() {
var env = opensocial.getEnvironment();
html = new Array();
camera1 = 'b3c7f1';
camera2 = 'fff';
last_camera = camera1;

html.push('<br/>Domain: ' + env.getDomain() );
html.push('<table style="border: 1px solid black;">');
html.push('<tr style="background-color: c8eaa9;"><td>Type</td><td>Field</td><td>Supported?</td></tr>');

var fieldType = opensocial.Environment.ObjectType.ACTIVITY;
var fields = [opensocial.Activity.Field.TITLE,
opensocial.Activity.Field.TITLE_ID,
opensocial.Activity.Field.BODY,
opensocial.Activity.Field.BODY_ID,
opensocial.Activity.Field.EXTERNAL_ID,
opensocial.Activity.Field.ID,
opensocial.Activity.Field.MEDIA_ITEMS,
opensocial.Activity.Field.POSTED_TIME,
opensocial.Activity.Field.PRIORITY,
opensocial.Activity.Field.STREAM_FAVICON_URL,
opensocial.Activity.Field.STREAM_TITLE,
opensocial.Activity.Field.STREAM_SOURCE_URL,
opensocial.Activity.Field.STREAM_URL,
opensocial.Activity.Field.TEMPLATE_PARAMS,
opensocial.Activity.Field.URL,
opensocial.Activity.Field.USER_ID,
opensocial.Activity.Field.APP_ID];
inspectType(env, fieldType, fields);

fieldType = opensocial.Environment.ObjectType.ACTIVITY_MEDIA_ITEM;
fields = [opensocial.Activity.MediaItem.Field.MIME_TYPE,
opensocial.Activity.MediaItem.Field.TYPE,
opensocial.Activity.MediaItem.Field.URL];
inspectType(env, fieldType, fields);

fieldType = opensocial.Environment.ObjectType.PERSON;
fields = [opensocial.Person.Field.ABOUT_ME,
opensocial.Person.Field.ACTIVITIES,
opensocial.Person.Field.ADDRESSES,
opensocial.Person.Field.AGE,
opensocial.Person.Field.BODY_TYPE,
opensocial.Person.Field.BOOKS,
opensocial.Person.Field.CARS,
opensocial.Person.Field.CHILDREN,
opensocial.Person.Field.CURRENT_LOCATION,
opensocial.Person.Field.DATE_OF_BIRTH,
opensocial.Person.Field.DRINKER,
opensocial.Person.Field.EMAILS,
opensocial.Person.Field.ETHNICITY,
opensocial.Person.Field.FASHION,
opensocial.Person.Field.FOOD,
opensocial.Person.Field.GENDER,
opensocial.Person.Field.HAPPIEST_WHEN,
opensocial.Person.Field.HEROES,
opensocial.Person.Field.ID,
opensocial.Person.Field.INTERESTS,
opensocial.Person.Field.JOB_INTERESTS,
opensocial.Person.Field.JOBS,
opensocial.Person.Field.LANGUAGES_SPOKEN,
opensocial.Person.Field.LIVING_ARRANGEMENT,
opensocial.Person.Field.LOOKING_FOR,
opensocial.Person.Field.MOVIES,
opensocial.Person.Field.MUSIC,
opensocial.Person.Field.NAME,
opensocial.Person.Field.NICKNAME,
opensocial.Person.Field.PETS,
opensocial.Person.Field.PHONE_NUMBERS,
opensocial.Person.Field.POLITICAL_VIEWS,
opensocial.Person.Field.PROFILE_SONG,
opensocial.Person.Field.PROFILE_URL,
opensocial.Person.Field.PROFILE_VIDEO,
opensocial.Person.Field.QUOTES,
opensocial.Person.Field.RELATIONSHIP_STATUS,
opensocial.Person.Field.RELIGION,
opensocial.Person.Field.ROMANCE,
opensocial.Person.Field.SCARED_OF,
opensocial.Person.Field.SCHOOLS,
opensocial.Person.Field.SEXUAL_ORIENTATION,
opensocial.Person.Field.SMOKER,
opensocial.Person.Field.SPORTS,
opensocial.Person.Field.STATUS,
opensocial.Person.Field.TAGS,
opensocial.Person.Field.THUMBNAIL_URL,
opensocial.Person.Field.TIME_ZONE,
opensocial.Person.Field.TURN_OFFS,
opensocial.Person.Field.TURN_ONS,
opensocial.Person.Field.TV_SHOWS,
opensocial.Person.Field.URLS];
inspectType(env, fieldType, fields);
html.push('>/table>');
document.getElementById('snoop').innerHTML = html.join('');
}

function inspectType(env, fieldType, fields) {
for(j = 0; j < fields.length; j++) {
supports = env.supportsField(fieldType, fields[j]);
display = supports ? 'style="font-weight: bold;"' : ''
html.push('>tr style="background-color: ' + last_camera + '">>td>' + fieldType + '>/td>>td>' + fields[j] + '>/td>>td ' + display + '>' + supports + '>/td>>/tr>');
last_camera = last_camera == camera1 ? camera2 : camera1
}
}
init();
</script>


Mind you this only shows you the supported datatypes for what's in the opensocial V0.6 spec. MySpace has a few extension of their own that are available for you to use. I won't cover that here, you can go check out their API docs for that. The word is that they'll be upgrading to V0.7 soon. I sure hope so but I also wish that Orkut would implement the full spec too. You'll see how much is still missing when you install the inspector app.

That's it for now. I'm sure I'll have lots more I could post about on this topic as well as Facebook development.

Enjoy!

This page is powered by Blogger. Isn't yours?