angular.js + rails at wework or: the accidental feature
DESCRIPTION
for 2013-03-19 Angular.js NYC MeetupTRANSCRIPT
Angular.js + Rails = ♥or: The Accidental Feature
By Sr. Software Engineer, Jonathan E. Magen / @yonkeltron WeWork
Identify yourselfJonathan E. Magen / errywhere elsePractitioner of the Computer SciencesResearch @ Advanced OS LabWeWork engineer
yonkeltron
The Evergreen State College
About WeWork
- Adam Neumann, WeWork Co-Founder
- Miguel McKelvey, WeWork Co-Founder
7 Buildings3 Cities4000+ MembersServices for the community
“ We want to change the way people work. ”
“ Design to make people happy. ”
Computing at WeWorkTech stack
RailsHerokuPostgreSQL
Agile!
WeWork.com MemberNetwork
Spaceman: WeWorkbackoffice hawtness
Space managementBillingReservationsAccounting system integration
Things we've built withAngular
Audit trail + changelog inspector using gemInfinite scroll using gemInline editLots of stuff using
auditedwill_paginate
ngResource
Angular.js + Rails♥
Free RESTful JSON endpoints
ActiveRecord → JSON → HTTP
class OfferingsController < ApplicationController
def index @offerings = Offering.page(params[:page])
respond_to do |format| format.html # index.html.erb format.json { render json: @offerings, root: false } end end
# other actions...end
ngResource
Rails → JSON/HTTP → ngResource
angular.module('offeringsService', ['ngResource']). factory('Offering', function offering($resource) { return $resource('/offerings/:id', {id: '@id'}, { create: { method: 'POST' }, index: { method: 'GET', isArray: true }, show: { method: 'GET' }, update: { method: 'PUT' } }); });
Rails renders Angular template HTMLTimes you might want to do this:
Permissions: only rendering parts of a templateDynamically populate form select optionsInternationalization!
Permission-driven rendering
Only render the audit inspector directive iff the current user has theauthorization to access audit data
<%= if current_user.has_role? :auditor %> <div data-ng-app="spaceman"> <div data-audits="yep" data-auditable-type="<%= Offering.to_s %>" data-auditable-id="<%= @offering.id %>"></div> </div><% end %>
i18n API column headers
thead rendered by Rails includes localized column headers. Thislets Angular render the tbody containing actual data.
<table class="table table-striped" data-ng-app="spaceman"> <thead> <th><%= Offering.human_attribute_name(:name) %></th> <th><%= Offering.human_attribute_name(:categorization) %></th> <th><%= Offering.human_attribute_name(:price) %></th> <th></th> <th></th> </thead>
One minor snag
config/environments/production.rb
config.assets.js_compressor = Sprockets::LazyCompressor.new do Uglifier.new(mangle: false) # don't obliterate namesend
Angular on RailsRails gives you RESTful JSON endpoints for free
lets you consume them for freeLet Rails help you render templatesngResource
The Accidental Feature
It started with a spikeThe requirement:
Allow a user to quickly inline edit inventory on the index page.
*Actual Spaceman wireframe
The search forinline edit
Oh God, please make it stop
Yeah, I know there were easierways to do thisGorillions of jQuery pluginsFar fewer Angular-friendly options
We knew we'd need Angular.js butwanted to ease our way in gradually
Google GroupsStackOverflow
A subtle rejection of Reimplementing table in terms of divDoes way more than what we neededSub project of
TBH: Angular.js could really use widgets
ngGrid
Angular UI
Web Components?
How we did it
Offerings controller
spaceman.controller('offerings', function ($scope, searchParamService, Offering) { console.log('Offerings Contoller');
$scope.offerings = Offering.query(); // ngResource ZOMG!
$scope.editingToggle = function editingToggle(offering) { if (offering.editing) { Offering.update(offering); offering.editing = false; } else { offering.editing = true; } };});
The show/hide model for cellsin the table body
show(value) ⊕ show(input)
<tbody data-ng-controller="offerings"> <tr data-ng-repeat="offering in offerings"> <td> <a href="/offerings/{{offering.id}}" data-ng-show="!offering.editing">{{offering.name}}</a>
<input data-ng-show="offering.editing" type="text" data-ng-model="offering.name" required/> </td>
+ ngShow ngResource
ProblemsVerboseSoaking wet (non-DRY)Not easily reused
Next steps for inline editFigure out how to turn this in to a reusable component
Accidental success↓
Angular empowerment
Changelog + audit inspectorStrong audit trailData integrity
gemaudited
Who, what, when, where, why, how
class Offering < ActiveRecord::Base audited
# ...end
Audited::Adapters::ActiveRecord::Audit < ActiveRecord::Base { :id => :integer, :auditable_id => :integer, :auditable_type => :string, :associated_id => :integer, :associated_type => :string, :user_id => :integer, :user_type => :string, :username => :string, :action => :string, :audited_changes => :text, # <- ZOMG diff :version => :integer, :comment => :string, :remote_address => :string, :created_at => :datetime}
END / FIN / סוף
Thanks to GoogleThanks to WeWork
Thanks to you