350 likes | 561 Views
Rails and AngularJS. Building the Assay Pipeline http://assaypipeline.com Sean Carroll / Alain Mir. Assay. A laboratory test used in Disease diagnosis DNA matching Drug testing Assay Design is a complex, iterative process Scientists use an array of FOSS tools
E N D
Rails and AngularJS Building the Assay Pipeline http://assaypipeline.com Sean Carroll / Alain Mir
Assay • A laboratory test used in • Disease diagnosis • DNA matching • Drug testing • Assay Design is a complex, iterative process • Scientists use an array of FOSS tools • Commercial products now appearing
Assay An assay is an investigative (analytic) procedure in laboratory medicine, pharmacology, environmental biology, continuous delivery, and molecular biology for qualitatively assessing or quantitatively measuring the presence or amount or the functional activity of a target entity (the analyte). - Wikipedia
Project Background “Do you know about databases” ? What does it do ? Cancer tumor database ~ 1.2m entries Large datasets Application built as a Rails 3.2 application
Demo Application Demo
UX Components AngularJS Restangular Bootstrap (CSS only) UI Bootstrap (js) UI Select 2 UI Sortable Xeditable
Why Angular Immersive Experience Organize the JavaScript and Rails Helper spaghetti Lots of Ajax functionality Clear separation between the view and model It’s new-ish and cool It’s the most popular JS framework today
Challenges • Learning curve!! • Angular • Javascript • Promises ! • Changing technologies 1 -> 1.2 • Version 2 of Angular quite different • Building two distinct applications
Architecture Rails Model Angular Routes Rails Controller Angular Controller / Model Rails API JSON / HTTP Rails Serializer Rails Routes HTML Templates
Why keep Rails • Why not go with a full stack Javascript solution? • Gems • Framework Stability • What I already knew • This application doesn’t need the real-time push of Meteor.js
How to Integrate Angular • Option 1: Completely separate applications • Build the front end with Grunt • Can still live in the same Github repo • Option 2: Server Angular with the Asset Pipeline • Definitely easier • Need LiveReload to keep assets up to date (including Angular.js application code) • Can continue to use Rails ERB views for some parts of the application
Rails and Angular Turn off Turbolinks Turn off / replace JavascriptMangling Create a API for Rails – Angular communication Create Angular “View-Controllers” Create Angular Routing Create Angular HTML pages
Gemfile gem 'angularjs-rails','~> 1.2.6’ gem 'ngannotate-rails'
layouts/application.html.erb Bootstrap the Angular application <body ng-app="assaypipelineApp"> <%= yield %> </body>
layouts/application.js //= require angular //= require angular-resource //= require angular-route //= require lib/restangular.min //= require assaypipelineApp //= require_tree .
layouts/application.js //= require angular //= require angular-resource //= require angular-route //= require lib/restangular.min //= require assaypipelineApp //= require_tree .
javascripts/assaypipelineApp.js varassaypipelineApp = angular.module('assaypipelineApp', [ 'ngRoute', 'ngResource', 'ui.sortable', 'ui.bootstrap', 'angularFileUpload', 'ui.select2', 'restangular', 'xeditable' ]);
javascripts/assaypipelineApp.js assaypipelineApp.config([ '$httpProvider', function ($httpProvider) { varauthToken; authToken = $('meta[name="csrf-token"]').attr('content'); $httpProvider.defaults.headers.common['X-CSRF-TOKEN'] = authToken; } ]);
Rails API class Api::BaseController < ApplicationController before_filter :authenticate_user! private defpermission_denied render json: {error: 'unauthorized'}, status: :unauthorized end end
class Api::UsersController < Api::BaseController before_filter :verify_is_admin def index renderjson: User.order(:id).all, root: false end def show renderjson: user, root: false end defcreate user = User.create!(safe_params) renderjson: user, root: false end def update user.update_attributes(safe_params) head :no_content end
def destroy user.destroy render nothing: true end private defverify_is_admin (current_user.nil?) ? redirect_to(root_path) : (redirect_to(root_path) unless current_user.admin?) end def user @user ||= User.find(params[:id]) end defsafe_params params.permit(:id, :title, :first_name, :last_name, :name, :email, :job_title, :organisation, :approved, :admin) end end
Rails Routes namespace :api, defaults: {format: :json} do devise_scope :user do resource :session, only: [:create, :destroy] resource :registrations end
Rails Routes $ rake routes api_usersGET api/users#index {:format=>:json} POST api/users#create {:format=>:json} new_api_user GET api/users#new {:format=>:json} edit_api_user GET api/users#edit {:format=>:json} ....etc
Rails Serializer Serializers/users_serializer.rb class UserSerializer < ActiveModel::Serializer attributes :id, :title, :first_name, :last_name, :email, :approved, :admin end
Angular Routes Javascripts/assaypipelineApp.js $routeProvider.when('/design', { templateUrl: '/templates/design/design.html', controller: 'DesignViewCtrl' }); $routeProvider.when('/batches/:panel_id', { templateUrl: '/templates/panel/panel.html', controller: 'PanelController' }); $routeProvider.when('/datasets', { templateUrl: '/templates/datasets/datasets.html', controller: 'DatasetsController' });
Angular“ViewController” angular.module('assaypipelineApp').controller("PanelsViewCtrl", ['$http', '$scope', '$routeParams', '$location', 'Batch', function($http, $scope, $routeParams, $location, Batch) { // Initialize by loading panels Batch.index().$promise.then(function(batches) { $scope.panels = batches; }, reportError("loading panels"));
Angular Model $scope.panels = batches; Fields defined in Rails controller / serializer def index data = panels.as_json(methods: :user_name, only [:id, :name, :description… render json: data, root: false end
More complex Angular models $scope.selector = { selectedGene: '', selectedExons: [], exons: [], regionChrom: '', regionStart: '', regionEnd: '', configVisible: false, paneldata: { batch_type: 'singleplex', primer_runs: 20 tiling_sequence: '', min_primer_tm: 60,
panels.html <span ng-if="panel.public_panel == true"> <span class="label label-primary">Public</span> </span> <button class="btnbtn-info btn-xs" ng-click="duplicatePanel(panel)”type="button"> Copy </button> <a href="/batches/{{panel.id}}">{{panel.name}} - {{panel.id}} </a><br/> <span>{{panel.description}}</span><br/> <small>Updated {{panel.updated_at}}</small>
Angular < = > Rails • $http • Lowest level • Construct urls manually • Similar to jQuery / AJAX • $resource • Angular wrapper around $http • Gives the basic REST verbs for free • Usually factor out into a service • Restangular • Easiest by far • Handles nesting • Overkill for non-REST fall back to http for non-restful
$http function getAssemblies() { $http({ method: 'GET', url: '/api/assemblies/' }).success(function(data) { console.log(data); $scope.availableAssemblies = data; }) };
Restangular // Restangular returns promises Restangular.all('users').getList() // GET: /users .then(function(users) { // returns a list of users $scope.user = users[0]; // first Restangularobj in list: { id: 123 } }) // Restangular objects are self-aware and know how to make their own RESTful requests $scope.user.getList('cars'); // GET: /users/123/cars // You can also use your own custom methods on Restangular objects $scope.user.sendMessage(); // POST: /users/123/sendMessage // Chain methods together to easily build complex requests $scope.user.one('messages', 123).one('from', 123).getList('unread'); // GET: /user/123/messages/123/from/123/unread
Resources Riding Rails with AngularJS http://www.fullstack.io/edu/angular/rails/ AngularJSRails http://www.angularails.com/ egghead.io