Inception – manipulating cross-domain iframes with JavaScript
The requirement seemed simple enough: We had this cool search page on our website (the “widget”), and we wanted to provide other webmasters with a way to embed this search onto their own site. They would simply insert a small block of JavaScript which in turn populates some div with our content, just like Google Maps.
What promised to be a very straightforward feature turned out to become a pretty Nolan-esque experience, where different layers of JavaScript had to coerce other layers of JavaScript into…
Okay, it’s actually not quite as bad.
It was pretty clear we needed to use an iframe for this; other webmasters should not have to bother with our stylesheets and JavaScript libraries. However, using a conventional iframe does not allow the external site and our iframe – living on two different domains – to communicate at all. Not even to resize the iframe to fit its contents. While there are some solutions to the resizing problem, we simply did not want to give up JavaScript communication in general. The conclusion: the iframe may not actually point to our domain.
Browser security disallows absolutely all JavaScript communication between domains. What is allowed however, is to embed a <script> tag that fetches its code from another domain. For us, it look something like:
<div id="widget_canvas"></div>
<-- better move the following to the bottom of the page -->
<script type="text/javascript">
document.write(unescape('%3Cscript src="' +
(("https:" == document.location.protocol) ? "https://our-domain.com" : "http://our-domain.com") +
'/widget?embed=true" type="text/javascript"%3E%3C/script%3E'
));
</script>This might be familiar, Google Analytics does exactly the same. We simply write a <script> tag to the document which is instantly executed and fetches our actual widget code.
This code will then in turn
- contain our widget’s HTML embedded in a JavaScript string
- return some javascript code that will create an iframe (with a blank src attribute) and
- populate the iframe with the widget’s HTML
Et voilà, no domain boundaries any more, the iframe is considered to be in the external site’s domain. But while the idea is straightforward, the details are tricky.
The bulk of the JavaScript lives in a Rails layout, the regular views look like always. We confine ourselves to vanilla JavaScript in the external page, but expect jQuery within the iframe.
The following is a small excerpt of the final code, you can find a (slightly adapted) full version here. Make sure to look there, even if you plan to just copy small parts.
# app/layouts/widget.js.erb:
<% if params[:embed] %>
Widget = (function() {
var iFrame; var initialContent; var iFrameDoc;
function htmlLayout() {
return '<%= escape_javascript(<<EOHTML)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="#{I18n.locale}" lang="#{I18n.locale}">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
#{stylesheet_link_tag 'YOUR STYLESHEETS HERE', :cache => 'widget'}
#{javascript_include_tag 'jquery', 'YOUR JAVASCRIPT LIBRARIES HERE', 'widget_support', :cache => 'widget'}
</head>
<body id="top" class="partner_search" data-lang="#{I18n.locale}">
<div id="iframe_content"></div>
</body>
</html>
EOHTML
%>';
}
function createIFrame() {
var canvas = document.getElementById('widget_canvas');
iFrame = document.createElement('iframe');
canvas.appendChild(iFrame);
iFrame.onload = insertInitialContent;
iFrameDoc = iFrame.contentDocument || iFrame.contentWindow.document;
}
function populateIFrame() {
iFrameDoc.write(htmlLayout());
iFrameDoc.close();
initialContent = '<%= escape_javascript(yield) %>';
}
function replaceContent(content) {
iFrame.contentWindow.replaceIFrameContent(content);
resizeIFrame();
}
function insertInitialContent() {
replaceContent(initialContent);
}
function resizeIFrame() {
iFrame.height = iFrameDoc.body.scrollHeight;
}
function replaceContentWithScript(scriptHref) {
var scriptTag = document.createElement('script');
scriptTag.type = 'text/javascript';
scriptTag.src = scriptHref;
document.body.appendChild(scriptTag);
}
function init() {
createIFrame();
populateIFrame();
}
init();
return { replaceContent: replaceContent, replaceContentWithScript: replaceContentWithScript };
})();
<% else %>
Widget.replaceContent('<%= escape_javascript(yield) %>');
<% end %># public/javascripts/widget_support.js:
function replaceIFrameContent(content) {
$('#iframe_content').html(content);
}# somewhere in app/controllers/widget_controller: def show # ... render :layout => 'widget.js.erb' end
Phew, lots of code. I hope it is somewhat self-explanatory, but I’ll point out some of the more interesting problems we had to solve.
JavaScript execution order
IE7 is very creative about the order it executes JavaScripts. Inline scripts are executed when encountered, external scripts when they come in. This is fatal for libraries included in the <head>. This is why we fill in the actual content in the iframe’s load event, where all libraries should be parsed.
Make sure you have only one JavaScript library you actually include (that is, turn caching on). Moreover, if the JavaScript is already cached, the document-ready event seems to be fired before your JavaScript is even parsed. So all $(function() { … }) blocks in your code are executed instantly where they are defined.
Inline script tags
When assigning HTML via element.innerHTML = …, included script tags are not executed. This is why we need the “widget_support.js”; we call the jQuery of the inner frame to actually replace our HTML. JQuery will actually “grep” out the script tags and execute them manually.
Links in the iframe
Left to do is to handle links within the iframe. If they are supposed to bring up new content inside the widget, they obviously have to use the same mechanisms to do so.
Included above is the replaceContentWithScript method which takes a url (that needs to point to another action rendered with the “widget.js.erb” layout), which again fetches JavaScript injecting the new content. So we can wire our links from javascript inside our iframe with:
$('a').click(function(event) {
event.preventDefault();
parent.Widget.replaceContentWithScript($(this).attr('href'));
});The whole solution seems to work on all major browser starting with IE7. We haven’t dared to try IE6. This is also very fresh; we’ll let you know if it turns out to be less than stable for production use.
How to dump the database whenever you deploy with Capistrano
We often want to do a database dump before deployment of new releases. This backs you up in case migrations touch your data in a unfavorable way or a new feature is buggy.
I already introduced you to our dumple script in order to dump from within rails projects.
We often use it for this purpose. To do so, you need dumple on the remote machine. Additionally add two tasks to the database namespace and two callbacks in config/deploy.rb:
namespace :db do
...
desc "Do a dump of the DB on the remote machine using dumple"
task :dump do
rails_env = fetch(:rails_env, 'production')
run "cd #{current_path}; dumple --fail-gently #{rails_env}"
end
desc "Show usage of ~/dumps/ on remote host"
task :show_dump_usage do
run "dumple -i "
end
end
...
before "deploy", "db:dump"
after "deploy", "db:show_dump_usage"
Next time you cap deploy you will see some output before and after the deployment.
The first (db:dump) indicates whether the dump was successful and prints its size. The second one (db:show_dump_usage) shows the overall consumption of the dumps-directory.
If you still wonder about the --fail-gently parameter: database.yml does not exists when you run your first deployment of a project. If you raise within a Ruby script the exit code is 1 and Capistrano stops. The gently parameter makes dumple use exit, which results in a exit code of 0 and Capistrano continues!
How to warn of pending migrations when deploying with Capistrano
We use Capistrano for automatic deployment. A common problem is that you don't remember whether your current release includes migrations and you therefore have to run cap deploy:migrate to update your remote database after deployment. A small piece of code warns us if there are pending migrations:
namespace :db do
desc "Warns if there are pending migrations"
task :warn_if_pending_migrations => :environment do
if defined? ActiveRecord
pending_migrations = ActiveRecord::Migrator.new(:up, 'db/migrate').pending_migrations
puts ""
puts " * * * * * * * * * * * * * * * * * * * * * * * * * * * *"
puts " * *"
if pending_migrations.any?
puts " * You have #{pending_migrations.size} pending migrations:"
pending_migrations.each do |pending_migration|
puts ' * %4d %s' % [pending_migration.version, pending_migration.name]
end
puts " *"
puts " * Run cap deploy:migrate!"
else
puts " * No pending migrations. *"
end
puts " * *"
puts " * * * * * * * * * * * * * * * * * * * * * * * * * * * *"
puts ""
end
end
end
All you need to do is put this snippet into lib/tasks/pending_migrations.rb and add this line end the end of config/deploy.rb in your project:
after "deploy", "db:warn_if_pending_migrations"How to define helper methods in magic DSL code
Many Ruby gems expose their API as a DSL instead of a set of classes. A popular example of this is RSpec, which lets you write tests like this:
describe User do
describe '#full_name' do
it 'should return the concatenated first and last names' do
user = User.new(:first_name => 'Henry', :last_name => 'Cook')
user.full_name.should == 'Henry Cook'
end
end
endUnder the hood RSpec converts the describe something do syntax to plain Ruby classes and objects. However, RSpec never tells you the names of those magic classes and in fact they might not even have names. This makes it hard to find the right place for helper methods you'd like to use in your DSL blocks.
For the sake of this example, let's assume you'd like to extract the User.new(...) invokation into a helper method new_user(...).
Don't do this (although it happens to work):
def new_user(first_name, last_name)
User.new(:first_name => first_name, :last_name => last_name)
end
describe User do
describe '#full_name' do
it 'should return the concatenated first and last names' do
user = new_user('Henry', 'Cook')
user.full_name.should == 'Henry Cook'
end
end
endBy defining a method in the void you are extending Object, the mother of all Ruby classes. This means that every single object now has a new_user method.
A more humble way to define that method is to use a lambda:
describe User do
new_user = lambda do |first_name, last_name|
User.new(:first_name => first_name, :last_name => last_name)
end
describe '#full_name' do
it 'should return the concatenated first and last names' do
user = instance_exec('Henry', 'Cook', &new_user)
user.full_name.should == 'Henry Cook'
end
end
endHere new_user is a local variable that happens to contain a method. You can invoke it with instance_exec. It's not visible to other classes but can still be captured by every code block below it – exactly what we need.
We use the lambda-technique to DRY up our Rails routes machinist blueprints, and, of course, RSpec examples.
What CSS3 PIE means for your Internet Explorer support
CSS3 PIE is a script to support for many useful CSS3 properties in Internet Explorer. With PIE you can use standard CSS3-properties in your stylesheet, which PIE will render using some arcane, IE-only rain dance. Other browsers are not affected by PIE.
We took a closer look at PIE to see if this could be the hack we've all been waiting for. tldr-version for the kids on Twitter: You should use CSS3 PIE today, but it comes with minor caveats.
Support for rounded corners (
border-radius) is amazing. You should stop using hacks like CurvyCorners today and never implement rounded corners with background images again.Drop shadows (
box-shadow) works, but blurred shadows look darker than they do in Firefox and Webkit. This might or might not be ok with you, you should check the difference yourself. If you take a look at the "Learn more" buttons on our website, these were done with CSS3 PIE.PIE has some support for linear gradients, but it comes with strings attached. Since Opera has no support for gradients at all, and Firefox only supports them start version 3.6, we will continue to implement gradients with background images for the time being. Combining background images and rounded corners works great with PIE.
There is no support for
text-shadowyet.Because of the way PIE works, elements that use PIE need to be
position: relative. This gave us pause at first, but then we realized we don't mindposition: relativein most cases.
How to use CSS PIE unobstrusively
There are two annoyances when using CSS3 PIE. First, you need to apply a magic property to every CSS rule that should benefit from PIE. Second, you also need to make affected elements position: relative.
We found that we can eliminate any cruft introduced by PIE by adding those two additional properties to the Sass mixins we use for rounded corners and box shadows. We need those mixins anyway to support the vendor specific CSS properties like -webkit-border-radius.
This is how our mixin for rounded corners looks now (note the last two lines):
=round_corners(!radius)
-moz-border-radius = !radius
-webkit-border-radius = !radius
border-radius = !radius
behavior: url(/stylesheets/hacks/PIE.htc)
position: relativeAnd this is how we include this mixin in any CSS rule:
.sidebar_box
+round_corners
background-color: #fe9Aegis now supports multiple roles per user
Even though we sort of said we were never going to do it, Aegis now supports multiple roles per user. You can give a user multiple roles like this:
user.role_names = ['author', 'editor']Permission is granted when at least one role is allowed access, even if other roles are not.
Mail Magnet makes your e-mails go where you want them
Remember that nifty way to override e-mail recipients we showed you a while ago? It evolved into a gem: Mail Magnet makes changing e-mail recipients for your staging environments even easier.
Just go with a sudo gem install mail_magnet and add something like this to your staging environment file:
config.gem 'mail_magnet'
config.after_initialize do
ActionMailer::Base.override_recipients = 'overridden@example.com'
endMore information can be found at the gem’s github page.
Dumping database from within a Rails project
Dumping a database is a trivial task. Nevertheless it takes time to copy & paste credentials when using mysqldump or the like. Aside from that it's pretty boring.
We have a script to quickly dump a MySQL database. It saves all output to dumps in your home directory. The script will create the directory and set proper permissions for you.
A good place to put the following script into is ~/bin/dumple or /usr/local/bin/dumple when used by others on the machine. Remember to assign the executable flag: chmod +x ~/bin/dumple.
#!/usr/bin/env ruby
fail_gently = ARGV.include?("--fail-gently")
if ARGV.include?("-i")
puts "*******************************************************"
puts
system("du -sh ~/dumps")
puts
puts "*******************************************************"
exit
end
require "yaml"
config_path = 'config/database.yml'
unless File.exist?(config_path)
if fail_gently
puts "* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *"
puts "* *"
puts "* *"
puts "* Script is not called from inside a Rails project, *"
puts "* *"
puts "* THE DATABASE WILL NOT BE DUMPED. *"
puts "* *"
puts "* *"
puts "* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *"
sleep 5
exit
else
raise "Call me from inside a Rails project."
end
end
config = YAML::load(File.open(config_path))
environment = ARGV.reject{ |arg| arg[0].chr == '-' }.first || 'production'
config = config[environment] or raise "No production environment found. Please do `dumple [env_name]`"
dump_dir = "#{ENV['HOME']}/dumps"
unless File.directory?(dump_dir)
Dir.mkdir(dump_dir)
system("chmod 700 #{dump_dir}")
end
dump_path = "#{dump_dir}/#{config['database']}_#{Time.now.strftime("%Y%m%d_%H%M%S")}.dump"
puts
puts "Dumping database for environment \"#{environment}\"..."
system "mysqldump -u\"#{config['username']}\" -p\"#{config['password']}\" #{config['database']} -r #{dump_path}"
system "chmod 600 #{dump_path}"
dump_size_kb = (File.size(dump_path) / 1024).round
puts "Dumped to #{dump_path} (#{dump_size_kb} KB)"
puts
Run the script at the root folder of a Rails project and it will (try to) dump the production database. To dump a database belonging to a different environment, give it as first parameter:
user@host:~/rails/current$ dumple dev.example.com
Dumping database for environment "dev.example.com"...
Dumped to /home/thomas/dumps/dev_example_20100723_091715.dump (4127 KB)Maybe you spotted that --fail-gently can be given as argument and affects the way the script fails. This has to do with Unix exit codes. I'll tell you in the next article why we need it!
Cucumber is not for domain experts
The notion that non-technical people should write Cucumber scenarios for later implementation by a programmer needs to die. Only because someone can fall on their keyboard and produce a document that Cucumber considers valid does not mean the result is in any way useful to the process that follows.
Programmers want non-programmers to better appreciate the complexities revealed by the execution of seemingly simple ideas. But creating more artefacts disconnected from the actual implementation is only a rehash of failed techniques from the 90s. Tight integration, not increased division of labor is the way to go.
Solving the N + 1 query problem with Query Diet
As developers we use O/R mappers to pretend our data is stored as objects instead of flat tables. Unfortunately that abstraction breaks down frequently. Or as Joel would say:
All non-trivial abstractions, to some degree, are leaky.
One particular leak of O/R mapping is the N + 1 query problem, where the most innocent code can flood your database with hundreds or thousands of queries.
N + 1 queries are easy to overlook during development because we are used to mediocre response time on our development machines with disabled caches and class reloading. Relief is promised by debug bars like Rack::Bug and rails-footnotes which include the number of triggered database queries in the HTML of every page coming out of your application. Unfortunately they also do a million other things we don't care about. We wanted something that would alert us of excessive queries, but stays out of our way otherwise.
Enter Query Diet.
Query Diet inserts a tiny, translucent box into the upper right corner of your screen, informing you about the number of queries triggered by the last request and the time spent waiting for the database.
This is Query Diet being happy about 3 requests taking 66ms:

This is Query Diet being angry about 103 requests taking 164ms:

We're also quiet satisfied with Query Diet's installation procedure, which goes like this:
config.gem 'query_diet'
That's it. No second step required.
Check out Query Diet on github.



Great bit of code there, I’m always seeming to forget the migrations with capistrano deployments!
However, there does seem to be a bit of an error in the task definition. When I run it here it errors out with:
undefined method `to_sym' for {:warn_if_pending_migrations=>:environment}:Hash (NoMethodError)Can you give us some details e.g. full stacktrace etc.? Where did you place the code?