diff --git a/.gitignore b/.gitignore
index 6809ebb..ce55809 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,6 +18,9 @@
# Ignore environment variables
.env
+# Ignore Active Storage
+/storage
+
# Ignore dragonfly uploads
/public/system
/public/packs
diff --git a/Gemfile b/Gemfile
index f7ae0a7..f12124c 100644
--- a/Gemfile
+++ b/Gemfile
@@ -7,8 +7,8 @@ gem "dotenv-rails", groups: %i[development test]
gem "rails", "5.2.1"
gem "delayed_job_active_record"
-gem "dragonfly"
gem "http"
+gem "mini_magick"
gem "omniauth-github"
gem "pg"
gem "puma"
diff --git a/Gemfile.lock b/Gemfile.lock
index a10b1af..14f53d4 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -82,10 +82,6 @@ GEM
dotenv-rails (2.5.0)
dotenv (= 2.5.0)
railties (>= 3.2, < 6.0)
- dragonfly (1.1.5)
- addressable (~> 2.3)
- multi_json (~> 1.0)
- rack (>= 1.3)
erubi (1.7.1)
execjs (2.7.0)
faraday (0.12.2)
@@ -121,6 +117,7 @@ GEM
mimemagic (~> 0.3.2)
method_source (0.9.1)
mimemagic (0.3.2)
+ mini_magick (4.9.2)
mini_mime (1.0.1)
mini_portile2 (2.3.0)
minitest (5.11.3)
@@ -266,10 +263,10 @@ DEPENDENCIES
capybara
delayed_job_active_record
dotenv-rails
- dragonfly
geckodriver-helper
http
launchy
+ mini_magick
omniauth-github
pg
pry
@@ -289,4 +286,4 @@ RUBY VERSION
ruby 2.5.1p57
BUNDLED WITH
- 1.16.4
+ 1.16.5
diff --git a/README.md b/README.md
index b531719..6ad2828 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
[ ](https://app.codeship.com/projects/300990)
-A simple DragonFly powered image storage application.
+A simple Active Storage powered image storage application.

diff --git a/app/helpers/image_helper.rb b/app/helpers/image_helper.rb
index 62e5ec7..0298353 100644
--- a/app/helpers/image_helper.rb
+++ b/app/helpers/image_helper.rb
@@ -1,9 +1,15 @@
module ImageHelper
+ def image_variant(image, variant)
+ image.variant(variant)
+ end
+
def image_thumb(image, size)
- if image.format == "jpeg"
- image.thumb(size).encode("jpg", "-quality 90")
- else
- image.thumb(size)
- end
+ image.variant(
+ combine_options: {
+ gravity: "center",
+ resize: "#{size}x#{size}^",
+ extent: "#{size}x#{size}",
+ },
+ )
end
end
diff --git a/app/jobs/tag_image_job.rb b/app/jobs/tag_image_job.rb
index ac84935..5a9603d 100644
--- a/app/jobs/tag_image_job.rb
+++ b/app/jobs/tag_image_job.rb
@@ -4,7 +4,7 @@ class TagImageJob < ApplicationJob
def perform(image_id:)
image = Image.find(image_id)
- tags = Clarifai.new(image.image.file.path).tags
+ tags = Clarifai.new(image.image.download).tags
image.update_attributes(tags: tags)
end
end
diff --git a/app/models/image.rb b/app/models/image.rb
index 379ff1a..abf3346 100644
--- a/app/models/image.rb
+++ b/app/models/image.rb
@@ -1,7 +1,7 @@
class Image < ActiveRecord::Base
- dragonfly_accessor :image
+ has_one_attached :image
- validates :image, presence: true
+ validates :image, attached: true
belongs_to :user
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 1fcfad9..e818b79 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1,6 +1,4 @@
class User < ActiveRecord::Base
- dragonfly_accessor :avatar
-
has_many :images
def self.find_or_create_from_auth(auth)
@@ -13,7 +11,6 @@ class User < ActiveRecord::Base
uid: auth.uid,
username: auth.info.nickname,
name: auth.info.name,
- avatar_url: auth.info.image,
)
end
end
diff --git a/app/validators/attached_validator.rb b/app/validators/attached_validator.rb
new file mode 100644
index 0000000..b860bc1
--- /dev/null
+++ b/app/validators/attached_validator.rb
@@ -0,0 +1,7 @@
+class AttachedValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ if !value.attached?
+ record.errors.add(attribute, :attached, options)
+ end
+ end
+end
diff --git a/app/views/images/_image.html.erb b/app/views/images/_image.html.erb
index 43a3604..31ca994 100644
--- a/app/views/images/_image.html.erb
+++ b/app/views/images/_image.html.erb
@@ -1,17 +1,17 @@
- "
+ "
media="(min-width: 960px) and (-webkit-min-device-pixel-ratio: 2),
(min-width: 960px) and (min-resolution: 192dpi)">
- "
+ "
media="(min-width: 960px)">
- "
+ "
media="(min-width: 640px) and (-webkit-min-device-pixel-ratio: 2),
(min-width: 640px) and (min-resolution: 192dpi)">
- "
+ "
media="(min-width: 640px)">
- "
+ "
media="(-webkit-min-device-pixel-ratio: 2),
(min-resolution: 192dpi)">
- ">
-
" itemprop="image">
+ ">
+
" itemprop="image">
diff --git a/app/views/images/index.html.erb b/app/views/images/index.html.erb
index 803ed02..6b6d926 100644
--- a/app/views/images/index.html.erb
+++ b/app/views/images/index.html.erb
@@ -5,7 +5,7 @@
<% @images.each do |image| %>
-
- <%= link_to image_tag(image.image.thumb('200x200#').url),
+ <%= link_to image_tag(image_thumb(image.image, 200)),
user_image_path(image), class: :image %>
<%= link_to 'Delete', user_image_path(image),
method: :delete, data: { confirm: 'Are you sure?' } %>
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 2a1934a..f78382a 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -16,6 +16,9 @@ MyImages::Application.configure do
# Don't care if the mailer can't send.
config.action_mailer.raise_delivery_errors = false
+ # Store files locally.
+ config.active_storage.service = :local
+
# Print deprecation notices to the Rails logger.
config.active_support.deprecation = :log
diff --git a/config/environments/production.rb b/config/environments/production.rb
index c992235..8a46fbd 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -72,6 +72,9 @@ MyImages::Application.configure do
# Send deprecation notices to registered listeners.
config.active_support.deprecation = :notify
+ # Store files locally.
+ config.active_storage.service = :local
+
# Disable automatic flushing of the log to improve performance.
# config.autoflush_log = false
diff --git a/config/environments/test.rb b/config/environments/test.rb
index edb380f..240f562 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -33,4 +33,7 @@ MyImages::Application.configure do
# Print deprecation notices to the stderr.
config.active_support.deprecation = :stderr
+
+ # Store files locally.
+ config.active_storage.service = :test
end
diff --git a/config/initializers/dragonfly.rb b/config/initializers/dragonfly.rb
deleted file mode 100644
index 8e8ef5f..0000000
--- a/config/initializers/dragonfly.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-require "dragonfly"
-
-# Configure
-Dragonfly.app.configure do
- plugin :imagemagick
-
- verify_urls true
- secret "0da375f9754c75707f2a3b4e0c75dffb7ec7d6bb1a4a77e8a8dcfba9037bebce"
-
- url_format "/media/:job/:name"
-
- datastore :file,
- root_path: Rails.root.join("public/system/dragonfly", Rails.env),
- server_root: Rails.root.join("public")
-end
-
-# Logger
-Dragonfly.logger = Rails.logger
-
-# Mount as middleware
-Rails.application.middleware.use Dragonfly::Middleware
-
-# Add model functionality
-if defined?(ActiveRecord::Base)
- ActiveRecord::Base.extend Dragonfly::Model
- ActiveRecord::Base.extend Dragonfly::Model::Validations
-end
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 0653957..c0523a6 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -21,3 +21,8 @@
en:
hello: "Hello world"
+
+ activerecord:
+ errors:
+ messages:
+ attached: is not attached
diff --git a/config/storage.yml b/config/storage.yml
new file mode 100644
index 0000000..1f93f73
--- /dev/null
+++ b/config/storage.yml
@@ -0,0 +1,7 @@
+local:
+ service: Disk
+ root: <%= Rails.root.join("storage") %>
+
+test:
+ service: Disk
+ root: <%= Rails.root.join("tmp/storage") %>
diff --git a/db/migrate/20181019204358_create_active_storage_tables.active_storage.rb b/db/migrate/20181019204358_create_active_storage_tables.active_storage.rb
new file mode 100644
index 0000000..360e0d1
--- /dev/null
+++ b/db/migrate/20181019204358_create_active_storage_tables.active_storage.rb
@@ -0,0 +1,26 @@
+# This migration comes from active_storage (originally 20170806125915)
+class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
+ def change
+ 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.datetime :created_at, null: false
+
+ t.index [ :key ], unique: true
+ end
+
+ create_table :active_storage_attachments do |t|
+ t.string :name, null: false
+ t.references :record, null: false, polymorphic: true, index: false
+ t.references :blob, null: false
+
+ t.datetime :created_at, null: false
+
+ t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
+ end
+ end
+end
diff --git a/db/migrate/20181019210305_rename_dragonfly_columns.rb b/db/migrate/20181019210305_rename_dragonfly_columns.rb
new file mode 100644
index 0000000..3a50e5e
--- /dev/null
+++ b/db/migrate/20181019210305_rename_dragonfly_columns.rb
@@ -0,0 +1,6 @@
+class RenameDragonflyColumns < ActiveRecord::Migration[5.2]
+ def change
+ rename_column :images, :image_uid, :df_image_uid
+ rename_column :images, :image_name, :df_image_name
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 491eb0b..254c7b0 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,11 +10,32 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2018_08_26_163510) do
+ActiveRecord::Schema.define(version: 2018_10_19_210305) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
+ create_table "active_storage_attachments", force: :cascade do |t|
+ t.string "name", null: false
+ t.string "record_type", null: false
+ t.bigint "record_id", null: false
+ t.bigint "blob_id", null: false
+ t.datetime "created_at", null: false
+ t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
+ t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
+ end
+
+ create_table "active_storage_blobs", force: :cascade 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.datetime "created_at", null: false
+ t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
+ end
+
create_table "delayed_jobs", force: :cascade do |t|
t.integer "priority", default: 0, null: false
t.integer "attempts", default: 0, null: false
@@ -32,8 +53,8 @@ ActiveRecord::Schema.define(version: 2018_08_26_163510) do
create_table "images", force: :cascade do |t|
t.integer "user_id"
- t.string "image_uid"
- t.string "image_name"
+ t.string "df_image_uid"
+ t.string "df_image_name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "tags", default: [], array: true
diff --git a/lib/clarifai.rb b/lib/clarifai.rb
index a12efb2..60be6ae 100644
--- a/lib/clarifai.rb
+++ b/lib/clarifai.rb
@@ -6,8 +6,8 @@ class Clarifai
API_URL = "https://api.clarifai.com/v2/models/" \
"aaa03c23b3724a16a56b629203edc62c/outputs".freeze
- def initialize(image_path)
- @image_path = image_path
+ def initialize(image_data)
+ @image_data = image_data
end
def tags
@@ -16,6 +16,8 @@ class Clarifai
private
+ attr_reader :image_data
+
def extract_tags(response_hash)
response_hash["outputs"][0]["data"]["concepts"].map do |concept|
concept["name"]
@@ -40,10 +42,6 @@ class Clarifai
end
def image_base64
- Base64.encode64(image_file)
- end
-
- def image_file
- File.read(@image_path)
+ Base64.encode64(image_data)
end
end
diff --git a/lib/tasks/images_migrate.rake b/lib/tasks/images_migrate.rake
new file mode 100644
index 0000000..4f1f813
--- /dev/null
+++ b/lib/tasks/images_migrate.rake
@@ -0,0 +1,11 @@
+namespace :images do
+ desc "Migrate all the images to Active Storage"
+ task migrate: :environment do
+ Image.all.each do |image|
+ image.image.attach(
+ io: image.df_image.file,
+ filename: image.df_image.name,
+ )
+ end
+ end
+end
diff --git a/spec/features/user_manages_image_spec.rb b/spec/features/user_manages_image_spec.rb
index 66c7a46..bd94a1a 100644
--- a/spec/features/user_manages_image_spec.rb
+++ b/spec/features/user_manages_image_spec.rb
@@ -15,10 +15,14 @@ feature "user manages images" do
it "deletes the image" do
user = User.create(uid: "1")
- Image.create(
+ image = Image.new(
user: user,
- image: File.new("#{Rails.root}/spec/fixtures/spectrum.jpg"),
)
+ image.image.attach(
+ io: File.open("#{Rails.root}/spec/fixtures/spectrum.jpg"),
+ filename: "spectrum.jpg",
+ )
+ image.save
sign_in(user)
click_on("Delete")
diff --git a/spec/features/user_manages_image_tags_spec.rb b/spec/features/user_manages_image_tags_spec.rb
index ce41408..318d4cd 100644
--- a/spec/features/user_manages_image_tags_spec.rb
+++ b/spec/features/user_manages_image_tags_spec.rb
@@ -4,11 +4,15 @@ feature "user manages image tags by visitng images show page" do
scenario "and can see an X to link for deletion on a tag" do
tags = ["one", "two"]
user = User.create(uid: "123")
- Image.create(
+ image = Image.new(
user: user,
tags: tags,
- image: File.new("#{Rails.root}/spec/fixtures/spectrum.jpg"),
)
+ image.image.attach(
+ io: File.open("#{Rails.root}/spec/fixtures/spectrum.jpg"),
+ filename: "spectrum.jpg",
+ )
+ image.save
sign_in(user)
page.find(".image").click
@@ -21,11 +25,15 @@ feature "user manages image tags by visitng images show page" do
scenario "and can click the link to delete a tag", js: true do
tags = ["one", "two"]
user = User.create(uid: "123")
- Image.create(
+ image = Image.new(
user: user,
tags: tags,
- image: File.new("#{Rails.root}/spec/fixtures/spectrum.jpg"),
)
+ image.image.attach(
+ io: File.open("#{Rails.root}/spec/fixtures/spectrum.jpg"),
+ filename: "spectrum.jpg",
+ )
+ image.save
sign_in(user)
page.find(".image").click
@@ -41,11 +49,15 @@ feature "user manages image tags by visitng images show page" do
tags = ["one", "two"]
user = User.create(uid: "123")
new_tag = "newtag"
- Image.create(
+ image = Image.new(
user: user,
tags: tags,
- image: File.new("#{Rails.root}/spec/fixtures/spectrum.jpg"),
)
+ image.image.attach(
+ io: File.open("#{Rails.root}/spec/fixtures/spectrum.jpg"),
+ filename: "spectrum.jpg",
+ )
+ image.save
sign_in(user)
page.find(".image").click
diff --git a/spec/jobs/tag_image_job_spec.rb b/spec/jobs/tag_image_job_spec.rb
index 2244725..d88d503 100644
--- a/spec/jobs/tag_image_job_spec.rb
+++ b/spec/jobs/tag_image_job_spec.rb
@@ -4,9 +4,12 @@ describe TagImageJob, type: :job do
describe ".perform" do
it "tags an image" do
stub_clarifai(%w[computers technology])
- image = Image.create(
- image: "#{Rails.root}/spec/fixtures/spectrum.jpg",
+ image = Image.new
+ image.image.attach(
+ io: File.open("#{Rails.root}/spec/fixtures/spectrum.jpg"),
+ filename: "spectrum.jpg",
)
+ image.save
TagImageJob.perform_now(image_id: image.id)
image.reload
diff --git a/spec/lib/clarifai_spec.rb b/spec/lib/clarifai_spec.rb
index cef4a8d..80e6ed5 100644
--- a/spec/lib/clarifai_spec.rb
+++ b/spec/lib/clarifai_spec.rb
@@ -11,7 +11,7 @@ describe Clarifai do
it "predicts tags for our image" do
stub_api(%w[computer technology])
- clarifai = Clarifai.new("spec/fixtures/spectrum.jpg")
+ clarifai = Clarifai.new(File.read("spec/fixtures/spectrum.jpg"))
expect(clarifai.tags).to eq(%w[computer technology])
end
diff --git a/spec/models/image_spec.rb b/spec/models/image_spec.rb
index c534807..3c490b6 100644
--- a/spec/models/image_spec.rb
+++ b/spec/models/image_spec.rb
@@ -1,11 +1,7 @@
require "rails_helper"
describe Image do
- it { is_expected.to validate_presence_of(:image) }
it { is_expected.to belong_to(:user) }
-
- it "should not be valid without an image" do
- image = Image.new
- expect(image).not_to be_valid
- end
+ it { is_expected.to have_attached_file(:image) }
+ it { is_expected.to validate_attachment_of(:image) }
end
diff --git a/spec/support/matchers/have_attached_file.rb b/spec/support/matchers/have_attached_file.rb
new file mode 100644
index 0000000..107e026
--- /dev/null
+++ b/spec/support/matchers/have_attached_file.rb
@@ -0,0 +1,7 @@
+RSpec::Matchers.define :have_attached_file do |name|
+ match do |record|
+ file = record.send(name)
+
+ file.respond_to?(:attach)
+ end
+end
diff --git a/spec/support/matchers/validate_attachment_of.rb b/spec/support/matchers/validate_attachment_of.rb
new file mode 100644
index 0000000..955cc81
--- /dev/null
+++ b/spec/support/matchers/validate_attachment_of.rb
@@ -0,0 +1,45 @@
+require "rspec/expectations"
+
+RSpec::Matchers.define :validate_attachment_of do |attr_name|
+ match do |record|
+ matcher.matches?(record, attr_name)
+ end
+
+ chain :on do |validation_context|
+ matcher.on(validation_context)
+ end
+
+ chain :with_message do |message|
+ matcher.with_message(message)
+ end
+
+ private
+
+ def matcher
+ @matcher ||= ValidateAttachmentOfMatcher.new
+ end
+
+ class ValidateAttachmentOfMatcher
+ def on(validation_context)
+ @validation_context = validation_context
+ end
+
+ def with_message(message)
+ @message = message
+ end
+
+ def matches?(record, attr_name)
+ record.send(attr_name).purge
+ record.valid?(validation_context)
+ record.errors[attr_name].include? message
+ end
+
+ private
+
+ attr_reader :validation_context
+
+ def message
+ @message || I18n.translate("activerecord.errors.messages.attached")
+ end
+ end
+end