Elixir 变更集自定义验证

Elixir changeset custom validation

我在尝试实施自定义变更集验证时遇到了一些麻烦。 我的架构是:

defenum(VersionStateEnum, ["draft", "active"])
schema "versions" do
  field :expires_at, :utc_datetime
  field :state, VersionStateEnum
end

我要实现的验证是:expires_at 只能在状态为草稿时设置(这对更新也应该有效,我应该无法删除 expires_at 如果状态仍然是草稿) 我尝试了以下方法:

defp validate_expires_at(changeset) do                                                                                                                                                                           
    expires_at = get_change(changeset, :expires_at)                                                                                                                                                                
    cond do                                                                                                                                                                                                        
      get_change(changeset, :state) == :draft ->                                                                                                                                                                   
        case expires_at do                                                                                                                                                                                         
          nil -> add_error(changeset, :expires_at, "can't be blank when state is draft")                                                                                                                           
          _ -> changeset                                                                                                                                                                                           
        end                                                                                                                                                                                                        
      get_change(changeset, :state) == :active ->                                                                                                                                                                  
        case expires_at do                                                                                                                                                                                         
          nil -> changeset                                                                                                                                                                                         
          _ -> add_error(changeset, :expires_at, "cannot be set when state is not draft")                                                                                                                          
        end                                                                                                                                                                                                        
      true ->                                                                                                                                                                                                      
        changeset                                                                                                                                                                                                  
    end                                                                                                                                                                                                            
  end
end

但它并没有真正起作用,因为即使状态是草稿,我也可以将 expires_at 更新为 nil。感谢任何帮助。

编辑 1: 我的变更集:

@required_fields [                                                                                                                                                                                                                                                                                                                                                                                               
    :state                                                                                                                                                                                                                                                                                                                                                                                                         
  ]                                                                                                                                                                                                                
@optional_fields [:expires_at]
def changeset(model, params \ nil) do                                                                                                                                                                           
  model                                                                                                                                                                                                          
  |> cast(params, @required_fields ++ @optional_fields)                                                                                                                                                          
  |> validate_required(@required_fields)                                                                                                                                                                         
  |> validate_expires_at()                                                                                                                                                                                       
end

调用位置:

def create_document(attrs \ %{}) do                                                                                                                                                                             
  %Document{}                                                                                                                                                                                                    
  |> Document.changeset(attrs)                                                                                                                                                                                   
  |> Repo.insert()                                                                                                                                                                                               
end

如果我对你的问题的理解正确,我认为要解决它你应该也考虑给变更集的结构。

因为,按照您的代码的方式,您仅从变更集中的更改中检查 state,但如果您尝试单独更新 expires_atchanges 在变更集中将不包括可能已经设置为 "draft"state,因此,validate_expires_at 函数中的 cond 块将始终匹配 true,因为该值将是 nil.

一种解决方法是像这样更新函数:

defp validate_expires_at(changeset) do                                                                                                                                                                           
    state = get_field(changeset, :state)
    expires_at = get_change(changeset, :expires_at)   
    case state do
      :draft ->
        case expires_at do                                                                                                                                                                                         
          nil -> add_error(changeset, :expires_at, "can't be blank when state is draft")                                                                                                                           
          _ -> changeset
        end
      :active ->
        case expires_at do
          nil -> changeset
          _ -> add_error(changeset, :expires_at, "cannot be set when state is not draft")
        end
      _ -> changeset
    end                                                                                                                                                                                                   
  end
end

使用 get_field 而不是 get_change 将尝试从更改中获取字段,但如果未更改,则可以从现有结构和函数的其余部分获取应该可以正常工作

Not sure how the atom/String handling works when inserting and retrieving from the DB. You might need to check if state could be a String when taken from the changeset's data