【问题标题】:How to trigger Rails ActiveRecord Validations manually ? (ActiveAdmin)如何手动触发 Rails ActiveRecord 验证? (活动管理员)
【发布时间】:2021-11-28 10:00:10
【问题描述】:

我目前正在开发一个使用 Rails 5.2.6 的项目。 (这是一个相当大的项目,我将无法更新rails版本)

我们使用 ActiveAdmin 来处理管理部分,我有一个模型,我在其中使用 ActiveStorage 保存徽标。

最近,我需要对该徽标属性进行验证。 (文件格式、大小和比例)。为此,我一直在搜索多种解决方案,包括 ActiveStorageValidations gem。

这个为我提供了一半的解决方案,因为验证器按预期工作,但即使验证器失败,徽标也会被存储并关联到模型。 (我被重定向到编辑表单,在徽标字段上显示不同的错误,但徽标仍会更新)。这显然是一个已知问题,来自据说在 Rails 6 中修复的 ActiveStorage,但我无法更新项目。 (根据我在 GitHub 上发现的问题,ActiveStorageValidations 也不想做任何事情)

最后,我设法“手动”制作了一个可行的解决方案,使用一些 before_actions 对图像进行必要的检查,并在某些检查失败时再次呈现编辑表单。

我还在此过程中向我的模型添加了一些错误,以便在呈现来自活动管理员的编辑视图时,错误会正确显示在表单和徽标字段的顶部。

这是后面的代码 (admin/mymodel.rb)

controller do
    before_action :prevent_save_if_invalid_logo, only: [:create, :update]

    private

    # Active Storage Validations display error messages but still attaches the file and persist the model
    # That's a known issue, which is solved in Rails 6
    # This is a workaround to make it work for our use case
    def prevent_save_if_invalid_logo
      return unless params[:my_model][:logo]

      file = params[:my_model][:logo]
      return if valid_logo_file_format(file) && valid_logo_aspect_ratio(file) && valid_logo_file_size(file)

      if @my_model.errors.any?
        render 'edit' 
      end
    end

    def valid_logo_aspect_ratio(file)
      width, height = IO.read(file.tempfile.path)[0x10..0x18].unpack('NN')
      
      valid = (2.to_f / 1).round(3) == (width.to_f / height).round(3) ? true : false
      @my_model.errors[:logo] << "Aspect ratio must be 2 x 1" unless valid
      valid
    end

    def valid_logo_file_size(file)
      valid = File.size(file.tempfile) < 200.kilobytes ? true : false
      @my_model.errors[:logo] << "File size must be < 200 kb" unless valid
      valid
    end

    def valid_logo_file_format(file)
      content_type = file.present? && file.content_type
      @my_model.errors[:logo] << "File must be a PNG" unless content_type
      content_type == "image/png" ? true : content_type
    end
end

这很好用,但现在我的问题是,如果表单上同时出现任何其他错误而不是徽标错误(必填字段或其他内容),那么它不会得到验证,并且错误不会显示因为这会在其他验证发生之前呈现编辑视图。

我的问题是,我有没有办法在这个级别手动触发我的模型上的验证,以便每个其他字段都得到验证,@my_model.errors 填充了正确的错误,从而使表单能够显示每个表单错误,无论是否涉及徽标。

像这样:

    ...
    def prevent_save_if_invalid_logo
       return unless params[:my_model][:logo]
    
       file = params[:my_model][:logo]
       return if valid_logo_file_format(file) && valid_logo_aspect_ratio(file) && valid_logo_file_size(file)
    
       if @my_model.errors.any?
         # VALIDATE WHOLE FORM SO OTHER ERRORS ARE CHECKED
         render 'edit' 
       end
   end
...

如果有人知道如何做到这一点,或者知道如何以更好的方式做事,任何线索都将不胜感激!

【问题讨论】:

  • 嗨,我想我遇到了同样的问题。我修复了覆盖创建和更新方法以将错误放入@my_model.errorsgithub.com/activeadmin/inherited_resources/blob/…
  • 是的,或多或少的实现了。但是 chumakoff 对我们所做的事情给出了一个很好、更清晰的答案。

标签: ruby-on-rails ruby validation activeadmin


【解决方案1】:

方法一:

您可以简单地删除文件表单参数并让事情按照标准方式进行,而不是显式呈现停止默认 ActiveAdmin 流程的“编辑”:

before_action :prevent_logo_assignment_if_invalid, only: [:create, :update]

def prevent_logo_assignment_if_invalid
  return unless params[:my_model][:logo]

  file = params[:my_model][:logo]
  return if valid_logo_file_format?(file) && valid_logo_aspect_ratio?(file) && valid_logo_file_size?(file)

  params[:my_model][:logo] = nil
  # or params[:my_model].delete(:logo)
end

方法2:

这个想法是一样的,但你也可以在模型级别上做到这一点。 您可以通过覆盖 ActiveStorage 的 setter 方法来防止文件分配:

class MyModel < ApplicationModel
  def logo=(file)
    return unless valid_logo?(file) 

    super
  end

顺便说一句,您的valid_logo_file_format 方法将始终返回true

【讨论】:

  • 感谢您的输入,我通过从 params 对象创建 my_model 实例找到了自己的解决方法,我从不坚持但运行验证,然后将错误与我的真实错误合并将被保留的模型。但是您的解决方案绝对听起来更干净,也更尊重 ActiveAdmin 流程,我会尝试实现这个:)
猜你喜欢
  • 1970-01-01
  • 2013-08-10
  • 2015-04-26
  • 1970-01-01
  • 2015-11-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多