Slide 1

Slide 1 text

Inside Active Storage: a code review of Rails' new framework speakerdeck.com/claudiob

Slide 2

Slide 2 text

I work for a storage company clutter.com

Slide 3

Slide 3 text

I will talk about Active Storage guides.rubyonrails.org/active_storage_overview.html

Slide 4

Slide 4 text

Inside Active Storage - How to use in a Rails app (5 min) - The main classes of the library (10 min) - How the library works (10 min)

Slide 5

Slide 5 text

Inside Active Storage - How to use in a Rails app (5 min) - The main classes of the library (10 min) - How the library works (10 min)

Slide 6

Slide 6 text

How to use Active Storage Let's add an image uploader to a Rails 5.2 scaffold app

Slide 7

Slide 7 text

How to use Active Storage Start with a Rails scaffold app rails new catalog cd catalog bin/rails generate scaffold cat name:string bin/rails db:create db:migrate bin/rails server

Slide 8

Slide 8 text

How to use Active Storage Start with a Rails scaffold app

Slide 9

Slide 9 text

How to use Active Storage Step 1: add a file field called “picture”
<%= form.label :picture %> <%= form.file_field :picture %>

Slide 10

Slide 10 text

How to use Active Storage Step 2: add “picture” to the whitelisted params class CatsController < ApplicationController def index […] def cat_params params.require(:cat).permit(:name, :picture) end end

Slide 11

Slide 11 text

How to use Active Storage Step 3: add “has_one_attached” to the model class Cat < ApplicationRecord […] has_one_attached :picture […] end

Slide 12

Slide 12 text

How to use Active Storage Bonus step: display the picture <%= image_tag @cat.picture %>

Slide 13

Slide 13 text

How to use Active Storage More features: variants, previewers, analyzers @cat.picture.variant(monochrome: true, flip: "90") has_one_attached :document @cat.document.preview(resize: "100x100") has_many_attached :videos @cat.videos.map(&:metadata) # size, angle, duration

Slide 14

Slide 14 text

Inside Active Storage - How to use in a Rails app (5 min) - The main classes of the library (10 min) - How the library works (10 min)

Slide 15

Slide 15 text

The main classes of Active Storage github.com/rails/rails/tree/master/activestorage

Slide 16

Slide 16 text

The main classes of Active Storage bundle open activestorage

Slide 17

Slide 17 text

The main classes of Active Storage 1. ActiveStorage::Service 2. ActiveStorage::Blob 3. ActiveStorage::Attachment

Slide 18

Slide 18 text

The main classes of Active Storage 1. ActiveStorage::Service 2. ActiveStorage::Blob 3. ActiveStorage::Attachment

Slide 19

Slide 19 text

ActiveStorage::Service Moves bytes from memory to disk Disk Storage (5.8ms) Uploaded file to key: nty3U4Bzagqj3ekd (checksum: LPzlVe+CBQ==) #{Rails.root}/storage/ nt/y3/nty3U4Bzagqj3ekd ActionDispatch:: Http::UploadedFile

Slide 20

Slide 20 text

What is ActiveStorage::Service An abstract class serving as an interface for implementations class ActiveStorage::Service def upload(key, io, checksum: nil) raise NotImplementedError end def download(key) raise NotImplementedError end end

Slide 21

Slide 21 text

Inside ActiveStorage::Service (disk) Moves bytes from memory to local disk and back class ActiveStorage::Service::DiskService < Service def upload(key, io, checksum: nil) IO.copy_stream(io, make_path_for(key)) end def download(key) File.binread path_for(key) end end

Slide 22

Slide 22 text

Inside ActiveStorage::Service (S3) Moves bytes from memory to AWS S3 and back class ActiveStorage::Service::S3Service < Service def upload(key, io, checksum: nil) object_for(key).put(upload_options.merge(body: io)) end def download(key) object_for(key).get.body.read end end

Slide 23

Slide 23 text

The main classes of Active Storage 1. ActiveStorage::Service 2. ActiveStorage::Blob 3. ActiveStorage::Attachment

Slide 24

Slide 24 text

ActiveStorage::Blob Stores a reference to the uploaded file ActiveStorage::Blob Create (5.8ms) INSERT INTO active_storage_blobs (key, filename, … key: "nty3U4Bzagqj3ekd" filename: "fluffy.jpg" content_type: "image/jpeg" byte_size: 1565751 ActionDispatch:: Http::UploadedFile

Slide 25

Slide 25 text

What is ActiveStorage::Blob A record in the database with information about one file create_table "active_storage_blobs", do |t| t.string "key", null: false t.string "filename", null: false t.string "content_type" t.text "metadata" t.bigint "byte_size", null: false t.string "checksum", null: false t.index ["key"], unique: true end

Slide 26

Slide 26 text

What is ActiveStorage::Blob Migrations are provided to generate the required tables

Slide 27

Slide 27 text

Inside ActiveStorage::Blob Extracts file metadata and invokes the service upload class ActiveStorage::Blob < ActiveRecord::Base def upload(io) self.checksum = compute_checksum_in_chunks(io) self.content_type = extract_content_type(io) self.byte_size = io.size service.upload(key, io, checksum: checksum) end end

Slide 28

Slide 28 text

The main classes of Active Storage 1. ActiveStorage::Service 2. ActiveStorage::Blob 3. ActiveStorage::Attachment

Slide 29

Slide 29 text

ActiveStorage::Attachment Associates a blob with a record ActiveStorage::Blob Create (5.8ms) INSERT INTO active_storage_attachments (name, … name: "picture" record_type: "Cat" record_id: 4 blob_id: 2 ActionDispatch:: Http::UploadedFile

Slide 30

Slide 30 text

What is ActiveStorage::Attachment A record in the database associating a blob to its owner create_table "active_storage_attachments" do |t| t.string "name", null: false t.string "record_type", null: false t.integer "record_id", null: false t.integer "blob_id", null: false t.index ["blob_id"] t.index ["record_type", "record_id", "name", "blob_id"], unique: true end

Slide 31

Slide 31 text

Inside ActiveStorage::Attachment A record in the database associating a blob to its owner class ActiveStorage::Attachment < ActiveRecord::Base self.table_name = "active_storage_attachments" belongs_to :record, polymorphic: true, touch: true belongs_to :blob, class_name: "ActiveStorage::Blob" end

Slide 32

Slide 32 text

The main classes of Active Storage 1. ActiveStorage::Service 2. ActiveStorage::Blob 3. ActiveStorage::Attachment

Slide 33

Slide 33 text

Inside Active Storage - How to use in a Rails app (5 min) - The main classes of the library (10 min) - How the library works (10 min)

Slide 34

Slide 34 text

How Active Storage works How does a single method add this whole behavior? class Cat < ApplicationRecord […] has_one_attached :picture […] end

Slide 35

Slide 35 text

How Active Storage works When loading Active Record, extend it with new methods class ActiveStorage::Engine < Rails::Engine initializer "active_storage.attached" do ActiveSupport.on_load(:active_record) do extend ActiveStorage::Attached::Macros end end end

Slide 36

Slide 36 text

How Active Storage works Meta-programming: define methods with a given name module ActiveStorage::Attached::Macros def has_one_attached(name) class_eval <<-CODE, __FILE__, __LINE__ + 1 def #{name}=(attachable) #{name}.attach(attachable) end [… 5 more methods…]

Slide 37

Slide 37 text

How Active Storage works Meta-programming: define methods with a given name module ActiveStorage::Attached::Macros def has_one_attached(:picture) class_eval <<-CODE, __FILE__, __LINE__ + 1 def picture=(attachable) picture.attach(attachable) end [… 5 more methods…]

Slide 38

Slide 38 text

How Active Storage works When a picture is attached to a cat… class Cat < ApplicationRecord has_one_attached :picture def picture=(attachable) picture.attach(attachable) end end

Slide 39

Slide 39 text

How Active Storage works …a picture is instantiated… class Cat < ApplicationRecord has_one_attached :picture def picture ActiveStorage::Attached::One.new( "picture", self, dependent: :purge_later) end end

Slide 40

Slide 40 text

How Active Storage works …and a blob and an attachment are added to the database. class ActiveStorage::Attached::One < Attached def attach(attachable) blob = create_blob_from(attachable) write_attachment build_attachment(blob: blob) […] end end

Slide 41

Slide 41 text

How Active Storage works When a cat is destroyed… class Cat < ApplicationRecord has_one_attached :picture if dependent == :purge_later # true by default after_destroy_commit { picture.purge_later } end end

Slide 42

Slide 42 text

How Active Storage works …the picture purges the attachment… class ActiveStorage::Attached::One < Attached def purge_later attachment.purge_later if attached? end end

Slide 43

Slide 43 text

How Active Storage works …the attachment purges the blob… class ActiveStorage::Attachment < ActiveRecord::Base def purge_later blob.purge_later destroy end end

Slide 44

Slide 44 text

How Active Storage works …the blob enqueues a purge job… class ActiveStorage::Blob < Attached def purge_later ActiveStorage::PurgeJob.perform_later(self) end end

Slide 45

Slide 45 text

How Active Storage works …the job purges the blob… class ActiveStorage::PurgeJob < ActiveJob::Base def perform(blob) blob.purge end end

Slide 46

Slide 46 text

How Active Storage works …the job purges the blob… class ActiveStorage::Blob < Attached def purge delete destroy end end

Slide 47

Slide 47 text

How Active Storage works …the blob asks the service to delete the file… class ActiveStorage::Blob < Attached def delete service.delete(key) end end

Slide 48

Slide 48 text

How Active Storage works …the service deletes the file. class ActiveStorage::DiskService def delete(key) File.delete path_for(key) end end

Slide 49

Slide 49 text

More to explore about Active Storage - JavaScript module - ActiveSupport::Notification - Direct and resolve routes

Slide 50

Slide 50 text

Inside Active Storage: a code review of Rails' new framework speakerdeck.com/claudiob