由于验证错误而无法保存模型时,不会丢失回形针附件

时间:2011-03-04 19:58:33

标签: ruby-on-rails validation paperclip attachment

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

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

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

8 个答案:

答案 0 :(得分:12)

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

首先,您可以按照关于设置载波的精彩轨道广播: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中的readonly,你需要让它使用tmp文件夹,并让机架从那里提供静态文件。

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

在你的config / initializers / carrierwave.rb中(如果不存在则随意创建),添加:

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

配置机架以从tmp / carrierwave文件夹

提供静态文件

在config.ru文件中,添加:

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

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

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

希望这有帮助!

答案 1 :(得分:3)

我必须在最近使用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: ''}

答案 2 :(得分:3)

遵循@galatians的想法,我得到了这个解决方案(并且工作得非常漂亮)

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

  1. 要做的第一件事是在您的基本活动记录中放置一些方法,因此每个使用附加的模型都可以使其工作
  2. 在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. 之后,在您的模型和附加字段中包含上面的代码
    2. 例如,我将其包含在/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 = params [:user] [:avatar_cache]

        @ user.cache_images

        @ user.save

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

      3.   

        f.hidden_​​field:avatar_cache

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

答案 3 :(得分:1)

截至2013年9月,paperclip无意在验证后“修复”附加文件的丢失。 “问题是(恕我直言)比解决问题更容易和更正确地避免”

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

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

答案 4 :(得分:1)

同时查看refile(更新选项)

功能

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

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

答案 5 :(得分:0)

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

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

答案 6 :(得分:0)

首先保存你的照片而不是尝试其余的

假设您的用户使用了回形针头像:

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

答案 7 :(得分:0)

在视图文件中只放入if条件,只接受有效id的记录。 在我的场景中,这是代码片段

            <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>