I'm confused as to why view logic is being moved to the Model layer, directly breaking MVC. Why is this better than using the tools in Rails that already exist for every use case here? Is there a performance increase?
I came here to say the exact same thing. After having worked on/scaled an API that served >100k requests per minute, I feel I can say with some experience that defining your JSON in your model is a very bad idea.
A classic example that we ran into repeatedly: Assume you want to have an endpoint that populates a user's profile (for the user to see). You probably want id, name, email, age, location, created_at, updated_at, etc. However, when exposing this same user instance through an endpoint that publicly aggregates users (e.g. search), sending over all those fields is 1) overkill, and 2) raises privacy concerns.
So far, my favorite approach to this problem uses the ActiveModel::Serializer gem (https://github.com/rails-api/active_model_serializers). It moves the JSON logic to something more akin to a view, and provides an easy way to present instances differently depending on the context.
I don't think there's actually a good way to solve this (that I know of) for applications with complex enough permission and role hierarchies.
Once you need a list of all the glass marbles with their attributes, but can only see the color of marbles you supervise, and only the radius of a marble if it has a shooter role and only the names and emails of marbles ranked above you... ...and so on... it becomes a situation where each result has to be filtered item by item pretty much. And the filter hierarchies will snowball like tribbles.
The `djangorestframework-composed-permissions` package for DRF gets pretty close to handling this situation effectively. Basically roles and permissions arbitrarily composable with boolean expressions.
Although you can model permission hierarchies, I think there are enough corner cases that it's worthwhile to declare permissions as explicitly as conveniently possible.
View models/decorators have served me very well in this case. The view model would take the marble instance, viewing user instance, etc and decide which attributes it should emit as JSON. The result is frequently very clean, very maintainable code.
ActiveModel::Serializer is really nice, but it doesn't seem particularly fast when large responses are involved - when we have 500+ comments to serialize it can take 400ms or more in the views code.
Is there a way to speed it up when building large responses?
Good question. If you consider each API endpoint a very specific serialization of a model, are the serialization parameters considered part of the model itself? (I would consider them such, and decided to approach the development with that in mind.)
I don't think so. The data as presented to the user through the API is a view concern. The model should never, ever know or care about how it is presented; serialization (which is used to marshal the data as a whole for transport and reconstitution) and presentation (which is used to transform the data into some form acceptable for consumption by an external entity) are separate processes with separate goals.
I think you've conflated serialization and presentation here; if I can't reconstitute the whole object from the serialization provided, then it's not a serialization, it's a presentation. A valid presentation might be "the serialization of this object", but "a subset of this object suitable for display" is not a valid serialization.
Good points. I think it boils down to an issue of semantics, though. FastAPI effectively eliminates (or hides) the View layer by allowing the Model to determine which fields it should allow the View to access. If you don't believe this is explicit enough (which could be a fair argument!), it's perfectly reasonable to suggest this "breaks" standard MVC conventions (as it can easily be construed as the View being part of the Model!).
I would argue that it merely allows for more automated (albeit implicit) generation of a View.
The goal here was to simplify the API endpoint creation process for the vast majority of use cases, and provide a standard output that could be easily recognized and consistently managed by any external software.
Well, it certainly meets those goals, but it does so by putting view logic into the model layer.
You might take a look at the Presenter pattern; Draper is an excellent implementation of it, which permits for this kind of "model-attached" view behavior, while maintaining proper MVC separations.
The goal of this project is for easy model accession / querying via JavaScript or some other external interface. The most powerful part of this implementation, I would argue, is the filtering of endpoint data via request.query_parameters.
This allows for "people/?age__gte=25" and other simple logic filters that can be communicated over the HTTP request, and is standard across all models and endpoints.
Yes, I'm sure you could --- in combination with ActiveModel::Serializer.
Our approach was meant to attack a combination of common, specific problems (standardized way to output API data, queryable with logic via HTTP parameters, a single SQL query to collect and aggregate data) with a dead simple, low-overhead solution.
The reason this project (and many others like it) exist is pretty obvious: Lots of people have to build different kinds APIs using rails, get some experience with Rails, discover it sucks for building APIs out of the box (to much anti-DRY repetition, doesn't deal with auth/permission dichotomy well), and then proceed to roll their own API layer/Auth layer/Permission layer because either:
a) information on existing solutions is hard to find/not obvious or otherwise in the world of unknowns to the author
or
b) existing solutions are too generalized or otherwise structured in ways that don't work for whatever real world particular business cases.
I ran into this with permissions in my current project. Nothing obvious in my realm of knowledge fit (sometimes I suspect this happens because projects only advertise their basic features clearly). So I now have a massive custom permission layer.
Sometimes you also end up with things like this by accident - you need one thing, it's not worth going whole hog to begin with, but then suddenly stuff gets tacked on until you have a whole system you never set out to build.
I like the idea, but I would be really reluctant to use a gem that has no tests, especially when the core methods are clearly not trivial, a bunch of them are ~100 lines long. Not that the number of lines tells how complicated a method is, but I think it is a good indicator.
Perfectly reasonable. Tests are on the (close) horizon. We'll be rolling out more iterations in the coming weeks on the path to 1.0 (support for other data layers is also on the to-do list). Feedback is much appreciated.
I was going to say the same thing. I wouldn't even think about touching this gem. Multiple methods close to 200 lines long, all in a single file with no test suite. I shudder to think what the cyclomatic complexity of the 'api_comparison' method is. Have fun contributing to this project.
There is grape, grape-swagger, grape-swagger rails, and a bunch of other gems for creating API's. They work amazingly well, and have a pretty active community. They have solved a ton of issues: Jsonp, Cors, authentication, rate limiting, versioning, and DOCUMENTATION. The auto-generated Swagger documentation is a real thing of beauty.
I'm a little scared that this builds SQL statements directly. Did you consider at least using prepared statements? Was there such an awful performance hit using ActiveRecord, or something that couldn't be done with it?
25 comments
[ 3.0 ms ] story [ 49.9 ms ] threadA classic example that we ran into repeatedly: Assume you want to have an endpoint that populates a user's profile (for the user to see). You probably want id, name, email, age, location, created_at, updated_at, etc. However, when exposing this same user instance through an endpoint that publicly aggregates users (e.g. search), sending over all those fields is 1) overkill, and 2) raises privacy concerns.
So far, my favorite approach to this problem uses the ActiveModel::Serializer gem (https://github.com/rails-api/active_model_serializers). It moves the JSON logic to something more akin to a view, and provides an easy way to present instances differently depending on the context.
Once you need a list of all the glass marbles with their attributes, but can only see the color of marbles you supervise, and only the radius of a marble if it has a shooter role and only the names and emails of marbles ranked above you... ...and so on... it becomes a situation where each result has to be filtered item by item pretty much. And the filter hierarchies will snowball like tribbles.
Although you can model permission hierarchies, I think there are enough corner cases that it's worthwhile to declare permissions as explicitly as conveniently possible.
API Docs: https://djangorestframework-composed-permissions.readthedocs...
Is there a way to speed it up when building large responses?
AMS have unfortunately no caching by default, and the development was stale for almost a year.
The mailing list is active again since the last couple days, so it might get fixed soon.
I think you've conflated serialization and presentation here; if I can't reconstitute the whole object from the serialization provided, then it's not a serialization, it's a presentation. A valid presentation might be "the serialization of this object", but "a subset of this object suitable for display" is not a valid serialization.
I would argue that it merely allows for more automated (albeit implicit) generation of a View.
The goal here was to simplify the API endpoint creation process for the vast majority of use cases, and provide a standard output that could be easily recognized and consistently managed by any external software.
You might take a look at the Presenter pattern; Draper is an excellent implementation of it, which permits for this kind of "model-attached" view behavior, while maintaining proper MVC separations.
This allows for "people/?age__gte=25" and other simple logic filters that can be communicated over the HTTP request, and is standard across all models and endpoints.
https://github.com/activerecord-hackery/ransack/wiki/Basic-S...
Our approach was meant to attack a combination of common, specific problems (standardized way to output API data, queryable with logic via HTTP parameters, a single SQL query to collect and aggregate data) with a dead simple, low-overhead solution.
a) information on existing solutions is hard to find/not obvious or otherwise in the world of unknowns to the author
or
b) existing solutions are too generalized or otherwise structured in ways that don't work for whatever real world particular business cases.
I ran into this with permissions in my current project. Nothing obvious in my realm of knowledge fit (sometimes I suspect this happens because projects only advertise their basic features clearly). So I now have a massive custom permission layer.
Sometimes you also end up with things like this by accident - you need one thing, it's not worth going whole hog to begin with, but then suddenly stuff gets tacked on until you have a whole system you never set out to build.
[0] https://github.com/intridea/grape [1] https://github.com/jrhe/grape-active_model_serializers