Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use #direct method to initialize url helpers #3

Merged
merged 4 commits into from
Aug 8, 2023
Merged
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
40 changes: 14 additions & 26 deletions lib/imgproxy-rails/railtie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,26 @@
require "imgproxy-rails/transformer"

module ImgproxyRails
module UrlHelpers
class Railtie < ::Rails::Railtie
VARIANT_CLASSES = [
"ActiveStorage::Variant",
"ActiveStorage::VariantWithRecord"
].freeze

def imgproxy_active_storage_url(*args)
record = args[0]

if VARIANT_CLASSES.any? { |klass| record.is_a?(klass.constantize) }
imgproxy_url(record)
else
rails_storage_proxy_url(*args)
initializer "imgproxy-rails.routes" do
config.after_initialize do |app|
app.routes.prepend do
direct :imgproxy_active_storage do |model, options|
if VARIANT_CLASSES.any? { |klass| model.is_a?(klass.constantize) }
url = route_for(:rails_storage_proxy, model.blob, options)
transformations = model.variation.transformations
Imgproxy.url_for(url, Transformer.call(transformations, model.blob.metadata))
else
route_for(:rails_storage_proxy, model, options)
end
end
end
end
end

def imgproxy_active_storage_path(*args)
rails_storage_proxy_path(*args)
end

private

def imgproxy_url(record)
transformations = record.variation.transformations
url = rails_storage_proxy_url(record.blob)
::Imgproxy.url_for(url, Transformer.call(transformations))
end
end

class RailtieHelpers < ::Rails::Railtie
initializer "imgproxy-rails.include_url_helpers" do
ActionDispatch::Routing::UrlFor.include UrlHelpers
end
end
end
109 changes: 82 additions & 27 deletions lib/imgproxy-rails/transformer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,96 @@

module ImgproxyRails
class Transformer
MAP = {
resize: proc do |p|
width, height = p.split("x")
{width: width, height: height}
end,
resize_to_limit: proc { |p| {width: p[0], height: p[1]} },
resize_to_fit: proc { |p| {width: p[0], height: p[1]} },
resize_to_fill: proc { |p| {width: p[0], height: p[1], resizing_type: :fill} },
resize_and_pad: proc { |p, m| resize_and_pad(p, m) },
crop: proc { false }, # Cropping API is different in imgproxy
monochrome: proc { false },
rotate: proc { true },
convert: proc { |p| {format: p} },
sharpen: proc { true }, # TODO: needs to be weighted
blur: proc { true }, # TODO: needs to be weighted
quality: proc { true }, # TODO: needs to be weighted
trim: proc { {trim: 0} },
modulate: proc { |p| modulate(p) }
}

class << self
MAP = {
resize: ->(p) do
width, height = p.split("x")
{width: width, height: height}
end,
resize_to_limit: ->(p) { {width: p[0], height: p[1], type: :fit} },
resize_to_fit: ->(p) { {width: p[0], height: p[1], type: :fit} },
resize_to_fill: ->(p) { {width: p[0], height: p[1], type: :fill} },
resize_and_pad: ->(p) { false },
crop: ->(p) { false },
rotate: ->(p) { p },
convert: ->(p) { {format: p} },
define: ->(p) { false },
monochrome: ->(p) { false },
flip: ->(p) { false },
sharpen: ->(p) { p }
}

def call(transformations)
def call(transformations, meta)
passed_options = transformations.delete(:imgproxy_options) || {}
mapped_options = transformations.each_with_object({}) do |(key, value), memo|
next unless MAP.key?(key)

value = MAP[key].call(value)
next if value.is_a?(FalseClass)
mapped_options = transformations.each_with_object({}) do |(t_key, t_value), memo|
next unless MAP.key?(t_key)

if value.is_a?(Hash)
memo.merge!(value)
map_value = MAP[t_key].call(t_value, meta)
case map_value
when FalseClass
next # TODO: log warning?
when TrueClass
memo[t_key] = t_value
when Hash
memo.merge!(map_value)
else
memo[key] = value
raise "Unexpected map value class"
end
end
mapped_options.merge(passed_options)
end

private

def convert_color(color)
return unless color || color =~ /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/

color.sub(/^#/, "")
end

def resize_and_pad(p, m)
target_width, target_height, options = p

result = {width: target_width, height: target_height}
return result unless m["width"] && m["height"]

aspect_ratio = m["width"].to_f / m["height"]
if aspect_ratio > 1
# add vertical padding
final_height = target_width.to_f / aspect_ratio
padding_length = ((target_height - final_height) / 2).round
result[:padding] = [padding_length, 0]

# setting min-width for correct upscaling
result[:mw] = target_width
else
# add horizontal padding
final_width = target_height.to_f * aspect_ratio
padding_length = ((target_width - final_width) / 2).round
result[:padding] = [0, padding_length]

# setting min-height for correct upscaling
result[:mh] = target_height
end

if (background = convert_color(options[:background]))
result[:background] = background
end

result
end

def modulate(p)
brightness, saturation, _ = p.split(",").map(&:to_i)

result = {}
result[:brightness] = brightness if brightness
result[:saturation] = saturation if saturation

result
end
end
end
end
34 changes: 14 additions & 20 deletions spec/internal_spec.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
require "rails_helper"

RSpec.describe "Dummy app", type: :request do
include ActionView::Helpers::AssetTagHelper

let(:option) { record }
let(:record) { user.avatar.variant(resize_to_limit: [500, 500]) }
let(:user) do
Expand All @@ -15,9 +13,14 @@
end
end

before { Rails.application.routes.default_url_options[:host] = "http://example.com" }
before do
Rails.application.routes.default_url_options[:host] = "http://example.com"
Imgproxy.configure { |config| config.endpoint = "http://imgproxy.io" }
end

describe "image_tag" do
include ActionView::Helpers::AssetTagHelper

subject { image_tag(option) }

shared_context "handles string correctly" do
Expand All @@ -34,12 +37,7 @@
end

describe "with resolve_model_to_route = :imgproxy_active_storage" do
before do
ActiveStorage.resolve_model_to_route = :imgproxy_active_storage
Imgproxy.configure do |config|
config.endpoint = "http://imgproxy.io"
end
end
before { ActiveStorage.resolve_model_to_route = :imgproxy_active_storage }

it { is_expected.to include('<img src="http://imgproxy.io', "blobs") }

Expand All @@ -64,20 +62,16 @@
end

describe "url helpers" do
describe "#imgproxy_active_storage_path" do
subject { imgproxy_active_storage_path(option) }
describe "#imgproxy_active_storage_url" do
subject { imgproxy_active_storage_url(option) }

describe "with resolve_model_to_route == :imgproxy_active_storage" do
before { ActiveStorage.resolve_model_to_route = :imgproxy_active_storage }

it { is_expected.to start_with("/rails/active_storage") }
end
it { is_expected.to start_with("http://imgproxy.io/unsafe").and include("rails/active_storage") }
end

describe "with resolve_model_to_route == :rails_storage_proxy" do
before { ActiveStorage.resolve_model_to_route = :rails_storage_proxy }
describe "#imgproxy_active_storage_path" do
subject { imgproxy_active_storage_path(option) }

it { is_expected.to start_with("/rails/active_storage") }
end
it { is_expected.to start_with("/unsafe").and include("rails/active_storage") }
end
end
end
99 changes: 93 additions & 6 deletions spec/lib/imgproxy-rails/transformer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

describe ImgproxyRails::Transformer do
describe ".call" do
subject { described_class.call(transformations) }
subject { described_class.call(transformations, {width: 1000, height: 1000}) }

let(:transformations) do
{
Expand All @@ -13,17 +13,19 @@
resize_to_fill: [103, 103],
resize_and_pad: [104, 104],
crop: [20, 50, 300, 300],
monochrome: true,
rotate: 90,
convert: :jpg,
define: "webp:method=6",
monochrome: true,
flip: true,
sharpen: 4,
blur: 40,
quality: 80,
trim: true,
modulate: "150,151,152",
lol: 5,
imgproxy_options: {
width: 500,
height: 500,
type: :fit,
resizing_type: :fit,
lol: 6
}
}
Expand All @@ -33,14 +35,99 @@
{
width: 500,
height: 500,
type: :fit,
resizing_type: :fit,
rotate: 90,
format: :jpg,
sharpen: 4,
blur: 40,
brightness: 150,
quality: 80,
saturation: 151,
trim: 0,
lol: 6
}
end

it { is_expected.to eq(expected_transformations) }
end

describe ".resize_and_pad" do
subject { described_class.send(:resize_and_pad, params, meta) }

shared_examples "transforms to" do |expected|
it { is_expected.to eq(expected) }
end

context "landscape" do
let(:params) { [1000, 1000, {background: "#bbbbc4"}] }
let(:meta) { {"width" => 1664, "height" => 960} }

it_behaves_like "transforms to",
width: 1000,
height: 1000,
padding: [212, 0],
background: "bbbbc4",
mw: 1000

context "target height and width > original image" do
let(:params) { [2000, 2000, {background: "#bbbbc4"}] }
let(:meta) { {"width" => 1664, "height" => 960} }

it_behaves_like "transforms to",
width: 2000,
height: 2000,
padding: [423, 0],
background: "bbbbc4",
mw: 2000
end

context "target height and width < original image" do
let(:params) { [500, 500, {background: "#bbbbc4"}] }
let(:meta) { {"width" => 1664, "height" => 960} }

it_behaves_like "transforms to",
width: 500,
height: 500,
padding: [106, 0],
background: "bbbbc4",
mw: 500
end
end

context "vertical" do
let(:params) { [1000, 1000, {background: "#bbbbc4"}] }
let(:meta) { {"width" => 799, "height" => 1280} }

it_behaves_like "transforms to",
width: 1000,
height: 1000,
padding: [0, 188],
background: "bbbbc4",
mh: 1000

context "target height and width > original image" do
let(:params) { [2000, 2000, {background: "#bbbbc4"}] }
let(:meta) { {"width" => 799, "height" => 1280} }

it_behaves_like "transforms to",
width: 2000,
height: 2000,
padding: [0, 376],
background: "bbbbc4",
mh: 2000
end

context "target height and width < original image" do
let(:params) { [500, 500, {background: "#bbbbc4"}] }
let(:meta) { {"width" => 799, "height" => 1280} }

it_behaves_like "transforms to",
width: 500,
height: 500,
padding: [0, 94],
background: "bbbbc4",
mh: 500
end
end
end
end
Loading