Select fields in read action

Is it possible to define a subset of fields to be selected from a resource in a read action?

Ash.Query has a select function, so that is possible when using that directly or passing a existing read action with Ash.Query.for_read, but what if I want to keep that abstracted away in a read action?

Here is my use case:

Let’s say I have an User resource that has a bunch of attributes and relationships. I want to load the logged user from that resource, but I don’t want to store the full resource (and relationship full resources as-well) in memory if I just need a couple of its fields to use for authorization.

In that case, I want to have a read action for each resource I want to load that will return just a subset of the attributes loaded and leave the rest as nil.

I can probably do that with custom actions, but it would be nice to have it in a read action for simplicity.

Also, I know that an reasonable solution would be to have another resource for the same table with just the fields I need (ex. AuthenticatedUser). The issue is that I also need to select a subset of attributes from other relationships that need to be loaded with the User resource, meaning that I would need to create a new resource for each one of them.

Ash read actions always return structs, so all fields will be present on the structs. But if you want to avoid selecting them you can use prepare build(select: [:foo, :bar]) to limit the selected fields in a given action

Copied from Zach’s message in Discord.

3 Likes

Amazing, that’s what I needed, thanks man!

1 Like

I have a follow-up question. Elixir and Ash newbie here.

I’m trying to build a simple index view that shows all Users, for the standard User resource created by Ash Authentication. I want to limit the columns returned, but select seems to have no effect. What am I doing wrong?

    read :get_all do
      description "Looks up all users"

      prepare build(select: [:email]). <--- has no effect

      pagination offset?: true, countable: :by_default, default_limit: 20
    end

It will still return the full struct of the resource, but all the deselected fields should be Ash.NotLoaded instead of a value. Is that not what you’re seeing?

Thanks @frankdugan3 for the speedy reply.

I see the full User resource with all fields selected and populated with values from the database table.

Are you sure you’re using that action? :thinking:

How are you calling it?

I think so. I’m using Ash Admin UI.

Ahh, OK. That adds some layers of complexity. I’m not familiar with AshAdmin, which may automatically add selections or something. Hopefully someone else will chime in with regard to that use case.

To see if that’s what’s happening, I suggest running the query in iex to validate the action is behaving as expected. Something like:

Ash.read!(User, action :get_users, authorize?: false, actor: nil)

Ah yes, you’re right, this works in iex…

Ash.read!(Example.Accounts.User, action: :get_all, authorize?: false, actor: nil)

… I see in iex that select limits the returned fields. So the problem I’m running into is in AshAdmin. Thanks for the pointer.

Just a suggestion: This might be a good use case to create a separate User resource (maybe Profile?) for this use case that only has the attributes you want exposed. Multiple resources can point to the same DB table, and the migration generator will concat them together.

Having domain-specific resources is a good practice to exclude sensitive fields from a security perspective.

1 Like

Ah yes, great point. Kill two birds with one stone.

This is helpful, helps me to understand a bit better what Ash is about.

Is this really the best approach? If a resource still has the same meaning in the context of different Domains I would have thought it would be better to solve this issue using policies?

Otherwise, how would you work with relationships and things like that?

What you are suggesting sounds a bit like using different serializers for the same data structure to control things like serialized files (something that is common in django world). However, a serializer is purely a presentation of a piece of data but a resource is much more than that.