Talks
The Ruby Racer: Under the Hood
Summarized using AI

The Ruby Racer: Under the Hood

by Charles Lowell

In the video titled "The Ruby Racer: Under the Hood," Charles Lowell introduces viewers to the Ruby Racer, a gem that integrates the V8 JavaScript engine into Ruby applications.

  • Introduction: Lowell begins by explaining the use and capabilities of the Ruby Racer for embedding JavaScript within Ruby processes, encouraging audience interaction throughout his presentation.

  • Purpose of Ruby Racer: He outlines various motivations for using Ruby Racer, particularly in a Rails 3.1 context where CoffeeScript is prevalent. He emphasizes the ability to test JavaScript environments, leverage JavaScript tools, and share code between server and client applications.

  • Core Features:

    • The Ruby Racer allows executing JavaScript within a hermetically sealed environment, where developers can evaluate JavaScript code and manipulate JavaScript objects as if they are Ruby objects.
    • He demonstrates creating a V8 context, evaluating JavaScript, and managing object references, which resemble Ruby hashes. This interaction includes calling JavaScript functions within Ruby codes seamlessly.
  • Ruby to JavaScript Interaction: Lowell showcases how JavaScript can directly call Ruby methods and access Ruby properties. He highlights the importance of lambdas over procs due to their more predictable behavior in the V8 engine.

  • Performance Insights: He notes the possibility of optimizing performance by executing heavy logic in JavaScript instead of Ruby. This potential for speed enhancement, along with the convenience of calling across languages, demonstrates the Ruby Racer's prowess.

  • Future Development: Lowell discusses plans for improving the Ruby Racer, focusing on implementing a 'safe eval' feature, enhancing multithreading capabilities, and managing memory more efficiently. By addressing garbage collection challenges, he aims to create a smoother developer experience.

  • Call to Action: He invites interested individuals to contribute to the project, signaling that his efforts are ongoing and collaborative.

  • Conclusion: The video concludes with a Q&A session where Lowell answers audience questions about integration and performance comparisons, sharing insights on available resources such as slides for further learning. The overarching takeaway is the transformative potential of the Ruby Racer to merge Ruby and JavaScript capabilities, signaling a shift towards more integrated and efficient web development practices.

00:00:20.000 My name is Charles Lowell, and I'm here to talk about the Ruby Racer. I don't know how many of you have actually used the Ruby Racer, so can I get a quick show of hands?
00:00:31.920 Okay, not too many. That's fine. So we're starting from scratch, which is good. I'm going to talk a little bit about what it is and why you might want to use it.
00:00:44.160 Then, we’re actually going to go through some pretty deep code samples so you can see the things that are possible with it. Because of this, I think it's probably better if you ask questions as we go along.
00:00:56.239 I know we’re supposed to do Q&A at the end, but it’s better to understand everything up front since the examples build on each other. So don’t be shy; just stop me, and we can all go over it together.
00:01:08.640 The Ruby Racer allows you to embed the Google V8 JavaScript engine right into your Ruby process. It does this through a C extension, giving you all the power of that JavaScript engine at your disposal.
00:01:19.119 But why might you want to do this? One obvious reason is that the new hotness is Rails 3.1, which has a big controversy because it comes with CoffeeScript enabled by default. CoffeeScript is implemented in JavaScript and uses a gem called exec.js to compile your CoffeeScript into JavaScript.
00:01:36.799 If you have it installed, the default runtime actually is the Ruby Racer, so you may be using it without even knowing, just by accident, with several layers in between. But that's only the tip of the iceberg; there are actually many other uses for it.
00:02:06.399 Before we dive into the code, let’s go over a few of the other applications. The first one is unit testing JavaScript. This was actually the very first use case for the Ruby Racer. It's a great entry point because with the Ruby Racer, you can spin up a JavaScript environment, load whatever you want into it, and it doesn't require a display or a browser.
00:02:31.440 This is what we use on our CI server. We actually use JS DOM and Node HTML parser to simulate a browser environment so we can run all of our JavaScript tests on our CI server without worrying about displays or anything like that, and they run really fast.
00:03:06.080 Right now, there’s an absolute explosion—a Precambrian explosion—of diversity in the JavaScript environment. A lot of this has to do with Node, but not exclusively. There are many utilities that you can access that you can't if you're only working with a regular Ruby environment.
00:03:30.720 This list is by no means complete but we use several JavaScript utilities every day in our development and production. I compare it to using JRuby. With JRuby, you have access to all these goodies implemented in other languages like Java, Clojure, and Scala.
00:03:41.200 You get access to all these great JavaScript libraries that aren't necessarily dependent on the DOM; and if they are dependent on the DOM, you can implement DOMs in JavaScript. You can also share code between your client and server. I realize this could be considered the holy grail for many, and while there may not be a definitive right way to do it, we share our views implemented in Mustache and Handlebars between our client and server.
00:04:20.959 There are people who have contacted me on the list about sharing their validation code and some interesting implementations where they build large parts of their business logic in JavaScript, allowing them to include it via Ruby modules into their Ruby classes.
00:05:12.639 Finally, let's talk about performance. We've only begun to explore this area, but some others have been doing this more extensively. If you have a tight loop or something that you need to optimize, you can do it in JavaScript, which is a lot easier than doing it in C. It's just simpler and faster. This isn't an exhaustive list of why you would want to use it, but people keep coming up with new uses all the time.
00:05:56.640 Now let’s go straight into how it works.
00:06:01.199 First, we need to ask: what does it mean to have a JavaScript runtime embedded in your Ruby process? This is conceptually a bit of a hill for some people to get over, but I think it means mainly three things. The first is that you can evaluate JavaScript code.
00:06:35.840 Here, I'm creating a new V8 context. This is a completely hermetically sealed JavaScript world with just the JavaScript syntax—objects, functions, arrays, and regular expressions—along with the standard library. It's absolutely tiny, with no access to Ruby, and no DOM. This spartan nature is one of the things I love about it. Whenever you create one of these new contexts, you create a new world. If programmers are gods, this is your world, and you can literally put anything you want into it.
00:07:24.000 Once I've created the context, I can evaluate JavaScript code. The strings there have actual JavaScript syntax and I'm evaluating objects. You can also directly reference JavaScript objects, which masquerade as Ruby references. For example, we'll evaluate an object literal and assign it to the variable 'horse'. We can pull it out of the context and from Ruby code, we can invoke that JavaScript method and get the return value back into Ruby.
00:08:18.399 There's a great deal of synergy between JavaScript objects and Ruby objects. JavaScript objects behave like property bags and look similar to Ruby hashes, so when you get a reference, you can use hash syntax. You can access properties by name, and it doesn’t matter if you use a symbol or a string; they are treated the same.
00:09:15.679 If your property name is a valid Ruby identifier, you can send it as a method and it will work correctly because JavaScript methods are essentially properties. Remember, JavaScript has only two classes—object and function.
00:09:39.600 You can assign and evaluate those properties as you normally would. For instance, if you have a reference to a JavaScript object, you can evaluate it as an object literal and set a property via Ruby; that value becomes accessible through JavaScript.
00:10:07.520 JavaScript objects are enumerable just like hashes, so you can iterate through all their keys and values. You almost never need to use eval; that's something newcomers often think they have to do, but you can do almost everything directly through Ruby, which is actually faster.
00:10:44.399 When it comes to accessing methods in JavaScript from Ruby, things can behave unexpectedly sometimes. JavaScript functions differ from Ruby functions, as JavaScript methods are actually properties that are functions. There are three main ways to invoke a JavaScript function.
00:11:30.639 You can invoke it as a bare function, you can pass a context to it—setting what the 'this' variable will be—or you can invoke a function as a constructor. The example I'm providing shows calling it as a proc: we’ll evaluate it and get a reference to our JavaScript function called 'greet'. Then we can invoke it with the 'call' method, which looks like any other Ruby callable. Notice that 'this.title' is not defined, meaning it doesn’t print out.
00:12:34.560 However, there’s a method called 'methodCall' that accepts arguments. The first argument sets what 'this' will refer to, followed by the natural arguments. By changing the context, we make it print something entirely different. You can also invoke any JavaScript function as a constructor by using a new method.
00:13:40.159 In this case, we define a square constructor in JavaScript, creating a new square with a radius. It’s interesting to realize that if I were to take this away, you'd not even know that it wasn't a Ruby class; it’s actually pure JavaScript being used.
00:14:28.479 It’s important to remember that JavaScript methods happen to just be functions. Another fascinating consequence is that you can take a function property off of one object and invoke it on another. For example, I've demonstrated pulling the area function off of the first square and invoking it on a second square.
00:15:04.799 To summarize, one significant aspect of this interaction is that you can evaluate JavaScript code and call it from Ruby. Later, we’ll see how you can also call Ruby from JavaScript. This half of the bridge involves evaluating JavaScript directly, getting in there to manipulate properties of a JavaScript object, and invoking JavaScript functions from Ruby.
00:15:52.639 But, interestingly enough, we need to talk about how to have your JavaScript code call back into your Ruby code. Now, let’s explore that.
00:16:23.399 Here’s a simple class called 'Dog' with one method, 'bark'. We’ll take an instance of that Ruby object and put it in our V8 context.
00:16:44.079 When I say 'rover.bark', it behaves just like a normal JavaScript object, invoking the Ruby class or method directly. I don't need to embed the instance; I can pull off the 'bark' method from the 'Dog' object and place it inside the context, so it can be evaluated from JavaScript.
00:17:11.440 Lambdas require a special note since they avoid an interesting pitfall present in procs. Returns inside procs can result in unexpected behavior, breaking the V8 stack. It's better to use lambdas, as they do not have the same side effects.
00:18:04.640 You can embed a Ruby class into your context, which creates a corresponding JavaScript constructor. This allows instantiation of the Ruby class from JavaScript, returning a reference that allows you to manipulate the instance.
00:18:54.080 A noteworthy side effect is the ability to extend Ruby classes from JavaScript. For instance, if I've embedded the Dog class, I can access its prototype and find its bark and wag functions, allowing a mix of Ruby and JavaScript implementations.
00:19:27.680 We’ve covered how to get references and invoke Ruby functions. You can also access properties similarly. For example, I could define a struct and read its properties using JavaScript.
00:20:01.280 You can see that my properties like 'my name' and 'my city' are accessible from the JavaScript runtime, and you can also set values directly from JavaScript. If the property doesn’t exist or you want to access it dynamically, JavaScript can still retrieve it.
00:20:34.760 The results are indeed seamless. For example, if you have a 'Processes' class that finds and returns processes associated with a username, this is accessible within JavaScript without JavaScript comprehending the background logic.
00:21:01.440 To summarize, we’ve discussed how to access JavaScript from Ruby and vice-versa. This ability enables vast functionality where JavaScript can leverage Ruby capabilities and Ruby can utilize JavaScript logic.
00:21:37.760 Now, let’s discuss some future plans for the project. I think it's crucial to develop a 'safe eval' capability, especially since right now, many individuals might be unaware of the risks involved in executing untrusted JavaScript.
00:22:07.120 It could potentially pose serious risks to their machines, similar to the warnings game from developers in the '90s advising against the use of 'eval' due to its dangers.
00:22:50.080 While this feature exists in the client-side view of JavaScript, we need to work on making it applicable to the server-side. The Ruby Rhino gem uses these concepts effectively, allowing untrusted code to run smoothly.
00:23:20.160 Next, we need to ensure detection of undesirable constructs like malicious while loops and to easily terminate execution if they appear. We also want to manage object allocation effectively to avoid overwhelming the system.
00:23:58.120 Multithreading has posed its challenges. Since the release candidate for Rails 3.1, many issues have been reported, and there’s been considerable hardening on this front. It's crucial to run the Ruby Racer in a multithreaded environment without concern.
00:24:17.760 Another plan is to enhance custom heap snapshots in V8, allowing for better serialization of object structures when starting the interpreter. This speeds up initialization and provides high efficiency.
00:24:42.960 Garbage collection has been a significant challenge. Working with two garbage collectors that don't know about each other and managing memory leaks poses complicated issues. Effectively aligning the various garbage collection approaches of different Ruby implementations has been demanding.
00:25:43.760 My main goal is to provide a seamless interface, helping you forget about these complexities. You should worry about connecting your JavaScript and Ruby worlds without being bogged down by garbage collection.
00:26:11.519 Lastly, I aim to solidify the Ruby Racer to ensure it runs quickly, cross-platform, and is simple to install. The motivation comes from the empowerment it has provided me, helping people to think creatively without getting stuck on technicalities.
00:26:52.280 Throughout this talk, I've met numerous individuals with innovative uses for the Ruby Racer that I couldn't have foreseen. With the ability to execute JavaScript directly in Ruby, this area continues to expand in possibilities, I believe in five years, this will be commonplace.
00:27:53.800 To that end, if anyone wants to help out, please contact me. Currently, it’s mostly just me working on this. While I have grand plans, the pace can be slower than desired, since I work on this project every Thursday at 8 PM Central. You are free to join me.
00:28:34.720 Thank you!
00:29:00.879 Are there any questions? Was garbage collection a challenge?
00:29:12.000 Are your slides available anywhere? I want to reference the code samples.
00:29:51.120 Yes, the slides are available on SlideShare. I’ll update them with the most recent versions, which will include better and simpler samples compared to those that are currently on the Internet.
00:30:13.440 One of your points was about not having to use C because this is just as fast. What measures have you done about its speed compared to native C?
00:30:23.920 I haven’t personally measured that. However, people have posted some of their metrics showing speed improvements. The Ruby Racer relies on V8, which compiles your JavaScript down to native code.
00:30:52.360 We utilize it daily for JavaScript testing. It’s especially useful with Less, as we have a significant investment in it, and when it shifted focus to Node, we adapted via the Ruby Racer to compile Less using JavaScript.
00:31:43.600 There’s also a gem called 'include_js' which allows for easier integration between Ruby and JavaScript, useful for sharing business logic between frameworks. Many use cases arise as more people explore the Ruby Racer.
00:32:06.120 Are there any other questions? No? Alright! Thank you!
Explore all talks recorded at LoneStarRuby Conf 2011
+15