strapyourself.in and flouri.sh

I'm working at Pivotal

April 30th, 2008
After a 4 month interlude at SpongeFish, I'm now working at Pivotal Labs. Pivotal's a really fun place to do agile rails consulting and I'm loving it so far...

MySQL query_reviewer plugin

February 13th, 2008

query_analyzer on steriods!

Features

QueryReviewer is an advanced SQL query analyzer. It accomplishes the following goals:

  • View all EXPLAIN output for all SELECT queries to generate a page
  • Rate a page's SQL usage into one of three categories: OK, WARNING, CRITICAL
  • Attach meaningful warnings to individual queries
  • Find out where the query was executed with a stack trace
  • Display advanced interactive summary on page

It accomplishes this by injecting an absolutely positioned div into your HTML markup before it's sent out of rails. View injection can be turned off, if necessary and replaced with <%= query_review_output %> somewhere in your view.

Screenshot

View larger

Project homepage

http://code.google.com/p/query-reviewer/

Installing

svn export http://query-reviewer.googlecode.com/svn/trunk/ vendor/plugins/query_reviewer

Then optionally run "rake query_reviewer:setup" and edit config/query_reviewer.yml to change the default settings.

Mephisto Flickr AJAX Loader

December 1st, 2007

I'm tired of waiting for my blog to load because of a slow flickr feed request! I designed this new plugin by copying the flickr photostream plugin, but making it actually load the pictures and the feed through an immediate AJAX request. This tremendously increased the performance of my mephisto pages!

The plugin defines a flickr controller, which accepts the AJAX request. Unfortunately, you can't change how the photos are laid out anymore (I'm not setting up a liquid context for this ajax response). Of course, if you don't like the result of the plugin, just change it to match what you want:

class FlickrController < ActionController::Base
  def index
    result = ""
    pics = find_pictures
    pics.each do |pic|
      result << "<a href='#{pic.link}'><img src='#{pic.send(params[:format].to_sym)}' alt='#{pic.title}'></a>"
    end

    render :inline => result
  end
end

How to install and use

  • Uninstall the flickr photostream plugin
  • ./script/plugin install http://wush.net/svn/public/plugins/mephisto/mephisto_flickr_ajax
  • Use the following tag in your liquid template:
    {{flickrajaxphotostream feed:<YOUR_FEED_URL> count:<NUMBER_IMAGES> format:<[square, small, etc]>}}
    {{endflickrajaxphotostream}}
  • Originally posted on ELC's blog

Rendering views without a web request in rails

November 13th, 2007

Why the heck do you want to do that?

Views are very well integrated into the rails framework, but they're only typically rendered when an http request comes in. ActionMailer is the main exception, but what if you want to render a view for use in another backend application? These days, document fragments are being used everywhere, and often times I'll need rendered HTML for a use other than just sending it back to the requesting user.

A solution: instantiate a controller and view

Controllers are just objects, and so are views. We can instantiate a controller, instantiate a view, then point the view to the controller and we're ready to go. The only think we're missing is the session and the request objects, but not every view needs those. I used this once for updating a facebook profile using a backgroundrb worker:

class FakeView < ActionView::Base
  include SomeHelper
  include SomeOtherHelper
end

class FakeController < ActionController::Base
  def render_some_view
    action_view = FakeView.new(File.join(RAILS_ROOT, "app", "views"), {})
    action_view.instance_variable_set("@controller", self)
    markup = action_view.render(:partial => 'facebook/your_profile')    
  end
end

By subclasses ActionView::Base, you can mix helpers into the view class, making their methods available.

Another solution: use the test framework!

The rails TestProcess is the only place where views are rendered. If you really want to simulate a real experience when rendering a view, use the test process. First, you'll need an actual controller with an actual action you want to render in it, like this one:

class FacebookController
  before_filter :login_required
  
  def your_profile    
  end
end

Then, we create and instante a test as follows:

class FacebookTest
  include ActionController::TestProcess
  attr_reader :response
  
  def initialize
    require_dependency 'application' unless defined?(ApplicationController)
    @controller = UserScheduleEntryController.new
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new
  end
  
  def render_your_profile(user)
    @controller.instance_variable_set("@user", user) # bypass login required
    get :your_profile
    @response
  end  
end

test = FacebookTest.new
test.render_your_profile(user)
markup = test.response.body

The require_dependency was something I threw in because backgroundrb didn't have some of the required classes loaded at that point, it may not be something you need in your application.

I originally posted this article on ELC's blog

Duplicate Migrations in Rails

November 6th, 2007

Why we need duplicate migrations

Have you ever been working on a large project, and had people check in migrations with the same numbers? It's happened to me probably no less than 10 times in the last year. In each case, the situation is recoverable, but sometimes requires a lot of manual rolling back of specific migrations on possibly several machines. Then you have to renumber all the migrations after the conflict, of course.

An even worse situation is when a project is branched and remerged. For example, you might want to branch out several complicated features from trunk for a few weeks, then bring them back when complete. Assuming you create 2 feature branches (for adding profiles and friends to your users), you could end up with something like this:

  • 036_modify_users_to_include_first_name.rb
  • 037_create_profiles.rb
  • 037_create_friendships.rb
  • 037_fix_a_bug.rb
  • 038_add_timestamps_to_friendships.rb
  • 038_modify_accounts_to_limit_length.rb
  • 039_modify_users_to_include_gender.rb

In the above situation, the person merging the two branches has a very difficult situation ahead. Everyone working on the project is probably on revision 37 (profiles branch), 38 (friends branch), or 39 (trunk). The safe way to proceed with traditional rails migrations is to force all machines be migrated down to 36. No new migrations can be added while the migrations are then renumbered so they range from 036 to 042. Finally, all users can update from trunk and run rake db:migrate. Of course, people often forget to migrate down, and end up stuck in the middle of a sequence of migrations that has been renumbered (I am so tired of reversing migrations by hand).

Solution: Allowing duplicate migration version numbers

In the above example, the 3 migrations numbered 37 are not dependent in any way. Because they had to be developed independently, duplicate version numbers are very rarely dependent. For this reason, we beleive that it is usually safe to create a "partial ordering" of migrations rather than an exact ordering. In this partial ordering (which can be represented as a lattice), migrations with the same version number will be run in an arbitrary order:

lattice

Since all of the dependencies in the above lattice flow downward, we can satisfy the partial ordering by running the migrations alphabetically by filename, alphabetizing them first by version number and then by class name. This will only work if we can make the assumption that when new migrations are added, they can only be dependent on those with smaller version numbers.

How the plugin works

Traditional rails schema_info table cannot hold enough information to keep track of which migrations have been run, so we need to adopt a new schema format, which we place in a new schema_infos table:

schema_infos schema

In this new schema, every record represents a migration that has been run. By traversing this table, we can get an accurate picture of the state of the system, and decide which migration to run next.

If we want to migrate to version 10, for example, we create an alphabetical listing of migrations up to and including version 10(s). Then we traverse that list in order, running "up" on migrations which have not been previously run, and inserting a record into schema_infos. Finally, we create a list of migrations with version numbers larger than 10, and run "down" on those in reverse alphabetical order, removing the entries in schema_info.

A little under the hood

Below is the main migrate function. It does exactly what is discussed in the previous section:

def migrate_with_duplicates
  migration_classes_before(@target_version).each do |(version, migration_class)|    
    next if schema_information_contains?(migration_class)
    ActiveRecord::Base.logger.info "Migrating up #{migration_class} (#{version})"
    migration_class.migrate(:up)
    insert_schema_information(migration_class)
  end
  
  migration_classes_after(@target_version).each do |(version, migration_class)|              
    next if !schema_information_contains?(migration_class)
    ActiveRecord::Base.logger.info "Migrating down #{migration_class} (#{version})"
    migration_class.migrate(:down)
    remove_schema_information(migration_class)
  end
end

What would be even better...

I've always wanted to write a migration system based on partial orderings where dependencies are explicit, and version numbers are history. Such a system would work nicely on top of the new schema_infos table format. The tricky part would be how to state the dependencies without forcing the migration author to work too hard.

Download

From the ELC plugin repository: http://wush.net/svn/public/plugins/duplicate_migrations

To install:
./script/plugin install -x http://wush.net/svn/public/plugins/duplicate_migrations
(installing automatically creates the schema_infos table and populates it, but does NOT delete your old schema_info table... don't panic!)

Originally posted on ELC's blog

Writing view helpers with 'yield'

November 3rd, 2007

Ever wanted to pass in blocks of view code to a helper? Think of the power! Say we want to make a helper method which surrounds whatever block you pass in with a firefox html designer iframe, here's how I'd imagine a user would want to use it:

<% html_editor(:width => 600, :height => 400) do %>
  <p><b>This html should be editable by the user!</b></p> 
<% end %>

Here's how you could write such a helper function:

class ApplicationHelper
  def html_editor(options = {}, &block)
    concat("<IFRAME WIDTH=#{options[:width] || 400} HEIGHT=#{options[:height || 200]} ID=myEditor>")
    yield
    concat("</IFRAME>")
    concat("<script>frames.myEditor.document.designMode = 'On'</script>")
  end
end

Notice that I pass the block in as the last parameter "&block", but I never use it in the function! Ruby doesn't require that you pass the name of the block in when you yield, because it only supports the passing in of 0 or 1 blocks to a function (hence it always knows which one you're talking about). I like to pass it in as a named method parameter because it makes the method signature more clear.

Now let's improve on our initial version, by passing an object back to the view. This is exactly the way form_for passes back a |form| object to the view. Our updated use case is as follows:

<% html_editor(:width => 600, :height => 400) do |editor| %>
  <p><b>This html should be editable by the user!</b></p>
  <% editor.command_button("Bold", "bold") %>
<% end %>

The "command_button" method above should insert a standard html button with the text "Bold" that executes the "bold" command on the selected text range inside the html editor. Let's see how this can be implemented:

class JsHtmlEditor
  attr_reader :output
  def command_button(name, command)
    @output ||= ""
    @output << "<button 
        onclick='javascript: frames.myEditor.document.selection.createRange().execCommand(\"#{command}\")'>
        #{name}</button>\n"
  end
end

module ApplicationHelper
  def html_editor(options = {}, &block)
    editor = JsHtmlEditor.new("myEditor")
    concat("<IFRAME WIDTH=#{options[:width] || 400} HEIGHT=#{options[:height || 200]} ID='myEditor'>")
    yield editor
    concat("</IFRAME>")
    concat("<script>frames.myEditor.document.designMode = 'On'</script>")
    concat(editor.output) unless editor.output.blank?  # stick the user buttons at the bottom
  end
end

Pretty cool huh? When we create a new editor and yield it, we make its methods accessible to the view block. In this example, the user can execute editor.command_button whereever they want inside the block, but we collect the output of those statements and force them all to the bottom of the iframe (where they probably belong).

Another common use of blocks inside view helpers is simply to control whether or not they get yielded at all. If you don't call yield, the block never executes and never gets rendered. Often times we have complex conditions determining when a certain block of code should be rendered, and this approach can clean that up immensely.

I work at ELC

November 2nd, 2007

I've been working at ELC Technologies/Rightcart.com since February of 2006... and I love it there! In this section of my blog, you'll find all the posts that I did on the request of ELC.

Nesting document.write

September 23rd, 2007

Not a good idea - IE and Firefox treat differently

I'm not an advocate of document.write by any means, but it seems unavoidable when dealing with 3rd parties. Ad networks especially seem to rely on delivering ads using a remote script tag and document.write. They typically do not provide an alternative using the more modern and well defined XML DOM manipulation functions such as createElement and appendChild.

Let's examine two simple examples of using nested document.writes, and see how they are treated in both IE and firefox:

<div id="before_main_script">Before Main Script</div>
<div id="main_script">
 <script>
  document.write("<div id='before_sub_script'>Before Sub Script</div>");
  document.write("<scr"+"ipt>document.write('<div id="hello_div">hello!</div>');</scr"+"ipt>")
  document.write("<div id='before_sub_script'>After Sub Script</div>");
 </script>
</div>
<div id="after_main_script">After Main Script</div>

You can see how I escape the inside <script> and </script> tag so the browser doesn't think I'm closing the outside script tag. In both IE and firefox, this renders how you'd expect:

Output in both IE7 and Firefox2:

Before Main Script
Before Sub Script
hello!
After Sub Script
After Main Script

Firefox2 DOM using Firebug:

IE7 DOM using Internet Explorer Developer Toolbar:

Now let's make the example more complicated and watch things break down. All we've done is refactored the embedded script tag into an external script with a src attribute:

<div id="before_main_script">Before Main Script</div>
<div id="main_script">
 <script>
  document.write("<div id='before_sub_script'>Before Sub Script</div>");
  document.write("<scr"+"ipt src='/say_hello.js'></scr"+"ipt>")
  document.write("<div id='after_sub_script'>After Sub Script</div>");
 </script>
</div>
<div id="after_main_script">After Main Script</div>

Output in FireFox2:

Before Main Script
Before Sub Script
hello!
After Sub Script
After Main Script

Output in IE7:

Before Main Script
Before Sub Script
After Sub Script
hello!
After Main Script

IE7 DOM using Internet Explorer Developer Toolbar:

Unexpectedly, IE7 did not process the nested script tag as it was being written into the document stream, instead waiting until it had finished processing the parent script. As browser implementation goes, this approach should be easier, because you wouldn't have to save the running script context temporarily to being processing a new one, and finally resume the saved context. We've also tried the same code without the nested script tags, and this weird behavior goes away. Naturally, this can get you into serious trouble if you're including a remote script that defines certain variables which you make use of immediately!


You might be skeptical that this could ever become an issue in the real world. One of our clients had to generate some javascript to skin a 3rd party site to look like theirs, and they wanted to put double-click ads in. This requirement resulted in a javascript which executed a series of document.writes to skin the 3rd party site, including writing out a 2 inline script tags and 1 with SRC to doubleclick. The doubleclick script then used document.write to print out the ad. We first noticed the difference in placements of the ads in IE7 and Firefox, and ran the above tests to determine what the problem is. Here's how we solved it:

document.write("<scr"+"ipt src='/before_3rd_party.js'></scr"+"ipt>");
document.write("<scr"+"ipt src='3RD_PARTY_URL.js'></scr"+"ipt>");
document.write("<scr"+"ipt src='/after_3rd_party.js'></scr"+"ipt>");

I originally posted this article on ELC

Preloading fixtures

May 31st, 2007

Ever wonder why it takes so gosh darn long to run your tests?

Do you hate it when forgetting to load a valuable fixture breaks your test?

In my experience, long test times are due to two reasons: fixture loading and fixture instantiation

If you don't use instantiated fixtures such as @users_one, and instead use users(:one), then you can safely turn off instantiated fixtures, saving you all the time of finding the data. But what about fixture load time? If you use transactional fixtures, the loading only takes place once per test file, which could be 100-150 times in a given project. Without transactional fixtures, they are loaded before every test, clearly a waste of time!

My solution was to develop a plugin which proloads and preinstantiates the fixtures only once (well, 3 times was the best I could get). It takes advantage of transactions to rollback the state of the database between each individual test (don't try this on MySQL with bad storage engines like MyIASM, kids). It usually takes a few seconds, of course, but cut a down "rake test" for a large project of ours from 20 minutes to 90 seconds. Plus, you don't have to remember which fixtures you need for every single test file anymore! Try adding the globalization plugin, for example, and you'll realize you need 2 fixtures on every single page.

In my opinion, there's no reason not to preload all your fixtures once before running tests, but that's up to you to decide.

Plugin Usage

  1. Install the plugin
  2. Add the following to test/test_helper.rb
    PreloadFixtures.preload!
    PreloadFixtures.instantiate!(Test::Unit::TestCase)
    
  3. The "fixtures" function in a test is now overloaded to do nothing. All fixtures in the directory will be loaded once and then the tests will be run.

Download Plugin

From our svn respository

./script/plugin install https://wush.net/svn/public/preload_fixtures

Originally posted on ELC

See ELC's other Rails Plugins

Using and testing multiple databases in rails part 2

May 30th, 2007

I previously published a plugin on this blog that allows you to easily use, test, and migrate multiple databases in rails.

Since then, I've found numerous problems and areas that could be improved about this process. Here they are:

  • All databases are automatically cloned through rake tasks that are dependent on standard "rake test" commands.
  • Transactions are properly set up across ALL open connections during testing (if transactional fixtures are enabled)
  • This plugin is now in use in a large-scale production environment!
  • Compatibility with my preload fixtures plugin to make fixture loading super fast and transactional (coming soon to this blog)

Examples of how to use it:

To point a model at a different database:

class OtherDbBase < ActiveRecord::Base
  use_db :prefix => "otherdb"
  # will look for otherdb_development, otherdb_test, etc. in database.yml
end

To have a migration run on a different database

class AddOtherDbStuff < ActiveRecord::Migration
  def self.database_model
    OtherDbBase
  end
end

Sample config/use_db.yml file:

db1:
  prefix: db1_
db2:
  prefix: db2_ 

To run tests across multiple databases (requires file config/use_db.yml):

rake test

To have fixtures load into a different database:

# Do nothing!  Just name the fixture the same name as the model is represents (in our case it would be other_db_bases.yml), and the fixture loader will use that class's database connection to insert fixtures.

# I realize this might limit some people, especially those with habtm fixutres on other DBs (since there's no model).  I'd love your input on where we should specify which DB connection a fixture should use to load itself.

Want to download this plugin? Download from ELC's SVN repository OR:

./script/plugin install https://wush.net/svn/public/use_db

Originally posted on ELC

See ELC's other Rails Plugins

Why associated models don't save

May 21st, 2007

Acts_as_taggable conflicting with has_one

In our dashboard app, we attempted to use the following in a controller:

def create
  @employee = Employee.new(params[:employee])
  @employee.address = Address.new(params[:address])
  @employee.save
end

class Employee < ActiveRecord::Base
  has_one :address, :as => :addressable
  acts_as_taggable
end

Result: tags in params[:employee][:tag_list] weren't being saved correctly. I then changed the order in the model:

class Employee < ActiveRecord::Base
  acts_as_taggable
  has_one :address, :as => :addressable
end

The tag_list gets saved! What gives?

Failed validation weirdness

The culprit in this case was validation. There are two after_save filters involved: acts_as_taggable and has_one. Acts_as_taggable always returns true, continuing the the filter chain, regardless of the success of tagging/tag updating (I this that's a bug actually). Not the case for has_one, however, where the rails system checks that the associated model saves before continuing the filter chain. In our test, we were not passing in enough params[:address] to make the Address.new save correctly.

class Address < ActiveRecord::Base
  belongs_to :addressable, :polymorphic => true
  
  validates_presence_of :street, :city, :state, :postal, :country

This situation was our own fault, but nonetheless not trivial to debug. Rails did not help us understand what went wrong, just that no SQL to save the tags was ever getting run.

class Employee < ActiveRecord::Base
  has_one :address, :as => :addressable
  validates_associated :address
  acts_as_taggable
end

Moral of the story: always use validates_associated for has_one and has_many associations because they will not stop the main model from being saved if they fail validation!

Originally posted on ELC

Using and Testing Rails with Multiple Databases

March 7th, 2007

Using multiple databases

I recently wrote a rails plugin called "use_db", which allowed you to use a different database for some of your ActiveRecord models. I started by reading this article (which was borrowed from the rails wiki), and decided to make a plugin out of it. You can use it in the following way:
class SomeBase < ActiveRecord::Base  
  use_db :prefix => "secdb_"
  self.abstract_class = true
end

class OtherDbModel < SomeBase
end
Now any calls to data in OtherDbModel will go to a database called "secdb_development" (or secdb_test, secdb_production, etc). The database.yml file could have the following additions to support this:
secdb_development:
  adapter: mysql
  database: secdb_development
  username: root

secdb_test:
  adapter: mysql
  database: secdb_test
  username: root

Testing multiple databases

One issue with my plugin, as stated on the original article, is that testing becomes very difficult. First, fixtures are automatically inserted into the primary database. Second, other databases will not automatically have their schemas migrated from dev to test.

Solving the fixture problem

I first examined active_record/fixtures.rb and noticed the following problem:

def delete_existing_fixtures
    @connection.delete "DELETE FROM #{@table_name}", 'Fixture Delete'
  end

  def insert_fixtures
    values.each do |fixture|
      @connection.execute "INSERT INTO #{@table_name} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert'
    end
  end

These two methods are called automatically by the test helper when loading fixtures for a test. @connection was originally set to ActiveRecord::Base.connection, so the existing solution was not going to work. To solve this, I overrode those two methods in my plugin and replaced them with the following code:

alias_method :rails_delete_existing_fixtures, :delete_existing_fixtures

  def delete_existing_fixtures    
    m = get_model
    return rails_delete_existing_fixtures unless m && m.respond_to?(:uses_db?) && m.uses_db?
    connection = m.connection
    connection.delete "DELETE FROM #{m.table_name}", 'Fixture Delete'
  end

  alias_method :rails_insert_fixtures, :insert_fixtures

  def insert_fixtures
    m = get_model
    return rails_insert_fixtures unless m && m.respond_to?(:uses_db?) && m.uses_db?
    connection = m.connection
    values.each do |fixture|
      connection.execute "INSERT INTO #{m.table_name} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert'
    end
  end

This first code attempts to get the model associated with a fixture. If found, it asks that model if it uses a different database. FInally, it uses the connection of the model to execute the fixture INSERT and DELETE SQL commands. If any of this process fails, it falls back on the existing rails fixture methods.

Solving the schema migration problem

Rails typically does schema migrations using a rake task which runs before "rake test". It typically divides the work into 3 segments, dump_db_structure, clone_db_structure, and purge_db. The sequence is as follows:

  • dump_db_structure dumps the development schema without data to an adapter-specific SQL file
  • purge_db deletes all rows from the test database
  • clone_db_structure imports the SQL dump into the test database

I simply duplicated the existing rake code, and modified it to use a different database connection. At the end of the day, I could execute a single command to migrate a second database. I chose to execute the command in my test helped in the following manner:

unless defined?(MIGRATED_SEC_DB_FOR_TEST)
  UseDbTest.prepare_test_db(:prefix => "secdb_")
  MIGRATED_SEC_DB_FOR_TEST = true
end

The syntax is very similar to the "use_db" helper.

Source code

Download the first release 0.0.1 of use_db rails plugin here.

UPDATE: Look for the next version of this plugin in this blog

Originally posted on ELC

See ELC's other Rails Plugins

RubyGems 0.91 and the "refresh" error

February 24th, 2007

All of us over at ELCTech recently upgraded to RubyGems 0.91, only to find the following error during "gem" operations:

$ sudo gem install acts_as_ferret
ERROR:  While executing gem ... (NoMethodError)
    undefined method `refresh' for #<Hash:0x127c854>

Apparently the format of the gem cache files have changed, the new version cannot read some older versions of the cache. To fix this problem, delete your source_cache files in the following locations:

sudo rm /usr/local/lib/ruby/gems/1.8/source_cache
rm ~/.gem/source_cache

Looks like someone forgot to think about us old folks with ancient versions of RubyGems. In the future, I'll plan on running "gem update --system" more often!

Originally posted on ELC

Installing RMagick properly in OSX

February 24th, 2007

I've seen the craziest problems with people installing the RMagick gem before. In this article, I will show you the procedure I use to fix any and all rmagick problems. First, I start by cleaning out any of the following packages:

sudo port uninstall imagemagick
sudo port uninstall graphicsmagick
sudo port uninstall ghostscript
sudo port uninstall freetype
sudo gem uninstall rmagick

Then, I reinstall them:

sudo port install freetype
sudo port install ghostscript
sudo port install imagemagick
sudo port install graphicsmagick
sudo gem install rmagick

This software cocktail has solved the following problems:

  • RMagick gem won't compile native extensions
  • RMagick can't find fonts - This is a really common problem, and we have seen a case where this approach will not solve this problem.
  • RMagick renders text incorrectly and/or unreadable in the validates_captcha plugin

ImageScience is an alternative to RMagick which requires fewer native libraries, but not zero unfortunately. It may or may not be easier for you to use, depending on what you need RMagick for (it won't do text rendering, for example).

Originally posted on ELC

Beating The Browser's Iframe Security

January 1st, 2007

I recently ran into the problem of cross domain communication between iframes while working on RightCart. We wanted to be able to pop up a LightBox outside the cart iframe, but the event had to be triggered by something happening inside the iframe. To make matters worse, the parent frame could be any domain and the child frame was always RightCart. After some research, I found that DoJo and the Windows Live Team claimed to have done this using nested iFrames.

No one else who is using this technique is willing to describe EXACTLY how it is to be done, so here we go:

The situation:

  • Parent frame is http://www.somedomain.com/somepath/somefile
  • Child iframe is http://rightcart.com/cart
  • Parent executes some javascript initially which we control
  • Child is completely controlled by us, and wants to call functions in parent

How I did it:

The child knows the URL of the parent through the HTTP referer (sic) header. If it creates a "subchild" iframe within the child pointing to the same top level URL as the parent, then 1-way communication is possible. How? The child can set the location of the subchild (but not read the location) and the parent can read the location of subchild, as long as the domain is the same as the parent.

So how do you transmit messages on a URL? Use the hash!
The hash is the portion of the URL after the # sign. You can change that portion of the URL and not mess with the browser. Most importantly, the browser will not go make a new request if it already has the page, regardless of what the hash value is.

For example, I could send a message to the parent by setting the location of the subchild using document.getElementById('...').src to http://www.somedomain.com#message_is_here. The parent can get the hash of the subchild by executing some javascript like window.frames[0].frames[0].location.hash. Got it? In order to send more complex messages, you'll want to Base64 encode the contents of the hash.

As for synchronization, I gave the messages sequence numbers, so that the parent knew if they had already seen the message. I also set a limit to how often messages could be sent (every 500 ms in my case). So the child had to queue messages if they came in too quickly:

function sendJavascriptToParent(js) {
  rightcart_comm_queue[rightcart_comm_queue.length] = js;
}

This is important, because there's only 1-way communication involved and the parent cannot tell the child that it is ready for the next message. I set both frames up at a 500 ms interval to update and check the message respectively. This is not 100% reliable, but if you're worried, try simply checking for messages twice as often as your minimum update delay.

Here's my code to check for messages in the parent:

function check_for_comm()
{
	var rcFrame = null;
	var innerFrame = null;
	rcFrame = window.frames["rightcart"];
	if (rcFrame) {
		innerFrame = rcFrame.frames[0];
		if (innerFrame) {
			var loc = innerFrame.location;
			debug_print("InnerFrame location = "+loc.href);
			var frag = loc.hash.substr(1);
			var seqnum = frag.split("!")[0];
			var data = frag.split("!")[1];
			debug_print("SeqNum: "+seqnum+",  Data="+data);
			if (data.length &gt; 0 && seqnum != comm_last_seq)
			{
				comm_last_seq = seqnum;
				var decoded_data = decodeBase64(data);
				debug_print("Running: "+decoded_data);
				eval(decoded_data);
			}
		}
	}
}

And my code to send a message from the child:

function doSend() {
      if (rightcart_comm_queue.length > 0) {
        var seq = new Date().getTime();
        var ifr = document.getElementById('rightcart_comm_frame');
        var all_js = '';
        for(var i=0; i&lt;rightcart_comm_queue.length; i++) {
          var js = rightcart_comm_queue[i];
          all_js += js+'\\n';
        }
        var data = encodeBase64(all_js);
        var url = rightcart_comm_baseurl+'#'+seq+'!'+data;
        /*alert('Sending Data: '+all_js+' using url '+url);*/
        ifr.src = url;
        rightcart_comm_queue = [];
      }
}

I had trouble using the EXACT same URL as the parent for the subchild. To solve this, I dissected the URL and set it to robots.txt in the same domain. This will cause a browser request for the first message.

Originally posted on ELC

original design by gorotron ported by railsgrunt powered by mephisto