首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >ActiveStorage -上传后获取图像维数

ActiveStorage -上传后获取图像维数
EN

Stack Overflow用户
提问于 2020-02-08 20:32:07
回答 3查看 6.8K关注 0票数 10

我使用Rails + ActiveStorage上传图像文件,并希望在上传后保存数据库中的宽度和高度。然而,我在任何地方都很难找到这样的例子。

这就是我从各种API文档中拼凑出来的内容,但最终却出现了一个错误:private method 'open' called for #<String:0x00007f9480610118>。将blob替换为image.file会导致rails记录“跳过图像分析,因为ImageMagick不支持文件”(analyzer.rb#L39)。

代码:

代码语言:javascript
运行
复制
class Image < ApplicationRecord
  after_commit { |image| set_dimensions image }

  has_one_attached :file

  def set_dimensions(image)
    if (image.file.attached?)
      blob = image.file.download

      # error: private method `open' called for #<String:0x00007f9480610118>
      meta = ActiveStorage::Analyzer::ImageAnalyzer.new(blob).metadata
    end
  end
end

这种方法也是有问题的,因为after_commit也会被调用破坏。

TLDR:是否有一种在上传后立即获取图像元数据的“适当”方法?

EN

Stack Overflow用户

回答已采纳

发布于 2020-11-20 12:09:39

Rails内置解决方案

根据ActiveStorage概览行会的说法,已经存在使用ActiveStorage::Analyzer::ImageAnalyzer的解决方案image.file.analyzeimage.file.analyze_later (文档 )。

根据#分析文档的说法:

新的blobs在第一次附加时通过analyze_later自动和异步地进行分析。

这意味着您可以使用

代码语言:javascript
运行
复制
image.file.metadata
#=> {"identified"=>true, "width"=>2448, "height"=>3264, "analyzed"=>true}

image.file.metadata['width']
image.file.metadata['height']

所以你的模型看起来像:

代码语言:javascript
运行
复制
class Image < ApplicationRecord
  has_one_attached :file

  def height
    file.metadata['height']
  end

  def width
    file.metadata['width']
  end
end

对于90%的常规情况您对此很在行

但是:问题是,这是“异步分析”(#analyze_later),这意味着上传后不会立即存储元数据。

代码语言:javascript
运行
复制
image.save!
image.file.metadata
#=> {"identified"=>true}
image.file.analyzed?
# => nil

# .... after ActiveJob for analyze_later finish
image.reload
image.file.analyzed?
# => true
#=> {"identified"=>true, "width"=>2448, "height"=>3264, "analyzed"=>true}

这意味着,如果需要实时访问宽度/高度(例如,新上传文件的大小的API响应),则可能需要这样做。

代码语言:javascript
运行
复制
class Image < ApplicationRecord
  has_one_attached :file
  after_commit :save_dimensions_now

  def height
    file.metadata['height']
  end

  def width
    file.metadata['width']
  end

  private
  def save_dimensions_now
    file.analyze if file.attached?
  end
end

注意:在作业中执行异步操作有一个很好的原因。由于需要执行额外的代码,您的请求的响应会稍微慢一些。因此,您需要有一个很好的理由“立即保存维度”,这个解决方案的镜像可以在如何在Rails ActiveStorage中存储图像宽度高度上找到。

DIY溶液

建议:不要这么做,依靠现有的香草Rails解决方案

需要更新附件的模型

博格丹·巴兰解会工作的。下面是不使用skip_set_dimensions attr_accessor的相同解决方案的重写

代码语言:javascript
运行
复制
class Image < ApplicationRecord
  after_commit :set_dimensions

  has_one_attached :file

  private

  def set_dimensions
    if (file.attached?)
      meta = ActiveStorage::Analyzer::ImageAnalyzer.new(file).metadata
      height = meta[:height]
      width  = meta[:width]
    else
      height = 0
      width  = 0
    end

    update_columns(width: width, height: height) # this will save to DB without Rails callbacks
  end
end

列文档

不需要更新附件的模型

很可能您正在创建模型,您希望在该模型中存储文件附件,并且不再更新它。(因此,如果您需要更新附件,只需创建新的模型记录并删除旧的)

在这种情况下,代码甚至是光滑的:

代码语言:javascript
运行
复制
class Image < ApplicationRecord
  after_commit :set_dimensions, on: :create

  has_one_attached :file

  private

  def set_dimensions
    meta = ActiveStorage::Analyzer::ImageAnalyzer.new(file).metadata
    self.height = meta[:height] || 0
    self.width  = meta[:width] || 0
    save!
  end
end

在保存之前,您可能要验证附件是否存在。您可以使用验证宝石

代码语言:javascript
运行
复制
class Image < ApplicationRecord
  after_commit :set_dimensions, on: :create

  has_one_attached :file

  # validations by active_storage_validations
  validates :file, attached: true,
    size: { less_than: 12.megabytes , message: 'image too large' },
    content_type: { in: ['image/png', 'image/jpg', 'image/jpeg'], message: 'needs to be an PNG or JPEG image' }

  private

  def set_dimensions
    meta = ActiveStorage::Analyzer::ImageAnalyzer.new(file).metadata
    self.height = meta[:height] || 0
    self.width  = meta[:width] || 0
    save!
  end
end

测试

代码语言:javascript
运行
复制
require 'rails_helper'
RSpec.describe Image, type: :model do
  let(:image) { build :image, file: image_file }

  context 'when trying to upload jpg' do
    let(:image_file) { FilesTestHelper.jpg } # https://blog.eq8.eu/til/factory-bot-trait-for-active-storange-has_attached.html

    it do
      expect { image.save }.to change { image.height }.from(nil).to(35)
    end

    it do
      expect { image.save }.to change { image.width }.from(nil).to(37)
    end

    it 'on update it should not cause infinitte loop' do
      image.save! # creates
      image.rotation = 90 # whatever change, some random property on Image model
      image.save! # updates
      # no stack ofverflow happens => good
    end
  end

  context 'when trying to upload pdf' do
    let(:image_file) { FilesTestHelper.pdf } # https://blog.eq8.eu/til/factory-bot-trait-for-active-storange-has_attached.html

    it do
      expect { image.save }.not_to change { image.height }
    end
  end
end

FilesTestHelper.jpg的工作方式在文章在工厂机器人上附加主动Storange中作了解释

票数 12
EN
查看全部 3 条回答
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/60130926

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档