Skip to content

Instantly share code, notes, and snippets.

@TylerRick
Created October 6, 2010 23:40
Show Gist options
  • Save TylerRick/614311 to your computer and use it in GitHub Desktop.
Save TylerRick/614311 to your computer and use it in GitHub Desktop.
Proc#to_json, JavascriptCode#to_json
# This allows you to represents a JavaScript function with a proc. When converted to JSON, it will
# automatically wrap the method body with "function(){" and "}" and list as many arguments as you
# had listed in your proc, automatically giving them the names a, b, c, etc.
#
# This is convenient when you want to create a Ruby hash representating options to be passed to
# a JavaScript method, which you plan to call to_json on when you actually output the method call
# to the web page. If you tried to represent your JavaScript function with a string value in the
# hash, then when you called to_json on the hash, it would in turn call String#to_json, which
# returns an escaped copy of the string surrounded by quotes.
#
# For example, this:
# { :onComplete => 'function() { alert("Done loading") }' }.to_json
#
# would become:
# {"onComplete":"function() { alert(\"Done loading\") }"}
#
# and your callback would probably not do anything (unless they eval'ed the string first) because
# the API probably expects you to pass in an actual JavaScript *function*, not a string.
#
# What we need is a way to represent literal JavaScript *code* rather than a literal JavaScript string.
#
# By defining Proc#to_json, we can do just that: use a Proc to represents in Ruby a JavaScript function.
#
# Now this:
# { :onComplete => proc{'alert("Done loading")'} }.to_json
#
# becomes this:
# {"onComplete":function() { alert("Done loading") }}
#
# If you want to represent a JavaScript function that takes arguments, you just list your arguments
# in the Ruby proc, like so:
# { :onComplete => proc{|a, b| 'alert("Finished loading page "+a+" in "+b+" seconds")'} }.to_json
#
# Which becomes this:
# {"onComplete":function(a, b) { alert("Finished loading page "+a+" in "+b+" seconds") }}
#
# The only stipulation is that you write your method body such that it expects the arguments to be
# named a, b, c, etc. (There's no way that I know of to inspect a Ruby Proc object and find out the
# original names given to its arguments when it was created. But there is a way to ask it how *many*
# arguments it takes, and that is what I used.)
#
# Author: Tyler Rick
class Proc
def to_json(options = nil) #:nodoc:
arg_list = (1..arity).map {|i| ('a'.ord + i - 1).chr}.join(', ')
"function(#{arg_list}) { #{call} }"
end
def javascript_call
JavascriptCode("(#{to_json})()")
end
end
# Now suppose you need to represent not a JavaScript *function*, but some snippet of arbitrary
# JavaScript code that should get executed *immediately* (assuming you output to a web page).
# This code could be a calculation, evaluating a variable, or anything, really.
#
# In this case, using a Proc does not work. Instead, you can use a "JavascriptCode" object to
# represent this JavaScript code in Ruby.
#
# Example:
# JavascriptCode('some_config_option * 100').to_json
# Becomes this:
# some_config_option * 100
#
# Alternatively, you can still use the Proc, but instead of passing it into your hash directly,
# you can call its 'javascript_call' method, which as the name implies, creates a JavascriptCode
# that calls the anonymous JavaScript function by appending '()' to the end.
#
# Example:
# proc{'return some_config_option * 100'}.javascript_call
# Becomes this JavascriptCode:
# (function() { return some_config_option * 100 })()
#
# Authors:
# * Danny Beardsley (http://forthecommunity.blogspot.com/2010/06/function-definitions-in-json-un-quoted.html)
# * Tyler Rick
class JavascriptCode < String
def to_json(options = nil)
self
end
end
module Kernel
# A convenience factory method
def JavascriptCode(str)
JavascriptCode.new(str)
end
end
@jwoffindin
Copy link

Hi,

Didn't work for me, then I realized I was including active_support/* which includes it's own to_json. Adding this to your JavascriptCode class did the trick:

def as_json(options = nil)
  ActiveSupport::JSON::Variable.new(to_s).freeze
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment