Corey Innis's blog



Corey InnisCorey Innis
Bundler + Cruise... Take Two
edit Posted by Corey Innis on Wednesday November 25, 2009 at 07:00AM

Responding to my last post, Josh pointed out what should probably be obvious: It's likely a bad idea to bundle bundler. There's a potential for version conflicts.

For our second attempt, we're

As a second attempt, we're now cribbing from the continuous integration setup from the Rails project. So far, so good:

Our RAILS_ROOT/cruise_config.rb...

require 'fileutils'

Project.configure do |project|
  project.build_command = 'sudo gem update --system && ruby lib/cruise/build.rb'
end

And, the referenced lib/cruise/build.rb (the important parts)...

#!/usr/bin/env ruby
require 'fileutils'
include FileUtils

def root_dir
    @root_dir ||= File.expand_path(File.dirname(__FILE__) + '/../..')
end

def rake(*tasks)
  tasks.each { |task| return false unless system("#{root_dir}/bin/rake", task, 'RAILS_ENV=test')}
end

build_results = {}

cd root_dir do
  build_results[:bundle] = system 'gem bundle'  # bundling here, rather than in a task (not in Rails context)
  build_results[:spec] = rake 'cruise:spec'
end

failures = build_results.select { |key, value| value == false }

if failures.empty?
  exit(0)
else
  exit(-1)
end

Thanks go to

  • Josh Susser for help via email
  • John Pignata for suggesting we look at the Rails project
  • Rails team for the reference scripts

More comments and suggestions are encouraged.

Corey InnisCorey Innis
Bundle(r) Yourself... and Get Ready to Cruise!
edit Posted by Corey Innis on Tuesday November 17, 2009 at 07:00PM

On a current project, we've just switched from GemInstaller to Bundler for managing our application's gems.

All in all, the transition was painless... in our development environments. Of course, in order to keep things on running smoothly on the continuous integration box, we amended our rake cruise:spec task to start by running sh "gem bundle".

So, wonderful! Any changes to our gem dependency list will picked up when cruise starts and made available for that "build"; there's no need to log in and make any manual updates. Right?

Not quite. We're using the disable_system_gems option and, in that case, Bundler (very intentionally) modifies your GEM_PATH such that only "vendor'd" gems are available to the application. Which of course means that Bundler itself, being "a gem to bundle gems", is unavailable when that sh "gem bundle" command is run.

Our solution: bundle Bundler, obviously! That's right, in our Bundler Gemfile we've included gem "bundler". Now, after a single manual execution of gem bundle to pick up the bundled Gem Bundler gem bundle (heh), any subsequent Gemfile changes (to gems other than Bundler) get picked up at the start of the build... and we're cruisin'

Have another solution? Please let us know in the comments.

The Label

"Tasty! No Trans-Fats."

I've now worked with a number clients who have a taste for a "rich text" editor experience. Each time, the team initially looked in to using an off-the-shelf package, e.g.:

Each of these client projects had fairly basic needs. Pretty much everyone wants "bold", "italic", "underline". Throw in bulleted and numbered lists and perhaps a few other trinkets and you've pretty much covered the use cases.

It turns out that the existing packages are generally tricky to integrate and modify or configure per your particular needs. This makes sense. A full-blown Javascript RTE package needs to have capabilities far in excess of anything a given project requires, simply in order to meet every project's requirements. You end up with a massive library that is not trivially integrated into your site. What to do some TDD/BDD on the RTE interaction with your page? Good luck.

So, I've generally suggested that we roll our own.

I know, crazy. Why write something new and custom when so many others have already solved all of the issues with, for example, cursor movement and text editing and command handling (e.g., making the text "bold") and image placement, and...?

Well, it turns out that all of that behavior is already built in to the browser. Yes, my browser, your browser. Pretty much every browser you need to care about1. On top of that, the basics are extremely simple.

To many, this may not be news at all. The basic capability has been around since MSIE 5.5 was released in 2000. On the other hand, I've worked with a number of very smart and seasoned web developers who were unfamiliar with the extent to which it is the browser, rather than the libraries, that has taken care of the hard parts.

Ingredients

What you'll need to make this work2:

// enable editing:
document.contentEditable = true;
document.designMode      = 'on';

// make something bold:
document.execCommand('bold', false, []);

It's pretty much as simple as that. Give it a try: paste the following snippet into your browser's address bar, hit enter, and edit text or resize images on this page to your tummy's delight.

javascript: function enable_DM() { document.contentEditable = true; document.designMode = 'on'; }; enable_DM();

Preparation

So, with those ingredients we can make quite a mess of the page we're visiting, but we've hardly created a delicious RTE. To go the next step, we need a way grab the HTML representation of our edited content to send with a form submission.

The basic idea here, which is what every editor out there employs, is to embed an <iframe />, the document of which is editable, and when appropriate copy the HTML of that document to a (likely hidden) textarea within your form.

Why an iframe? There are two important reasons:

  1. You probably want to protect the look and behavior of your edited document from the styles and scripts of the rest of your site.
  2. Until version 3, Firefox only supported designMode, which is applicable to the document, but not to an element within your page.

There are slight browser differences that will come in to play, e.g. MSIE's IFrameElement.document property versus Firefox's (and the W3C standard) IFrameElement.contentDocument. As long as your editor remains fairly simple these intricacies will cause very little trouble.

I'll leave it to you to hook things up to your liking, but will toss in this morsel: On a current project, we're using a home-baked RTE which, from 100 very-nicely-formatted lines of Javascript (not counting the supporting jQuery and Disco libraries), is providing...

  • DOM building of the iframe and command toolbar (<ul /> with "buttons"), etc.
  • Document editability
  • Registration and handling of 8 commands (e.g., "bold", "italic", "indent")
  • Content transfer to-and-from an associated textarea
  • Additional helpers (e.g., get the currently-selected text as HTML)

Tasting Notes

  • 1 My apologies to visitors using a specialized browser for accessibility.
  • 2 Setting document.designMode should be enough, but there are reports of discrepancies in MSIE's handling of the two.

Shopping List

Corey InnisCorey Innis
New York Standup 03/19/2009
edit Posted by Corey Innis on Thursday March 19, 2009 at 02:35PM

Interesting

  • We've created a Pivotal fork of the Ctags bundle for TextMate (the original).

    Ctags provides a mechanism for indexing your source code files for the purpose of locating and navigating between bits of code; something IntelliJ/RubyMine is very good at, while TextMate is found lacking.

    The forked bundle aims to be more inline with Pivotal practices, where developers are often switching between IDE/editor worlds. For example, code completion has been re-mapped to ctrl-space, to be more like IntelliJ.

    Have you played with Ctags? Take a look at the bundle; see what you think.

Help Wanted

  • We're on the hunt for quality burritos in NYC. Ben gave Burrito Loco a try last night; thought the burritos were "supple". Dave is desperately seeking breakfast burritos.

    Suggestions?

Corey InnisCorey Innis
New York Standup 03/12/2009
edit Posted by Corey Innis on Friday March 13, 2009 at 02:59AM

Interesting

  • Ben noted, you can give your Ruby command-line tools some (RDoc::)usage
  • Rails Boost, where one may "generate a ... bootstrapped Rails app", now includes fixjour as an option to the generator
  • Joe and Ryan recently created a Pivotal fork of Pat's acts_as_fu to get it to play nicely with your application and its database connections

Corey InnisCorey Innis
New York Standup 03/11/2009
edit Posted by Corey Innis on Wednesday March 11, 2009 at 06:59PM

Help Wanted

  • We're interested in running some sort of javascript validation and syntax checking suite on one of our projects. JSLint looks reasonable for the framework and can be run from the command line with Rhino.

    If you have experience with or thoughts about the idea, please share.

Corey InnisCorey Innis
New York Standup, Overdue from the Week of 03/02/2009
edit Posted by Corey Innis on Wednesday March 11, 2009 at 01:13PM

Interesting Things

  • An EngineYard-hosted project had an issue with monit attempting to restart mongrel too often. It turned out that the mongrel processes were not dropping pid files soon enough. The EngineYard-suggested fix:

    • Upgrade mongrel to 1.1.5.1 (patched to drop the pid file faster)
    • Upgrade to monit 5.0 beta 6
    • Update to the latest ey-monit-scripts
  • FiveRuns dash is a cool, customizable metrics service. Pat created a plug-in for sending continuous integration stats there: dash-ci.
  • Using Cucumber to test Capistrano deployment:

    cap --dry-run will run Capistrano without completing the actual task (e.g., deployment). Cucumber can then be used to write some nice, story-like deployment expectations that search the Capistrano output to document your project's deployment process and ensure the documentation remains valid. Something like:

    Feature: Deployment
      In order to deploy the application
      As an administrator
      I should run Capistrano commands
    
    
      Scenario: Deploying
        Given I am working from the RAILS_ROOT directory
          And the parent directory is a Git repository
        When  I run a deployment task
        Then  'scm_user' for the deployment should be derived from the Git config for the remote origin
    
    
      Scenario: Deploying to demo
        Given I am working from the RAILS_ROOT directory
        When  I run 'cap demo deploy'
        Then  the deploy should succeed
          And the deployed code matches the latest 'web/stable' tag
          And the deployed code should be marked with a new 'web/demo' tag
    

    More on this to come.

  • Using Selenium to ensure unique IDs in your DOM:

    # ----------------------------------------------------------------------
    # The examples below illustrate the technique with the Prototype and
    # jQuery libraries, respectively.  Both use Pat Nakajima's selenium
    # helper for executing javascript in the tested browser window.
    # 
    # For more on that helper, see:
    # http://pivotallabs.com/users/patn/blog/articles/717-run-javascript-in-selenium-tests-easily-
    # ----------------------------------------------------------------------
    
    
    # ----------------------------------------------------------------------
    # with prototype
    # note the exception catching... prototype chokes on invalid IDs
    # e.g., "invalid_id[][]"
    # ----------------------------------------------------------------------
    def assert_unique_ids
      audit_json = run_javascript <<-JS
        audit_ids = function() {
          var results = {};
          $A($$('*[id]')).each(function(element) {
            if(element.id.replace(' ', '').length > 0) {
              try {
                if($$('#' + element.id ).length > 1) {
                  if( ! results.duplicates) {
                    results.duplicates = {};
                  }
                  var count = results.duplicates[element.id] || 0;
                  count ++;
                  results.duplicates[element.id] = count;
                }
              }
              catch(err) {
                // uncomment to capture invalid IDs
                // var invalid = results.invalid || [];
                // invalid.push(element.id);
                // results.invalid = invalid;
              }
            }
          });
          return ($H(results).toJSON());
        }
        audit_ids();
      JS
      assert_equal({}, JSON.parse(audit_json)), 'Expected no duplicate IDs')
    end
    
    
    # ----------------------------------------------------------------------
    # with jQuery
    # additionally depends on the jquery-json plugin:
    # http://code.google.com/p/jquery-json/
    # ----------------------------------------------------------------------
    def assert_unique_ids
      audit_json = run_javascript <<-JS
        audit_ids = function() {
          var results = {};
          $('*[id]').each(function() {
            if(this.id.replace(' ', '').length > 0) {
              if($('*[id=' + this.id + ']').length > 1) {
                if( ! results.duplicates) {
                  results.duplicates = {};
                }
                var count = results.duplicates[this.id] || 0;
                count ++;
                results.duplicates[this.id] = count;
              }
            }
          });
          return $.toJSON(results);
        }
        audit_ids();
      JS
      assert_equal({}, JSON.parse(audit_json)), 'Expected no duplicate IDs')
    end
    

Help Wanted

  • One project recently moved from Rimu to EngineYard, only to find their Mongrel processes double in memory consumption. Any thoughts on why?

Corey InnisCorey Innis
Standup 05/30/2008
edit Posted by Corey Innis on Friday May 30, 2008 at 06:25PM

Interesting Things

  • ActiveRecord's #method_missing takes precedence over private methods, which means you cannot simply mark "private" database-derived attributes.
    code:

    # File: app/models/rock_star.rb
    #
    # == Schema Information
    # Schema version: 1
    #
    # Table name: rock_stars
    #
    #  id            :integer         not null, primary key
    #  real_name     :string(255)     
    #  band_name     :string(255)     
    #  personal_life :string(255)     
    #
    
    
    class RockStar < ActiveRecord::Base
      def method_missing(method, *arguments, &block)
        puts "I see you've sent my #{method} back and my ActiveRecords and they're all scratched"
        super
      end
    
    
      private
    
    
      def personal_life=(arg)
        puts "Vanish in the air you'll never find me"
        attributes[:personal_life] = arg
      end
    end
    

    script/console:

    Loading development environment (Rails 2.0.2)
    >> sting = RockStar.new(:real_name => 'Gordon Sumner', :band_name => 'The Police')
    I see you've sent my real_name= back and my ActiveRecords and they're all scratched
    => #<RockStar id: nil, real_name: "Gordon Sumner", band_name: "The Police", personal_life: nil>
    >> sting.personal_life = "I'll be watching you"
    I see you've sent my personal_life= back and my ActiveRecords and they're all scratched
    => "I'll be watching you"
    

    Potential solutions:

    • Convention... name "private" database attributes with leading underscores
    • Exception:

      class RockStar < ActiveRecord::Base
        def personal_life=(arg)
          raise "Protest is futile, nothing seems to get through"
        end
      end
      
    • Have another? Post a comment.

  • ||= ("or equal") blows up you have a public "writer", but a private "reader"; makes sense, but still worth a mention.
    code:

    class Model < ActiveRecord::Base
      def field_name=(arg)
        @field_name = arg
      end
    
    
      private
    
    
      def field_name
        @field_name
      end
    end
    

    script/console:

    Loading development environment (Rails 2.0.2)
    >> instance = Model.new
    => #<Model id: nil, field_name: nil>
    >> instance.field_name = 'lala'
    => "lala"
    >> instance.field_name ||= 'dodo'
    NoMethodError: private method `field_name' called for #<Model:0x17df6d0>
      from /Library/Ruby/Gems/1.8/gems/activerecord-2.0.2/lib/active_record/attribute_methods.rb:205:in `method_missing'
      from (irb):4
    
  • ActiveRecord writers always return the passed in argument, even if you define some other return value. This also makes sense -- necessary for chaining, etc., but what the heck...
    code:

    class Model < ActiveRecord::Base
      def field_name=(arg)
        @field_name = arg            
        return "custom return value"
      end
    end
    

    script/console:

    Loading development environment (Rails 2.0.2)
    >> instance = Model.new
    => #<Model id: nil, field_name: nil>
    >> instance.field_name = 'lala'
    => "lala"
    

Corey InnisCorey Innis
Firebug Makes...
edit Posted by Corey Innis on Saturday March 01, 2008 at 02:43AM

And you thought it helped us be more productive, ha!

Alt text

Corey InnisCorey Innis
Standup 02/11/2008
edit Posted by Corey Innis on Monday February 11, 2008 at 11:57PM

Interesting Things

  • Got Example?

    Don't forget to make use of the reserved (top- and second-level) domain names set aside by RFC 2606... especially if you find yourself writing something like:

    result = @model.do_request('http://www.somebogusdomain.com')
    result.code.should == 1001
    
    
    # NOTE: www.somebogusdomain.com actually exists!
    

Other articles: