web-dev-qa-db-fra.com

Envoi de fichiers à un Rails API JSON

Je sais qu'il y a des questions similaires à celle-ci, mais je n'ai pas encore trouvé de bonne réponse. Ce que je dois faire, c'est envoyer une description d'un objet à l'une de mes méthodes de création, qui comprend différents attributs, dont un appelé: image, un attachement Paperclip:

has_attached_file :image

Maintenant, j'ai lu que l'envoi de l'image pouvait se faire directement en JSON en encodant et décodant l'image en base64, mais cela me semble une sale solution. Il doit y avoir de meilleures façons.

Une autre solution consiste à envoyer une requête multipart/form-data, un peu comme celle LEEjava décrit ici. Le problème avec celle-ci est que les paramètres de requête ne sont pas interprétés correctement dans Rails 3.2.2 et JSON.parse crache une erreur lorsqu'il essaie d'analyser les paramètres, ou c'est peut-être Rails qui interprète mal quelque chose.

Démarré POST "/api/v1/somemodel.json?token=ZoipX7yhcGfrWauoGyog" pour 127.0.0.1 à 2012-03-18 15:53:30 +0200 Traitement par Api :: V1 :: SomeController # créer en JSON Paramètres: {"{\ n
\"parentmodel \": {\ n\"superparent_id \": 1,\n
\"description \":\"Profitez de la fleur \",\n\"\": "=> {"\n
{\ n\"someattribute \": 1,\n
\"someotherattribute \": 2,\n\"image \":\"image1 \"\n
}\n "=> {"\n}\n} "=> nil}}," token "=>" ZoipX7yhcGfrWauoGyog "}

C'est assez difficile à lire, désolé. JSON.parse (params [: parentmodel]) n'est pas possible ici, et je ne peux pas non plus JSON.parse (params) en raison de l'attribut token, JSON.parse (params) renvoie cette erreur:

TypeError (impossible de convertir ActiveSupport :: HashWithIndifferentAccess en chaîne)

Ce qui m'amène à croire que j'aborde ce problème totalement mal ou que je fais juste quelque chose. Quoi qu'il en soit, nous pouvons être sûrs que je me trompe sur quelque chose. :)

Y a-t-il une meilleure manière de faire cela? Quelqu'un peut-il me diriger vers un guide/tutoriel ou écrire une réponse décrivant comment je dois aborder cela?

Merci d'avance

MISE À JOUR: Donc, je l'ai fait fonctionner maintenant, mais seulement dans les tests. Je ne sais pas trop comment cela fonctionne, mais peut-être que quelqu'un peut combler les lacunes pour moi? Cela fait partie du code de test (l'image: fixture_file_upload (...) est la partie importante).

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

Mes paramètres [] ressemblent à un formulaire HTML normal qui a été soumis, ce qui est étrange (et génial):

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

La demande est faite comme et la demande de post est faite avec rspec:

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

Je l'ai donc tout fonctionne. Je ne sais tout simplement pas comment cela fonctionne exactement! Je veux également pouvoir créer une réponse comme celle-ci, pas seulement à partir de RSpec. :-)

30
Emil Ahlbäck

Hier, je me suis vraiment amusé avec cette question pour faire quelque chose de très similaire. En fait, j'ai écrit la question: téléchargement Base64 depuis Android/Java vers RoR Carrierwave

Il en est résulté la création de cet objet image téléchargé dans le contrôleur, puis sa réinjection dans les paramètres.

Pour cet exemple spécifique, nous prenons un fichier base64 (que je suppose que vous avez, car JSON ne prend pas en charge les fichiers incorporés) et l'enregistrons en tant que fichier temporaire dans le système, puis nous créons cet objet UploadedFile et enfin le réinjectons dans le params.

À quoi ressemble mon json/params:

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

Voici à quoi ressemble mon contrôleur maintenant:

  # 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

La seule chose qui reste à faire à ce stade est de supprimer le fichier temporaire, ce qui, je pense, peut être fait avec tempfile.delete

J'espère que cela répond à votre question! J'ai passé toute la journée à chercher une solution hier, et tout ce que j'ai vu est une impasse. Cela fonctionne cependant sur mes cas de test.

40
TomJ

TomJ a donné une bonne réponse, mais au moins dans Rails 3/Ruby 1.9 il y a quelques trous mineurs.

Tout d'abord, n'essayez pas d'appeler [] sur ce qui pourrait être un objet UploadedFile dans votre objet params. Assurez-vous de vérifier d'abord .is_a?(Hash), par exemple.

Assurez-vous également que vous tempfile.rewind() après avoir écrit, sinon vous obtiendrez des fichiers de longueur 0.

La clé :original_filename Dans les paramètres du constructeur de UploadedFile est inutile/inutilisée. D'un autre côté, vous souhaiterez peut-être fournir une clé :type. mime_type = Mime::Type.lookup_by_extension(File.extname(original_filename)[1..-1]).to_s pour trouver facilement la valeur du type

Voici une version avec les changements appliqués:

# 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

fin

9
Shannon

Il y a un bijou génial à cet effet si vous utilisez carrierwave

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

4
peeyush singla