2016-12-07 60 views
2

我有一個控制器,我想添加Facebook頁面ID和訪問令牌到我的數據庫,以便我可以代表用戶發佈。Phoenix,conn重定向停止所有進一步處理?

爲此,我需要對Facebook進行GET調用,然後使用我必須插入到數據庫中的訪問令牌。

這意味着我有一個嵌套的case語句,因爲有錯誤的2點,來自Facebook的電話,從插入式回購:

case response_from_facebook do 
    {:ok, token} -> 
     case Repo.insert(token) do 
      {:ok, _} -> ... redirect the user 
      {:error, _} -> ... show an error message 
    {:error, _} -> redirect and show an error 

這是醜陋的,如果我不喜歡的東西一個海事組織所以我想知道後衛是否會停止進一步處理:

token = case response_from_facebook do 
    {:ok, token} -> token 
    {error,_ } -> conn |> redirect(to: user_path(conn, :show, user_id)) 
end 

# Does it ever try to insert into the repo if an error occurs? 

Repo.update ... etc. 

這將使我的代碼清潔理解/閱讀,但我不是,如果重定向將阻止試圖插入我的回購完全肯定。如果這個「警衛」不是一個解決方案,我怎樣才能防止一個多層嵌套的case語句難以閱讀?

+1

'response_from_facebook'是否會返回一個與'Repo.insert'錯誤分開的簡單模式匹配的錯誤(您可以發佈它可以返回的錯誤類型嗎?)?如果是的話,你可以使用新的'with'宏來簡化這段代碼。 – Dogbert

+0

@Dogbert你能提供一個答案/例子嗎?我在網上查看了宏,但似乎沒有任何實例顯示錯誤是如何處理的。你應該把'with'的結果分配給某個東西嗎?來自Facebook的錯誤形式爲{:ok,%{「error」=>%{「message」=> message}}}'而ecto的錯誤形式爲{:error,reason}'。因此它們可以被模式匹配。不過,我不完全確定應該如何發生。 (我應該在'with'語句中分支一個錯誤嗎?我應該將'with'語句的結果分配給進一步處理嗎?) –

+0

在'with'裏面用'else'代碼塊檢查這個例子:http:// elixir-lang.org/docs/stable/elixir/Kernel.SpecialForms.html#with/1。 – Dogbert

回答

1

一個更清潔的方式來做到這一點:

defmodule SomeApp.FacebookController do 
    ... 

    def create(conn, params) do 
    get_from_facebook 
    |> process_facebook(conn) 
    end 

    defp get_from_facebook do 
    # this is a function you implement 
    end 

    defp process_facebook({:ok, token}, conn) do 
    Repo.insert(token) 
    |> process_insert(conn) 
    end 

    defp process_facebook({:error, error}, conn) do 
    conn 
    |> redirect_with_error(error) # this is a function you will write 
    end 

    defp process_insert({:ok, _}, conn) do 
    conn 
    |> redirect(to: user_path(conn, :show, user_id)) 
    end 

    defp process_insert({:error, error}, conn) do 
    conn 
    |> redirect_with_error(error) # this is a function you will write 
    end 

    ... 
end 

無不良影響可以用這個理邏輯的發生,它是多「漂亮」。

更妙的是,如果你打破了大多數這個邏輯出到另一個模塊:

defmodule SomeApp.FacebookCommand do 

    def call do 
    make_request 
    |> process_response 
    end 

    defp make_request do 
    # whatever you are doing to request from Facebook 
    end 

    defp process_response({:ok, token}) do 
    Repo.insert(token) 
    |> process_insert(conn) 
    end 

    defp process_response({:error, error}) do 
    {:error, error} 
    end 

    defp process_insert({:ok, record}) do 
    {:ok, record} 
    end 

    defp process_insert({:error, error}) do 
    {:error, error} 
    end 

end 

,然後使用它的控制器:

defmodule SomeApp.FcebookController do 
    ... 

    def create(conn, params) do 
    case SomeApp.FacebookCommand.call do 
     {:ok, user} -> 
     conn 
     |> redirect(to: user_path(conn, :show, user.id)) 
     {:error, error} -> 
     # redirect with error 
    end 
    end 

    ... 
end 

顯然,如果要更新現有的用戶,那麼你會把你的命令改寫成你將call作爲參數傳遞給現有用戶。

請記住我的模塊和函數命名吸吮,因爲我不知道你到底在做什麼。更好的命名是必須的。

+1

這可能只是一個風格問題,但是當沒有多個管道時,儘量避免使用'|>'。 'redirect_with_error(conn,error)'比'conn |> redirect_with_error(error)'好。另外,當您使用管道時,您應該使用一個值來啓動該組。 'token |> Repo.insert()|> process_insert(conn)'會比'Repo.insert(token)|> process_insert(conn)更好' –

+1

我主要同意你的觀點。但是,我喜歡使用管道操作員來顯示高級別的工作流程,這就是爲什麼我在只有1個參數的情況下使用它。也就是說,其他一些風格問題是由於上面提到的Terence原創思想的複製和粘貼,即。 'Repo.insert(令牌)'。感謝您的意見。 –