JRuby and Angular - Client-Server Web Apps with JavaScript and Java (2014)

Client-Server Web Apps with JavaScript and Java (2014)

Chapter 10. JRuby and Angular

We live in a beautiful and orderly world, not in a chaos without norms, even though that is how it sometimes appears.

—M. C. Escher

Sorting and filtering are activities that have been used since ancient times to organize the world. Ancient acrostics used each letter in an alphabet to start a line of a poem. This involves both filtering (selection of first letter) and sorting (alphabetical). The Sieve of Eratosthenes, shown inFigure 10-1, can be visualized by writing out, in order, the integers from two to the upper bound in question, and then filtering out composites (nonprime numbers) as the multiples of primes.

Sieve of Eratosthenes

Figure 10-1. Sieve of Eratosthenes

A quick search on filtering and sorting returns many results geared toward the manipulation of data in spreadsheet programs. The project in this chapter will show how, with a relatively small amount of code, JRuby and Angular can be used to filter and sort an HTML table containing data from Google Finance stock market data.

Server Side: JRuby and Sinatra

Simple, dynamic web APIs can be created using Ruby and a microframework called Sinatra. Sinatra is essentially a Ruby-based wrapper of HTTP.

Ruby was developed by Yukihiro “Matz” Matsumoto, who incorporated parts of his favorite languages (Perl, Smalltalk, Eiffel, Ada, and Lisp) to create a new language that balanced functional and imperative approaches. Although it was released in 1995, and he wrote a book called Ruby in a Nutshell about it in 2001, it saw a significant spike in popularity when Ruby on Rails (or simply Rails) became popular several years later. The language is, in the words of its author “simple in appearance, but is very complex inside, just like our human body.” Matz coauthored a more recent book on Ruby that goes into the details of this fascinating language.

Rails is the Ruby MVC web framework that first brought Ruby to the attention of many developers. David Heinemeier Hansson extracted the framework from Basecamp (a project management tool he worked on at 37signals) and released it in 2004. In many languages and frameworks, writing no code results in no behavior. In Ruby (as well as Rails and other Ruby projects), default behavior is included when no code is written. The Rails philosophy expresses this under the principle of convention over configuration. This greatly limits the amount of setup required to get a project up and running. This principle is evident in other web frameworks written in Ruby, most notably Sinatra, which is much smaller than Rails and ideal for creating streamlined applications that don’t require all of the bells and whistles of a larger framework.

Workflow

One approach to using JRuby (or other JVM language) is incorporating them from a Java perspective. The module containing the language implementation can be included in a project as a dependency:

<dependency>

<groupId>org.jruby</groupId>

<artifactId>jruby-complete</artifactId>

<version>1.6.3</version>

<type>jar</type>

<scope>compile</scope>

</dependency>

In the case of web applications, this makes sense if you have Java application servers already installed. Warbler can be used to bundle Rack applications into WAR files, and there are some interesting experiments like the Rack Servlet from Square Engineering to embed a Ruby-powered servlet into an existing WAR.

A Java developer who approaches Ruby in this manner will have the benefit of focusing on differences in syntax, but will miss the tools and workflow that have helped make the language so successful. These differences begin with the very tools used to initially set up the language and associated packages.

Interactive Ruby Shell

The Interactive Ruby Shell (IRB) is a shell for executing Ruby commands. It is comparable to an OS shell in that it provides an immediate result for any expression it evaluates. This sort of exploratory programming using a read–eval–print loop (REPL) is available in a number of other languages. When experimenting, learning a new bit of syntax, or when it is not clear what algorithms or data structures might be used, the usual cycle of edit/compile/run/debug can be burdensome. Java does not have any directly comparable tool. Executing a bit of code while paused in a debugger is similar, and the Eclipse IDE has a feature called a Scrapbook Page, which is similar. IRB is worth exploring when learning Ruby, and when using JRuby, it can also be used to access Java classes.

For an example of how IRB can be used to explore Java JARs, see Appendix A.

Ruby Version Manager (RVM)

Rather than accessing JRuby as a dependency of a Java project, a JRuby environment can be set up using the Ruby Version Manager (RVM). RVM is not limited to JRuby, but can be used with many other Ruby implementations. RVM supports deployments of multiple Ruby environments. Each one is self-contained and includes a specific version of Ruby, and associated set of required gems. RVM makes it easy to set up projects with a specific set of gems dependent on a particular Ruby version and keep these projects independent of ones using other versions of Ruby. It is interactive and gives intelligent advice during installations regarding additional configuration or troubleshooting. If you are working on multiple Ruby projects, it lets you develop in a given environment and switch to another environment with a single command.

RUBY/JRUBY/RVM/DEPENDENCY MANAGEMENT

JRuby can be used in several different ways, which can be the source of some confusion.

Ruby is a programming language in its own right. JRuby is a version of the Ruby programming language that runs on the Java Virtual Machine. RVM provides a mechanism for managing several different versions of Ruby, including JRuby distributions. The RVM environment includes language installs and associated Ruby packages and is maintained locally on a machine outside of a project. RVM provides a way of working with Ruby implementations, including JRuby, from a Ruby perspective.

JRuby can also be included as a standard module dependency in Maven or other build tool. RVM is not used in this case, and JRuby is treated from a Java perspective.

To identify which version of Ruby RVM is using:

$ which rvm-auto-ruby

/Users/cas/.rvm/bin/rvm-auto-ruby

# Generally, you don't call this directly...

$rvm-auto-ruby --version

# The version in Ruby in use will match the one in the previous command

$ruby --version

To list installed Ruby interpreters and the version currently in use:

rvm ls

To switch to an environment, call the rvm use command along with a portion of the Ruby interpreter name:

rvm use 2.0.0

OTHER RVM FUNCTIONS

RVM does a great deal more than simply let you interactively maintain Ruby versions. With it, you can set up project-specific Ruby environments called gemsets that are independent of other Ruby projects. It can be called at the command line to execute scripts that use a specific Ruby version and gemset. It can even go beyond simple diagnostic advice; for instance, SSL certificate issues can sometimes be resolved by simply running the following RVM command:

rvm osx-ssl-certs update all

Packages

Ruby packages are called gems and are maintained using the gem utility. Gems can be released with various versions, and a project can be developed that uses a rather specific set of dependencies. A set of gems can be associated with a single RVM environment, which allows for simultaneous development of several different Ruby projects that use different versions of Ruby and different sets of gems. In order to deploy a given project to other machines, the specific set of dependencies can be maintained declaratively using the bundler gem. Bundler dependencies are listed in a Gemfile.

Using RVM, you can set up a new Ruby environment. You can then install a bunch of gems individually and tweak the environment until all of the correct dependencies and versions are available. When these are available, populate a bundler Gemfile with all of the versions in use in your current environment. The following sequence of bash commands can be used to create a new Gemfile, add a comment referencing the Ruby version in use, and include a list of gem dependencies that matches the current environment. The bulk of the work is done in the last command, which lists the gems available in the current environment, formats the list, removes references to rvm and bundle, and uses the list to create the corresponding gem entries in the Gemfile:

#!/bin/bash

bundle init

echo "# Ruby Version: `ruby --version`">>Gemfile

gem list --local |

grep -v '\*\*'|

sed 's/[)(,]//g' |

egrep -v 'rvm|bundle'|

awk '{print "gem \""$1"\",\""$2"\""}' >>Gemfile

The generated Gemfile can be distributed to other machines that need the same environment (such as other development machines or separate deployment environments). These otherwise would have to be set up using a Ruby installation or RVM, and a tedious manual gem installation.

Sinatra

Sinatra, as described on its website, is a DSL for “quickly creating web applications in Ruby with minimal effort.” It essentially wraps HTTP in a Ruby-accessible interface that runs on Rack (a common interface for Ruby web servers and web frameworks). A Sinatra application is made up ofroutes (each one an HTTP method paired with a URL-matching pattern). A route is associated with a block, which can be used to fulfill the request (by executing Ruby code, rendering a template, and so on).

Sinatra can also serve static files from a directory named public by default (see Figure 10-2). This makes it ideal for creating a set of web APIs in Ruby that back HTML/CSS/JavaScript applications. It is by no means limited to this approach. A standard server-side MVC approach involves Ruby-based templates (ERB) in the views directory (by default).

Sinatra is minimal and thus sometimes referred to as a microframework. Like the best of other Ruby libraries, it stays out of your way and exposes a clear interface that can be referenced as you see fit.

Or perhaps more accurately stated, as a Domain Specific Language, it improves programmer productivity and communication due to its close alignment with the domain it represents: HTTP for web applications.

Sinatra lends itself to being expanded from trivial apps (designed in classic style) to a form considered more suitable for production deployments (written modular style). So it is feasible to begin work on a tiny Sinatra app that is gradually built out into a final application. Because of its simple and transparent nature, it can also be used for prototyping, which results in an application that is essentially formal documentation that can be used to implement a separate system.

Sinatra default directory structure

Figure 10-2. Sinatra default directory structure

Sinatra is a relatively simple framework with great documentation online and has garnered enough attention to have a book written on it as well. It is worth learning for its own sake, and has inspired a variety of similar frameworks in other languages.

JSON Processing

The JSON gem is rather straightforward; it converts Ruby objects to and from JSON strings. With a straightforward mapping between JSON types and Ruby types, it makes working with JSON data a breeze. Consider the following IRB session:

>require 'json'

=> true

> o = {:A=>[1,2,3], :B=>{:C=>:D}}

=> {:A=>[1, 2, 3], :B=>{:C=>:D}}

> o.class

=> Hash

> s=JSON.pretty_generate(o)

=> ...

> puts s

{

"A": [

1,

2,

3

],

"B": {

"C": "D"

}

}

s.class

=> String

o2=JSON.parse(s)

=> {"A"=>[1, 2, 3], "B"=>{"C"=>"D"}}

o2.class

=> Hash

There are some subtleties that Ruby aficionados will notice (Ruby symbols and strings are both converted to JSON strings, for example). But in general, the example shows that it is very simple and straightforward to parse and generate JSON using this package.

Client Side: AngularJS

AngularJS (often referred to simply as Angular) is an MV* JavaScript framework developed in 2009 by Miško Hevery and Adam Abrons. As of 2014, a team at Google that includes Igor Minár and Vojta Jína maintain the project. Angular reads the DOM of its associated HTML document, processes custom elements and attributes (directives), and binds data in an associated model to the page.

Angular seeks to provide a comprehensive approach to the development of web applications. It recognizes the declarative nature of HTML and enhances its behaviors. It is a very sophisticated framework that has entire books dedicated to in-depth coverage. The purpose here is not to exhaustively describe the project, but to demonstrate how to quickly get up and running with it. There are a number of concepts related to Angular that need to be understood in order to use it effectively.

Model

If you have done server-side MVC development, you probably think of a model as a class that contains attributes and is associated with a relational database. Hibernate and iBatis are used by Java developers, and ActiveRecord is included in Rails to serve in this capacity. There are also JavaScript frameworks like Backbone that include a specific model object that is recognized by the framework.

Angular is quite different in this regard. Unlike some other JavaScript frameworks that have a specific model object, an Angular model is a plain JavaScript object. The model in Angular is simply the data. Because of how little attention is given to the model in Angular, some have seen it as more of a templating solution, as cited in the Angular FAQs. However, the rest of the features of the framework, including bidirectional data binding, make Angular more than simply a templating system.

Views

The view is a representation of the model through an HTML template. Whenever the model changes, Angular’s two-way data binding is activated so that updates to the view are rendered as needed. Expressions are JavaScript-like snippets of code enclosed in bindings, such as {{ _expression_ }}.

Directives can be included to effectively extend the capabilities of HTML. During DOM compilation, directives are executed to perform a wide range of tasks, including DOM manipulation (showing or hiding elements, looping and creating new elements, and so on) or a variety of other tasks. Angular comes with a set of directives, and programmers can add additional ones that effectively serve as independent web components.

One of the most pervasive problems with JavaScript is issues related to the use of globals. Angular mitigates this to prevent pollution of the global namespace. Scopes allows the template, model, and controller to work together. They are nested in a hierarchical structure corresponding to the structure of the DOM. Scopes detect model changes and provide the execution context for expressions.

Controllers

A controller constructs the model and publishes it to the view. A JavaScript function contains the code associated with the controller, and the ngController directive attaches a controller function to the view.

The Angular Seed project provides an example of how requests can be routed to various controllers. The $routeProvider service is associated with a function that matches the portion of the URL after the hash with a corresponding template and controller.

Services

A variety of services are built into Angular. The $parse service processes expressions. A single injector per Angular application is used to locate services. As is the case with directives, programmers can encapsulate their own logic in custom services as needed.

Comparing jQuery and Angular

It is natural to compare jQuery and Angular. Both are popular, influential libraries. jQuery is a clearly established leader, and Angular promises to address a slew of issues encountered when using jQuery alone. A cursory look at the Angular documentation reveals that Angular is compatible with jQuery, and viewing a few sites will establish many examples of jQuery and Angular being used in the same page. Though both are effective tools in developing large-scale web applications, there are some areas of consideration that influence successful design and development.

DOM Versus Model Manipulation

Angular does work with jQuery if it is present. If it is not present, Angular uses a built-in subset of jQuery. In this sense, the libraries are compatible, and both manipulate the DOM.

However, it is safer to consider Angular as primary when both libraries are present. This is because there is a fundamental difference in how each library maintains application state. A jQuery application directly manipulates the DOM and views it as containing the application state. An Angular application treats the model rather than the DOM as the “source of truth.” The model, not the DOM, is directly manipulated. Changes to the model result in the DOM being updated. Relying on jQuery DOM manipulation in an Angular application leads to subtle problems that are hard to address because the Angular model gets out of sync and doesn’t know about changes made by jQuery.

Due to this fundamental difference in approach, it is difficult to integrate Angular into an existing jQuery-based application. Angular DOM manipulation should be done using Angular directives. Packaging up jQuery functionality into directives from an existing application can be challenging. It is far easier to write an application from the ground up using Angular.

Unobtrusiveness of Angular

There are some differences of opinion on whether Angular is in line with the principles of unobtrusive JavaScript, but connecting presentation and behavior needs to occur at some point in an MV* framework. The problem can be seen in the following examples.

Placing JavaScript in HTML has widely been regarded as a bad thing:

<button onclick='someFunction()'>Click Me</button>

In jQuery, an HTML element is identified using a CSS selector. A common way of selecting an element is based on its ID:

<button id='myButton'>Click Me</button>

A connection between HTML and JavaScript can be made by binding the element using a jQuery selector event handler:

$("#myButton").on('click', function(){someFunction();});

Angular makes the point of connection within the HTML page using directives (customized attributes):

<button ng-click="someFunction()">Click Me</button>

This does not really violate the goals of unobtrusive JavaScript. Angular attributes have the distinct advantage of having a single, well-understood meaning. jQuery selectors require the use of arbitrary HTML attributes (ID and class). Use of these standard attributes introduces ambiguity. It is not evident whether the value affects presentation, behavior, or both. HTML5 introduced data attributes that help mitigate this problem a bit.

HTML5 DATA ATTRIBUTES

A data attribute is an attribute that starts with “data-” and does not affect layout or presentation. Such attributes are specifically used to store data instead. Multiple attributes are differentiated by the string that follows “data” in the attribute name.

<li class="pet" data-name="Katniss" data-type="Civet" >

Hi Kat

</li>

Project

There are two basic approaches to working with any JavaScript framework. You can start with the smallest possible project and expand it, or you can start with a fully featured starter project and fill in pieces in the set of largely empty files that comprise the project. Both approaches are valid and suited for particular circumstances.

The smallest possible examples are excellent for troubleshooting and communication (especially when demonstrating working examples at sites like JSFiddle). The starter projects provide stable foundations for subsequent development of full-scale projects. The following example starts with the smallest possible example and gradually adds functionality.

The application can be run using JRuby 1.7.4 and the Sinatra and JSON gems. Each page iteratively adds additional functionality.

To run the application, download the code, install RVM and a version of JRuby, install the dependent gems, and kick off the server:

$ rvm list

rvm rubies

jruby-1.7.4 [ x86_64 ]

=* ruby-1.9.3-p194 [ x86_64 ]

ruby-2.0.0-p247 [ x86_64 ]

# => - current

# =* - current && default

# * - default

$ ls

README.md public webapp.rb

$ ruby webapp.rb

[2013-08-13 21:20:01] INFO WEBrick 1.3.1

[2013-08-13 21:20:01] INFO ruby 1.9.3 (2013-05-16) [java]

== Sinatra/1.4.3 has taken the stage on 4567 for development...

[2013-08-13 21:20:01] INFO WEBrick::HTTPServer#start: pid=71632 port=4567

The root of the web app displays a list of links based on HTML files residing in the public directory. Although a bit of a gimmick, this requirement is a fun example of how concise and expressive Ruby can be. The API call defined in webapp.rb in the following code is about as long as the English description of what it does:

get '/' do

Dir.entries('public').entries.map{|f|

"<a href='#{f}'>#{f}</a><br/>" if f=~/.*\.html/

}.join

end

Normally, HTML is generated server side and rendered in separate templates; or JSON, XML, or another data type is returned. Sinatra, being essentially an HTTP DSL, does not place many restrictions on what is returned. Figure 10-3 shows the index page for the web application, which lists links to several AngularJS examples.

Web app links

Figure 10-3. Web app links

The first example, shown in Figure 10-4, will simply display two text boxes. When the text is changed in one text box, the corresponding text will be displayed in the other as well.

Web app text boxes

Figure 10-4. Web app text boxes

A minimized version of the Angular JavaScript file (referenced in the following script tag) is available from the Google Hosted Library. After this JavaScript file is loaded, the DOM is traversed to find Angular directives (which are represented as HTML element attributes). The ng-appdirective identifies the tag it occupies as the outer boundary for an Angular application. The ng-model directive is used to identify a model, which can be modified in either of two text boxes and is updated in the other due to built-in, two-way data binding:

<!DOCTYPE html>

<html ng-app>

<head>

<meta http-equiv="Content-type" content="text/html; charset=utf-8">

<title>angular1</title>

<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.3/angular.min.js">

</script>

</head>

<body>

<input type="text" ng-model="myModel" value="{{myModel}}" />

<br />

<input type="text" ng-model="myModel" value="{{myModel}}" />

</body>

</html>

The next example will show how an external service (in this case, Google Finance) can be called and the JSON object returned displayed, as shown in Figure 10-5. The Google Finance API has been deprecated but no shutdown date is currently scheduled. The initial example will not be terribly pretty or fully functional, but only a small amount of Angular functionality is required to perform all of this work.

Google Finance data for Apple

Figure 10-5. Google Finance data for Apple

OBTRUSIVE JAVASCRIPT FOR CONSOLIDATED EXAMPLES

In these examples, JavaScript is included inline to see all of the moving parts in one place. An unobtrusive approach dictates that such code should be externalized. In addition, Angular controllers ideally should be small, and complex code should be extracted into Angular services. These examples displayed in the context of a book are kept to a single file for ease of explanation. They are intentionally obtrusive for purposes of explanation and illustration. An unobtrusive approach is to be preferred for actual projects.

In the previous example, the ng-app directive stood alone. In this case, the angular module is specifically named app (the value assigned to the ng-app attribute in the HTML element). Angular is broken up into several different JavaScript files that can be included as needed. The Angular JavaScript library resource is included immediately after the base library and contains the code for the ngResource module. This module allows for RESTful interactions rather than using calls to the lower level $http service. The app module we defined is dependent on the ngResourcemodule.

In this and our previous example, an ng-model mapped to a JavaScript variable. The ng-controller directive here references a JavaScript function called AppCtrl. Though all Angular controllers are JavaScript functions, the converse is not true. The lookup() function is contained within the execution context (or $scope) of the controller function. The function is called whenever the button is clicked as specified by the ng-click directive. The lookup function calls the $resource service, which is defined in the ngResource module. The parameters passed to the service reference the Google Finance API. An HTTP GET uses JSONP (required because the call requests data from a server in a different domain) and an array is expected to be returned. The array returned is assigned to $scope.result, the first record of which is displayed in its raw form within the pre element. The double brackets are bindings that contain Angular expressions that are processed by the $parse service:

<!DOCTYPE html>

<html ng-app="app">

<head>

<meta http-equiv="Content-type" content="text/html; charset=utf-8">

<title>angular2</title>

<script src=

"http://ajax.googleapis.com/ajax/libs/angularjs/1.0.3/angular.min.js">

</script>

<script src=

"http://ajax.googleapis.com/ajax/libs/angularjs/1.0.3/angular-resource.min.js">

</script>

<script>

angular.module('app', ['ngResource']);

function AppCtrl($scope, $resource) {

$scope.lookup = function(){

$scope.result = $resource(

'https://finance.google.com/finance/info',

{

client:'ig',

callback:'JSON_CALLBACK'

},

{

get: {

method:'JSONP',

params:{q: $scope.stockSymbol},

isArray: true

}

}

).get();

}

}

</script>

</head>

<body>

<div ng-controller="AppCtrl">

<input type="text" ng-model="stockSymbol" />

<pre>{{result[0]}}</pre>

<button ng-click='lookup()'>Lookup</button>

</div>

</body>

</html>

The display of raw JSON can be replaced with a formatted display of the data it contains. For instance, to display the current price, use this:

{{result[0].l_cur}}

It is a bit unwieldy to show all of the code examples inline; the full code is available at GitHub.

The angular3.html example, shown in Figure 10-6, adds the ability to add a stock to a “portfolio” displayed in a tabular form. An input text field can be used to filter data, and columns can be sorted by clicking on the headers. Records can also be deleted from the table. Unfortunately, there is no persistence in place, so refreshing the browser results in all records that have been added to the portfolio being forgotten.

Portfolio listing

Figure 10-6. Portfolio listing

The angular4.html example remedies this situation by including server-side integration. Data is simply stored in server memory that persists until the server restarts. The webapp.rb is a Ruby application built using the Sinatra microframework. The first two lines import the relevant Ruby and Java resources. Strictly speaking, Java could be omitted altogether and this would run in C-based Ruby implementations. It is included to illustrate how Java can be included, with a call to System.currentTimeMillis() included in a call to GET of /version, as shown in Figure 10-7.

Page showing that both Java and Ruby are available and functioning

Figure 10-7. Page showing that both Java and Ruby are available and functioning

A class variable ($stocks) is defined as an array that holds the portfolio of stock records. HTTP PUT, DELETE, and GET can be used to perform CRUD operations on individual stock records, and the list of all stocks can be returned through the /stocks GET URL:

%w{rubygems sinatra java json}.each{|r|require r}

java_import 'java.lang.System'

$stocks = []

get '/' do

Dir.entries('public').entries.map{|f|

"<a href='#{f}'>#{f}</a><br/>" if f=~/.*\.html/

}.join

end

get '/version' do

"(Ruby Platform: #{RUBY_PLATFORM} "+

"Ruby Version: #{RUBY_VERSION}) " +

"Call From Java: #{System.currentTimeMillis()}"

end

get '/stocks' do

$stocks.to_json

end

get '/stock/:t' do

stock = $stocks.find{|e|e['t']==params['t']}

if stock.nil?

status 404

else

status 200

body(stock.to_json)

end

end

delete '/stock/:t' do

stock = $stocks.find{|e|e['t']==params['t']}

if stock.nil?

status 404

else

$stocks.delete(stock)

status 202

body(stock.to_json)

end

end

put '/stock/:t' do

o = JSON.parse(request.body.read)['data']

puts "---\n #{o['name']} \n---\n"

$stocks << o

status 200

end

The angular5.html example is our first nod toward styling. Twitter Bootstrap CSS is included, a few styles are defined, and HTML elements that follow Bootstrap conventions are added, as shown in Figure 10-8.

Styled portfolio

Figure 10-8. Styled portfolio

At this point, it would make sense to refactor the project to extract CSS and JavaScript in HTML into external files. The entire project might be retrofitted into a starter project geared toward the intended audience and development tools.

Conclusion

Angular and Sinatra can be used to produce highly dynamic applications with far less code than is possible using other popular libraries. They are worth reviewing not only for their own value, but because of the continuing influence they are exerting on other technologies. Sinatra has inspired a number of other HTTP DSLs. Angular anticipates technologies like HTML5 web components with similarities such as declarative templates and data binding. They are mature technologies that are usable on current projects and also suggest the trajectory of future web development efforts.