Installing ruby 1.9.2 on Ubuntu 10.04 with update-alternatives

This is an update to a previous post.

Credit goes to roberto.thomas and alexn.

I downloaded the ruby source code into my home temp dir and then extracted it.

I open the command prompt and typed in:

    autoconf
    ./configure --with-ruby-version=1.9.2-p180 --prefix=/usr --program-suffix=1.9.2-p180
    make
    sudo make install

    sudo update-alternatives --install /usr/bin/ruby ruby /usr/bin/ruby1.9.2-p180 400 \
        --slave /usr/share/man/man1/ruby.1.gz ruby.1.gz \
                /usr/share/man/man1/ruby1.9.2-p180.1 \
        --slave /usr/bin/ri ri /usr/bin/ri1.9.2-p180 \
        --slave /usr/bin/irb irb /usr/bin/irb1.9.2-p180 \
        --slave /usr/bin/gem gem /usr/bin/gem1.9.2-p180 \
        --slave /usr/lib/ruby/gems/bin gem-bin /usr/lib/ruby/gems/1.9.2-p180/bin

    sudo mkdir /usr/lib/ruby/gems/1.9.2-p180/bin

    sudo update-alternatives --config ruby

The “make” builds and installs into /usr/bin/ruby1.9.2-p180. The “–program-suffix=1.9.2-p180″ option appends the version number to the program name.

Note that the gem home directory is “/usr/lib/ruby/gems/1.9.2-p180/bin”. For some programs, you may have to “export GEM_HOME=/usr/lib/ruby/gems/1.9.2-p180/bin”.

NOTE: Remove gem option already registered in update-alternatives
For some reason, Ubuntu has gem as a selectable option within update-alternatives. We want gem to be dependent on Ruby so let’s remove it.

sudo update-alternatives --remove-all gem

We now control which gem to use based on the version of ruby selected (i.e. “–slave /usr/bin/gem gem” and “–slave /usr/lib/ruby/gems/bin gem-bin”).

HornetQ Stomp Producer with Core/JMS Consumer

I had trouble sending a message via STOMP and consuming the message via the Core/JMS client API.

HornetQ’s StompProtocolManager adds the stomp message to the queue in a different way to other messages added via core or JMS.

To consume the contents of a stomp message, you cannot just call readNullableSimpleString();. You need to do some buffer manipulation first.

I got this hint from org.hornetq.core.protocol.stomp.StompSession.sendMessage().

            ClientConsumer messageConsumer = session.createConsumer(queueName);
            session.start();

            ClientMessage messageReceived = messageConsumer.receive(1000);

            HornetQBuffer buffer = messageReceived.getBodyBuffer();
            buffer.readerIndex(MessageImpl.BUFFER_HEADER_SPACE + DataConstants.SIZE_INT);
            SimpleString text = buffer.readNullableSimpleString();
            String myString = text.toString();

Embedded HornetQ logging to Log4J

The documentation just says you have to set:

org.hornetq.core.logging.Logger.setDelegateFactory(new Log4jLogDelegateFactory());

However, according to http://community.jboss.org/message/579555, you ALSO have to set it in the configuration:

Configuration config = new ConfigurationImpl();
config.setPersistenceEnabled(false);
config.setSecurityEnabled(true);
config.setLogDelegateFactoryClassName(Log4jLogDelegateFactory.class.getName());

...

 _hornetqServer = new HornetQServerImpl(config, ManagementFactory.getPlatformMBeanServer(), securityManager);
_hornetqServer.start();

Do both!

Developing Sproutcore Guides on Ubuntu

EDIT 2001-09-03: This seems to be working now with the latest ruby, blunder and gems. I don’t get the errors below anymore.

1. Need to update gems to 1.4.1.

First apt-get the rubygems1.9.1 package. This will get you gems 1.3.5

Next, follow the instructions here to install rubygems from source.

2. Need to install bundle 1.3.6

sudo gem install bundler

However, I got the error message:

method_missing': undefined method `user_home' for Gem:Module

The fix is explained here.

In /usr/local/lib/ruby/gems/1.9/gems/bundler-1.0.0/lib/bundler/rubygems_ext.rb, change

-  Gem::QuickLoader.load_full_rubygems_library
+  Gem::QuickLoader.load_full_rubygems_library unless RUBY_VERSION.eql?('1.9.1')

4. Install Guides

cd sproutcoreguides
sudo bundle install --binstubs

When you try to run bin/guide preview, I got the following error.

`require': no such file to load -- bundler

The fix is to set GEM_HOME environment variable.

export GEM_HOME=/usr/lib/ruby/gems/1.9.1/

5. Run Guides

cd sproutcoreguides
bin/guides preview

A web server will start up. View your files at http://localhost:9292/.

GitHub Ubuntu SSH error: Agent admitted failure to sign using the key

Having signed up for a new account on Github.com, SSH failed to authenticate

Using ssh -v git@github.com, I get this error:

debug1: Server accepts key: pkalg ssh-rsa blen 277
Agent admitted failure to sign using the key.
debug1: Trying private key: /home/xxxx/.ssh/identity
debug1: Trying private key: /home/xxxx/.ssh/id_dsa
debug1: No more authentication methods to try.
Permission denied (publickey).

The solution is to explicitly add the id_rsa file to ssh. Must be a permission thing or something on ubuntu.

$ ssh-add ~/.ssh/id_rsa

See

http://stackoverflow.com/questions/2122465/ssh-cannot-authenticate-to-gitgithub-com

Open Source Log Consolidation Tool

Are there any out there?

I’m looking for something that consolidates Web Server, Load Balancer, FireWall, my app log and database logs in 1 place ….

Sproutcore Tutorial: Custom ListView with Reordering

I’ve been reading a few different blog posts on creating a custom list view and re-ordering. I’ve decided to consolidate what I’ve learnt into this tutorial.

Get the code here.

See it in action here.

This sample app allows you to:

  1. View a list with custom row views.
  2. Reorder items in the list by way of drag and drop.
  3. Select and delete by pressing the DELETE key.

This sample app demonstrates:

  1. How to implement a custom SC.ListView row.
  2. How to hi-light an item when hovering with a mouse.
  3. How to implement drag-and-drop re ordering.

1. Prerequisite Reading

  • ToDo tutorial on the Sproutcore wiki
  • My CRUD tutorial

2. Model

The app simulate my NBA power ranking. Guess which team I like!

The data model is really simple. There is only a single team record containing the following attributes:

  • Ranking: numeric ranking – 1 is the best team.
  • Name: name of team.
  • Reason: why the team is ranked so.

The team record is defined in reorder_sample/models/team_record.js.

ReorderSample.TeamRecord = SC.Record.extend(
  /** @scope ReorderSample.Team.prototype */ {

  ranking: SC.Record.attr(Number, { isRequired: YES }),
  name: SC.Record.attr(String, { isRequired: YES }),
  reason: SC.Record.attr(String, { isRequired: YES })

});

Fixtures for 5 teams is defined in reorder_sample/fixtures/team_record.js.

sc_require('models/team_record');

ReorderSample.TeamRecord.FIXTURES = [
  {
    guid: 1,
    ranking: 1,
    name: 'LA Lakers',
    reason: 'KB24. Enough said.'
  },

  {
    guid: 2,
    ranking: 2,
    name: 'Boston Celtics',
    reason: "Aren't these guys getting a bit old?"
  },

  {
    guid: 3,
    ranking: 3,
    name: 'Miami Heat',
    reason: 'The three headed monster has moved to South Beach.'
  },

  {
    guid: 4,
    ranking: 4,
    name: 'OKC Thunder',
    reason: 'The Durantula is hungry.'
  },

  {
    guid: 5,
    ranking: 5,
    name: 'Dallas Mavericks',
    reason: "Mark Cuban's cheering must be worth some points."
  }
];

3. Controller

This is where the majority of the work gets done.

  • The teamArrayController instances SC.ArrayController with the SC.CollectionViewDelegate mixin.
  • The SC.CollectionViewDelegate mixin provides the functionality we need to handle delete and reordering
  • Delete is handled in the collectionViewDeleteContent() method. Details are provided in the Sproutcore wiki.
  • Reordering is handled by collectionViewDragDataTypes(), collectionViewDragDataForType() and collectionViewPerformDragOperation().
  • collectionViewDragDataTypes() is called when drag is started to check if the row being dragged is “draggable”. The type of record contained in the row must be in the array passed back by this method.
  • collectionViewDragDataForType() is called next if the above check was passed. This method allows us to return some contextual data associated with the drag event. In this case, we return a SC.SelectionSet containing the selected team record. Because we have disallowed multiple selections, we should only ever have 1 team record in the selection set.
  • Finally collectionViewPerformDragOperation() is called when the user drops the row. We retrieve our selection set saved in collectionViewDragDataForType() and extract the record to reorder. We then change the rankings accordingly. Note that we turn off property change notifications during the reorder to improve performance.

The code is located in reorder_sample/controllers/team_array.js.

ReorderSample.teamArrayController = SC.ArrayController.create(SC.CollectionViewDelegate, {

  /**
   * Only allow 1 row to be selected at anyone time.
   * SC.CollectionView looks for this variable in it's content (which is this array)
   */
  allowsMultipleSelection: NO,

  /**
   * Allows this controller to properly respond to ListView delete
   * See http://wiki.sproutcore.com/Todos+05-Finishing+the+UI
   * @param view View calling the delete
   * @param content
   * @param indexes Indexes of the item to be deleted
   */
  collectionViewDeleteContent: function(view, content, indexes) {
    // destroy the records
    var records = indexes.map(function(idx) {
      return this.objectAt(idx);
    }, this);
    records.invoke('destroy');

    var selIndex = indexes.get('min') - 1;
    if (selIndex < 0) {
      selIndex = 0;
    }
    this.selectObject(this.objectAt(selIndex));
  },

  /**
   * Specifies that we are allowed to drag teams
   */
  collectionViewDragDataTypes: function(view) {
    return [ReorderSample.TeamRecord];
  },

  /**
   Called by a collection view when a drag concludes to give you the option
   to provide the drag data for the drop.

   This method should be implemented essentially as you would implement the
   dragDataForType() if you were a drag data source.  You will never be asked
   to provide drag data for a reorder event, only for other types of data.

   The default implementation returns null.

   @param view {SC.CollectionView}
   the collection view that initiated the drag

   @param dataType {String} the data type to provide
   @param drag {SC.Drag} the drag object
   @returns {Object} the data object or null if the data could not be provided.
   */
  collectionViewDragDataForType: function(view, drag, dataType) {
    var ret = null;

    if (dataType === ReorderSample.TeamRecord) {
      return view.get('selection');
    }

    return ret;
  },

  /**
   Called by the collection view to actually accept a drop.  This method will
   only be invoked AFTER your validateDrop method has been called to
   determine if you want to even allow the drag operation to go through.

   You should actually make changes to the data model if needed here and
   then return the actual drag operation that was performed.  If you return
   SC.DRAG_NONE and the dragOperation was SC.DRAG_REORDER, then the default
   reorder behavior will be provided by the collection view.

   @param view {SC.CollectionView}
   @param drag {SC.Drag} the current drag object
   @param op {Number} proposed logical OR of allowed drag operations.
   @param proposedInsertionIndex {Number} an index into the content array representing the proposed insertion point.
   @param proposedDropOperation {String} the proposed drop operation.  Will be one of SC.DROP_ON, SC.DROP_BEFORE, or SC.DROP_ANY.
   @returns the allowed drag operation.  Defaults to proposedDragOperation
   */
  collectionViewPerformDragOperation: function(view, drag, op, proposedInsertionIndex, proposedDropOperation) {
    // content is just a reference to this object.
    var content = view.get('content');
    var ret = SC.DRAG_NONE;

    // Continue only if data is available from drag
    var selectionSet = drag.dataForType(ReorderSample.TeamRecord);
    if (!selectionSet) {
      return ret;
    }

    // Get our record - there should only be 1 selection
    var record = selectionSet.firstObject();

    // Suspend notifications for bulk changes to properties
    content.beginPropertyChanges();

    // Re ordering
    var oldIndex = record.get('ranking') - 1;  // -1 to convert from ranking # to index
    if (proposedInsertionIndex < oldIndex) {
      // Move up list
      for (var i = proposedInsertionIndex; i < oldIndex; i++) {
        this.objectAt(i).set('ranking', i + 1 + 1);  // add 1 to convert from ranking to sequence #
      }
    } else {
      // Move down list
      for (var i = oldIndex + 1; i <= proposedInsertionIndex; i++) {
        this.objectAt(i).set('ranking', i - 1 + 1);  // add 1 to convert from ranking to sequence #
      }
    }
    record.set('ranking', proposedInsertionIndex + 1);

    // Restart notifications
    content.endPropertyChanges();

    // Return the requested op, usually SC.DRAG_REORDER, to flag that the event has been handled
    return op;
  },

  /**
   * Denotes if the data source is ready
   */
  isReady: function() {
    var status = this.get('status');
    return status & SC.Record.READY;
  }.property('status').cacheable(),

  /**
   * Provides a summary of the status of the controller.
   */
  summary: function() {
    var ret = '';

    var status = this.get('status');
    if (status & SC.Record.READY) {
      var len = this.get('length');
      if (len && len > 0) {
        ret = len === 1 ? "1 team" : "%@ teams".fmt(len);
      } else {
        ret = "No teams";
      }
    }
    if (status & SC.Record.BUSY) {
      ret = "Loading..."
    }
    if (status & SC.Record.ERROR) {
      ret = "Error"
    }

    return ret;
  }.property('length', 'status').cacheable()  

});

4. View

4.1 Main Page and ListView

The main page and pane is setup as per the ToDo example in the Sproutcore wiki.

  • The contentView is a SC.ListView that is bound to our teamArrayController.
  • The rowHeight is set to 91. This is 90px for the custom row view and 1 px in order for the bottom border of each row to show.
  • The exampleView is set to our custom row view class: ReorderSample.TeamView
  • The canReorderContent and isEditable is set to YES to enable reorder functionality in the SC.CollectionView from which SC.ListView is derived.

The code can be found at reorder_sample/resources/main_page.js.

ReorderSample.mainPage = SC.Page.design({

  // The main pane is made visible on screen as soon as your app is loaded.
  // Add childViews to this pane for views to display immediately on page
  // load.
  mainPane: SC.MainPane.design({
    childViews: 'middleView topView bottomView'.w(),

    topView: SC.ToolbarView.design({
      layout: { top: 0, left: 0, right: 0, height: 36 },
      childViews: 'titleLabel'.w(),
      anchorLocation: SC.ANCHOR_TOP,

      titleLabel: SC.LabelView.design({
        layout: { centerY: 0, height: 24, left: 8, width: 300 },
        controlSize: SC.LARGE_CONTROL_SIZE,
        fontWeight: SC.BOLD_WEIGHT,
        value: 'Veeb\'s Power Rankings'
      })
    }),

    middleView: SC.ScrollView.design({
      hasHorizontalScroller: NO,
      layout: { top: 36, bottom: 32, left: 0, right: 0 },
      backgroundColor: 'white',

      contentView: SC.ListView.design({
        layout: { left: 15, right: 15, top: 15, bottom: 15 },
        contentBinding:   'ReorderSample.teamArrayController.arrangedObjects',
        selectionBinding: 'ReorderSample.teamArrayController.selection',
        selectOnMouseDown: YES,
        canDeleteContent: YES,
        rowHeight: 91,
        exampleView: ReorderSample.TeamView,
        recordType: ReorderSample.TeamRecord,
        canReorderContent: YES,
        isEditable: YES
      })
    }),

    bottomView: SC.ToolbarView.design({
      layout: { bottom: 0, left: 0, right: 0, height: 32 },
      childViews: 'summaryLabel'.w(),
      anchorLocation: SC.ANCHOR_BOTTOM,

      summaryLabel: SC.LabelView.design({
        layout: { centerY: 0, height: 18, left: 20, right: 20 },
        valueBinding: 'ReorderSample.teamArrayController.summary'
      })
    })

  })

});

4.2 Custom ListView Row

ReorderSample.TeamView is where we implement our custom row view.

  • contentDisplayProperties lists all the properties in our content (TeamRecord) that we are going to display in our custom view. It allows the row to be re-rendered if any of the listed properties changes.
  • displayProperties lists the properties of this custom view that requires the row the be re-rendered if the property is changed. We’ve listed the isHovering property because if that changes, we will have to change the background of this row to show/not show hi-lighting.
  • Hovering is triggered when the user moves the mouse over/out of the row. As such, we handle the mouseEnteredand mouseExited events and set the isHovering property accordingly. Because isHovering is listed in displayProperties, it will cause the render() method to be called.
  • The render() method gets the data for the row from the content property (set by the ListView). The context is used to render the html.
  • Other useful properties set by ListView are: contentIndex, layerId, isEnabled, isSelected, outlineLevel, disclosureState, isVisibleInWindow, isGroupView, page and parentView. See SC.CollectionView.itemViewForContentIndex().

The code can be found at reorder_sample/views/team.js.

ReorderSample.TeamView = SC.View.extend(SC.ContentDisplay, {

  layout: { left: 10 },

  classNames: ['team-view'],

  /**
   * List of content (TeamRecord) properties that needs to trigger a re-rendering when the property is changed
   *
   * Add an array with the names of any property on the content object that
   * should trigger an update of the display for your view.  Changes to the
   * content object will only invoke your display method once per runloop.
   *
   * @property {Array}
   */
  contentDisplayProperties: 'ranking name reason'.w(),

  /**
   * Flag so that we know if the mouse if hovering over this item or not
   */
  isHovering: NO,

  /**
   * List of view properties that needs to trigger a re-rendering when the property is changed.
   * We want to re-render when hovering so that we can hi-light the row
   */
  displayProperties: 'isHovering isSelected'.w(),

  /**
   * When the mouse is over this view, set the isHovering flag in order to trigger hi-lighting
   */
  mouseEntered: function() {
    this.set('isHovering', YES);
  },

  /**
   * When the mouse leaves this view, set the isHovering flag in order to turn off hi-lighting
   */
  mouseExited: function() {
    this.set('isHovering', NO);
  },

  /**
   * Render our row
   *
   * @param context
   * @param firstTime
   */
  render: function(context, firstTime) {
    var ranking = '';
    var name = '';
    var reason = '';
    var content = this.get('content');
    if (content != null) {
      ranking = content.get('ranking') + '.';
      name = content.get('name');
      reason = content.get('reason');
    }

    // If hovering, add the hovering CSS class to the DIV that is this view
    if (this.get('isSelected')) {
      context.setClass('team-view-selected', this.get('isSelected'));
    } else {
      context.setClass('team-view-hover', this.get('isHovering'));
    }

    // Output HTML. Create inner DIV to show our data
    context = context.begin('div').addClass('team-view-topfiller').push('&nbsp;').end();
    context = context.begin('div').addClass('team-view-line1').push(ranking + ' ' + name).end();
    context = context.begin('div').addClass('team-view-line2').push(reason).end();
    context = context.begin('div').addClass('team-view-bottomfiller').push('&nbsp;').end();

    sc_super();
  }
});

5. Starting Up

The last thing we need to do is to hook up the data to our controller in reorder_sample/main.js.

Note that we order by ranking. When the ranking of a record is changed during a reorder, the ListView will automatically re-render each row because the order of each item in the SC.RecordArray returned by the query would have changed.

ReorderSample.main = function main() {

  // Step 1: Instantiate Your Views
  // The default code here will make the mainPane for your application visible
  // on screen.  If you app gets any level of complexity, you will probably
  // create multiple pages and panes.
  ReorderSample.getPath('mainPage.mainPane').append();

  // Step 2. Set the content property on your primary controller.
  // This will make your app come alive!
  var query = SC.Query.local(ReorderSample.TeamRecord, {orderBy: 'ranking'});
  var teamRecords = ReorderSample.store.find(query);
  ReorderSample.teamArrayController.set('content', teamRecords);
};

function main() {
  ReorderSample.main();
}

Sproutcore: Cannot call method ‘create’ of undefined

I was playing around with a SC.View (MyApp.AView) which has a SC.ListView with a custom exampleView (MyApp.BView).

I kept getting the error when SC.ListItem was trying to render the child view.

Cannot call method 'create' of undefined

Turned out to be a script sequencing problem. MyApp.BView was instanced AFTER MyApp.AView. It needed to be instanced before MyApp.AView.

The fix is to use sc_require to make sure that dependencies are enforced.

sc_require('views/b');

That was a good morning’s work!

Thoughts on Node.js

Here’s my research and thoughts on Node.js to date.

Some details of the Ryan Dahl, creator of Node.js here. Here are the 9 biggest challenges for Node.js from Ryan from his JSConf 2010 EU talk.

Best article that explains event loop based web servers and why you can get more performance out of them compared to thread based web servers.

Some Thread Based Web Servers

  • Apache
  • Microsoft IIS
  • Tomcat (however there is a comet event based module that can be used)

Some Event Based Web Servers

Am I ready to use Node.js?

I really want to use Node.js. It seems right to be using javascript on the client (with Sproutcore) and server side.

However, I’ve turned into a soft programmer that needs a visual debugger. My excuse is that I’m now on the wrong side of 40. I’ll wait it out a bit long to see if a javascript visual debugger for Node.js script eventuates before switching over.

In the meantime, I think I will look into using Twisted and Grizzly (and the associated Jersey project).

What’s with the guid/uuid directory created by sc-build?

When I run sc-build, the output is placed in ./tmp/build/static/app_name/en/b82a3bf91217954903a4fd7da14707caa9f47520.

This page in the Sproutcore wiki suggests that the guid/uuid directory is the build number.

Deploying

  • When you are ready to deploy your app, all you need to do is run sc-build.
  • sc-build generates a bundle of HTML, JS, and CSS inside a directory with a MD5 hash code as a build number.
  • The hash code is based on the contents of the files, so changing your files will change the hash code.
  • When you deploy your app, you just need to copy the entire contents of the tmp/build directory to a static web server.
  • Your web server should be configured to serve everything EXCEPT for the index.html file with a 1-year expiration header. This will allow your assets to be cached “permanently” on browsers once they load them.
  • Since the index.html file references the resources it needs by build number, deploying a new version of your app will automatically load the new assets.
  • Once you’ve copied your files out, you should symlink the build directory for the app you want to load to the actual URL you want the user to visit to load your app.
  • Example: if you want ppl to visit your app at http://myapp.com/appname and your built project has a directory called static/appname/123efab45aeb29c3de4, then you should symlink /appname -> /static/appname/123efab45aeb29c3de4.
  • You can actually have multiple built versions of your app deployed this way since each one will have a different build number. Just create a different symlink to each version you want to load.

So, if you specify a build number when using sc-build, the guid/uuid will be replaced with a nice looking build number.

For example, sc-build –build=0001 -rcv –languages=en will create the directory structure:

./tmp/build/static/app_name/en/0001.