我已经看过Elixir GenServer的几个例子,但它们主要处理数组值(例如购物车)或计数器增量。因此,他们演示了如何处理简单的数据类型。
我想知道在更新某些模型记录时如何在Phoenix应用程序中传递状态。
我可以提供的例子是:
Notification
Notification
内的消息,读取s3对象filename
。然后将其存储到新的Document
模型来自 Ruby on Rails 我会这样做:
Notification
然后安排第二步的后台作业(Sidekiq)示例:
class NotificationController
def create
# ...
notification = Notification.create(body: message_body)
ProcessNotification.perform_later("Notification", notification.id)
# ...
end
end
class ProcessNotification
# ...
def process(resource_class, resource_id)
notification = resource_class.constantize.find(resource_id)
document = Document.new(filename: parse_filename(notification.body))
document.save
PullMetadata.perform_later("Document", document.id)
end
# ...
end
class PullMetadata
# ...
def process(resource_class, resource_id)
document = resource_class.constantize.find(resource_id)
document.original_name = fetch_original_name(document.filename)
document.save
end
# ...
end
现在我试图使用Genserver 复制与Phoenix类似的内容(逐步调用)
第一步(创建Notification
由Phoenix控制器完成,我想将其他两个步骤隔离为2个genserver调用:
defmodule NotificationController do
# ...
def create(conn, params) do
notification = # ... store body to %{}Notification
# ...
pid = GenServer.start_link(ProcessNotification, {Notification, notification.id})
GenServer.cast(pid, :process_to_document)
end
end
defmodule ProcessNotification do
def handle_cast(:process_to_document, {Notification, notification_id}) do
notification = Repo.get(Notification, notification_id)
filename = not_important_how_i_parse_body(notification)
doc = %{}Document |> Document.changeset(%{filename: filename}) |> Repo.insert!
{:noreply, {Document, document.id}}
end
def handle_cast(:pull_metadata, {Document, document_id}) do
document = Repo.get(Document, document_id)
original_name = not_important_how_i_pull_the_metadata(document)
doc = %{}Document |> Document.changeset(%{original_name: original_name}) |> Repo.update!
{:noreply, {Document, document.id}}
end
end
现在我的问题是:
{Notification, id}
,然后它是{Document, id}
。感觉像Genserver一直期待可能是同一类型?所以也许我应该总是返回`{Notification,id}并从关联中拉出Document?或者这样可以吗?process_to_document
我会投射pull_metadata
。或者我应该在控制器中安排这些:示例:
defmodule NotificationController do
# ...
def create(conn, params) do
notification = # ... store body to %{}Notification
# ...
pid = GenServer.start_link(ProcessNotification, {Notification, notification.id})
GenServer.cast(pid, :process_to_document)
GenServer.cast(pid, :pull_metadata)
end
end
我很确定我所做的事情是错的,所以我很欣赏这应该如何做得更好。
答案 0 :(得分:2)
演员阵容很简单。
GenServer.cast self(), {:another_event, some_data}
但是,我甚至不确定为什么你需要这样做,因为你似乎是从你的控制器启动gen服务器。我不认为这是正确的做法。你现在需要的就是产生一个完成所有工作的过程。
您可以在此处使用任务模块
Task.start fn ->
# do my heady lifting here
end
如果您想处理错误处理和重试,那么您可以使用主管来启动任务。
如果您担心要创建的进程数,那么您将查看工作池。
BTW,GenServer状态非常简单。初始状态是从init / 1返回的状态。下一个状态是您从handle_cast / 2或handle_call / 3 返回的内容defmodule MyGenServer do
use GenServer
def start_link(args) do
init_args = # do something with args
GenServer.start_link(__MODULE__, init_args)
end
def init(init_args) do
initial_state = # perhaps manipulate init_args
{:ok, initial_state}
end
def handle_cast(event, current_state) do
new_state = # manipulate current_state
GenServer.cast self(), {:another_event, some_data}
{:noreply, new_state}
end
def handle_call(event, sender, current_date) do
new_state = # manipulate current_state
{:reply, return_value, new_state}
end
end
请记住,每个handle_xxx都需要在另一个handle_xxx被调用之前运行完成。因此,您无法将GenServer.call调用到同一个GenServer,因为这会使进程死锁。
但是GenServer.cast是异步的所以它不是问题。
另一个选项是从GenServer处理程序中执行send self(), {:event, data}
。这将在GenServer上运行handle_info/2
处理程序。