title: (J)Ruby in Action - Web Apps & Services w/ Rails n Jetty
%css
pre { padding: 4px 4px 4px 4px; border-top: #bbb 1px solid; border-bottom: #bbb 1px solid; background: #f3f3f3; }
%end
-
Friends of Ruby (Rails)? - Anyone?
-
Friends of Functional Programming (Haskell, Yesod) - Anyone?
-
Friends of Scala, Play! - Anyone?
-
Friends of Server-Side JavaScript (Node.js) - Anyone?
-
Friends of Dart - Anyone?
Java Enterprise Architecture
- Factories, Factories, Factories
- Over-engineering, Cult of Complexity
- The COBOL of the 21st Century (Java is Old technology, No Longer Hot Java or the New New Thing => Innovation Happens Elsewhere)
- There is only Java, Java, Java - 1,000,0000,000 % Java, The End of History, Java Rules the World Hybris
- Code Blocks (Lambda Expressions) - 20 years in Ruby! Coming to Java 8 in 2014
- Open Classes
- Mixins
- Everything is a Object
- Meta Programming (e.g. Ruby Code Creates Code at Runtime on Demand)
- List, Tree, Map Data Structures in Ruby
- Templates in Ruby
- Culture - Programmer Happiness (Productivity, Keep it Simple), Innovation
- Open, Free World (Not a Product and Trademark of Oracle, Inc.)
- No Enterprise Java Application Server
- Use Embedded Jetty Library to Run Container-Less
- No Enterprise Java Database Server
- Use Embedded SQLite Library
- No Enterprise Java Application Framework
- Use Embedded Ruby Library
- No Enterprise Java IDE
- Use Command Line, Programmer's Editor
Step 1: Copy jetty-webapp-7.x.jar
(~ 1 Meg)
Step 2: There is no step 2.
Container-Less? Run your web application as a plain old Java process.
Why:
- Simpler Development
- Simpler Testing
- Simpler Packaging
- Simpler Deployment
The simplest possible Jetty server:
import org.eclipse.jetty.server.Server;
public class SimpleServer
{
public static void main(String[] args) throws Exception
{
Server server = new Server(8080);
server.start();
server.join();
}
}
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.WebAppContext;
public class AppServer
{
public static void main(String[] args) throws Exception
{
Server server = new Server(8080);
WebAppContext webapp = new WebAppContext();
webapp.setContextPath("/");
webapp.setWar( "./links.war");
server.setHandler(webapp);
server.start();
server.join();
}
}
javac -cp .;./jetty-webapp-7.x.jar AppServer.java
- Post links
- Vote links up or down
- Sort links by the newest or hot
$ rails links
create_table :links do |t|
t.string :title, :null => false
t.string :url, :null => false
t.integer :points, :default => 0
t.timestamps # note: macro adds created_at, updated_at
end
$ rake db:setup # note: rake is ruby make (simple build tool)
class Link < ActiveRecord::Base
# self.table_name = 'links'
attr_accessor :score
def recalc_score
time_elapsed = (Time.now - self.created_at) / 36000 # in hours
self.score = ((self.points-1) / (time_elapsed+2)**1.8)
end
def self.hot
self.all.each { |rec| rec.recalc_score }.sort { |l,r| l.score <=> r.score }.reverse
end
end
class LinksController < ApplicationController
# GET /
def index
@links = Link.order( 'created_at desc' ).all
end
# GET /hot
def hot
@links = Link.hot
render :index
end
# POST /
def create
l = Link.new( params[:link] )
l.save!
redirect_to :back
end
# PUT /:id/vote/:type
def vote
l = Link.find( params[:id] )
l.points += params[:type].to_i
l.save!
redirect_to :back
end
end
<table id='links'>
<%% @links.each do |l| %>
<tr>
<td class='points'>
<%%= form_for l, :url => vote_link_path( l, :type => '1' ), :method => 'PUT' do |f| %>
<%%= f.submit '+1' %>
<%% end %>
<%%= l.points %>
<%%= form_for l, :url => vote_link_path( l, :type => '-1' ), :method => 'PUT' do |f| %>
<%%= f.submit '-1' %>
<%% end %>
</td>
<td><span class='title'><%%= link_to l.title, l.url %></span>
<span class='host'>(<%%= l.url_host %>)</span>
<span class='created-at'>posted <%%= time_ago_in_words(l.created_at) %> ago</span>
</td>
</tr>
<%% end %>
</table>
<div id='post-link'>
<%%= form_for :link, :url => links_path() do |f| %>
<%%= f.text_field :title, :placeholder => 'Title' %>
<%%= f.text_field :url, :placeholder => 'URL' %>
<%%= f.submit 'Save Link' %>
<%% end %>
</div>
$ rake war # warble
$ java -cp .;./jetty-webapp-7.x.jar AppServer
=> One Plain Old Java Process - Embedded Jetty, Embedded SQLite, Embedded Ruby
Step 1: Copy jruby-complete-1.7.x.jar
Step 2: There is no step 2.
links.war
|_ images
| | _<empty>
|_ stylesheets
| |_ application.css
|_ javascripts
| | _<empty>
|_ WEB-INF
|_ app
| |_ controllers
| | |_ links_controller.rb
| |_ models
| | |_ link.rb
| |_ views
| |_ layouts
| | |_ application.html.erb
| |_ links
| |_ index.html.erb
|_ config
| |_ database.yml
| |_ routes.rb
|_ db
| |_ links.sqlite3
|_ lib
|_ jruby-complete-1.7.x.jar
|_ jruby-rack-1.x.jar
|_ gems.jar
Anyone?
Example:
{ customer:
{ id: '12345',
first_name: 'Carlos',
last_name: 'Lafata',
address:
[
{ typ: 'home',
line1: 'Burgring 11',
city: 'Wien',
zip_code: '1010' },
{ typ: 'work',
line1: 'Nestroyplatz 12',
city: 'Wien',
zip_code: '1020' }
] }
}
[ X ]
JavaScript
[ X ]
Ruby
[ X ]
Scala
[ ]
Java
[ ]
COBOL
Example:
// Assign Function to Variable
var greet = function() { document.write( 'Welcome. Benvenuti. Willkommen.' ); }
// Call Function Stored in Variable
greet();
// Function as Function Parameter
function say( what )
{
what();
what();
what();
}
// Pass Function Stored in Variable to Function
say( greet );
[ X ]
JavaScript
[ X ]
Ruby
[ X ]
Scala
[ ]
Java
[ ]
COBOL
Building Web Services (HTTP APIs) with Ruby (and Sinatra)
- What's Sinatra?
- Let a Thousand Sinatra Clones Bloom
- Why Sinatra? Goodies
- Example Web Service (HTTP API) - Routes
- Sinatra in Action -
get '/beer/:key'
- What's JSON?
- What's JSONP?
- Serializers - From Ruby Objects to JavaScript Objects
- Appendix: Sinatra Styles - Classic or Modern (Modular)
- Appendix: Database Connection Management
- Appendix: Sinatra Books
- Appendix: What's Rack?
Simple (yet powerful and flexible) micro webframework.
require 'sinatra'
get '/' do
'Hallo Wien!'
end
Sinatra itself less than 2000 lines of code.
Installation. Type in your terminal (shell):
$ gem install sinatra
Example - hallo.rb
:
require 'sinatra'
get '/' do
'Hallo Wien!'
end
Run script (server):
$ ruby hallo.rb
>> Sinatra has taken the stage...
>> Listening on 0.0.0.0:4567, CTRL+C to stop
Open browser:
Micro Frameworks Inspired by Sinatra
Express.js (in Server-Side JavaScript w/ Node.js):
var express = require( 'express' );
var app = express();
app.get( '/', function( req, res ) {
res.send( 'Hallo Wien!' );
});
app.listen( 4567 );
Scotty (in Haskell):
import Web.Scotty
main :: IO ()
main = scotty 4567 $ do
get "/" $ text "Hallo Wien!"
Dancer (Perl), Fitzgerald (PHP), Ratpack (Groovy), Zappa (CoffeeScript), Mercury (Lua), Frank (F#), Nancy (C#/.NET), Bogart (C), Flask (Python), and many more.
-
Single file scripts
-
Easy to package up into a gem. Example:
$ gem install beerdb # Yes, the beerdb includes a Sinatra app.
-
Lets you build command line tools. Example:
$ beerdb serve # Startup web service (HTTP API).
-
Lets you mount app inside app (including Rails). Example:
mount BeerDb::Server, :at => '/api/v1'
Lets build a beer and brewery API.
Get beer by key /beer/:key
. Examples:
/beer/guinness
/beer/murphysred
/beer/brooklynlager
/beer/ottakringerhelles
Get brewery by key /brewery/:key
. Examples:
/brewery/guinness
/brewery/fullers
/brewery/brooklyn
/brewery/ottakringer
Bonus:
Get random beer /beer/rand
and random brewery /brewery/rand
.
beerdb/server.rb
:
get '/beer/:key' do |key|
beer = Beer.find_by_key!( key )
json_or_jsonp( beer.as_json )
end
get '/brewery/:key' do |key|
brewery = Brewery.find_by_key!( key )
json_or_jsonp( brewery.as_json )
end
That's it.
Bonus:
get '/beer/:key' do |key|
if ['r', 'rnd', 'rand', 'random'].include?( key )
beer = Beer.rnd.first
else
beer = Beer.find_by_key!( key )
end
json_or_jsonp( beer.as_json )
end
get '/brewery/:key' do |key|
if ['r', 'rnd', 'rand', 'random'].include?( key )
brewery = Brewery.rnd.first
else
brewery = Brewery.find_by_key!( key )
end
json_or_jsonp( brewery.as_json )
end
JSON = JavaScript Object Notation
Example - GET /beer/ottakringerhelles
:
{
key: "ottakringerhelles",
title: "Ottakringer Helles",
synonyms: "16er Blech|16er Hüs'n",
abv: "5.2",
og: "11.8",
tags: [ "lager" ],
brewery: {
key: "ottakringer",
title: "Ottakringer Brauerei"
},
country: {
key: "at",
title: "Austria"
}
}
JSONP = JSON with Padding. Why?
Call Home Restriction. Cross-Domain Browser Requests Get Blocked.
Hack: Wrap JSON into a JavaScript function/callback
e.g. functionCallback( <json_data_here> )
and serve as plain old JavaScript.
Example - Content-Type: application/json
:
{
key: "ottakringerhelles",
title: "Ottakringer Helles",
synonyms: "16er Blech|16er Hüs'n",
abv: "5.2",
...
}
becomes Content-Type: application/javascript
:
functionCallback(
{
key: "ottakringerhelles",
title: "Ottakringer Helles",
synonyms: "16er Blech|16er Hüs'n",
abv: "5.2",
...
}
);
Bonus: Little Sinatra helper for JSON or JSONP response (depending on callback parameter).
def json_or_jsonp( json )
callback = params.delete('callback')
if callback
content_type :js
response = "#{callback}(#{json})"
else
content_type :json
response = json
end
end
JSON built into Ruby 2.0 as a standard library. Example:
require 'json'
hash =
{
key: "ottakringerhelles",
title: "Ottakringer Helles"
}
puts JSON.generate( hash )
>> {"key":"ottakringerhelles","title":"Ottakringer Helles"}
puts hash.to_json
>> {"key":"ottakringerhelles","title":"Ottakringer Helles"}
Serializers for your Models. Example:
class BeerSerializer
def initialize( beer )
@beer = beer
end
attr_reader :beer
def as_json
data = { key: beer.key,
title: beer.title,
synonyms: beer.synonyms,
abv: beer.abv,
...
}
data.to_json
end
end # class BeerSerializer
And add as_json
to your Model. Example:
class Beer < ActiveRecord::Base
def as_json_v2( opts={} )
BeerSerializer.new( self ).as_json
end
end # class Beer
Lets you mix 'n' match servers and apps.
Lets you stack apps inside apps inside apps inside apps inside apps.
Good News: A Sinatra app is a Rack app.
Learn more about Rack @ rack.github.io
.
Sinatra: Up and Running by Alan Harris, Konstantin Haase; November 2011, O'Reilly, 122 Pages
Jump Start Sinatra by Darren Jones; January 2013, SitePoint, 150 Pages
Next meetup: Fri, October 11th @ Sektor5
Talks (*)
Jakob Sommerhuber - sponsor talk
Martin Schürrer - Erlang/OTP in production for highly-available, scalable systems
Markus Prinz - How to improve your code
Gerald Bauer - working with Sinatra
Kathrin Folkendt - 'Chapter one' (lightning talk on getting started with Rails, and building her first web app)
(*) preliminary program
- Don't put yourself in the Java ghetto (or Java rules the world hybris)
=> Learn new concepts or languages
- Web architecture is more than Java enterprise architecture
=> Learn HTML, JS, CSS, HTTP (REST), SQL/NoSQL, etc.