2016-12-07 57 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(令牌)'。感谢您的意见。 –