Elixir 模式和模型之间有什么区别?

Elixir 模式和模型之间有什么区别?,elixir,phoenix-framework,Elixir,Phoenix Framework,我刚刚开始使用ate Phoenix,但我不太了解一件事,我搜索并看到有模式,数据库结构和模型具有较高的层次和逻辑处理能力,但在Phoenix中,我们只有模式,因此,例如,如果我要进行密码哈希,它应该在用户模式中?或者我应该把它放在控制器中?一般来说,你希望你的MVC应用程序有“瘦控制器”——这是一个跨多种语言的建议。在某些语言/框架中,这非常关键,因为测试控制器可能非常困难。尽管在Elixir等函数式语言和Phoenix等结构良好的框架中测试控制器相对简单,但您仍应尽可能保持控制器的精简。简单

我刚刚开始使用ate Phoenix,但我不太了解一件事,我搜索并看到有模式,数据库结构和模型具有较高的层次和逻辑处理能力,但在Phoenix中,我们只有模式,因此,例如,如果我要进行密码哈希,它应该在用户模式中?或者我应该把它放在控制器中?

一般来说,你希望你的MVC应用程序有“瘦控制器”——这是一个跨多种语言的建议。在某些语言/框架中,这非常关键,因为测试控制器可能非常困难。尽管在Elixir等函数式语言和Phoenix等结构良好的框架中测试控制器相对简单,但您仍应尽可能保持控制器的精简。简单地说,控制器应该将服务模块(在Phoenix中通常称为“上下文”)与视图连接起来。通常,它们根本没有太多的逻辑性

“模式”可能是一个令人困惑的术语——请花点时间了解不同的数据库使用不同的术语来表示(或多或少)相同的组件。例如,MySQL中的“数据库”在Oracle或Postgres中称为“模式”。在EXTO中,“模式”类似于许多其他框架中的ORM“模型”:EXTO模式在代码中表示特定数据库表的“形状”(因此可能这就是它们使用相同术语的原因)

但是,在您的情况下,密码散列的计算字段应该与EXTO模式位于同一模块中。(是的,当Ecto在代码中定义表的形状时,它确实发挥了一些宏的魔力,但它仍然是一个模块,建议将
changeset
函数放在这里,这些函数在数据进入数据库之前处理验证和变异数据)

如果您发现自己想知道某些功能应该放在哪里,那么问问自己,如果用户输入来自CLI而不是web,那么代码应该放在哪里。如果您有一个CLI mix任务,该任务创建了一个具有密码的用户,您会使用web控制器吗?不,您不会:控制器只是连接web请求/响应与底层模型/模式的中间人。您不希望仅仅因为要为CLI脚本创建密码散列而复制计算密码散列的代码,这样您就有了将密码散列放在模式中的逻辑位置

EXTO变更集允许您在进入数据库的过程中修改数据。在某些框架中,您可能会听到这被称为“变异”或“计算字段”

下面是一个计算“created_at”字段的示例(是的,您可以在数据库中执行此操作,但这是如何在代码中执行计算字段的一个有用示例):

下面是一个计算要存储在数据库中的密码散列的示例——使用
Argon2
包。这还添加了一个字段,用于标识用于散列密码的算法(作为字符串,可供参考):


希望有帮助。

哈希通常在确实属于模式的
changeset
回调中完成。我不确定你所说的“模型”是什么。谢谢,很明显,我只是凤凰城的新手,学习了2周的长生不老药,现在我正在练习长生不老药,做一个trello克隆。我的代码离那很近,我使用tdd,所以学习像凤凰城一样的结构真的很好,你可以跟随我的进度
  @doc false
  def changeset(user, attrs) do
    user
    |> cast(attrs, [:username,:email])
    |> validate_required([:username,:email])
    |> add_created_at()
    |> unique_constraint(:username)
    |> unique_constraint(:email)
  end

  defp add_created_at(changeset) do
    case changeset do
      %Ecto.Changeset{
        valid?: true,
        changes: _user
      } ->
        put_change(
          changeset,
          :created_at,
          DateTime.utc_now
          |> DateTime.truncate(:second)
        )
      _ ->
        changeset
    end
  end
  def changeset(user, attrs) do
    user
    |> cast(attrs, [:username, :password])
    |> validate_required([:username, :password])
    |> validate_length(:password, min: 8, max: 100)
    |> unique_constraint(:username)
    |> put_password_hash()
  end


  defp put_password_hash(changeset) do
    case changeset do
      %Ecto.Changeset{
        valid?: true,
        changes: %{
          password: plain_text
        }
      } ->
        put_change(changeset, :password_hash, Argon2.hash_pwd_salt(plain_text))
        |> put_change(:algorithm, "argon2")

      _ ->
        changeset
    end
  end