zac
I feel like there should be an easy way to do this, without creating a (relatively complicated) custom validation. But after combing the docs and forum, I’m thinking perhaps not…
Here’s a somewhat simplified case.
I have two resources, a sprint and an activity.
sprint’s have a date range, e.g., sprint.start_date and sprint.end_date.
Activities belong to sprints, and have a date, activity.date.
def Sprint do
attributes do
attribute :start_date, :date, allow_nil? false
attribute :end_date, :date, allow_nil? false
end
end
def Activity do
attributes do
attribute :date, :date, allow_nil? false
end
belongs_to :sprint, Sprint do
attribute_writable? true
allow_nil? false
end
end
The desired outcome is that an activity’s date must fall within the start and end dates of its sprint.
It seems I should be able to do something like the following – but, I haven’t been able to figure out how.
# This absolutely won't even compile, but it expresses the idea I'm after:
validations do
validate compare(:date, greater_than_or_equal_to: :sprint.start_date)
validate compare(:date, less_than_or_equal_to: :sprint.end_date)
end
Maybe I’m expecting too much and I need to do a custom validator, but thought I’d post the question. (I’m also just getting back into Ash… just upgraded a small project from 2.4 to 3.4, and really dusting off some very, very dusty recollection of how it all works…)
If there is a way to achieve the above without dropping into complicated (potentially atomic) validations and atomic_ref() and the like… well, I’d love some pointers. Not up to speed on atomics yet… Thanks!
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #blog-post
- #phoenix_html
- #iex
- #graphql
- #genstage
- #ai
- #elixirconf-us
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #hex
- #performance










First 10 of 10 Posts
zachdaniel
The main thing that makes validations across tables like this hard is that, unlike other validations, there is no guarantee that they will be true as the related resource can change on its own. But that doesn’t necessarily have to be a problem, it’s just on you to understand that that is the case.
Unfortunately there is no builtin validation, so you will in fact need to drop down to the complicated version of things. Good thing I’m here!
Something along those lines should do the trick. By defining it this way (with only an atomic callback) the validation will always be deferred to when the query is run. But in the case of checking related data, thats typically the time you’d want to do it anyway. What you can do if you want the eager validation, like in a form, is add a regular
validate/3callback that does the same work manually.I think this can be made much more ergonomic over time, but it’s good to know how to do this
zac
Thanks for this @zachdaniel, that was hugely helpful in furthering my understanding of atomics.
I did run headlong into:
Which I did not realize. I need to implement it as a non-atomic
validate/3function. (And presumably keep the atomic version for, e.g., updates).The parallel writeup here was also a great read: Does validations in update actions always require you to load the fields the validations are working on - #5 by anagrius.
My first attempt was to try and
load/2the sprint inside thevalidate/3:That failed. I’m assuming, because the
activityis not yet persisted. But I was a bit surprised. Thechangesetdoes have the relationship. I would have thoughtload/2would be smart enough but, my understanding is not complete.So… I proceeded (hopefully correctly) with the following
validate/3function.The only bit I’m a little uncomfortable with is the
read_onecall. The above works. I’m just wondering if it is less efficient than it could be, or if there’s a more idiomatic approach.zachdaniel
Only two notes:
before_action?: truewhen you use this validation, i.evalidate YourModule, before_action?: true.What that would look like:
zac
Thanks for that @zachdaniel! Changes implemented.
I’m getting the following compiler warning. I futzed with it for a while and can’t figure out any way to shut it up. Thinking it must have something to do with
Ash.Query.filter/1macro interpretation…?The specific line triggering it corresponding to
validate_within_sprint.ex:7:I did try factoring out the function calls so it’s just
|> Ash.Query.filter(id == ^sprint_id)but it makes no difference.Trying
|> Ash.Query.filter(id == "xyzzy")shuts up the warning, but obviously doesn’t work. Also tried wrapping it inexpr/1to no avail.It seems the
filter/1macro is somehow interpreting^sprint_idasnil.zac
Uh. Found one way to shut it up but…
zachdaniel
Have you determined for sure that sprint_id isn’t
nilin that case? That is pretty much the only case you should get that warning.zac
Ahh! In this case, the sprint is not nil.
But thanks to your note, I realized there are tests in the test suite where it would be nil to protect against a bad state. That’s what’s triggering the warning.
Adding a guard fixes it in the validation – but that raises an interesting question. There are constraints in place that prevent setting the sprint to
nil. It hadn’t occurred to me that the validation would run before the constraints. Is there any way to postpone the validations until after constraints are checked?Specifically, the
belongs_torelationship isallow_nil? false.zachdaniel
validate Something, only_when_valid?: trueto run it only when the changes are valid.The idea is that you may want to provide many validation errors at once instead of just the first one that fails.
For your particular case, you can decide how you want it to behave. On its own it might make sense to just ignore the validation entirely if there is no
sprint_id. It’s something else’s job to ensure that it is set.Then the experience of the caller would be “you must set spring_id”, and then “must be in range” if it’s not in range.
zac
@zachdaniel, while Ash Framework has a bit of a learning curve, as always you’ve thought ahead and built in a great deal of flexibility and power. Looking forward to (again!) getting more familiar with it!
Would love to see some of the above make its way into the docs. While I feel like all the pieces are there, putting it together can be a challenge. Perhaps building out the existing examples with some of these more subtle (and powerful) features would be helpful, especially now with atomics on the scene…
zachdaniel
You’re absolutely right
better wholistic examples is very important, and we need more of them.