Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
FROM ruby:2.7.1-alpine

ARG RAILS_ROOT=/app
ARG PACKAGES="vim openssl-dev postgresql-dev build-base curl nodejs yarn less tzdata git postgresql-client bash screen"
ARG PACKAGES="vim openssl-dev postgresql-dev build-base curl nodejs yarn less tzdata git postgresql-client bash screen imagemagick"

RUN apk update \
&& apk upgrade \
Expand Down
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,6 @@ gem 'sidekiq'
gem 'sidekiq-failures'
gem 'sidekiq-throttled'
gem 'sidekiq-unique-jobs', '~> 6.0.13'
gem 'mini_magick'
gem 'virtus'
gem 'file_validators'
23 changes: 23 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ GEM
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
ast (2.4.2)
axiom-types (0.1.1)
descendants_tracker (~> 0.0.4)
ice_nine (~> 0.11.0)
thread_safe (~> 0.3, >= 0.3.1)
bcrypt (3.1.16)
bindex (0.8.1)
bootsnap (1.4.8)
Expand All @@ -93,9 +97,13 @@ GEM
case_transform (0.2)
activesupport
childprocess (3.0.0)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
concurrent-ruby (1.1.9)
connection_pool (2.2.5)
crass (1.0.6)
descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1)
docile (1.4.0)
erubi (1.10.0)
factory_bot (6.1.0)
Expand All @@ -104,10 +112,14 @@ GEM
factory_bot (~> 6.1.0)
railties (>= 5.0.0)
ffi (1.13.1)
file_validators (3.0.0)
activemodel (>= 3.2)
mime-types (>= 1.0)
globalid (0.4.2)
activesupport (>= 4.2.0)
i18n (1.8.10)
concurrent-ruby (~> 1.0)
ice_nine (0.11.2)
jbuilder (2.10.1)
activesupport (>= 5.0.0)
js-routes (2.0.4)
Expand Down Expand Up @@ -143,6 +155,10 @@ GEM
mini_mime (>= 0.1.1)
marcel (1.0.1)
method_source (1.0.0)
mime-types (3.3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2021.0225)
mini_magick (4.11.0)
mini_mime (1.1.0)
mini_portile2 (2.5.3)
minitest (5.14.4)
Expand Down Expand Up @@ -282,6 +298,10 @@ GEM
thread_safe (~> 0.1)
unicode-display_width (2.0.0)
uniform_notifier (1.14.2)
virtus (2.0.0)
axiom-types (~> 0.1)
coercible (~> 1.0)
descendants_tracker (~> 0.0, >= 0.0.3)
web-console (4.0.4)
actionview (>= 6.0.0)
activemodel (>= 6.0.0)
Expand Down Expand Up @@ -315,12 +335,14 @@ DEPENDENCIES
byebug
capybara (>= 2.15)
factory_bot_rails
file_validators
jbuilder (~> 2.7)
js-routes
kaminari
letter_opener
letter_opener_web
listen (~> 3.2)
mini_magick
newrelic_rpm
pg (>= 0.18, < 2.0)
puma (~> 4.1)
Expand All @@ -341,6 +363,7 @@ DEPENDENCIES
state_machines
state_machines-activerecord
tzinfo-data
virtus
web-console (>= 3.3.0)
webdrivers
webpacker (~> 4.0)
Expand Down
28 changes: 27 additions & 1 deletion app/controllers/api/v1/tasks_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class Api::V1::TasksController < Api::V1::ApplicationController
def index
tasks = Task.all.
tasks = Task.with_attached_image.all.
ransack(ransack_params).
result.
page(page).
Expand Down Expand Up @@ -47,9 +47,35 @@ def destroy
respond_with(task)
end

def attach_image
task = Task.find(params[:id])
task_attach_image_form = TaskAttachImageForm.new(attachment_params)

if task_attach_image_form.invalid?
respond_with(task_attach_image_form)
return
end

image = task_attach_image_form.processed_image
task.image.attach(image)

respond_with(task, serializer: TaskSerializer)
end

def remove_image
task = Task.find(params[:id])
task.image.purge

respond_with(task, serializer: TaskSerializer)
end

private

def task_params
params.require(:task).permit(:name, :description, :author_id, :assignee_id, :state_event)
end

def attachment_params
params.require(:attachment).permit(:image, :crop_width, :crop_height, :crop_x, :crop_y)
end
end
31 changes: 31 additions & 0 deletions app/forms/task_attach_image_form.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
class TaskAttachImageForm
include ActiveModel::Validations
include Virtus.model

attribute :image, ActionDispatch::Http::UploadedFile
attribute :crop_width, Integer
attribute :crop_height, Integer
attribute :crop_x, Integer
attribute :crop_y, Integer

with_options numericality: { only_integer: true, greater_than_or_equal_to: 0 } do
validates :crop_width, if: -> { crop_width.present? }
validates :crop_height, if: -> { crop_height.present? }
validates :crop_x, if: -> { crop_x.present? }
validates :crop_y, if: -> { crop_y.present? }
end

validates :image, presence: true,
file_size: { less_than_or_equal_to: 2.megabytes },
file_content_type: { allow: ['image/jpeg', 'image/png'] }

def processed_image
ImageProcessingService.crop!(image.path, crop_width, crop_height, crop_x, crop_y) if cropping?

image
end

def cropping?
[crop_width, crop_height, crop_x, crop_y].all?(&:present?)
end
end
2 changes: 2 additions & 0 deletions app/models/task.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
class Task < ApplicationRecord
has_one_attached :image

state_machine initial: :new_task do
event :archive do
transition [:new_task, :released] => :archived
Expand Down
4 changes: 4 additions & 0 deletions app/serializers/task_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@ def transitions
}
end
end

def image_url
object.image.attached? ? AttachmentsService.file_url(object.image) : nil
end
end
7 changes: 7 additions & 0 deletions app/services/attachments_server.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module AttachmentsService
class << self
def file_url(file)
Rails.application.routes.url_helpers.rails_blob_url(file)
end
end
end
8 changes: 8 additions & 0 deletions app/services/image_processing_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module ImageProcessingService
class << self
def crop!(path_to_image, crop_width, crop_height, crop_x, crop_y)
image = MiniMagick::Image.new(path_to_image)
image.crop("#{crop_width}x#{crop_height}+#{crop_x}+#{crop_y}")
end
end
end
6 changes: 5 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@

namespace :api do
namespace :v1, defaults: {format: :json} do
resources :tasks, only: [:index, :show, :create, :update, :destroy]
resources :tasks, only: [:index, :show, :create, :update, :destroy] do
member do
put :attach_image, :remove_image
end
end
resources :users, only: [:index, :show]
end
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# 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
t.foreign_key :active_storage_blobs, column: :blob_id
end
end
end
24 changes: 23 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,32 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2021_06_15_161358) do
ActiveRecord::Schema.define(version: 2021_06_19_102125) 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 "tasks", force: :cascade do |t|
t.string "name"
t.text "description"
Expand All @@ -39,4 +60,5 @@
t.datetime "password_reset_sent_at"
end

add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
end
55 changes: 55 additions & 0 deletions test/controllers/api/v1/tasks_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,59 @@ class Api::V1::TasksControllerTest < ActionController::TestCase

assert !Task.where(id: task.id).exists?
end

test 'should put attach_image' do
author = create(:user)
task = create(:task, author: author)

image = file_fixture('image.jpg')
attachment_params = {
image: fixture_file_upload(image, 'image/jpeg'),
crop_x: 190,
crop_y: 100,
crop_width: 300,
crop_height: 300,
}

put :attach_image, params: { id: task.id, attachment: attachment_params, format: :json }
assert_response :success

task.reload
assert task.image.attached?
end

test 'should put remove_image' do
author = create(:user)
task = create(:task, author: author)

image = file_fixture('image.jpg')
# attachable_image = fixture_file_upload(image)
attachment_params = {
image: fixture_file_upload(image, 'image/jpeg'),
crop_x: 190,
crop_y: 100,
crop_width: 300,
crop_height: 300,
}

put :attach_image, params: { id: task.id, attachment: attachment_params, format: :json }

# task.image.attach(attachable_image)

put :remove_image, params: { id: task.id, format: :json }
assert_response :success

task.reload
refute task.image.attached?
end

def after_teardown
super

remove_uploaded_files
end

def remove_uploaded_files
FileUtils.rm_rf(ActiveStorage::Blob.service.root)
end
end
Binary file added test/fixtures/files/image.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.