架构和模型有什么区别?

Whats the difference between schema and model?

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

一般来说,您希望您的 MVC 应用程序具有 "thin controllers" -- 这是适用于多种语言的建议。在某些 languages/frameworks 中,这很关键,因为测试控制器可能非常困难。尽管在像 Elixir 这样的函数式语言和像 Phoenix 这样结构良好的框架中测试控制器相对简单,但您仍应尽可能保持控制器精简。简而言之,控制器应该将服务模块(在 Phoenix 中通常称为 "context")与视图连接起来。通常它们根本没有太多逻辑。

"Schemas" 可能是一个令人困惑的术语——花点时间理解不同的数据库使用不同的术语来指代(或多或少)相同的组件。例如。 MySQL 中的 "database" 在 Oracle 或 Postgres 中称为 "schema"。在 Ecto 中,"schema" 类似于许多其他框架中的 ORM "model":Ecto 模式在代码中表示特定数据库 table 的 "shape"(所以也许这就是为什么他们使用相同的术语)。

但是,在您的情况下,密码哈希的计算字段之类的东西应该与 Ecto 模式位于同一模块中。 (是的,Ecto 在代码中定义 table 的形状时做了一些宏魔术,但它仍然是一个模块,建议将 changeset 函数放在之前处理验证和变异数据的位置它进入数据库)。

如果您发现自己想知道某些功能应该放在哪里,请问问自己,如果用户输入来自 CLI 而不是 Web,您会将代码放在哪里。如果你有一个 CLI 混合任务创建一个有密码的用户,你会使用网络控制器吗?不,你不会:控制器只是连接网络 request/response 与底层 model/schema 的中间人。您不会仅仅因为要为 CLI 脚本创建计算密码散列的代码就复制它,这样您就有了放置它的合乎逻辑的位置:在模式中。

Ecto 变更集允许您修改进入数据库的数据。您可能会听到在某些框架中将其称为 "mutation" 或 "calculated field."

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

  @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

这里是计算要存储在数据库中的密码散列的示例——使用 Argon2 包。这还添加了一个字段,用于标识使用哪种算法对密码进行哈希处理(作为字符串,供参考):

  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

希望对您有所帮助。