Ruby-on-rails 将文件发送到 Rails JSON API

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9758879/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 03:04:21  来源:igfitidea点击:

Sending files to a Rails JSON API

ruby-on-railsjsonruby-on-rails-3.2

提问by Emil Ahlb?ck

I know there are questions similar to this one, but I've not found a good answer yet. What I need to do is send a description of an object to one of my create methods, which includes some different attributes including one called :image, a paperclip attachment:

我知道有与此类似的问题,但我还没有找到好的答案。我需要做的是将对象的描述发送到我的创建方法之一,其中包括一些不同的属性,包括一个名为 :image 的属性,一个回形针附件:

has_attached_file :image

Now I've read that sending the image could be done straight in JSON by encoding and decoding the image as base64, but that feels like a dirty solution to me. There must be better ways.

现在我读到可以通过将图像编码和解码为 base64 直接在 JSON 中发送图像,但这对我来说感觉像是一个肮脏的解决方案。必须有更好的方法。

Another solution is sending a multipart/form-data request, much like the one LEEjava describes here.The problem with that one is that the request params are not interpreted correctly in Rails 3.2.2, and JSON.parse spits out an error when it tries to parse the params, or perhaps it is Rails that is misinterpreting something.

另一种解决方案是发送一个 multipart/form-data 请求,就像LEEjava 在这里描述的那样。问题在于 Rails 3.2.2 中的请求参数没有正确解释,并且 JSON.parse 在尝试解析参数时吐出错误,或者可能是 Rails 误解了某些内容。

Started POST "/api/v1/somemodel.json?token=ZoipX7yhcGfrWauoGyog" for 127.0.0.1 at 2012-03-18 15:53:30 +0200 Processing by Api::V1::SomeController#create as JSON Parameters: {"{\n
\"parentmodel\": {\n \"superparent_id\": 1,\n
\"description\": \"Enjoy the flower\",\n \"\": "=>{"\n
{\n \"someattribute\": 1,\n
\"someotherattribute\": 2,\n \"image\": \"image1\"\n
}\n "=>{"\n }\n}"=>nil}}, "token"=>"ZoipX7yhcGfrWauoGyog"}

在 2012-03-18 15:53:30 +0200 开始 POST "/api/v1/somemodel.json?token=ZoipX7yhcGfrWauoGyog" for 127.0.0.1 由 Api::V1::SomeController#create 作为 JSON 参数处理:{" {\n
\"parentmodel\": {\n \"superparent_id\": 1,\n
\"description\": \"enjoy the flower\",\n \"\": "=>{"\n
{\n \"someattribute\": 1,\n
\"someotherattribute\": 2,\n \"image\": \"image1\"\n
}\n "=>{"\n }\n} "=>nil}}, "token"=>"ZoipX7yhcGfrWauoGyog"}

It is quite hard to read that, sorry. JSON.parse(params[:parentmodel]) is not possible here, and I can't JSON.parse(params) either because of the token attribute, JSON.parse(params) throws this error:

读起来很困难,抱歉。JSON.parse(params[:parentmodel]) 在这里是不可能的,我不能 JSON.parse(params) 要么因为 token 属性,JSON.parse(params) 抛出这个错误:

TypeError (can't convert ActiveSupport::HashWithIndifferentAccess into String)

TypeError(无法将 ActiveSupport::HashWithIndifferentAccess 转换为 String)

Which leads me to believe I'm either approaching this problem totally wrong, or I'm just doing something. Either way, we can be sure that I'm wrong about something. :)

这让我相信我要么完全错误地处理这个问题,要么我只是在做某事。无论哪种方式,我们都可以确定我在某些事情上是错的。:)

Is there a better way to do this? Can someone point me to any guide/tutorial, or write an answer describing how I should approach this?

有一个更好的方法吗?有人可以向我指出任何指南/教程,或者写一个描述我应该如何处理这个问题的答案吗?

Thank you in advance

先感谢您

UPDATE:So I've actually got it working now, but only in tests. I'm not totally sure how this works, but perhaps someone can fill in the gaps for me? This is part of the test code (the image: fixture_file_upload(...) is the important part).

更新:所以我现在实际上已经开始工作了,但仅限于测试。我不完全确定这是如何工作的,但也许有人可以为我填补空白?这是测试代码的一部分(图片:fixture_file_upload(...) 是重要的部分)。

parts_of_the_object = { someattribute: 0, someotherattribute: 0, image: fixture_file_upload('/images/plot.jpg', 'image/jpg') }

My params[] looks like a normal HTML form was submitted, which is strange (and awesome):

我的 params[] 看起来像提交了一个普通的 HTML 表单,这很奇怪(而且很棒):

Parameters: {"superparentid"=>"1", "plots"=>[{"someattribute"=>"0", "someotherattribute"=>"0", "image"=>#<ActionDispatch::Http::UploadedFile:0x007f812eab00e8 @original_filename="plot.jpg", @content_type="image/jpg", @headers="Content-Disposition: form-data; name=\"plots[][image]\"; filename=\"plot.jpg\"\r\nContent-Type: image/jpg\r\nContent-Length: 51818\r\n", @tempfile=#<File:/var/folders/45/rcdbb3p50bl2rgjzqp3f0grw0000gn/T/RackMultipart20120318-1242-1cn036o>>}], "token"=>"4L5LszuXQMY6rExfifio"}

The request is made just like and post request is made with rspec:

发出请求就像使用 rspec 发出请求一样:

post "/api/v1/mycontroller.json?token=#{@token}", thefull_object

So I've got it all working. I just don't know how exactly it works! I want to be able to create a response like this by myself too, not only from RSpec. :-)

所以我已经完成了所有工作。我只是不知道它究竟是如何工作的!我也希望能够自己创建这样的响应,而不仅仅是来自 RSpec。:-)

回答by TomJ

I was actually having a terrible time with this question yesterday to do something very similar. In fact, I wrote the question: Base64 upload from Android/Java to RoR Carrierwave

实际上,昨天我在这个问题上度过了一段非常糟糕的时光,因为我做了一些非常相似的事情。其实我写的问题是:Base64 upload from Android/Java to RoR Carrierwave

What it came down to was creating that uploaded image object in the controller and then injecting it back into the params.

它归结为在控制器中创建上传的图像对象,然后将其注入到参数中。

For this specific example, we are taking a base64 file (which I assume you have, as JSON doesn't support embeded files) and saving it as a temp file in the system then we are creating that UploadedFile object and finally reinjecting it into the params.

对于这个特定的例子,我们正在获取一个 base64 文件(我假设你有,因为 JSON 不支持嵌入文件)并将其保存为系统中的临时文件,然后我们创建该 UploadedFile 对象,最后将它重新注入到参数。

What my json/params looks like:

我的 json/params 是什么样的:

picture {:user_id => "1", :folder_id => 1, etc., :picture_path {:file => "base64 awesomeness", :original_filename => "my file name", :filename => "my file name"}}

Here is what my controller looks like now:

这是我的控制器现在的样子:

  # POST /pictures
  # POST /pictures.json
  def create

    #check if file is within picture_path
    if params[:picture][:picture_path]["file"]
         picture_path_params = params[:picture][:picture_path]
         #create a new tempfile named fileupload
         tempfile = Tempfile.new("fileupload")
         tempfile.binmode
         #get the file and decode it with base64 then write it to the tempfile
         tempfile.write(Base64.decode64(picture_path_params["file"]))

         #create a new uploaded file
         uploaded_file = ActionDispatch::Http::UploadedFile.new(:tempfile => tempfile, :filename => picture_path_params["filename"], :original_filename => picture_path_params["original_filename"]) 

         #replace picture_path with the new uploaded file
         params[:picture][:picture_path] =  uploaded_file

    end

    @picture = Picture.new(params[:picture])

    respond_to do |format|
      if @picture.save
        format.html { redirect_to @picture, notice: 'Picture was successfully created.' }
        format.json { render json: @picture, status: :created, location: @picture }
      else
        format.html { render action: "new" }
        format.json { render json: @picture.errors, status: :unprocessable_entity }
      end
    end
  end

The only thing left to do at this point is to delete the tempfile, which I believe can be done with tempfile.delete

此时唯一要做的就是删除临时文件,我相信这可以完成 tempfile.delete

I hope this helps with your question! I spent all day looking for a solution yesterday, and everything I have seen is a dead end. This, however, works on my test cases.

我希望这对您的问题有所帮助!昨天一整天都在寻找解决方案,我看到的一切都是死胡同。但是,这适用于我的测试用例。

回答by Shannon

TomJ gave a good answer, but at least in Rails 3/Ruby 1.9 there are some minor holes.

TomJ 给出了一个很好的答案,但至少在 Rails 3/Ruby 1.9 中存在一些小漏洞。

First, don't attempt to call [] on what might be an UploadedFile object in your params object. Make sure you check that it .is_a?(Hash)first, for example.

首先,不要尝试对 params 对象中的 UploadedFile 对象调用 [] 。例如,请确保先检查它.is_a?(Hash)

Also, make sure you tempfile.rewind()after you write, otherwise you'll get files with 0 length.

另外,确保你tempfile.rewind()写完之后,否则你会得到长度为 0 的文件。

The :original_filenamekey in the parameters to the constructor of UploadedFile is unnecessary/unused. On the other hand, you may want to provide a :typekey. An easy way to find the value for type is mime_type = Mime::Type.lookup_by_extension(File.extname(original_filename)[1..-1]).to_s

:original_filenameUploadedFile 构造函数的参数中的键是不必要的/未使用的。另一方面,您可能需要提供:type密钥。找到类型值的一种简单方法是mime_type = Mime::Type.lookup_by_extension(File.extname(original_filename)[1..-1]).to_s

Here is a version with the changes applied:

这是应用了更改的版本:

# POST /pictures
# POST /pictures.json
def create

  #check if file is within picture_path
  if params[:picture][:picture_path].is_a?(Hash)
    picture_path_params = params[:picture][:picture_path]
    #create a new tempfile named fileupload
    tempfile = Tempfile.new("fileupload")
    tempfile.binmode
    #get the file and decode it with base64 then write it to the tempfile
    tempfile.write(Base64.decode64(picture_path_params["file"]))
    tempfile.rewind()

    mime_type = Mime::Type.lookup_by_extension(File.extname(original_filename)[1..-1]).to_s
    #create a new uploaded file
    uploaded_file = ActionDispatch::Http::UploadedFile.new(
      :tempfile => tempfile,
      :filename => picture_path_params["filename"],
      :type => mime_type) 

    #replace picture_path with the new uploaded file
    params[:picture][:picture_path] =  uploaded_file
  end

  @picture = Picture.new(params[:picture])
  respond_to do |format|
    if @picture.save
      format.html { redirect_to @picture, notice: 'Picture was successfully created.' }
      format.json { render json: @picture, status: :created, location: @picture }
    else
     format.html { render action: "new" }
     format.json { render json: @picture.errors, status: :unprocessable_entity }
   end
 end

end

结尾

回答by peeyush singla

There is an awesome gem for this purpose if you are using carrierwave

如果您使用的是carrierwave,为此目的有一个很棒的宝石

https://github.com/lebedev-yury/carrierwave-base64

https://github.com/lebedev-yury/carrierwave-base64