SlideShare a Scribd company logo
Riding the Rails
@
DigitalOcean
• Shared business logic as an engine
• Form objects
• MySQL table as KV store
• Abusing ASN for cleanliness
gem 'core',
git: 'https://githubenterprise/digitalocean/core',
ref: '623ac1e785e092f7369d5cfa7e56ea2e98fb2e20'
“Core”
Our Rails Engine in Reverse
“Core”
Our Rails Engine in Reverse
gem 'core',
git: 'https://githubenterprise/digitalocean/core',
ref: '623ac1e785e092f7369d5cfa7e56ea2e98fb2e20'
# lib/blorgh/engine.rb
module Blorgh
class Engine < ::Rails::Engine
isolate_namespace Blorgh
config.to_prepare do
Dir.glob(Rails.root + "app/decorators/**/*_decorator*.rb").each do |c|
require_dependency(c)
end
end
end
end
# MyApp/app/decorators/models/blorgh/post_decorator.rb
Blorgh::Post.class_eval do
def time_since_created
Time.current - created_at
end
end
:thumbsdown:
# MyApp/app/models/blorgh/post.rb
class Blorgh::Post < ActiveRecord::Base
include Blorgh::Concerns::Models::Post
def time_since_created
Time.current - created_at
end
def summary
"#{title} - #{truncate(text)}"
end
end
:thumbsdown:
# Blorgh/lib/concerns/models/post
module Blorgh::Concerns::Models::Post
extend ActiveSupport::Concern
# 'included do' causes the included code to be evaluated in the
# context where it is included (post.rb), rather than being
# executed in the module's context (blorgh/concerns/models/post).
included do
attr_accessor :author_name
belongs_to :author, class_name: "User"
before_save :set_author
private
def set_author
self.author = User.find_or_create_by(name: author_name)
end
end
end
require_dependency Core::Engine.root.join('app', 'models', 'droplet').to_s
class Droplet
BACKUP_CHARGE_PERCENTAGE = 0.20
def monthly_backup_price
self.size.monthly_price * BACKUP_CHARGE_PERCENTAGE
end
end
:thumbsup:
Previously...
# app/controllers/events_controller.rb
event = Event.new(
:event_scope => 'droplet',
:event_type => params[:event_type], # from the route
:droplet_id => params[:droplet_id],
:image_id => params[:image_id],
:size_id => params[:size_id],
:user_id => current_user.id,
:name => name
)
# app/models/event.rb
# Validations Based on Event Type
case self.event_type
when 'resize'
errors.add(:size_id, "...") if size_id == droplet.size_id
errors.add(:size_id, "...") unless Size.active.include? size_id
end
class Resize
extend ActiveModel::Naming
include ActiveModel::Conversion
include ActiveModel::Validations
include Virtus
attribute :size, Size
attribute :user, User
attribute :droplet, Droplet
validates :size,
allowed_size: true,
presence: true
def save
if valid?
event.save!
else
false
end
end
def persisted?
false
end
def event
@_event ||= EventFactory.build(:resize, event_params)
end
end
# app/views/resizes/_form.erb
= form_for [@droplet, Resize.new] do |f|
= render 'sizes', sizes: @sizes_available_for_resize, form: f
= f.submit 'Resize'
class ResizesController < ApplicationController
def create
droplet = current_user.droplets.find(params[:droplet_id])
size = Size.active.where(id: params[:size_id]).first
resize = Resize.new(droplet: droplet, size: size)
if resize.save
redirect_to resize.droplet, notice: 'Your resize is processing'
else
redirect_to resize.droplet, alert: resize.errors.first
end
end
end
<3Virtus
• Validations are contextual
• Slims down god objects
• Can be tested without ActiveRecord
• Makes testing ActiveRecord classes easier
WARNING:
YOU
PROBABLY
SHOULD
NOT
DO
THIS
CREATE TABLE `user_properties` (
`user_id` int(11) NOT NULL,
`name` varchar(32) NOT NULL DEFAULT '',
`value` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`user_id`,`name`),
KEY `name` (`name`,`value`),
KEY `user_id` (`user_id`,`name`,`value`)
)
class User
include Propertable
property :marked_as_sketchy_at, nil, :date_time
property :marked_as_verified_at, nil, :date_time
property :marked_as_abuse_at, nil, :date_time
property :marked_as_hold_at, nil, :date_time
property :marked_as_suspended_at, nil, :date_time
property :marked_as_review_at, nil, :date_time
end
module Propertable
def property(name, default, type = :string)
key = name.to_s
props = class_variable_get(:@@properties)
props[key] = default
define_method(key) do
property = __read_property__(key)
if property.nil? || property == default
default
else
Propertable::Coercer.call(property.value, property.value.class, type)
end
end
define_method("#{key}?") do
!!public_send(key)
end
define_method("#{key}=") do |value|
begin
coerced_value = Propertable::Coercer.call(value, value.class, type)
rescue
coerced_value = default
end
coerced_value = Propertable::Coercer.call(coerced_value, type.class, :string)
property = __write_property__(key, coerced_value)
end
end
end
ASN = ActiveSupport::Notifications
INSTRUMENT
EVERYTHING!
$statsd = Statsd.new(statsd_ip).tap { |s| s.namespace = Rails.env }
# Request Times
ActiveSupport::Notifications.subscribe /process_action.action_controller/ do |*args|
event = ActiveSupport::Notifications::Event.new(*args)
status = event.payload[:status]
key = "requests.cloud"
$statsd.timing "#{key}.time.total", event.duration
$statsd.timing "#{key}.time.db", event.payload[:db_runtime]
$statsd.timing "#{key}.time.view", event.payload[:view_runtime]
$statsd.increment "#{key}.status.#{status}"
end
# SQL Queries
ActiveSupport::Notifications.subscribe 'sql.active_record' do |*args|
event = ActiveSupport::Notifications::Event.new(*args)
key = 'queries.cloud'
$statsd.increment key
$statsd.timing key, event.duration
end
Digital Ocean Presentation - Ruby Dev Stackup - The Flatiron School
module Instrumentable
extend ActiveSupport::Concern
included do
build_instrumentation
end
module ClassMethods
def build_instrumentation
key = name.underscore + '.callbacks'
after_commit(on: :create) do |record|
ASN.instrument key, attributes: record.attributes, action: 'create'
end
after_rollback do |record|
ASN.instrument key, attributes: record.attributes, action: 'rollback'
end
end
end
end
ActiveSupport::Notifications.subscribe 'event.callbacks' do |*args|
event = ActiveSupport::Notifications::Event.new(*args)
type_id = event.payload[:attributes]['event_type_id']
action = event.payload[:action]
EventInstrumenter.perform_async(type_id, action)
end
after_commit(on: :update) do |record|
ASN.instrument key,
attributes: record.attributes,
changes: record.previous_changes,
action: 'update'
end
class TicketNotifier < BaseNotifier
setup :ticket
def created(ticket)
notify_about_admin_generated_ticket!(ticket) if ticket.opened_by_admin?
end
def updated(ticket, changes)
if category = modification(changes, 'category')
notify_networking(ticket) if category == 'networking'
notify_about_escalated_ticket(ticket) if category == 'engineering'
end
end
private
# TODO: move to base
def modification(changes, key)
changes.has_key?(key) && changes[key].last
end
end
ESB = ASN = ActiveSupport::Notifications
We’re Hiring!
module Rack
class Audit
def call(env)
audit = Thread.current[:audit] = {}
audit[:request] = Rack::Request.new(env)
@app.call(env)
end
end
end

More Related Content

PDF
Intro to Ember.js
PDF
Ember.js - A JavaScript framework for creating ambitious web applications
PDF
SproutCore and the Future of Web Apps
PDF
Drupal 8: Fields reborn
PPTX
Javascript first-class citizenery
PDF
Angular js routing options
PPTX
AngularJS Directives
PDF
Workshop 12: AngularJS Parte I
Intro to Ember.js
Ember.js - A JavaScript framework for creating ambitious web applications
SproutCore and the Future of Web Apps
Drupal 8: Fields reborn
Javascript first-class citizenery
Angular js routing options
AngularJS Directives
Workshop 12: AngularJS Parte I

What's hot (20)

PDF
iOS for ERREST - alternative version
PDF
Simpler Core Data with RubyMotion
PDF
Akka and the Zen of Reactive System Design
PDF
Bye bye $GLOBALS['TYPO3_DB']
PDF
Effective cassandra development with achilles
PDF
Introduction to AJAX In WordPress
PPTX
Oak Lucene Indexes
PDF
Laravel 8 export data as excel file with example
PPTX
Dart and AngularDart
PDF
Workshop 27: Isomorphic web apps with ReactJS
PDF
Ansible modules 101
PDF
Webapps without the web
PPTX
AngularJS $http Interceptors (Explanation and Examples)
KEY
Backbone.js
KEY
Requirejs
PDF
Angular JS blog tutorial
PDF
AngularJS Tips&Tricks
DOCX
How routing works in angular js
PDF
Workshop 24: React Native Introduction
KEY
Geotalk presentation
iOS for ERREST - alternative version
Simpler Core Data with RubyMotion
Akka and the Zen of Reactive System Design
Bye bye $GLOBALS['TYPO3_DB']
Effective cassandra development with achilles
Introduction to AJAX In WordPress
Oak Lucene Indexes
Laravel 8 export data as excel file with example
Dart and AngularDart
Workshop 27: Isomorphic web apps with ReactJS
Ansible modules 101
Webapps without the web
AngularJS $http Interceptors (Explanation and Examples)
Backbone.js
Requirejs
Angular JS blog tutorial
AngularJS Tips&Tricks
How routing works in angular js
Workshop 24: React Native Introduction
Geotalk presentation
Ad

Viewers also liked (20)

PPTX
Environment AND technology
PPTX
Technology environment in_india_and_the_world[1]
PDF
Using Technology to Monitor Marine Environments
PPTX
Effects of technology to environment
PPTX
technology and environment
PPT
Technological envt
PPSX
Role of information technology in environment
PPTX
Techonological environment
PPT
Technological environment
PDF
Effect of technology on environment
PPT
Business and the Technological Environment
PPTX
Technological Environment
PPT
Technological Environment
PPTX
Environment vs Technology
PPT
Technological environment
PPT
Business Environment
PPTX
Business Environments
PPTX
Business environment
PPT
Business environment
PPTX
Business Environment- Features,Meaning,Importance,Objectives & Porter's Model
Environment AND technology
Technology environment in_india_and_the_world[1]
Using Technology to Monitor Marine Environments
Effects of technology to environment
technology and environment
Technological envt
Role of information technology in environment
Techonological environment
Technological environment
Effect of technology on environment
Business and the Technological Environment
Technological Environment
Technological Environment
Environment vs Technology
Technological environment
Business Environment
Business Environments
Business environment
Business environment
Business Environment- Features,Meaning,Importance,Objectives & Porter's Model
Ad

Similar to Digital Ocean Presentation - Ruby Dev Stackup - The Flatiron School (20)

PDF
Connect.Tech- Swift Memory Management
PDF
RSpec on Rails Tutorial
PDF
Fast Web Applications Development with Ruby on Rails on Oracle
PPTX
Ruby on Rails + AngularJS + Twitter Bootstrap
PDF
Introduction to Active Record at MySQL Conference 2007
KEY
Wider than rails
PDF
Solid And Sustainable Development in Scala
ZIP
Drupal Development
ZIP
Rails 3 (beta) Roundup
PPTX
Spark Sql for Training
KEY
Django Pro ORM
PDF
Can puppet help you run docker on a T2.Micro?
PDF
Solid and Sustainable Development in Scala
PDF
Spring data requery
PDF
High Performance Django
PDF
High Performance Django 1
PDF
Module, AMD, RequireJS
PDF
Django Multi-DB in Anger
PDF
Scala active record
PDF
Rails Performance
Connect.Tech- Swift Memory Management
RSpec on Rails Tutorial
Fast Web Applications Development with Ruby on Rails on Oracle
Ruby on Rails + AngularJS + Twitter Bootstrap
Introduction to Active Record at MySQL Conference 2007
Wider than rails
Solid And Sustainable Development in Scala
Drupal Development
Rails 3 (beta) Roundup
Spark Sql for Training
Django Pro ORM
Can puppet help you run docker on a T2.Micro?
Solid and Sustainable Development in Scala
Spring data requery
High Performance Django
High Performance Django 1
Module, AMD, RequireJS
Django Multi-DB in Anger
Scala active record
Rails Performance

Recently uploaded (20)

PPTX
Digital-Transformation-Roadmap-for-Companies.pptx
PPTX
Big Data Technologies - Introduction.pptx
PPTX
Cloud computing and distributed systems.
PDF
Blue Purple Modern Animated Computer Science Presentation.pdf.pdf
PDF
Reach Out and Touch Someone: Haptics and Empathic Computing
PPTX
ACSFv1EN-58255 AWS Academy Cloud Security Foundations.pptx
PPT
Teaching material agriculture food technology
PDF
Advanced methodologies resolving dimensionality complications for autism neur...
PDF
Chapter 3 Spatial Domain Image Processing.pdf
PPT
“AI and Expert System Decision Support & Business Intelligence Systems”
PPTX
MYSQL Presentation for SQL database connectivity
PDF
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
PDF
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
PDF
Agricultural_Statistics_at_a_Glance_2022_0.pdf
PDF
Empathic Computing: Creating Shared Understanding
PDF
NewMind AI Weekly Chronicles - August'25-Week II
PDF
gpt5_lecture_notes_comprehensive_20250812015547.pdf
PDF
Spectral efficient network and resource selection model in 5G networks
PPTX
20250228 LYD VKU AI Blended-Learning.pptx
PPTX
sap open course for s4hana steps from ECC to s4
Digital-Transformation-Roadmap-for-Companies.pptx
Big Data Technologies - Introduction.pptx
Cloud computing and distributed systems.
Blue Purple Modern Animated Computer Science Presentation.pdf.pdf
Reach Out and Touch Someone: Haptics and Empathic Computing
ACSFv1EN-58255 AWS Academy Cloud Security Foundations.pptx
Teaching material agriculture food technology
Advanced methodologies resolving dimensionality complications for autism neur...
Chapter 3 Spatial Domain Image Processing.pdf
“AI and Expert System Decision Support & Business Intelligence Systems”
MYSQL Presentation for SQL database connectivity
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
Agricultural_Statistics_at_a_Glance_2022_0.pdf
Empathic Computing: Creating Shared Understanding
NewMind AI Weekly Chronicles - August'25-Week II
gpt5_lecture_notes_comprehensive_20250812015547.pdf
Spectral efficient network and resource selection model in 5G networks
20250228 LYD VKU AI Blended-Learning.pptx
sap open course for s4hana steps from ECC to s4

Digital Ocean Presentation - Ruby Dev Stackup - The Flatiron School

  • 2. • Shared business logic as an engine • Form objects • MySQL table as KV store • Abusing ASN for cleanliness
  • 3. gem 'core', git: 'https://githubenterprise/digitalocean/core', ref: '623ac1e785e092f7369d5cfa7e56ea2e98fb2e20' “Core” Our Rails Engine in Reverse
  • 4. “Core” Our Rails Engine in Reverse gem 'core', git: 'https://githubenterprise/digitalocean/core', ref: '623ac1e785e092f7369d5cfa7e56ea2e98fb2e20'
  • 5. # lib/blorgh/engine.rb module Blorgh class Engine < ::Rails::Engine isolate_namespace Blorgh config.to_prepare do Dir.glob(Rails.root + "app/decorators/**/*_decorator*.rb").each do |c| require_dependency(c) end end end end # MyApp/app/decorators/models/blorgh/post_decorator.rb Blorgh::Post.class_eval do def time_since_created Time.current - created_at end end :thumbsdown:
  • 6. # MyApp/app/models/blorgh/post.rb class Blorgh::Post < ActiveRecord::Base include Blorgh::Concerns::Models::Post def time_since_created Time.current - created_at end def summary "#{title} - #{truncate(text)}" end end :thumbsdown:
  • 7. # Blorgh/lib/concerns/models/post module Blorgh::Concerns::Models::Post extend ActiveSupport::Concern # 'included do' causes the included code to be evaluated in the # context where it is included (post.rb), rather than being # executed in the module's context (blorgh/concerns/models/post). included do attr_accessor :author_name belongs_to :author, class_name: "User" before_save :set_author private def set_author self.author = User.find_or_create_by(name: author_name) end end end
  • 8. require_dependency Core::Engine.root.join('app', 'models', 'droplet').to_s class Droplet BACKUP_CHARGE_PERCENTAGE = 0.20 def monthly_backup_price self.size.monthly_price * BACKUP_CHARGE_PERCENTAGE end end :thumbsup:
  • 9. Previously... # app/controllers/events_controller.rb event = Event.new( :event_scope => 'droplet', :event_type => params[:event_type], # from the route :droplet_id => params[:droplet_id], :image_id => params[:image_id], :size_id => params[:size_id], :user_id => current_user.id, :name => name ) # app/models/event.rb # Validations Based on Event Type case self.event_type when 'resize' errors.add(:size_id, "...") if size_id == droplet.size_id errors.add(:size_id, "...") unless Size.active.include? size_id end
  • 10. class Resize extend ActiveModel::Naming include ActiveModel::Conversion include ActiveModel::Validations include Virtus attribute :size, Size attribute :user, User attribute :droplet, Droplet validates :size, allowed_size: true, presence: true def save if valid? event.save! else false end end def persisted? false end def event @_event ||= EventFactory.build(:resize, event_params) end end
  • 11. # app/views/resizes/_form.erb = form_for [@droplet, Resize.new] do |f| = render 'sizes', sizes: @sizes_available_for_resize, form: f = f.submit 'Resize' class ResizesController < ApplicationController def create droplet = current_user.droplets.find(params[:droplet_id]) size = Size.active.where(id: params[:size_id]).first resize = Resize.new(droplet: droplet, size: size) if resize.save redirect_to resize.droplet, notice: 'Your resize is processing' else redirect_to resize.droplet, alert: resize.errors.first end end end
  • 12. <3Virtus • Validations are contextual • Slims down god objects • Can be tested without ActiveRecord • Makes testing ActiveRecord classes easier
  • 14. CREATE TABLE `user_properties` ( `user_id` int(11) NOT NULL, `name` varchar(32) NOT NULL DEFAULT '', `value` varchar(255) NOT NULL DEFAULT '', PRIMARY KEY (`user_id`,`name`), KEY `name` (`name`,`value`), KEY `user_id` (`user_id`,`name`,`value`) ) class User include Propertable property :marked_as_sketchy_at, nil, :date_time property :marked_as_verified_at, nil, :date_time property :marked_as_abuse_at, nil, :date_time property :marked_as_hold_at, nil, :date_time property :marked_as_suspended_at, nil, :date_time property :marked_as_review_at, nil, :date_time end
  • 15. module Propertable def property(name, default, type = :string) key = name.to_s props = class_variable_get(:@@properties) props[key] = default define_method(key) do property = __read_property__(key) if property.nil? || property == default default else Propertable::Coercer.call(property.value, property.value.class, type) end end define_method("#{key}?") do !!public_send(key) end define_method("#{key}=") do |value| begin coerced_value = Propertable::Coercer.call(value, value.class, type) rescue coerced_value = default end coerced_value = Propertable::Coercer.call(coerced_value, type.class, :string) property = __write_property__(key, coerced_value) end end end
  • 18. $statsd = Statsd.new(statsd_ip).tap { |s| s.namespace = Rails.env } # Request Times ActiveSupport::Notifications.subscribe /process_action.action_controller/ do |*args| event = ActiveSupport::Notifications::Event.new(*args) status = event.payload[:status] key = "requests.cloud" $statsd.timing "#{key}.time.total", event.duration $statsd.timing "#{key}.time.db", event.payload[:db_runtime] $statsd.timing "#{key}.time.view", event.payload[:view_runtime] $statsd.increment "#{key}.status.#{status}" end # SQL Queries ActiveSupport::Notifications.subscribe 'sql.active_record' do |*args| event = ActiveSupport::Notifications::Event.new(*args) key = 'queries.cloud' $statsd.increment key $statsd.timing key, event.duration end
  • 20. module Instrumentable extend ActiveSupport::Concern included do build_instrumentation end module ClassMethods def build_instrumentation key = name.underscore + '.callbacks' after_commit(on: :create) do |record| ASN.instrument key, attributes: record.attributes, action: 'create' end after_rollback do |record| ASN.instrument key, attributes: record.attributes, action: 'rollback' end end end end
  • 21. ActiveSupport::Notifications.subscribe 'event.callbacks' do |*args| event = ActiveSupport::Notifications::Event.new(*args) type_id = event.payload[:attributes]['event_type_id'] action = event.payload[:action] EventInstrumenter.perform_async(type_id, action) end
  • 22. after_commit(on: :update) do |record| ASN.instrument key, attributes: record.attributes, changes: record.previous_changes, action: 'update' end
  • 23. class TicketNotifier < BaseNotifier setup :ticket def created(ticket) notify_about_admin_generated_ticket!(ticket) if ticket.opened_by_admin? end def updated(ticket, changes) if category = modification(changes, 'category') notify_networking(ticket) if category == 'networking' notify_about_escalated_ticket(ticket) if category == 'engineering' end end private # TODO: move to base def modification(changes, key) changes.has_key?(key) && changes[key].last end end
  • 24. ESB = ASN = ActiveSupport::Notifications
  • 26. module Rack class Audit def call(env) audit = Thread.current[:audit] = {} audit[:request] = Rack::Request.new(env) @app.call(env) end end end