Не теряйте прикрепление скрепки, когда модель не может быть сохранена из-за ошибки проверки
Сценарий - это нормальная модель, которая содержит вложение скрепки вместе с некоторыми другими столбцами, которые имеют различные проверки. Когда форма для создания объекта не может быть сохранена из-за ошибки проверки, не связанной с вложением, столбцы, такие как строки, сохраняются и остаются предварительно заполненными для пользователя, но файл, выбранный для загрузки, полностью потерян и должен быть повторно выбран пользователем.
Существует ли стандартный подход к сохранению вложения в случае ошибки проверки модели? Это кажется очень распространенным вариантом использования.
Кажется нецелесообразным взломать решение, в котором файл сохраняется без владельца, а затем снова подключается к объекту после его успешного сохранения, поэтому я надеюсь избежать этого.
Ответы
Ответ 1
Переключитесь на использование 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, вам нужно, чтобы она использовала папку tmp вместо этого, и теперь она имеет статические файлы.
Сообщите операторской волне использовать папку 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'
Для примера полнофункционального barebones rails/carrierwave/s3/heroku app, проверьте:
https://github.com/trevorturk/carrierwave-heroku (никакой аффилиации, просто было полезно).
Надеюсь, это поможет!
Ответ 2
Мне пришлось исправить это в недавнем проекте, используя PaperClip. Я пробовал вызывать cache_images(), используя after_validation и before_save в модели, но не смог создать по какой-то причине, что я не могу определить, поэтому я просто вызываю его из контроллера.
Модель:
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: ''}
Ответ 3
Следуя идее @galatians, я получил это решение (и работал красиво)
Создал репо к этому примеру:
* https://github.com/mariohmol/paperclip-keeponvalidation
- Первое, что нужно сделать, это поместить некоторые методы в активную запись базы, поэтому каждая модель, использующая приложение, может заставить ее работать.
В 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
- После этого включите в свою модель и прикрепленное поле код выше
В качестве примера я включил это в /models/users.rb
has_attached_file :avatar, PaperclipUtils.config
attr_accessor :avatar_cache
def cache_images
@avatar_cache=cache_files(avatar,@avatar_cache)
end
-
В вашем контроллере добавьте это, чтобы получить из кеша изображение (непосредственно перед тем, где вы сохранили модель)
@user.avatar_cache = params [: user] [: avatar_cache]
@user.cache_images
@user.save
-
И, наконец, включите это в ваше мнение, чтобы записать местоположение текущего временного изображения
f.hidden_field: avatar_cache
- Если вы хотите показать в поле зрения фактический файл, включите его:
<% if @user.avatar.exists? %>
<label class="field">Actual Image </label>
<div class="field file-field">
<%= image_tag @user.avatar.url %>
</div>
<% end %>
Ответ 4
сначала сохраните изображение, чем попробуйте остальные
скажет, что у вас есть пользователь с архивом paperclip:
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
Ответ 5
По состоянию на сентябрь 2013 г. paperclip не намерен "исправлять" потерю прикрепленных файлов после проверки. "Проблема (IMHO) проще и правильнее избежать, чем решить"
https://github.com/thoughtbot/paperclip/issues/72#issuecomment-24072728
Я рассматриваю решение CarrierWave, предложенное ранее в решении Джона Гибба
Ответ 6
Также проверьте refile (более новая опция)
Функции
- Конфигурируемые бэкенды, файловая система, S3 и т.д.
- Удобная интеграция с ORM
- На лету манипулирование изображениями и другими файлами
- Streaming IO для быстрой и удобной загрузки.
- Работает через форму redisplays, то есть когда проверки не выполняются, даже на S3
- Удобные прямые загрузки, даже до S3
- Поддержка нескольких загрузок файлов
https://gorails.com/episodes/file-uploads-with-refile
Ответ 7
Если изображение не требуется, почему бы не разбить форму на две ступени, первая создаст объект, вторая страница позволит вам добавить дополнительную информацию (например, фотографию).
В качестве альтернативы вы можете подтвердить форму, поскольку пользователь вводит эту информацию, чтобы вам не нужно было отправлять форму, чтобы узнать ваши данные.
Ответ 8
В файле просмотра просто поставьте if условие, которое должно принять только запись, имеющую действительный идентификатор.
В моем сценарии это фрагмент кода
<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>