www.flickr.com
Michael Kovacs' photos More of Michael Kovacs' photos

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!

Thursday, January 31, 2008

Rails realities part 27 (Facebook and Bebo sittin in an app)

Long time no post. Way too long. I'm not a big fan of too much noise so I try to post when I have something that I think is important to share.

Anyway, today I've got something to share that all you web2.0-facebook-bebo-social-networking-rails-app-building monkeys have been clamoring for but so far have only had a half baked solution to.... Running your Facebook apps on Bebo using RFacebook.

"Wait a minute, you CAN do that right now dude, catch up!" Ahh yes my little ruby fanboy there IS a solution out there right now, the RFacebook on Bebo plugin, BUT that only allows you to run the same app on EITHER Facebook or Bebo. That means you need to setup another VHost and another cluster of mongrels which you may not want to do (I don't).

"Dude, you're still behind man, that wizard Chad Fowler done started his own Facebook integration project called Facebooker." Yeah I know about that thing too and while it is said RFacebook is not long for this world, right now I've got a "legacy" (hehe) rails facebook app and at the moment upgrading to Facebooker isn't as attractive of an option for a live app vs. modifying the existing plugin/gem that's being used.

I've done the dirty work of hacking through RFacebook 0.9.8 and performing the necessary surgery to make RFacebook work on any network that's compatible with the Facebook API. In my case I've added support for Bebo. (The only other "facebook compatible" network I know of)

How about some code?
In the RFacebook GEM in facebook_session.rb...
module RFacebook
# TODO: better handling of session expiration
# TODO: support Bebo's API
HOST_CONSTANTS = { 'facebook' => { :api_version => "1.0",
:api_host => "api.facebook.com",
:api_path_rest => "/restserver.php",
:www_host => "www.facebook.com",
:www_path_login => "/login.php",
:www_path_add => "/add.php",
:www_path_install => "/install.php"},
'bebo' => { :api_version => "1.0",
:api_host => "apps.bebo.com",
:api_path_rest => "/restserver.php",
:www_host => "bebo.com",
:www_path_login => "/SignIn.jsp",
:www_path_add => "/add.php",
:www_path_install => "/c/apps/add"}}

If there were another network to come along all you'd need to do is add a couple of constants in the RFacebook GEM for a new network and modify the config file for your new network. Now I don't know of any other "Facebook compatible" social networks out there besides Bebo at the moment but here's where you'd add their information if there were.

Now there are two burdens on you the user:

  1. The plugin determines what network requests are coming from by checking for a subdomain (i.e. http://bebo.mkovacs.com, http://facebook.mkovacs.com). Therefore you have to tell Facebook and Bebo your callback address is one that begins with the subdomain you're on.
  2. There is a slight change in the facebook.yml config file. Example:

development:
facebook:
key: (key)
secret: (secret)
canvas_path: /pitchwire/
callback_path: /facebook/
tunnel:
username: user
host: pitchwire.com
port: 1099
local_port: 3000
bebo:
key: (key)
secret: (secret)
canvas_path: /pitchwire/
callback_path: /facebook/
test:
facebook:
key: (key)
secret: (secret)
canvas_path: /pitchwire/
callback_path: /facebook/
bebo:
key: (key)
secret: (secret)
canvas_path: /pitchwire/
callback_path: /facebook/
production:
facebook:
key: (key)
secret: (secret)
canvas_path: /pitchwire/
callback_path: /facebook/
bebo:
key: (key)
secret: (secret)
canvas_path: /pitchwire/
callback_path: /facebook/

So as you can see it's pretty much the same except that for each environment there's a facebook: and a bebo: setting.

"OK great, gimme, gimme, gimme!!" Hmmm... well I've contacted the RFacebook project owner to provide him with the changes so he can integrate them and probably do a point release, but in this instant-gratification-web2.0-gotta-have-it-now-gimme-so-I-can-get-dugg world I'll put the files up on my rinky dink shared hosting site until there's an update to the codebase and a release.

Now for the disclaimer. This comes with the 'WTFPL' license and DIY support :-)

Update: I've checked the modified rfacebook code into the rfacebook project in a branch. For more information and to pickup the code go here: http://rfacebook.rubyforge.org/svn/branches/rfacebo/

Simply:

  1. Replace your installed RFacebook-0.9.8 GEM (or 0.9.7) with the contents of the GEM zip.
  2. Replace your installed rfacebook plugin (If you have 0.9.7 you can delete rfacebook_on_rails in your plugin directory) in your app with the zipped up plugin.
  3. Modify your facebook.yml file to match the format above and include your settings for Facebook and Bebo in whatever environment you choose.

(For you folks using the RFacebook on Bebo plugin you can go ahead and remove that)

After that you should be able to hit your application from either Bebo or Facebook without having to setup different mongrel instances. Let me know if I've missed something and I'll update this post or the zips. Or join the discussions on the google group. (I'd say to use the groups on the RFacebook site but the app is currently broken).

Enjoy!

Thursday, October 25, 2007

MicroPlace

I've been quite busy over the last several months and have neglected my blog for too long. So while I don't have a rails reality to post today I will make mention of a couple of things that I've been up to over the past few months.

In early June I started contracting with a small startup within eBay called MicroPlace. Josh just blogged about the launch yesterday. So rather than repeat what he said go read his writeup :-) Shortly after I started with MicroPlace Josh came on board and working with the great team from MicroPlace we began to gel as a team and things came together well.

When I started the team was pretty deep into using agile development processes as a guiding principal to their work. Test first was highly touted and code coverage was closely watched (>97% when I left). It made coming in as a consultant much easier because I was able to make sweeping changes to the code when needed and be confident that I could know what was impacted as a result. It was my first time really being in an environment where everyone had bought into the value of testing *mostly* first and it definitely makes things less stressful and more enjoyable.

Though it was a quick 3 month engagement I really am grateful for the great connections that I made with the MicroPlace team and am very proud of the job we were able to do in such a short time of getting something launched, with quality, that is for the greater good of mankind. Best of luck to MicroPlace!

After finishing up with MicroPlace in early/mid September I went on a long overdue vacation for a few weeks, to Italy. While I haven't posted them all you can check out my photos from the trip on Flickr. I just got back last Thursday night and while I'm still getting over the jetlag (waking up at 5-6AM has been great for getting the day started with a run down to the Marina green :-) I'll back on pacific time fairly soon.

Oh and a final note about my beloved Indians who found a new way to falter as I arrived back to the US (I think I'll stay overseas next year if they make the playoffs again), Go Rockies!! ;-) Thanks for a great season tribe and for the chance to watch you in the playoffs!

Saturday, June 30, 2007

iPhone activation blues for TDMA customers






That's right kids. Falling under mass hypnosis yet again, I waltzed down to the Apple store in downtown SF at about 8PM last night to see if the hypothesis would be true that I could walk right in and pick one up instead of camping out overnight. Sure enough there was no line around the corner. The line I waited in had maybe 10 people and I waited maybe 5 minutes.

Tons of phones, no problems, people all around, San Francisco's finest outside standing guard just in case.

Then it's time to go home and activate this thing before going out for the evening. Ahh yes. This is where you get a nice reminder that AT&T is the provider for this phone.

So to give a bit of history, I've been holding out for quite some time for a phone that seemed worthy of giving up my old Motorola V.60c on the old AT&T wireless TDMA network but hadn't found a worthy successor so I just kept kickin it old school. TDMA's signal reach is much further and when I'd go camping I was the only one with a phone that worked because it also was cell compatible.

So basically I've been living in the cell phone "stone age". Everytime I'd decide to go on a "date" in a Cingular, TMobile store, etc, the folks that worked there would always remark "Wow I haven't seen that phone in YEARS. How about a nice crappy new phone that has a weaker signal strength that won't work in your apartment?"

"No thanks" I'd say as I'd walk out with my old tattered TDMA phone and wonder how much longer I'd have it around. I mean after 3+ years it's just starting to get a little long in the tooth ;-) The battery lasts about a day now with any sort of usage.

A few months ago AT&T gave me an ultimatum over a text message. "Upgrade or die." They are turning off the old TDMA wireless network in Feb. 2008. Hmm... well I guess I have no choice now. Hopefully they've got more GSM coverage than the last time I looked and hopefully it works in my apt.

So back to last evening. I got home, hooked it up to the mac that I purchased during my last "mass hypnosis" episode in Jan. 2006. I updated iTunes, hooked up the phone, and started the registration process. It all was going pretty smoothly until I reached the end. AT&T then informs me that more time is needed to activate my phone and that I will receive an email once it's completed.

"Sigh. They're not able to handle this volume." But I suspected that my being on TDMA might have something to do with it. I spoke with two representatives last night that ensured me that wasn't a problem and that it was just due to volume and that they were backlogged. "By 7AM EDT your activation will have gone through sir." What a pain but OK I guess that's the way it's gotta be.

Fast forward to this morning. No activation, no email, nothing. I call up again. Each time I called I made it a point to tell them I'm on the TDMA network because for some reason the person on the other end of the line doesn't seem to have that information. This time the woman says "Oh! Yeah that's gonna be a problem. Can you please hold?" After 10-15 minutes of holding she comes back, apologizes and informs me that they can't help me over the phone. I now need to go down to the AT&T wireless store where they will convert my account to GSM by giving me a fake SIM. Then I have to go back home and activate my phone. Fun. Why this can't be done over the phone I don't know.

Anyway for all you old TDMA users, all 1% of your in the AT&T customer base, be prepared for this nonsense when you finally upgrade. Unless you convert to one of the other carriers which almost seems like the least painful way to go. If that blackberry curve with wifi was ready to roll on TMobile I might consider that instead solely based on AT&T's crappy service.

Time for this caveman to get unfrozen down at the local AT&T wireless store.




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