Digital Ocean Presentation - Ruby Dev Stackup - The Flatiron School

26
Riding the Rails @ DigitalOcean

description

Digital Ocean's presentation from the Ruby Dev Stackup held at The Flatiron School on 5/6

Transcript of Digital Ocean Presentation - Ruby Dev Stackup - The Flatiron School

Page 1: Digital Ocean Presentation - Ruby Dev Stackup - The Flatiron School

Riding the Rails @

DigitalOcean

Page 2: Digital Ocean Presentation - Ruby Dev Stackup - The Flatiron School

• Shared business logic as an engine

• Form objects

• MySQL table as KV store

• Abusing ASN for cleanliness

Page 3: Digital Ocean Presentation - Ruby Dev Stackup - The Flatiron School

gem 'core', git: 'https://githubenterprise/digitalocean/core', ref: '623ac1e785e092f7369d5cfa7e56ea2e98fb2e20'

“Core”Our Rails Engine in Reverse

Page 4: Digital Ocean Presentation - Ruby Dev Stackup - The Flatiron School

“Core”Our Rails Engine in Reverse

gem 'core', git: 'https://githubenterprise/digitalocean/core', ref: '623ac1e785e092f7369d5cfa7e56ea2e98fb2e20'

Page 5: Digital Ocean Presentation - Ruby Dev Stackup - The Flatiron School

# lib/blorgh/engine.rbmodule 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 endend

# MyApp/app/decorators/models/blorgh/post_decorator.rb

Blorgh::Post.class_eval do def time_since_created Time.current - created_at endend

:thumbsdown:

Page 6: Digital Ocean Presentation - Ruby Dev Stackup - The Flatiron School

# 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)}" endend

:thumbsdown:

Page 7: Digital Ocean Presentation - Ruby Dev Stackup - The Flatiron School

# 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 endend

Page 8: Digital Ocean Presentation - Ruby Dev Stackup - The Flatiron School

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 endend

:thumbsup:

Page 9: Digital Ocean Presentation - Ruby Dev Stackup - The Flatiron School

Previously...# app/controllers/events_controller.rbevent = 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 Typecase self.event_type when 'resize' errors.add(:size_id, "...") if size_id == droplet.size_id errors.add(:size_id, "...") unless Size.active.include? size_idend

Page 10: Digital Ocean Presentation - Ruby Dev Stackup - The Flatiron School

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) endend

Page 11: Digital Ocean Presentation - Ruby Dev Stackup - The Flatiron School

# 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 endend

Page 12: Digital Ocean Presentation - Ruby Dev Stackup - The Flatiron School

<3 Virtus

• Validations are contextual

• Slims down god objects

• Can be tested without ActiveRecord

• Makes testing ActiveRecord classes easier

Page 13: Digital Ocean Presentation - Ruby Dev Stackup - The Flatiron School

WARNING:YOU

PROBABLYSHOULD

NOTDOTHIS

Page 14: Digital Ocean Presentation - Ruby Dev Stackup - The Flatiron School

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_timeend

Page 15: Digital Ocean Presentation - Ruby Dev Stackup - The Flatiron School

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 endend

Page 16: Digital Ocean Presentation - Ruby Dev Stackup - The Flatiron School

ASN = ActiveSupport::Notifications

Page 17: Digital Ocean Presentation - Ruby Dev Stackup - The Flatiron School

INSTRUMENT EVERYTHING!

Page 18: Digital Ocean Presentation - Ruby Dev Stackup - The Flatiron School

$statsd = Statsd.new(statsd_ip).tap { |s| s.namespace = Rails.env }

# Request TimesActiveSupport::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 QueriesActiveSupport::Notifications.subscribe 'sql.active_record' do |*args| event = ActiveSupport::Notifications::Event.new(*args)

key = 'queries.cloud'

$statsd.increment key $statsd.timing key, event.durationend

Page 19: Digital Ocean Presentation - Ruby Dev Stackup - The Flatiron School
Page 20: 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 endend

Page 21: Digital Ocean Presentation - Ruby Dev Stackup - The Flatiron School

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

Page 22: Digital Ocean Presentation - Ruby Dev Stackup - The Flatiron School

after_commit(on: :update) do |record| ASN.instrument key, attributes: record.attributes, changes: record.previous_changes, action: 'update'end

Page 23: Digital Ocean Presentation - Ruby Dev Stackup - The Flatiron School

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 endend

Page 24: Digital Ocean Presentation - Ruby Dev Stackup - The Flatiron School

ESB = ASN = ActiveSupport::Notifications

Page 25: Digital Ocean Presentation - Ruby Dev Stackup - The Flatiron School

We’re Hiring!

Page 26: Digital Ocean Presentation - Ruby Dev Stackup - The Flatiron School

module Rack class Audit def call(env) audit = Thread.current[:audit] = {} audit[:request] = Rack::Request.new(env)

@app.call(env) end endend