7. Howās that predeļ¬ned styles
doing for you?
has_attached_file :asset, styles: {
thumb: "100x100#", thumb2x: "200x200#",
medium: "320x>", medium2x: "640x>",āØ
big: "640x>", big2x: "1280x>",
large: "1024x>", large2x: "2048x>"
}
Reprocess all the production ļ¬les, each
time, we make changes.
404 while rake runs or do at midnight?
9. Howās that transformation
juggling doing for you?
class MyUploader < CarrierWave::Uploader::Base
version :thumb do
process resize_to_fill: [280, 280]
end
version :small_thumb, from_version: :thumb do
process resize_to_fill: [20, 20]
end
end
10. Howās that transformation
juggling doing for you?
class MyUploader < CarrierWave::Uploader::Base
version :thumb do
process resize_to_fill: [280, 280]
end
version :small_thumb, from_version: :thumb do
process resize_to_fill: [20, 20]
end
end
Did your users wait in the foreground
or background?
12. Howās that ļ¬le path conļ¬g
doing for you?
class Avatar < ActiveRecord::Base
has_attached_file :image,āØ
url: '/system/:class/:attachment/:id/:hash-:style.:extension',
hash_secret: Rails.application.secrets.paperclip
end
13. Howās that ļ¬le path conļ¬g
doing for you?
class Avatar < ActiveRecord::Base
has_attached_file :image,āØ
url: '/system/:class/:attachment/:id/:hash-:style.:extension',
hash_secret: Rails.application.secrets.paperclip
end
You sure this is the format?
Or will they need to change?
14. Howās that ļ¬le path conļ¬g
doing for you?
class Avatar < ActiveRecord::Base
has_attached_file :image,āØ
url: '/system/:class/:attachment/:id/:hash-:style.:extension',
hash_secret: Rails.application.secrets.paperclip
end
15. Howās that ļ¬le path conļ¬g
doing for you?
class Avatar < ActiveRecord::Base
has_attached_file :image,āØ
url: '/system/:class/:attachment/:id/:hash-:style.:extension',
hash_secret: Rails.application.secrets.paperclip
end
16. Howās that ļ¬le path conļ¬g
doing for you?
CarrierWave.configure do |config|
config.permissions = 0666
config.directory_permissions = 0777
config.storage = :file
end
17. Howās that ļ¬le path conļ¬g
doing for you?
CarrierWave.configure do |config|
config.permissions = 0666
config.directory_permissions = 0777
config.storage = :file
end
Did you conļ¬gure your MySQL
data ļ¬le in your app too?
18. Howās that ļ¬le path conļ¬g
doing for you?
class Avatar < ActiveRecord::Base
self.table = {
name: "avatars",
data: "/var/lib/mysql/data/avatars.MYD",
index: "/var/lib/mysql/data/avatars.MYI"
}
end
Did you conļ¬gure your MySQL
data ļ¬le in your app too?
20. Howās that form validation
error dance doing for you?
ā¢ User chooses a ļ¬le
ā¢ Submit & wait for ļ¬le to upload āā¦
ā¢ Validation error: āUsername is already taken!ā
ā¢ Re-render form
21. Howās that form validation
error dance doing for you?
ā¢ User chooses a ļ¬le
ā¢ Submit & wait for ļ¬le to upload āā¦
ā¢ Validation error: āUsername is already taken!ā
ā¢ Re-render form
Where dat ļ¬le go?
22. Howās that form validation
error dance doing for you?
http://stackoverļ¬ow.com/questions/5198602/not-losing-paperclip-attachment-when-model-cannot-be-saved-due-to-validation-err
Closed: Wonāt Fix
23. Howās that form validation
error dance doing for you?
http://stackoverļ¬ow.com/questions/5198602/not-losing-paperclip-attachment-when-model-cannot-be-saved-due-to-validation-err
24. Howās that form validation
error dance doing for you?
http://stackoverļ¬ow.com/questions/5198602/not-losing-paperclip-attachment-when-model-cannot-be-saved-due-to-validation-err
Answer: use CarrierWave
31. Howās that multiple ļ¬les
doing for you?
class Post < ActiveRecord::Base
has_many :images, dependent: :destroy
end
32. Howās that multiple ļ¬les
doing for you?
class Post < ActiveRecord::Base
has_many :images, dependent: :destroy
end
class Image < ActiveRecord::Base
belongs_to :post
attachment :file
end
33. Howās that multiple ļ¬les
doing for you?
class Post < ActiveRecord::Base
has_many :images, dependent: :destroy
end
class Image < ActiveRecord::Base
belongs_to :post
attachment :file
end
34. Howās that multiple ļ¬les
doing for you?
class Post < ActiveRecord::Base
has_many :images, dependent: :destroy
end
class Image < ActiveRecord::Base
belongs_to :post
attachment :file
end
Is this what you want or just
what youāre accustomed to?
35. Howās Amazon Lambda
doing for you?
ā¢ User chooses a ļ¬le
ā¢ Submit & wait for ļ¬le to upload āā¦
ā¢ Success!
ā¢ Render page with thumbnailā¦
How many thumbnails -
404?
36. Howās Amazon Lambda
doing for you?
ā¢ User chooses a ļ¬le
ā¢ Submit & wait for ļ¬le to upload āā¦
ā¢ Success!
ā¢ Render page with thumbnailā¦
Direct upload to AWS?
Cancel form submit -
delete ļ¬les & thumbnails?
Deep integration &
assumption
43. Take a step back
<img src={photo}>
<h1>{title}</h1>
{body}
44. Take a step back
ā¢ Why should photo be a disproportionately
complicated attribute in my Article model?
ā¢ stored ļ¬le path
ā¢ conversion
ā¢ background job
ā¢ aws conļ¬g
ā¢ clean up on delete
45. Take a step back
ā¢ Why should photo be a disproportionately
complicated attribute in my Article model?
ā¢ stored ļ¬le path
ā¢ conversion
ā¢ background job
ā¢ validation error dance
ā¢ aws conļ¬g
46. Take a step back
ā¢ Frankly photo_url is best; least intrusiveāØ
āØ
āØ
āØ
āØ
āØ
āØ
āØ
āØ
āØ
47. Take a step back
ā¢ Frankly photo_url is best; least intrusive
ā¢ Problems
ā¢ Remote url 404? (not exclusive to your app)
ā¢ Asking users to give us a URL is a hard sell
ā¢ Need to render other sizes
ā¢ Filter by meta data
48. Take a step back
ā¢ Frankly photo_url is best; least intrusive
ā¢ Problems
ā¢ Remote url 404? (not exclusive to your app)
ā¢ Asking users to give us a URL is a hard sell
ā¢ Need to render other sizes
ā¢ Filter by meta data
49. Take a step back
ā¢ Frankly photo_url is best; least intrusive
ā¢ Problems
ā¢ Remote url 404? (not exclusive to your app)
ā¢ Asking users to give us a URL is a hard sell
ā¢ Need to render other sizes
ā¢ Filter by meta data
50. Take a step back
ā¢ Frankly photo_url is best; least intrusive
ā¢ Solutions
ā¢ Exclusive server for your app
ā¢ Upload to that server
ā¢ On-the-ļ¬y resize based on URL
ā¢ Store url with meta data: photo_json instead?
52. ā¢ PostgreSQL, MySQL
ā¢ Redis
ā¢ Memcached
ā¢ SMTP server (Mail)āØ
āØ
āØ
āØ
You are already
Generic server to do
specialised work
Not speciļ¬c to your
business logic
53. ā¢ Not a new pattern
ā¢ Mostly commercial servicesāØ
āØ
āØ
āØ
āØ
ā¢ Maybe it has to be Free & Open Source to become
a default pattern
Image server
54. Want
ā¢ Move the āconcernā out of my app
ā¢ photo is a regular attribute
ā¢ conļ¬gure my app & forget it existāØ
āØ
āØ
āØ
55. Want
ā¢ Move the āconcernā out of my app
ā¢ photo is a regular attribute
ā¢ conļ¬gure my app & forget it existāØ
āØ
āØ
āØ What would my app look
like in a better world?
56. My app: Bare minimum
create_table "users" do |t|
t.string "name"
t.json "avatar"
t.json "photos"
end
57. My app: Bare minimum
create_table "users" do |t|
t.string "name"
t.json "avatar"
t.json "photos" # multiple files in a column
end
58. My app: Bare minimum
Image serverRails appBrowser
{ avatar: #<File..> }
59. My app: Bare minimum
Image serverRails appBrowser
{ avatar: #<File..> }
{āpathā:āx.jpgā,
āgeometryā:ā200x600ā}
#<File..>
60. My app: Bare minimum
Image serverRails appBrowser
{āpathā:āx.jpgā,
āgeometryā:ā200x600ā}
#<File..>
user.avatar={āpathā: āx.jpgāā¦}
user.save
<img src=āx.jpgā>
{ avatar: #<File..> }
61. My app: Bare minimum
Image serverRails appBrowser
{āpathā:āx.jpgā,
āgeometryā:ā200x600ā}
<img src=āx.jpgā>
#<File..>
GET x.jpg
#<File..>
user.avatar={āpathā: āx.jpgāā¦}
user.save
{ avatar: #<File..> }
62. My app: Bare minimum
ā¢ Browser render <file> ļ¬eld; regular form submit
ā¢ Receive binary param, uploads to attache server
and stores the json response instead
ā¢ Your app render <img src> requesting for image in
certain size, e.g. http://example/200x/
file.pngāØ
āØ
āØ
66. ā¢ Browser render <file> ļ¬eld; regular form submit
ā¢ Receive binary param, uploads to attache server
and stores the json response instead
ā¢ Your app render <img src> requesting for image in
certain size, e.g. http://example/200x/
file.pngāØ
āØ
āØ
Progressive Enhancement
67. ā¢ Browser render <file> ļ¬eld; regular form submit
ā¢ Receive binary param, uploads to attache server
and stores the json response instead
ā¢ Your app render <img src> requesting for image in
certain size, e.g. http://example/200x/
file.pngāØ
āØ
āØ
ā¢ JS upload directly to attache server; āDirect uploadā in
AWS parlance
ā¢ No binary in params; receive and store json attribute
Progressive Enhancement
ā¢ When after_update & after_destroy
removes obsolete ļ¬le from attache via delete API
68. ā¢ Just use Ruby; just use your framework
ā¢ Pre-render multiple sizes
ā¢ fetch the urls, server will generate and cache
ā¢ Validation
ā¢ validating a regular json attribute
How do Iā¦
69. ā¢ Move the āconcernā out of my app
ā¢ photo is a regular attribute
ā¢ conļ¬gure my app & forget it existāØ
āØ
āØ
āØ
Want (contād)
70. Want (contād)
ā¢ Move the āconcernā out of my app
ā¢ photo is a regular attribute
ā¢ conļ¬gure my app & forget it exist
ā¢ Separate, standalone server
ā¢ Minimal / zero ongoing administration
71. Want (contād)
ā¢ Move the āconcernā out of my app
ā¢ photo is a regular attribute
ā¢ conļ¬gure my app & forget it exist
ā¢ Separate, standalone server
ā¢ Minimal / zero ongoing administration
How does this
server work?
72. Attache File Server
ā¢ HTTP server with simple APIs
ā¢ upload
ā¢ download
ā¢ delete
ā¢ Rack app + ImageMagick
ā¢ Go? Node? C++? PHP?
ā¢ GraphicsMagick? MyResizer.bash?
73. ā¢ Uploaded ļ¬les are stored locally
ā¢ Resize local ļ¬le on-the-ļ¬y
ā¢ conļ¬gurable pool size to limit concurrent resizing
ā¢ Sync upload to cloud storage
ā¢ 2 hop problem vs complexity
ā¢ Fixed local storage, purge LRU (zero maintenance)
ā¢ Spin up new fresh servers anytimeā¦ āØ
Attache File Server
74. ā¢ When requested ļ¬le does not exist locally
ā¢ fetch from cloud storage & write locally
ā¢ resume operationsāØ
āØ
āØ
āØ
āØ
āØ
Attache File Server
75. ā¢ Remove obsolete ļ¬le is ābest effortā
ā¢ If photo delete failed, do you Error 500 or stop the
world?āØ
āØ
āØ
āØ
ā¢ OCDs can schedule rake task remove dangling ļ¬les?
Attache File Server
76. ā¢ Caching in production
ā¢ Browser ā CDN ā Varnish ā Disk ā CloudāØ
āØ
āØ
āØ
āØ
āØ
āØ
Attache File Server
83. Dragonļ¬y & reļ¬le
ā¢ tldr: we can fuss over implementation, but it is
mostly about architecture
84. Dragonļ¬y & reļ¬le
ā¢ Rack middleware in your Rails app by default
ā¢ performing on-the-ļ¬y image resize š±
ā¢ Rack standalone end point
ā¢ Dragonļ¬y.app - upload, download, delete
ā¢ Reļ¬le::App - upload, download, delete
ā¢ Downloads are unthrottled
85. Dragonļ¬y & reļ¬le
ā¢ BEFORE: Rails integrate with AWS
ā¢ AFTER: Rails integrate with AWS + Rack app
ā¢ Maintain identical AWS conļ¬g in both apps
ā¢ Rails app couldnāt shed the āconcernā
ā¢ Multiple images still require multiple models
86. reļ¬le
class Post < ActiveRecord::Base
has_many :images, dependent: :destroy
accepts_attachments_for :images, attachment: :file
end
class Image < ActiveRecord::Base
belongs_to :post
attachment :file
end
87. reļ¬le
class Post < ActiveRecord::Base
has_many :images, dependent: :destroy
accepts_attachments_for :images, attachment: :file
end
class Image < ActiveRecord::Base
belongs_to :post
attachment :file
end
āNote it must be possible to persist images
given only the associated post and a ļ¬le. There
must not be any other validations or constraints
which prevent images from being savedā
i.e. Must be pure dummy wrapper; no
validations here
90. reļ¬le upload
https://github.com/reļ¬le/reļ¬le/blob/master/lib/reļ¬le/app.rb
post "/:backend" do
halt 404 unless upload_allowed?
tempfile = request.params.fetch("file").fetch(:tempfile)
file = backend.upload(tempfile)
content_type :json
{ id: file.id }.to_json
end
def file
file = backend.get(params[:id])
unless file.exists?
log_error("Could not find attachment by id: #{params[:id]}")
halt 404
end
file.download
end
91. post "/:backend" do
halt 404 unless upload_allowed?
tempfile = request.params.fetch("file").fetch(:tempfile)
file = backend.upload(tempfile)
content_type :json
{ id: file.id }.to_json
end
def file
file = backend.get(params[:id])
unless file.exists?
log_error("Could not find attachment by id: #{params[:id]}")
halt 404
end
file.download
end
reļ¬le upload
https://github.com/reļ¬le/reļ¬le/blob/master/lib/reļ¬le/app.rb
2 hops problem when
uploading and downloading
ā¢ 3mb ļ¬le becomes 6mb each
way
ā¢ #create and #show
becomes 12mb process
ā¢ has_many :images?
92. reļ¬le
ā¢ CarrierWave-styled named processors (e.g. :ļ¬ll, :thumb) vs
passing through syntax to underlying ImageMagick
ā¢ personally prefer leveraging off existing knowledge
ā¢ instead of manually conļ¬gured syntax sugar
ā¢ ā2 hop problemā however provide higher consistency
when running multiple reļ¬le servers
ā¢ upload-here-download-there problem
ā¢ (considering to perform 2-hop upload instead of async)
93. reļ¬le
ā¢ Can upload to S3 direct and/or upload to reļ¬le
ā¢ Redundancy interesting, but prefer less concern in Rails
app
ā¢ Concept of cache and store to manage ādangling ļ¬le
problemā is worth considering
ā¢ Download urls are signed-only (this practice should be
adopted)
ā¢ can partly counter motivation to abuse ādangling ļ¬le
problemā (aka free ļ¬le hosting, whee!)