【问题标题】:Not losing paperclip attachment when model cannot be saved due to validation error由于验证错误无法保存模型时不会丢失回形针附件
【发布时间】:2011-07-09 02:07:58
【问题描述】:

该场景是一个普通模型,其中包含一个回形针附件以及具有各种验证的其他一些列。当用于创建对象的表单由于与附件无关的验证错误而无法保存时,字符串之类的列将被保留并为用户预填充,但选择上传的文件完全丢失,必须由用户重新选择。

在模型验证错误的情况下,是否有标准方法来保留附件?这似乎是一个非常常见的用例。

破解一个在没有所有者的情况下保存文件然后在成功保存后重新连接到对象的解决方案似乎很不雅,所以我希望避免这种情况。

【问题讨论】:

  • 当保存不成功时,您可以删除除文件之外的所有参数并调用“update_attribute”,无需验证即可。
  • 虽然我想避免将未经验证的记录保存到数据库中,但我需要维护有关验证的状态。直觉上感觉回形针可能有一些更优雅的处理方式,因为它抽象了文件处理的许多其他部分。
  • 你找到解决办法了吗?

标签: ruby-on-rails validation paperclip attachment


【解决方案1】:

如果不需要图片,为什么不将表单分成两个阶段,第一个阶段创建对象,第二个页面让您添加可选信息(如照片)。

或者,您可以在用户输入信息时验证表单,这样您就不必提交表单来发现您的数据无效。

【讨论】:

  • 是的,这两种方法在技术上都是可行的。我希望能找到更优雅的东西,因为这似乎对 Rails 中的附件处理非常重要。
  • 这不是真正的rails问题,服务器没有发回图像。
  • 回形针的优点是它允许 Rails 应用程序透明地将附件视为另一列。在这种情况下,标准 Rails 模型验证似乎破坏了默认设置。因此,我正在寻找最优雅、最少破解的方法。
  • 最好的方法是找到另一个适合您的 UI 范例。照片的用途是什么?
  • 您应该关注的一件事是载波 (github.com/jnicklas/carrierwave),因为它已经为您想要做的事情提供了支持。
【解决方案2】:

切换到使用 CarrierWave。我知道这是在评论中,但我只是花了一整天的时间进行过渡,所以我的回答可能仍然有用。

首先,您可以关注关于设置载波的精彩 Railscast:http://railscasts.com/episodes/253-carrierwave-file-uploads

要让它在帖子之间保留图像,您需要添加一个后缀为“缓存”的隐藏字段:

<%= form_for @user, :html => {:multipart => true} do |f| %>
  <p>
    <label>My Avatar</label>
    <%= f.file_field :avatar %>
    <%= f.hidden_field :avatar_cache %>
  </p>
<% end %>

对于 Heroku

如果您像我一样部署到 Heroku,则需要进行一些更改才能使其正常工作,因为缓存通过将上传临时保存在名为 public/uploads 的目录中来工作。由于文件系统在 Heroku 中是只读的,因此您需要让它使用 tmp 文件夹,并让 rack 从那里提供静态文件。

告诉 carrierwave 使用 tmp 文件夹进行缓存。

在您的 config/initializers/carrierwave.rb 中(如果没有,请随意创建),添加:

CarrierWave.configure do |config|
  config.root = Rails.root.join('tmp')
  config.cache_dir = 'carrierwave'
end

配置 rack 以提供 tmp/carrierwave 文件夹中的静态文件

在您的 config.ru 文件中,添加:

use Rack::Static, :urls => ['/carrierwave'], :root => 'tmp'

有关功能齐全的准系统 rails/carrierwave/s3/heroku 应用的示例,请查看:

https://github.com/trevorturk/carrierwave-heroku(没有隶属关系,只是有用)。

希望这会有所帮助!

【讨论】:

    【解决方案3】:

    先保存您的照片,然后再尝试其他操作

    假设您有一个具有回形针头像的用户:

    def update
      @user = current_user
      unless params[:user][:avatar].nil?
        @user.update_attributes(avatar: params[:user][:avatar])
        params[:user].delete :avatar
      end
      if @user.update_attributes(params[:user])
        redirect_to edit_profile_path, notice: 'User was successfully updated.' 
      else
        render action: "edit" 
      end
    end
    

    【讨论】:

    • 这不仅对 create 方法不起作用,而且还会使模型处于不一致的状态。我们的想法是不丢失附件,而不是通过部分修改模型。
    【解决方案4】:

    截至 2013 年 9 月,回形针无意“修复”验证后附件丢失的问题。 “这个问题(恕我直言)比解决问题更容易、更正确地避免”

    https://github.com/thoughtbot/paperclip/issues/72#issuecomment-24072728

    我正在考虑 John Gibb 早期解决方案中提出的 CarrierWave 解决方案

    【讨论】:

      【解决方案5】:

      我不得不在最近使用 PaperClip 的项目中解决这个问题。我尝试在模型中使用 after_validation 和 before_save 调用 cache_images() 但由于某种我无法确定的原因创建失败,所以我只是从控制器调用它。

      型号:

      class Shop < ActiveRecord::Base    
        attr_accessor :logo_cache
      
        has_attached_file :logo
      
        def cache_images
          if logo.staged?
            if invalid?
              FileUtils.cp(logo.queued_for_write[:original].path, logo.path(:original))
              @logo_cache = encrypt(logo.path(:original))
            end
          else
            if @logo_cache.present?
              File.open(decrypt(@logo_cache)) {|f| assign_attributes(logo: f)}
            end
          end
        end
      
        private
      
        def decrypt(data)
          return '' unless data.present?
          cipher = build_cipher(:decrypt, 'mypassword')
          cipher.update(Base64.urlsafe_decode64(data).unpack('m')[0]) + cipher.final
        end
      
        def encrypt(data)
          return '' unless data.present?
          cipher = build_cipher(:encrypt, 'mypassword')
          Base64.urlsafe_encode64([cipher.update(data) + cipher.final].pack('m'))
        end
      
        def build_cipher(type, password)
          cipher = OpenSSL::Cipher::Cipher.new('DES-EDE3-CBC').send(type)
          cipher.pkcs5_keyivgen(password)
          cipher
        end
      
      end
      

      控制器:

      def create
        @shop = Shop.new(shop_params)
        @shop.user = current_user
        @shop.cache_images
      
        if @shop.save
          redirect_to account_path, notice: 'Shop created!'
        else
          render :new
        end
      end
      
      def update
        @shop = current_user.shop
        @shop.assign_attributes(shop_params)
        @shop.cache_images
      
        if @shop.save
          redirect_to account_path, notice: 'Shop updated.'
        else
          render :edit
        end
      end
      

      查看:

      = f.file_field :logo
      = f.hidden_field :logo_cache
      
      - if @shop.logo.file?
        %img{src: @shop.logo.url, alt: ''}
      

      【讨论】:

      • 在前端缓存文件只适用于小文件/少数文件,因为对于较大的文件,HTML 会变得臃肿,因此在读取这些数据时会产生巨大的瓶颈。
      【解决方案6】:

      按照@galatians 的想法,我得到了这个解决方案(并且工作得很好)

      为该示例创建了一个 repo: * https://github.com/mariohmol/paperclip-keeponvalidation

      1. 首先要做的就是将一些方法放入您的基本活动记录中,这样每个使用附加的模型都可以使其工作

      在 config/initializers/active_record.rb 中

      module ActiveRecord
          class Base
      
          def decrypt(data)
            return '' unless data.present?
            cipher = build_cipher(:decrypt, 'mypassword')
            cipher.update(Base64.urlsafe_decode64(data).unpack('m')[0]) + cipher.final
          end
      
          def encrypt(data)
            return '' unless data.present?
            cipher = build_cipher(:encrypt, 'mypassword')
            Base64.urlsafe_encode64([cipher.update(data) + cipher.final].pack('m'))
          end
      
          def build_cipher(type, password)
            cipher = OpenSSL::Cipher::Cipher.new('DES-EDE3-CBC').send(type)
            cipher.pkcs5_keyivgen(password)
            cipher
          end
      
          #ex: @avatar_cache = cache_files(avatar,@avatar_cache)
          def cache_files(avatar,avatar_cache)
            if avatar.queued_for_write[:original]
              FileUtils.cp(avatar.queued_for_write[:original].path, avatar.path(:original))
              avatar_cache = encrypt(avatar.path(:original))
            elsif avatar_cache.present?
              File.open(decrypt(avatar_cache)) {|f| assign_attributes(avatar: f)}
            end
            return avatar_cache
          end
      
          end
      end
      
      1. 之后,在您的模型和附加字段中包含上面的代码

      例如,我将其包含在 /models/users.rb 中

        has_attached_file :avatar, PaperclipUtils.config
        attr_accessor :avatar_cache
        def cache_images
          @avatar_cache=cache_files(avatar,@avatar_cache)
        end
      
      1. 在您的控制器中,添加它以从缓存中获取图像(就在您保存模型的位置之前)

        @user.avatar_cache = 参数[:user][:avatar_cache]

        @user.cache_images

        @user.save

      2. 最后将其包含在您的视图中,以记录当前临时图像的位置

      f.hidden_​​field :avatar_cache

      1. 如果您想在视图中显示实际文件,请包含它:
      <% if @user.avatar.exists?  %>
      <label class="field">Actual Image </label>
        <div class="field file-field">  
            <%= image_tag @user.avatar.url %>
          </div>
      <% end %>
      

      【讨论】:

      • “f.hidden_​​field :avatar_cache”有文件内容。
      【解决方案7】:

      还可以查看refile(较新选项)

      特点

      • 可配置的后端、文件系统、S3 等...
      • 与 ORM 的便捷集成
      • 动态处理图像和其他文件
      • 流式 IO 以实现快速且内存友好的上传
      • 跨表单重新显示工作,即当验证失败时,即使在 S3 上也是如此
      • 轻松直接上传,甚至上传到 S3
      • 支持多个文件上传

      https://gorails.com/episodes/file-uploads-with-refile

      【讨论】:

        【解决方案8】:

        在视图文件中只放置 if 条件应该只接受具有有效 id 的记录。 在我的场景中,这是代码 sn-p

                    <p>Uploaded files:</p>
                    <ul>
                        <% @user.org.crew.w9_files.each do |file| %>
                          <% if file.id.present? %>
                            <li> <%= rails code to display value %> </li>
                          <% end %>
                        <% end %>
                    </ul>
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-04-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多