Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

:only option for filtering #67

Closed
mb21 opened this issue Feb 27, 2016 · 6 comments
Closed

:only option for filtering #67

mb21 opened this issue Feb 27, 2016 · 6 comments

Comments

@mb21
Copy link

mb21 commented Feb 27, 2016

It would be great if we could override the default props somehow, with the :only options familiar from Rails, like:

UserSerializer.new(current_user,
     only: {
       property: [:id, :first_name, :last_name]
     }
  )

Or is there a better way to achieve that kind of flexibility?

(an additional :include option could also be added, but seems harder to implement since you'd have to specify how to render the additional properties...)

@mb21 mb21 changed the title :only and :include options :only option for filtering Feb 28, 2016
mb21 added a commit to mb21/oat that referenced this issue Feb 28, 2016
@ismasan
Copy link
Owner

ismasan commented Feb 29, 2016

I'm hesitant to include complex hash-based configuration and options. In my opinion that's an anti-pattern in Rails, not a feature.

In Oat a serializer is the unit that encapsulates a group of properties. Would it not be possible to use separate serializer classes? For example

MiniUserSerializer.new(current_user, context)

@mb21
Copy link
Author

mb21 commented Feb 29, 2016

Thanks for responding! Well, my use case is really less concerned with the properties, but more with the entities... sometimes I need a user, sometimes a user with his posts, sometimes a user, her posts and the tags of those posts. Writing a new serializer for each case seems not very DRY...

@ismasan
Copy link
Owner

ismasan commented Feb 29, 2016

I think I don't agree with that definition of DRY. I find it cleaner, more deterministic and easier to test to just have separate classes that do one thing each, instead of one class that does all sort of things depending on deeply nested options. With Oat I wanted to get away from the complexity that comes with that kind of approach.

Oat's schema block is the place where you should encapsulate your schema definitions. If you pass or override those schemas from the outside I think it defeats the point because now you start defining schema properties in controllers and other places.

As I see it, there are other options:

Multiple serializers

You can have specific serializers that expose the attributes you want for each case.

class FullUserSerializer < Oat::Serializer
  schema do
    type 'user'
    properties do |props|
      props.name item.name
      # etc...
    end

    entities :friends, item.friends, MiniUserSerializer
  end
end
class MiniUserSerializer < Oat::Serializer
  schema do
    type 'user'
    map_properties :name, :email, :id
  end
end

Use one serializer in combination with presenters

You can wrap your models in objects that expose an API that your serializers understand.

class FullUserPresenter < SimpleDelegator
  def include_friends?
    true
  end
end

class MiniUserPresenter < SimpleDelegator
  def include_friends?
    false
  end
end

Then your UserSerializer can rely on the #include_friends? API to know what to serialize

class UserSerializer < Oat::Serializer
  schema do
    type 'user'
    properties do |props|
      props.name item.name
      # etc
    end

    # Here we use the presenter API
    if item.include_friends?
      entities :friends, present_friends(item.friends), UserSerializer
    end
  end

  private
  def present_friends(friends)
    friends.map{|f| MiniUserPresenter.new(f) }
  end
end

Finally, you instantiate the serializer passing the right presenter

serializer = UserSerializer.new(MiniUserPresenter.new(user), context)

...

The first option gives you simple classes with little variance. Easy to test and understand. But with some duplication.

The second option gives you one class that does everything (less duplication) relying on presenters with well-defined APIs (which are testable) instead of hashes with a potentially unbounded number of options and permutations.

@mb21
Copy link
Author

mb21 commented Feb 29, 2016

The second options (with the presenters) is intriguing. However, it's still hard to specify what attributes to include for the nested resource (i.e. friends). I guess I'll have to write a special serializer for that case after all. Just thought this would be a quite common need...

@ismasan
Copy link
Owner

ismasan commented Feb 29, 2016

The nested resources can use one of the two options, too.

Using a separate serializer:

entities :friends, item.friends, NestedFriendSerializer

Using the presenter approach and the same serializer

entities :friends, present_friends(item.friends), UserSerializer

def present_friends(friends)
  friends.map{|friend| FriendPresenter.new(friend) }
end

Note that you can also write a custom base serializer that dries this up a bit for you.

class MySerializer < Oat::Serializer
  adapter Oat::Adapters::HAL # or whatever

  private

  # This is your own DSL
  def friends(friends)
    if item.include_friends?
      presented_friends = friends.map{|f| FriendPresenter.new(f) }
      entities :friends, presented_friends, self.class
    end
  end
end

So then your UserSerializer can be

class UserSerializer < MySerializer
  schema do
    # ... etc
    friends item.friends
  end
end

More on sub-classing here.

@ismasan ismasan closed this as completed Feb 29, 2016
@mb21
Copy link
Author

mb21 commented Feb 29, 2016

Thanks for your explanations! Looks I can get pretty far with this approach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants