Two Field comparison show some unknow error

Following is my schema

    field :minsalary, :decimal
    field :maxsalary, :decimal

And when I am comparing/validating them using the following code,

    |> validate_number(:maxsalary, greater_than: :minsalary, message: "Max. Salary should be greater than Min. Salary")

I am getting this unknown error. Your response/help will be highly appreciable.

:greater_than requires a value, not a field.

So if you want to compare to another field from the same struct you need to fetch it first.

Still, AFAIR, :greater_than also uses >/2, which will probably not do what you expect here.

1 Like

I also look into this post and tried same but getting error. Here is my code with error (if you need more info let me know)
Code-

def changeset(job_opening, attrs) do
    job_opening = %{data: %{minsalary: minsalary}} = job_opening
    |> validate_number(:maxsalary, greater_than: minsalary, message: "Max. Salary should be greater than Min. Salary")

Error-

undefined function minsalary/0

The pattern match only happens after validate_number has been evaluated. So minsalery is indeed undefined when you use it.

1 Like

I assume job_opening is a Changeset struct. Firstly you really shouldn’t go digging into the internals when there’s a provided accessor function. I would write this as:

minsalary = get_field(job_opening, :minsalary)
validate_number(job_opening, :maxsalary, greater_than: minsalary, message: "..."
1 Like

get_field seems to be user-defined function, right?

No. I said a “provided accessor”. See https://hexdocs.pm/ecto/Ecto.Changeset.html#get_field/3

1 Like

Oh…thanks. I was not aware of that. Thanks

Treid get_field

  def changeset(job_opening, attrs) do
    job_opening
    |> validate_number(:minsalary, greater_than: 0, message: "Min. Salary should be greater than 0")
    minsalary = get_field(job_opening, :minsalary)
    |> validate_number(job_opening, :maxsalary, greater_than: minsalary, message: "Max. Salary should be greater than Min. Salary")
  end

but getting Error that is undefined function minsalary/0
When I comment out these error-prone lines and inspect the job_opening, here is what I get

%Apollo.JobOpening.Schema.JobOpening{
  __meta__: #Ecto.Schema.Metadata<:loaded, "public", "job_openings">,
  applicants: #Ecto.Association.NotLoaded<association :applicants is not loaded>,
  applied_openings: #Ecto.Association.NotLoaded<association :applied_openings is not loaded>,
  company_admin: #Ecto.Association.NotLoaded<association :company_admin is not loaded>,
  company_admin_id: 1,
  description: "<p><strong>Join [Company] as a [Position]!</strong></p><p>We're looking for an experienced [Position] to come in and help us take our team to the next level.</p><p>To apply, take this <strong>[TimeLimit] minute test</strong>. All questions can be <strong>answered multiple times</strong>. And you will get rapid <strong>feedback</strong> after finishing.</p><p>You can take the test <strong>only once</strong>.</p>",
  duration: 900,
  employment_type: "Full Time",
  failure_message: "<p><strong>Thanks! You scored [TestScore]!</strong></p><p>No worries. A real human at [Company]. will now go through everything you sent. If you’re chosen for the next round, we'll share the good news by [7DaysFromApplying].</p><p>Talk to you soon!</p><p>[Admin] from [Company]</p>",
  id: 7,
  inserted_at: ~N[2019-05-19 17:08:32],
  is_public: false,
  job_stages: #Ecto.Association.NotLoaded<association :job_stages is not loaded>,
  maxsalary: #Decimal<10>,
  minsalary: #Decimal<10000>,
  name: "Machine Learning Engineer II",
  questions: [],
  share_url_route: "i2eJPs6hTM4ErdNCUbhB",
  skills: ["Deep Learning", "Data analysis", "python", "Data structure"],
  success_message: "<p><strong>Great start! You got [TestScore]!</strong></p><p>A real human will now go through everything you sent. If you’re chosen for the next round, we'll share the good news soon. If the journey stops here, we’ll also confirm that.</p><p>Talk to you soon!</p><p>[Admin] from [Company]</p>",
  test_required: true,
  threshold: 50.0,
  updated_at: ~N[2019-05-19 17:08:32],
  workspace: #Ecto.Association.NotLoaded<association :workspace is not loaded>,
  workspace_id: 1
}

You are passing the schema struct to the Changeset functions that expect a changeset as the first argument. Try

def changeset(job_opening, attrs) do
    job_opening
    |> cast(attrs, @allowed_fields)
    |> validate_number(:minsalary, greater_than: 0, message: "Min. Salary should be greater than 0")
    minsalary = get_field(job_opening, :minsalary)
    |> validate_number(job_opening, :maxsalary, greater_than: minsalary, message: "Max. Salary should be greater than Min. Salary")
  end

Note: you will need to also define the @allowed_fields attribute or an inline list of allowed field names.

1 Like

Let me reindent/reformat this piece of code, such that you see more clearly how the compiler sees it:

  def changeset(job_opening, attrs) do
    job_opening
    |> validate_number(:minsalary, greater_than: 0, message: "Min. Salary should be greater than 0")

    minsalary = job_opening
      |> get_field( :minsalary)
      |> validate_number(job_opening, :maxsalary, greater_than: minsalary, message: "Max. Salary should be greater than Min. Salary")
  end

When you want to compare dependent values, I’d first extract them and then pipe all validations one after the other, clearly separating concerns this way:

def changeset(job_opening, attrs) do
  minsalary = get_field(job_opening, :minsalary)

  job_opening
  |> validate_number(:minsalary, greater_than: 0, message: "Min. Salary should be greater than 0")
  |> validate_number(:maxsalary, greater_than: minsalary, message: "Max. Salary should be greater than Min. Salary")
end

But still I have to repeat my warning. validate_number with :greater_than does compare using >/2 as you can see here:

But you can not properly compare Decimals using >/2 and friends. Thats why there are Decimal.cmp/2 and Decimal.compare/2.

1 Like

Look further

Though I was a bit surprised that validate_number supported Decimal directly - but given that it is one of the Ecto primitive types I guess I shouldn’t have been.

2 Likes

Here is my working code on comparing two field

    |> validate_maxsalary()
  end


  def validate_maxsalary(changeset) do
    minsalary = get_field(changeset, :minsalary)
    maxsalary = get_field(changeset, :maxsalary)
    if minsalary > maxsalary do
    changeset |>  add_error(:maxsalary, "Max. Salary should be greater than Min. Salary")
    else
      changeset
    end
  end

Thanks to all for such actions.

This definitely will not work properly because of the reasons already mentioned.

Please stick to validate_number, it knows how to deal with decimals as @peerreynders pointed out. If you want to stick with your custom validator, make sure to use Decimal.cmp/2/Decimal.compare/2.

1 Like