Any way to validate a condition from a read actions instead of policies or filters?

Eduardo B. Alexandre
2023-06-06

Eduardo B. Alexandre:

So, normally I use filters modules when I want to validate something in read actions (ex. if I want to filter classes of a school (both resources), I pass the school_id and also filter it by checking if the actor is also from that same school.

That kind of filter works great for most of the cases, but sometimes I just want to actually check some validation and return an error (or empty list if the error is not possible) without having the add that validation directly in the resulting SQL query.

Normally, specially if the validation is more complex, I create a change module that does the validation and return an error if it didn’t pass. But I can’t use a change module in a read action.

I believe I could achieve that by creating policies, but from other discussions here, my understanding is that policies should be for simple things directly related to the actor struct, not something that is more complex and will query the database for more data.

I also took a look into the Preparation behavior, but that one always return a query, so I can’t see how I would actually return an error instead.

So, in this case, what is the best approach you would suggest <@197905764424089601> ?

zachdaniel:

Can you provide a concrete example of what you’re looking to do?

Eduardo B. Alexandre:

Sure,

So, in my system I have the following resources:

I have a organization resource which have multiple schools I have a school resource which has one organization and multiple teachers I have a teacher resource which has multiple organizations and multiple schools I have a template resource which has one organization and one school

In my template resource, I have a field called shared? , that field can have the following values: :private , :school , :organization and :global . :private means that the template is not shared, :school means that it is shared with all teachers from the same school, :organization means that it is shared with all teachers from the same organization, and :global is shared with everyone.

I also have a read action in the template resource called all_shared , that action accepts two arguments, a :school_id and a :organization_id (it can also receive other arguments to make the filter more specific but these are not relevant to this topic).

What that action does is create a filter that is something like this:

filter expr(
  shared? == :global
  or (shared? == :school and school_id == ^arg(:school_id))
  or (shared? == :organization and organization_id == ^arg(:organization_id))
)

In other words, it will just return templates that are either globally shared, or shared in either the school or organization that was provided by the action arguments.

This works great, but the thing here is that an user (teacher) can send whatever they want as the school_id or organization_id , meaning that if they know the id of another school or organization they are not part of, they would be able to get these information anyway because there is no safeguard in place to stop it.

Eduardo B. Alexandre:

At the same time, it seems odd to filter out the data also by the teachers organizations and schools since that would be something that doesn’t tells exactly what I’m trying to achieve and also would make the query more complex without an obvious benefit.

So, what I wanted to do in this case is to create a validation/change that would check if the actor is in the school and organization provided by the :school_id and :organization_id arguments before running the query.

Again, I think I can do that in a policy because, if I recall correctly, I do have access to these arguments there. But from what we already discussed about policies/validations/changes my understanding is that policies are for simpler things, not for something that would actually query the DB to check if that actor is from that school and organization.

Normally, if that was a create / edit / destroy action, I would just use a change for that, but in the case of a read action I’m kinda lost on what is the correct/best approach.

zachdaniel:

My suggestion is that when a user logs in, you load their school ids/organization ids using list aggregates onto the user

zachdaniel:

And then write checks like {UserInOrg, argument: :organization_id}

zachdaniel:

that is a Ash.Policy.SimpleCheck that does changeset.arguments[] in user.organization_ids

Eduardo B. Alexandre:

So the idea here is to just use policies in the end correct?

zachdaniel:

yep!