2016-04-15 8 views
8

私のエリクシール/フェニックスのバックエンドには2つのproductコントローラがあります。まず - APIエンドポイント(pipe_through :api)と第二のコントローラpiping through :browserbackx APIとしてのredux-formとelixir/phoenixの添付ファイル(シリアル化の問題)

# router.ex 
scope "/api", SecretApp.Api, as: :api do 
    pipe_through :api 

    resources "products", ProductController, only: [:create, :index] 
end 

scope "/", SecretApp do 
    pipe_through :browser # Use the default browser stack 

    resources "products", ProductController, only: [:new, :create, :index] 
end 

ProductControllerがエリキシルフォームヘルパーによって生成されたフォームからの要求を処理し、いくつかの添付ファイルを受け付けます。すべてはそれで大丈夫です。ここでは、アクションと、このアクションによって処理のparamsを作成することです:ログから

def create(conn, %{"product" => product_params}) do 
    changeset = Product.changeset(%Product{}, product_params) 

    case Repo.insert(changeset) do 
    {:ok, _product} -> 
     conn 
     |> put_flash(:info, "Product created successfully.") 
     |> redirect(to: product_path(conn, :index)) 
    {:error, changeset} -> 
     render(conn, "new.html", changeset: changeset) 
    end 
end 

のparams(私はエリキシルコードで画像のアップロードを処理するためのarcを使用しています)

[debug] Processing by SecretApp.ProductController.create/2 
    Parameters: %{"_csrf_token" => "Zl81JgdhIQ8GG2c+ei0WCQ9hTjI+AAAA0fwto+HMdQ7S7OCsLQ9Trg==", "_utf8" => "✓", 
       "product" => %{"description" => "description_name", 
       "image" => %Plug.Upload{content_type: "image/png", 
        filename: "wallpaper-466648.png", 
        path: "/tmp/plug-1460/multipart-754282-298907-1"}, 
       "name" => "product_name", "price" => "100"}} 
    Pipelines: [:browser] 

Api.ProductControllerredux-fromからの要求を処理します。ここでは、このアクションによって処理されているアクション、ビューとのparamsは、次のとおりです。

# action in controller 
def create(conn, %{"product" => product_params}) do 
    changeset = Product.changeset(%Product{}, product_params) 

    case Repo.insert(changeset) do 
    {:ok, _product} -> 
     conn 
     |> render("index.json", status: :ok) 
    {:error, changeset} -> 
     conn 
     |> put_status(:unprocessable_entity) 
     |> render("error.json", changeset: changeset) 
    end 
end 

# product_view.ex 
def render("index.json", resp=%{status: status}) do 
    %{status: status} 
end 

def render("error.json", %{changeset: changeset}) do 
    errors = Enum.into(changeset.errors, %{}) 

    %{ 
    errors: errors 
    } 
end 

[info] POST /api/products/ 
[debug] Processing by SecretApp.Api.ProductController.create/2 
    Parameters: %{"product" => %{"description" => "product_description", "image" => "wallpaper-466648.png", "name" => "product_name", "price" => "100"}} 
    Pipelines: [:api] 
[info] Sent 422 in 167ms 

は、画像がこれらのparamsと一緒に保存することができないため、アクションは、422個の状態で失敗し作成します。バックエンドコードからイメージにアクセスできないという私の問題は、私はFileListオブジェクトとして自分のJSコード内に持っています。私はバックエンドコードに画像を渡す方法を理解していません。ここでは、この添付ファイルがJSコード(FileList、アップロードされた画像に関する情報を含む)でどのように表現されているかを示します。

value:FileList 
    0: File 
    lastModified: 1381593256801 
    lastModifiedDate: Sat Oct 12 2013 18:54:16 GMT+0300 
    name: "wallpaper-466648.png" 
    size: 1787293 
    type: "image/png" 
    webkitRelativePath: "" 

IのみWebkitRelativePathいる(私は画像へのパスを有する第1のコントローラの場合には、 "/ TMP /プラグ-1460 /マルチ-754282-298907-1")と私は何ができるかわかりませんこのJSオブジェクトと、このJSオブジェクトが表す実際のイメージにアクセスする方法(ここではファイルのアップロードについてはredux-form referenceです)。

お手伝いできますか?エリクシルに画像を見つける方法を説明するには?私はバックエンドにJSコードを使用して添付ファイルを提出したいと思います(なぜなら非同期検証のための興味深い機能がたくさんあるからです)。それはあなたのログから

+0

写真のような静的資産は、 'priv/static /'の下にあるべきです。あなたは 'priv/static/tmp/plug-1460/multipart-754282-298907-1'の下にファイルを見つけることができますか?そうでない場合は、そこに画像を保存してみてください。 – tkowal

+0

バックエンドコードからイメージにアクセスできないという私の問題は、私はFileListオブジェクトとして自分のJSコード内に持っています。私はバックエンドコードに画像を渡す方法を理解していません。しかし、priv/static/tmpには何もありません –

答えて

3

最後に私はこの問題を解決することができました。解決策は、提出されたパラメータredux-formの正しいシリアル化です。ここで

はリクエストの出発点に、私のReduxの形式である:

// product_form.js 

import React, { PropTypes } from 'react'; 
import {reduxForm} from 'redux-form'; 

class ProductForm extends React.Component { 
    static propTypes = { 
    fields: PropTypes.object.isRequired, 
    handleSubmit: PropTypes.func.isRequired, 
    error: PropTypes.string, 
    resetForm: PropTypes.func.isRequired, 
    submitting: PropTypes.bool.isRequired 
    }; 

    render() { 
    const {fields: {name, description, price, image}, handleSubmit, resetForm, submitting, error} = this.props; 

    return (
     <div className="product_form"> 
     <div className="inner"> 
      <form onSubmit={handleSubmit} encType="multipart/form-data"> 
      <div className="form-group"> 
       <label className="control-label"> Name </label> 
       <input type="text" className="form-control" {...name} /> 
       {name.touched && name.error && <div className="col-xs-3 help-block">{name.error}</div>} 
      </div> 

      <div className="form-group"> 
       <label className="control-label"> Description </label> 
       <input type="textarea" className="form-control" {...description} /> 
       {description.touched && description.error && <div className="col-xs-3 help-block">{description.error}</div>} 
      </div> 

      <div className="form-group"> 
       <label className="control-label"> Price </label> 
       <input type="number" step="any" className="form-control" {...price} /> 
       {price.touched && price.error && <div className="col-xs-3 help-block">{price.error}</div>} 
      </div> 

      <div className="form-group"> 
       <label className="control-label"> Image </label> 
       <input type="file" className="form-control" {...image} value={ null } /> 
       {image.touched && image.error && <div className="col-xs-3 help-block">{image.error}</div>} 
      </div> 

      <div className="form-group"> 
       <button type="submit" className="btn btn-primary" >Submit</button> 
      </div> 
      </form> 
     </div> 
     </div> 
    ); 
    } 
} 

ProductForm = reduxForm({ 
    form: 'new_product_form', 
    fields: ['name', 'description', 'price', 'image'] 
})(ProductForm); 

export default ProductForm; 

ユーザーがボタンを押した後、このフォームは機能handleSubmitに次のparamsを渡す渡すに

# values variable 
Object {name: "1", description: "2", price: "3", image: FileList} 

# where image value is 
value:FileList 
    0: File 
    lastModified: 1381593256801 
    lastModifiedDate: Sat Oct 12 2013 18:54:16 GMT+0300 
    name: "wallpaper-466648.png" 
    size: 1787293 
    type: "image/png" 
    webkitRelativePath: "" 

を「送信します」これらのパラメータをバックエンドに使用しています。FormData Web APIfile-upload request using isomorphic-fetch npm module

ここでコードはtriですCK:

export function httpPostForm(url, data) { 
    return fetch(url, { 
    method: 'post', 
    headers: { 
     'Accept': 'application/json' 
    }, 
    body: data, 
    }) 
    .then(checkStatus) 
    .then(parseJSON); 
} 

そして、それはそれだ:httpPostFormfetchのラッパーです

// product_form_container.js (where form submit processed, see _handleSubmit function) 

import React     from 'react'; 
import ProductForm    from '../components/product_form'; 
import { Link }    from 'react-router'; 
import { connect }    from 'react-redux'; 
import Actions     from '../actions/products'; 
import * as form_actions   from 'redux-form'; 
import {httpGet, httpPost, httpPostForm} from '../utils'; 

class ProductFormContainer extends React.Component { 
    _handleSubmit(values) { 
    return new Promise((resolve, reject) => { 
     let form_data = new FormData(); 

     Object.keys(values).forEach((key) => { 
     if (values[key] instanceof FileList) { 
      form_data.append(`product[${key}]`, values[key][0], values[key][0].name); 
     } else { 
      form_data.append(`product[${key}]`, values[key]); 
     } 
     }); 

     httpPostForm(`/api/products/`, form_data) 
     .then((response) => { 
     resolve(); 
     }) 
     .catch((error) => { 
     error.response.json() 
     .then((json) => { 
      let responce = {}; 
      Object.keys(json.errors).map((key) => { 
      Object.assign(responce, {[key] : json.errors[key]}); 
      }); 

      if (json.errors) { 
      reject({...responce, _error: 'Login failed!'}); 
      } else { 
      reject({_error: 'Something went wrong!'}); 
      }; 
     }); 
     }); 
    }); 
    } 

    render() { 
    const { products } = this.props; 

    return (
     <div> 
     <h2> New product </h2> 
     <ProductForm title="Add product" onSubmit={::this._handleSubmit} /> 

     <Link to='/admin/products'> Back </Link> 
     </div> 
    ); 
    } 
} 

export default connect()(ProductFormContainer); 

。私のエリクシールコードで修正するものはありませんでしたが、Api.ProductControllerは同じままです(最初の記事を参照)。しかし今、次のパラメータでリクエストを受け取ります:

[info] POST /api/products/ 
[debug] Processing by SecretApp.Api.ProductController.create/2 
    Parameters: %{"product" => %{ 
       "description" => "2", 
       "image" => %Plug.Upload{ 
        content_type: "image/jpeg", 
        filename: "monkey_in_jungle-t3.jpg", 
        path: "/tmp/plug-1461/multipart-853391-603088-1" 
       }, 
       "name" => "1", 
       "price" => "3"}} 
    Pipelines: [:api] 

私を助けようとしている皆様に感謝します。これが同様のシリアライゼーションの問題に苦労するのを助けることができると期待しています。

2

役に立つことができればここで

がいっぱいappへのリンクである、それは画像がブラウザからコントローラにそれを作っていることは明らかです。

フェニックスのドキュメントのファイルアップロードガイドはあなたに助けになるはずです: http://www.phoenixframework.org/docs/file-uploads

ドキュメントから:

我々はコントローラで利用可能なPlug.Upload構造体を持っていたら、私たちが行うことができます私たちが望むすべての操作。 File.exists?/ 1でファイルが存在することを確認し、File.cp/2を持つファイルシステムのどこかにコピーしたり、外部ライブラリを使用してS3に送信したり、プラグでクライアントに送り返すこともできます.Conn.send_file/5。

あなたのケースで起こっていることは、一時ファイルを別の場所に保存していないので、プロセスが終了したときにアップロードされたファイルが削除されることです。 (私はあなたがまだDBに隠していないと仮定しています。)チェンジセットが有効であることを確認した後、あなたのコントローラにこれを行うコードを書いておきます。

関連する問題