Ruby on Rails (via JRuby) - OpenMRS

peruvianwageslaveInternet and Web Development

Feb 5, 2013 (4 years and 8 months ago)

119 views


As we rewrite the UI, we have a one
-
time
opportunity to pick a new web framework


All options are on the table


Criteria


Plug in our existing Java API


Support extensions via modules


Allow us to build reusable components


Easy! Fun! Beautiful!


Accessible to entry
-
level coders


Well
-
supported


Spring 3


Spring MVC + JQuery + roll
-
our
-
own widgets


Spring MVC + JQuery + StringTemplate


JSF + ICEFaces


Ruby on Rails (via JRuby)


JQuery


Grails


YUI


JQuery


GXT


“the
leading platform to build and run enterprise
Java applications. Led and sustained by
SpringSource, Spring delivers significant benefits
for many projects, increasing development
productivity and runtime performance while
improving test coverage and application quality”



Why?


We use it, we like it


Extremely flexible


Heavily used, and getting better all the time



http://svn.openmrs.org/openmrs
-
contrib/ui
-
frameworks/spring3mvc+jquery


Thanks
Harsha
!



The modern version of what we're doing now


Maven! Jetty!


Spring 3 MVC has lots of timesaving features
we haven’t taken advantage of


Jquery is powerful and we haven't taken
advantage


Consciously

build reusable UI widgets


@Controller

public

class

PatientController {



@RequestMapping
(value=
"/patient"
, method=RequestMethod.
GET
)


public

String viewPatient(
@RequestParam
(
"patientId"
) Patient patient,









Model model) {



model.addAttribute(patient);



if

(patient !=
null
)



model.addAttribute(
"encounters"
, Context.
getEncounterService
().getEncountersByPatient(patient));


}


}

<
script

type
=
"text/javascript"
>


$(
function
() {



$(
'#tabs'
).tabs();


});

</
script
>


...


<
div

id
=
"tabs"
>


<
ul
>



<
li
><
a

href
=
"#tabs
-
1"
>
Patient
</
a
></
li
>



<
li
><
a

href
=
"#tabs
-
2"
>
Encounters
</
a
></
li
>


</
ul
>


...


<
div

id
=
"tabs
-
2"
>



<
h1
>
Patient Encounters
</
h1
>



<openmrs
:encounters




encounterList
="
${encounters}
"
/>


</
div
>

<%@

attribute

name
=
"encounterList"

required
=
"
true”
type
=
"java.util.List"

%>

...

<
script

type
=
"text/javascript"
>


$(document).ready(
function
() {



$(
'#encounters'
).dataTable();


});

</
script
>


<
table

id
=
"encounters"

border
=
"1"

width
=
"500"

cellspacing
=
"0"
>

<
thead
>


<
tr
>


<
th
>
Encounter Id
</
th
>


<
th
>
Encounter Date
</
th
>


<
th
>
Encounter Patient
</
th
>


</
tr
>

</
thead
>

<
tbody
>


<
c:forEach

var
=
"encounter"

begin
=
"0"

items
=
"${encounterList}"
>



<
tr

class
=
"gradeX"
>



<
td
>
${
encounter.encounterId}
</
td
>



<
td
>
${
encounter.encounterDatetime}
</
td
>



<
td
>
${
encounter.patientId}
</
td
>



</
tr
>


</
c:forEach
>

</
tbody
>

</
table
>


Pros:


Flexibility (never “not be able to do” something)


Strong community


Configured right, it allows RAD


No learning curve for existing
devs



Cons:


Significant learning curve for
newbies

(lots of
technologies, and they are not simplified)



Open questions:


Can we leverage Hibernate annotations?


http://github.com/diptanu/openmrs
-
beta/tree/module



-
Thanks Thoughtworks!



[StringTemplate's] distinguishing characteristic is that it
strictly enforces model
-
view separation unlike other engines.
Strict separation makes websites and code generators more
flexible and maintainable.”



Controllers like you're used


StringTemplate instead of JSP


Recommended by Thoughtworks developers


Templates allow for page layouts and
reusable components

layout/layout.st

<html>

<head>


<title>Patient
Dashboard
</title>


<link
rel
="
stylesheet
"
href
="../../../styles/jquery.ui.all.css" type="text/
css
">


<script type="text/
javascript
"
src
="../../../scripts/
jquery
-
1.4.2.
js
"></script>


<script type="text/
javascript
"
src
="../../../scripts/
jquery
-
ui
-
1.8.2.custom.min.js"></script>


<script type="text/
javascript
"
src
="../../../scripts/jquery.dataTables.js"></script>

</head>


<body>


<
div class="header">$partials/header()$
</div>


<div class="content">$body$
</div>


<div class="footer">$partials/footer()$
</div>

</body>

</html>

patientdashboard.st

<div id="tabs">


<ul>


<li><a
href
="#tabs
-
1">Patient</a></li>


<li><a
href
="#tabs
-
2">Encounters</a></li>


$
menuext
:{item|


<li><a
href
="#tabs
-
3">$item$</a></li>


}$


</ul>


...


<div id="tabs
-
2">


$widgets/encounters(encounters=patient.encounters)$


</div>

...

widgets/encounters.st

$if(encounters)$

<script type="text/javascript">


jQuery(function() {



jQuery
('#encounters').dataTable();


});

</script>

<table id="encounters" border="1" width="500"
cellspacing
="0">

<thead>


<tr>


<th>Date</th>


<th>Location</th>


<th>Type</th>


<th>Provider</th>


</tr>

</thead>

<tbody>

$encounters:{ encounter |


<tr class="gradeX">


<td>$encounter.encounterDatetime$</td>


<td>$encounter.location.name$</td>


<td>$encounter.encounterType.name$</td>


<td>$encounter.provider.givenName$</td>


</tr>

}$

</tbody>

</table>


$else$


<b>There are currently no encounters</b>

$
endif
$


Pros:


Strict Model
-
View separation will make us program
better



Cons:


Unclear whether this approach has significant
advantages over more
-
commonly
-
used technologies



Open Questions:


When a module attaches to an extension point, that now
becomes view
-
only, and can’t add any data to the
model. Right?


http://svn.openmrs.org/openmrs
-
contrib/ui
-
frameworks/jsf+icefaces/


Thanks Shazin! (ICEFaces 1.8, JSF 1.2, no Spring)


JSF 2 with Spring in progress



JSF:
Developed through the Java Community Process under JSR
-

314, JavaServer Faces
technology establishes the standard for building server
-
side user interfaces. With the
contributions of the expert group, the JavaServer Faces APIs are being designed so that they
can be leveraged by tools that will make web application development even easier


ICEFaces:
J2EE Ajax framework for developing and deploying rich enterprise applications
(REAs). With ICEfaces, enterprise Java developers can easily develop rich enterprise applications
in Java, not JavaScript



JSF instead of Spring MVC


Component
-
based


Server
-
side state, stored in the session



Facelets instead of JSP



No Javascript required! JSF automatically reloads page
fragments as required

...

<
ice:form
>


<
ice:hiddenText

binding
="#{patient.init}"
/>


<
ice:panelTabSet
>



...



<
ice:panelTab

id
=
"encounters"

label
=
"#{msgs['encounters.tab.label']}"
>




<
ice:panelGrid

id
=
"panelGrid2"

>





...





<
ice:dataTable

value
=
"#{table.body}"
var
=
"item"
>






<
ice:column
>







<
f:facet

name
=
"header"
>








<
ice:outputText

value
=
"#{msgs['encounters.date.label']}"
/>







</
f:facet
>







<
ice:outputText

value
=
"#{item.date}"
/>






</
ice:column
>






<
ice:column
>







<
f:facet

name
=
"header"
>








<
ice:outputText

value
=
"#{msgs['encounters.location.label']}"
/>







</
f:facet
>







<
ice:outputText

value
=
"#{item.location}"
/>






</
ice:column
>






...





</
ice:dataTable
>




</
ice:panelGrid
>



</
ice:panelTab
>



</
ice:panelTabSet
>

</
ice:form
>

...

// JSF 1.2 with XML
-
defined beans


public

class

TableBean
implements

PageBean {


private

List
headers
;


private

List<EncounterDTO>
body
;



public

List<EncounterDTO> getBody() {


return

body
;


}



public

void

setBody(List<EncounterDTO> body) {


this
.
body

= body;


}



public

List getHeaders() {


return

headers
;


}



public

void

setHeaders(List headers) {


this
.
headers

= headers;


}

}

// JSF 2.0 with Spring
-
managed beans


@Component
(
"helloWorld"
)

@Scope
(
"session"
)

public

class

HelloWorldBean
{



public

String getMessage() {



return

"Hello World!"
;


}



public

User getAuthenticatedUser() {



return

Context.
getAuthenticatedUser
();


}


}


Pros:


JSF is the J2EE 6 Standard


ICEFaces

looks good



Cons:


Steep learning curve


Poor RAD (editing a single server
-
side component
requires restarting Jetty)



Open Questions:


How will module extensions work in a component
-
based framework?


http://svn.openmrs.org/openmrs
-
contrib/ui
-
frameworks/jruby+rails+jquery/



“Ruby on Rails is an open
-
source web framework
that’s optimized for programmer happiness and
sustainable productivity. It lets you write
beautiful code by favoring convention over
configuration”


“A domain
-
specific
-
language for database
-
backed web applications”



Thoughtworks

CTO recommends Java
backends

with
RoR
-
via
-
JRuby

front
-
ends

class
PatientController

<
ApplicationController




def view


@patient =
Context.patientService.getPatient
(
params
[:id].
to_i
)


if @
patient.nil
?


puts "No patient found!"


raise "No patient found!"


end


@encounters =
Context.encounterService.getEncountersByPatient
(@patient)


# record that this user viewed this patient


PatientViewed.record_view
($
omrs.authenticated_user
, @patient)


end




def find


ret = [];


$
omrs.patient_service.getPatients
(
params
[:query]).each do |patient|


ret <<
helpers.convert_to_dto
(patient)


end


render :
json

=> ret


end



end

Conventions for project layout


app


controllers


helpers


models


views


layouts


(controller)


(action)


config


environments


initializers


locales


public


images


javascripts


stylesheets

<script type="text/
javascript
">


$(document).ready(function() {


$("#tabs").tabs();


});

</script>


<h1><%= format @patient %>
-

<%= format @
patient.patient_identifier

%></h1>






<div id="tabs
-
2">


<%= render :partial => "widgets/
encounter_list
",


:locals => { :encounters => @encounters } %>


</div>


ActiveRecord

and Migrations make it very easy to create
(application
-
level) database
-
backed functionality


jruby

script/generate model
PatientViewed

user_id:int

patient_id:int

This migration is automatically created

class
CreatePatientVieweds

<
ActiveRecord
::Migration


def
self.up


create_table

:
patient_vieweds

do |t|


t.integer

:
user_id


t.integer

:
patient_id



t.timestamps


end


end



def
self.down


drop_table

:
patient_vieweds


end

end

The “domain object” doesn’t declare any properties, because those
are impl楥搠by the dat慢慳e tab汥


c污獳
偡瑩tn瑖楥睥d


Ac瑩癥o散潲o
㨺B慳a



# 来琠牥r敮琠
偡瑩tn瑖楥睥摳

f潲⁴o攠杩癥g us敲


摥f
s敬昮牥e敮瑬祟癩敷敤
(
use牟楤
Ⱐn㴵F

††
慬氨㩣潮摩瑩潮s 㴾=嬢
us敲e楤

㴠㼢Ⱐ
us敲e楤
崬z㩯牤敲e㴾="
c牥慴r摟慴

䑅千∬ 㩬業楴i㴾=n F


敮e




# 来琠楤i 潦⁰慴楥a瑳 牥r敮瑬礠癩敷v搠批 瑨攠cu牲敮琠us敲


摥f
s敬昮牥e敮瑬祟癩敷敤_楤i
(
us敲e楤
Ⱐn㴵F

††
牥r敮瑬祟癩敷vd
(
us敲e楤
Ⱐn⤮F潬汥o琠笠|


灶p灡瑩pn瑟楤

}


敮e



# record that a User viewed a Patient


def
self.record_view
(
openmrsuser
,
openmrspatient
)


destroy_all
({ :
user_id

=>
openmrsuser.user_id
, :
patient_id

=>
openmrspatient.patient_id

})


create({ :
user_id

=>
openmrsuser.user_id
, :
patient_id

=>
openmrspatient.patient_id

})


end



end


Pros:


It’s Fun!


Excellent RAD


Mongrel + Rails seems to be better than Jetty +Spring for this


ActiveRecord

and migrations



Cons:


Rails session != Java session => need to write our own session management


No idea of Services


Domain objects are supposed to be intelligent


Wouldn’t be able to use the full power of the framework, because of our dumb domain
objects.


Potential for some annoying incompatibility to hit us out of the blue



Open Questions:


How would module extensions work? Would an
omod

contain Java code *and* Ruby
code? (Yuck.)


How would Ruby migrations work with
Liquibase
?


How good is Eclipse integration?

From views/widgets/_
patient_search.html.erb

...

<script type="text/
javascript
">


var

options = {



clickUrl
: function(
rowNum
, item) {




return "/patient/view/" +
item.patient_id
;



},



icon: function(
rowNum
, item) {




return '<
img

src
="/images/' + (
item.gender

== 'M' ? 'male' : 'female') + '.
png
"/>';



},






};



$(document).ready(function() {



$('#<%= id %>_form').submit(function() {




$.
getJSON
("<%=
patient_search_opts.search_url

%>",






{ query: $('#<%= id %>_query').
val
() },






<%= id %>_
results_results_callback
(options)




);




return false;



})


});

</script>


<form id="<%= id %>_form">


ID or Name: <input id="<%= id %>_query" type="text"/> <input type="submit" id="<%= id %>_button"/>

</form>


<%= render :partial => "widgets/
vertical_panel
",


:locals => { :id => "#{id}_results", :options =>
patient_search_opts

} %>


“GRAILS: the search is over…

Have your next Web 2.0 project done in weeks instead of
months. Grails delivers a new age of Java web application
productivity.”



Attempts to replicate the magic of
RoR

in the
Java world (used to be called “Groovy on
Rails” until
RoR

asked them to stop)


Uses Spring, Hibernate,
SiteMesh
, Jetty, …



Groovy


Java + dynamic typing + closures + compiler magic


99.9% of legal Java is legal Groovy

def results =
Context.patientService.getPatients
(“
darius
”).collect {


[
ptId
:
it.patientId
,


name:
it.personName.toString
() ]

}


http://svn.openmrs.org/openmrs
-
contrib/ui
-
frameworks/grails
-
poc
-
dj/


class
PatientController

{



def view = {



def p =
Context.patientService.getPatient
(params.int['
patientId
']);



[ patient: p, encounters:
Context.encounterService.getEncountersByPatient
(p) ]


}




def
searchJson

= {



List<Patient> pats =
Context.getPatientService
().
getPatients
(
params
['query']);



def output =
pats.collect

{




[
patientId
:
it.patientId
,





age:
it.age
,





gender:
it.gender
,





name:
it.personName.toString
() ]



}



render output as JSON


}

}

class
OpenmrsTagLib

{


static namespace = '
openmrs
'



def format = {
attrs

-
>



def
obj

=
attrs
['object']



f (
obj

!= null) {




if (
obj

instanceof

User) {





out << "${
obj.username
} (${
formatName
(
obj.person
)})“




} else {





out << "${
obj
} (don't know how to handle ${
obj.class
})"




}


}



String
ajaxSearchHelper
(Map
attrs
) {





return
"
""

<form id="${id}_form“>


<input id="${id}" type="text" size="40"/>


<input type="submit" id="${id}_button" value="Search"/>

</form>

<div id="${id}_results" class="vertical
-
panel"></div>

<script type="text/
javascript
“>


${
createDecoratorIfNecessary
}


\
$(document).ready(function() {
\
$("#${id}_form").submit(function() {





"""


}



http://svn.openmrs.org/openmrs
-
contrib/ui
-
frameworks/grails
-
poc/


Thanks
Harsha
!

<
gui:tabView

id="
tabView
">





<
gui:tab

id="t1" label="Encounters">



<h2>Patient Encounters</h2>



<
gui:dataTable


draggableColumns
="true“




columnDefs
="[





[
date:'Date
',
sortable:true
,
resizeable
: true],





[
location:'Location
',
sortable:true
,
resizeable
: true],





[
type:'Type
',
sortable:true
,
resizeable
: true],





[
provider:'Provider
',
sortable:true
,
resizeable
: true]]“




sortedBy
='date‘





controller="patient”




action="
patientEncounters





params
="[id:'${model.id}']"




caption="click on a row, and it will expand"




paginate="true"




rowExpansion
="false"




rowsPerPage
="20"




totalRecordsKey
="
meTotalRecs
"




/>


</
gui:tab
>



<
openmrs:extensionPoint





pointId
="
org.openmrs.patientDashboardTab
" type="html">






</
openmrs:extensionPoint
>




</
gui:tabView
>

<head>



<meta name="layout" content="
openmrs
"/>


</head>


<body>


<h1>Find a patient</h1>


<g:form controller=“patient” action=“find">



ID or Name:



<g:textField name="id”/>



<g:submitButton name="submit" value=“Search"/>


</g:form>




<body>

It’s unlikely that we’d choose
YUI over
Jquery
, even if it
does come in the Grails
-
UI
plugin



Pros:


It’s Fun!


Almost no learning curve for current developers


RAD almost as good as in Rails


GORM (analog of
ActiveRecord
)


Jetty (needs to restart more often than Mongrel)


Sitemesh

handles page templates


Auto
-
recompiling groovy
taglibs

are great for pulling repeated code out of pages



Cons:


Small community, possible lack of future support


IDE integration is surprisingly mediocre


A couple times I’ve read: “we lost almost all the productivity gains Grails gave us
because of undocumented bugs in Grails”. (Personally Google searching has found
me answers for all errors I’ve searched for.)



Open Questions:


Can we use the embedded database to allow you to develop without even having a
MySQL

database?


Is Spring
Roo

worth looking into as an alternative?


http://svn.openmrs.org/openmrs
-
contrib/ui
-
frameworks/spring3+gxt/


-
Thanks
Sy
!



GWT is a development toolkit for building and optimizing
complex browser
-
based applications


Ext JS is a cross
-
browser JavaScript library for building rich
internet applications. Ext JS features high performance,
customizable UI widgets and a well
-
designed and extensible
component model.


=> Ext GWT is a Java library for building rich internet
applications with Google Web Toolkit (GWT)



You only write Java! GWT produces HTML,
Javascript
, AJAX, …


“Rich Internet Application” instead of “a
webapp


DTOs, for sending data between client and server

package
web.openmrs.common
;



public class
EncounterDTO

implements
Serializable

{


private Integer id;


private Date
date
;


private String location;


private String type;


private String provider;


private List<
ObsDTO
>
obs
;




}



Client UI code looks like a Swing app


public class
MainContent

extends
LayoutContainer

{





public void
openPatientDashboard
(
PatientDTO

patient) {



removeAll
();



add(new
PatientDashboard
(patient));



layout();


}

}



public class
PatientDashboard

extends
LayoutContainer

{





@Override


protected void
onRender
(Element parent,
int

pos) {



super.onRender
(parent, pos);



setLayout
(new
RowLayout
(
Orientation.VERTICAL
));







TabItem

demographics = new
TabItem
("Demographics");

demographics.add
(
createDemographicsForm
());




TabItem

encounters = new
TabItem
("Encounters");



encounters.add
(new PatientEncounterPanel2(patient));




tabs = new
TabPanel
();



tabs.add
(demographics);



tabs.add
(encounters);






}

You need lots of (formulaic) classes:


(client)

interface
PatientService

interface
PatientServiceAsync


(common)

class
PatientDTO


(server)

class
PatientServiceImpl


This would be a completely different sort of
application


Pros:


No HTML or
Javascript
!


“Since this is Java, the API and
JavaDocs

are available in the IDE as context
sensitive help and the source code is available to look at instead of
referring to (outdated)
webpages
, also has a large library of examples and
showcase”



Cons:


Have to build a set of DTOs and services specifically for GWT


Steep(?) learning curve for existing and newbie
devs


“the initial load to the client's browser is fairly large but a good framework
for loading is in place. the plus side to this is once it is loaded, the page
never has to be loaded again so page loads do not exist, only data calls
(reducing input/output over the wire as well as server
load)”



Open Questions:


Could modules plug in, given the client
-
server architecture?

Spring3
-
JSP

Spring3
-
StringTemplate

Spring3
-
JSF
-
ICEFaces

JRuby
-
Rails

Grails

GXT

Learning

curve (new
dev)

Medium

Medium

High

Low

Low

High?

Learning
curve
(existing

dev)

None

Low

High

Low

Trivial

High?

OpenMRS

Modules

Easy

???

???

???

Easy

???

Embedded
web
server

Good

(Jetty)

Good (Jetty)

OK

(Jetty)

Excellent
(Mongrel)

Good

(Jetty)

Good

Embedded DB

in dev mode

???

???

???

???

???

???

RAD domain
objects

??? (Hibernate
annotations?)

??? (Hibernate
annotations?)

???

Yes
(
ActiveRecord
)

Yes (GORM)

No?

Layouts

Need to
integrate
something

Yes

N/A?

Yes

Yes (
Sitemesh
)

N/A

Built
-
in
Widgets

None

None

ICEFaces

Won’t use

Won’t use

EXT

Tech
for
widgets

Roll our own

StringTemplate

JSF

Partials,
helpers

Taglibs
,
templates

“tight and
extensible”

Eclipse
integration

Good

Good

???

??? (
RadRails
)

Mediocre
(Spring
plugin
)

Excellent
(Google
plugin
)

Community

Large

Large*

Large

Large*

Small

Small

Concerns

No Services

Modules may
be impossible


More review of JSF 2 +
ICEFaces

2


Is GXT even an option?


Can it support modules?


Can we use an embedded DB in development
mode with any of these technologies?


Can Hibernate Annotations provide some of
the functionality of
ActiveRecord
/GORM in
Spring
-
3
-
based technologies?


JSR
-
286
Portlets