Crowdtap Presentation - Ruby Dev Stackup - The Flatiron School
Digital Ocean Presentation - Ruby Dev Stackup - The Flatiron School
-
Upload
elana-jacobs -
Category
Technology
-
view
217 -
download
1
description
Transcript of Digital Ocean Presentation - Ruby Dev Stackup - The Flatiron School
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.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:
# 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:
# 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
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:
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
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
# 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
<3 Virtus
• Validations are contextual
• Slims down god objects
• Can be tested without ActiveRecord
• Makes testing ActiveRecord classes easier
WARNING:YOU
PROBABLYSHOULD
NOTDOTHIS
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
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
ASN = ActiveSupport::Notifications
INSTRUMENT EVERYTHING!
$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
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
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 endend
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 endend