Writing modern web applications requires a ton of JS, and somewhere in that JS lies some application logic (we're not just talking DOM manipulations here). If you require that same logic on the server-side for say, generating reports, what do you do? I'll show you how ValuationUP.com pushes the single responsibility principle to the max by "embedding" V8 into our report generation code so the same JS that powers our Backbone.js frontend powers our PDF's generated by Prawn.
Thin wrappers, no duplication, practical IoC, ultimate SRP.
3. ValuationUP.com
Help entrepreneurs make REAL sense of their
management accounts
Comparative analysis within industry
Refactoring financials by getting from red to green
Take actions to increase their valuations
Valuation is the ultimate metric
4. The challenge
We need a very responsive UI
Realtime updates as the user inputs information
The RTT to the server is not worth the wait for such a
simple formula
18. JavaScript Context
1 require 'v8'
2 # require 'rhino'
3
4 # This service wraps a JS context and allows us to interact with our Javascript
5 # directly in Ruby.
6 class Javascript
7
8 # Our V8 context object
9 attr_reader :context
10
11 # Some delegations
12 delegate :load, :eval, :[], :[]=, to: :context
13
14 def initialize()
15 @context = V8::Context.new
16 #@context = Rhino::Context.new
17
18 # Setup a fake 'window' object
19 @context['window'] = FakeWindow.new( @context )
20 @context['console'] = FakeConsole.new
21
22 # Load our global setup
23 load_coffee Rails.root.join( 'app/assets/javascripts/setup.js.coffee' )
24 end
25
26 # truncated, lots more detracting stuff down below...
27
28 end
19. CoffeeScript sprinkles
1 # Compile and load a CoffeeScript file into the current context
2 def load_coffee( path )
3 compiled_coffeescript = CoffeeScript.compile( File.read( path ) )
4 context.eval compiled_coffeescript
5 end
20. Pause
We have a JavaScript context (V8) at our finger tips
Have some basic CoffeeScript loading abilities
And some additional plumbing
Far from a DOM
21. Server-side wrapper
1 class WaccLayout
2
3 def initialize( view, context = Javascript.new )
4 @context = context
5
6 load_dependencies
7
8 # Pass our Ruby view into the context
9 @context["view"] = view
10
11 # Setup
12 @context.eval <<-EOJS
13 var layout = new Demo.WaccLayout({
14 view: view, data: data
15 });
16 EOJS
17 end
18
19 def render
20 context['layout']['render'].methodcall( context['layout'] )
21 end
22
23 def load_dependencies
24 @context.load_coffee Rails.root.join("app/assets/javascripts/wacc.js.coffee")
25 @context.load_coffee Rails.root.join("app/assets/javascripts/wacc_layout.js.coffee")
26 end
27 end
22. Server-side view & usage
1 class WaccPdf
2
3 def initialize
4 @pdf = Prawn::Document.new
5 end
6
7 def render( options )
8 wacc = options.wacc
9
10 @pdf.text "WACC: #{wacc}"
11 end
12
13 end
14
15
16 # Setup a view
17 view = WaccPdf.new
18
19 # Setup a layout and render
20 layout = WaccLayout.new( view )
21 layout.render