Rails 与蜻蜓的交易

Rails transaction with dragonfly

我目前正在做一个 Rails 项目。我有一个嵌套表单,允许用户上传多张图片。我正在使用蜻蜓进行上传 我还对用户可以上传的图像的大小和格式设置了一些限制,并且它们工作正常。我的问题是,当我尝试上传无效图像时,即使它们不会被上传,表单的其余部分仍会被保存。为了缓解这个问题,我最初编写了如下创建方法:

def create
  @announcements = Announcement.new(announcement_params)
  images = []

  respond_to do |format|
    params[:photos]['image'].each do |pic|
    img = Photo.new(image: pic)
    if img.valid?
      images << img
    else
      @photos = @announcement.photos.build
      format.html { render: 'new' }
    end

    if @announcement.save
      params[:photos]['image'].each do |pic|
        @photos = @announcement.photos.create(image: pic)
      end
      format.html { redirect_to @announcement }
    else
      format.html { render: 'new' }
    end
  end

end

这很好用,但我相信你们中的很多人都会同意其中有很多重复,而且代码很丑陋。在找到更好的解决方案之前,我会写一个代码作为快速解决方法。我尝试使用如下交易:

def create
  @announcement = Announcement.new(announcement_params)

  respond_to do |format|
    ActiveRecord::Base.transaction do
      begin
        @announcement.save
        params[:photos]["image"].each do |pic|
          @photos = @announcement.photos.create!(image: pic)
        end
        format.html { redirect_to @announcement }
      rescue
        format.html { render action: 'new' }
      end
    end
end

结束

但它不起作用任何人都可以告诉我我做错了什么,或者建议更好的方法来做到这一点。谢谢

编辑: 这是我的日志的摘录:

Started POST "/announcements" for 127.0.0.1 at 2015-09-20 15:07:31 +0100

AnnouncementsController 处理#create as HTML 参数:{"utf8"=>"✓",

"authenticity_token"=>"fjU1kjnxqSdDwTqieOpTTCH56//p65AynqNyQQX6yiu84zwO0bJeQ3ZKr8tEBvGSZJphclxKkoys2bFp771hWg==", "announcement"=>{"title"=>"Testing wrong image size", "content"=>"Testing wrong image format with transaction code "}, "photos"=>{"image"=>[#<ActionDispatch::Http::UploadedFile:0x007fe9a83f9b18 @tempfile=#<Tempfile:/tmp/RackMultipart20150920-10097-muom0i.jpg>, @original_filename="AhiiZFK2.jpg", @content_type="image/jpeg", @headers="Content-Disposition: form-data; name=\"photos[image][]\"; filename=\"AhiiZFK2.jpg\"\r\nContent-Type: image/jpeg\r\n">]}, "commit"=>"Créer l'announce"}
  User Load (0.6ms)  SELECT  "users".* FROM "users" WHERE "users"."id" =   ORDER BY "users"."id" ASC LIMIT 1  [["id", 1]]
   (0.3ms)  BEGIN
  SQL (0.9ms)  INSERT INTO "announcements" ("title", "content", "created_at", "updated_at") VALUES (, , , ) RETURNING "id"  [["title", "Testing wrong image size"], ["content", "Testing wrong image format with transaction code "], ["created_at", "2015-09-20 14:07:31.247186"], ["updated_at", "2015-09-20 14:07:31.247186"]]
DRAGONFLY: shell command: 'identify' '-ping' '-format' '%m %w %h' '/tmp/RackMultipart20150920-10097-muom0i.jpg'
   (41.4ms)  COMMIT
  Photo Load (0.8ms)  SELECT "photos".* FROM "photos" WHERE "photos"."announcement_id" =   [["announcement_id", 1]]
  Rendered announcements/new.html.erb within layouts/application (24.1ms)
  Rendered layouts/_header.html.erb (1.9ms)
  Rendered layouts/_sidebar.html.erb (0.5ms)
Completed 200 OK in 3287ms (Views: 3154.3ms | ActiveRecord: 44.1ms)

如果您不希望数据库中的数据在记录无效时出现,您需要中止交易。这是在引发异常时完成的。 但是你嵌套你的代码的方式,一个来自 create! 的异常被拯救了,事务块可以正常完成。

你需要这样嵌套:

begin
  ActiveRecord::Base.transaction do
    @announcement.save!
    params[:photos]["image"].each do |pic|
      @announcement.photos.create!(image: pic)
    end
  end # transaction
  format.html { redirect_to @announcement }
rescue
  logger.warn "transaction aborted"
  format.html { render action: 'new' }
end

为了进一步改进,您将数据库的东西放在一个自己的方法中,这样数据库调用和渲染调用就不会混在一起。

要显式中止(或用数据库术语 "rollback")事务,您可以引发 ActiveRecord::Rollback 异常。这会中止交易并且不会重新抛出交易块之外。 http://api.rubyonrails.org/classes/ActiveRecord/Rollback.html