Skip to content

Add support to AND and OR groups #203

Open
@lazarljubenovic

Description

@lazarljubenovic

First of all, thanks for the amazing lib. I love its potential! I'm fiddling with the groups functionality and I really like how it enables me to keep my "transformers" in on the class itself. When the model updates and I need to add another field, I write how that field will be visible to different "views" immediately instead of hunting down ten thousand transformer files.

Details and backstory

I'm trying to find a good pattern to transform data for sending an API response, but I'm finding limitations since the "groups" are doing the OR operator between them. This sort-of-a-feature-request and sort-of-a-question and sort-of-a-proposal at the same time.

For example, my User model has (at least) two "axes" on how we can categorize the response which includes the user of id 1.

  • How is user 1 related to me? (Is 1 actually me, are we friends on the platform or are we strangers?) This gives us three categories: me, friend and stranger.
  • Am I fetching this user as part of a list (eg. search results, less info) or a single user (full profile info)? This gives us two categories: list and single.

Notice how the first three categories cover the entire spectrum of possible users. Also, it's not possible that a user is in two of the three categories at the same time -- they are disjunctive. Same with the last two.

Problem

The problem is that I cannot say that a certain field is visible only when fetching my own info (me) in the single view (single). If I say @Expose({ groups: ['me', 'single'] }), that would mean me OR single, so fetching a stranger's info on the single view would include that field as well.

As the lib currently stands, to do what I want to do would mean having to create the Cartesian product of all categories. Instead of me, friend, stranger, list and single, I'd need to have me-single, me-list, friend-single, friend-list, stranger-single, stranger-list. The problem is that this list grows quickly and is very difficult to maintain.

Proposal 1

One way I see this possible is to introduce some sort of query builder for the AND/OR expressions.

@Expose({
  groups: and(or('me', 'friend'), 'single') // ['me-single', 'friend-single']
})

classToPlain(User, user, { groups: ['me', 'single'] }) // matches
classToPlain(User, user, { groups: ['friend', 'list'] }) // doesn't match (requires single)

But this is a bit wonky and not very scalable. I like the second idea much better.

Proposal 2

The other way would be to make groups a function instead. I think this is a more flexible approach and also probably much easier to implement in class-transformer itself. Just pass in an array of groups and let the function (which returns a boolean) decide if it's included or not.

@Expose({
  groups: groups => (groups.some(g => g == 'me') || groups.some(g => g == 'friend')) && groups.some(g => g == 'single'),
})

// usage same as previous
classToPlain(User, user, { groups: ['me', 'single'] }) // matches
classToPlain(User, user, { groups: ['friend', 'list'] }) // doesn't match (requires single)

Basically, the current implementation accepts groups as string[] and then just checks if there's an intersection:

return this.options.groups.some(optionGroup => groups.indexOf(optionGroup) !== -1);

My suggestion is that, if groups satisfies typeof groups == 'function', we just call this.options.groups(groups). I haven't tested it at all, but it seems like that's the only thing that needs to change... I think. I'll give it a shot soon.

If would be up to developer to define more complex utility functions to enable syntax like in Proposal 1.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions