Enforce that a Grape Entity always returns an array? - ruby

How can I enforce that my Grape Entity always returns an array (collection) even if its just a singular object? I have heard that some people create a helper method that gets called inside their endpoint, but I have not found any examples of anyone doing that, online.
The default functionality of an Entity is that it returns an object if only a single document (mongoid object) is returned. If a collection of documents is returned then it returns an array, I dont want my client application having to do a check every time to see if an object or an array got returned from my API.
## Resource (HTTP Endpoint)
desc 'List departments a user can and cannot access'
params do
requires :user_id
end
get :department_access do
#user = BACKBONE::User.find(#access_key.user_id)
requires_admin!
user = BACKBONE::User.find(params[:user_id])
can_access = BACKBONE::Department.user_can_access(user)
no_access = BACKBONE::Department.user_cannot_access(user)
present_success can_access
present :can_access, can_access, with: BACKBONE::Entities::DepartmentBase
present :no_access, no_access, with: BACKBONE::Entities::DepartmentBase
end
-
## Entity
module BACKBONE
module Entities
class DepartmentBase < BACKBONE::Entities::Mongoid
expose :name
expose :prefix
with_options(format_with: :mongo_id) do
expose :company_id
end
end
end
end
JSON Response
{
"status": "success",
"request_time": 0.009812,
"records": 1,
"can_access": {
"id": "59699d1a78cee4f8d07528fc",
"created_at": "2017-07-14T21:42:02.666-07:00",
"updated_at": "2017-07-14T21:42:02.666-07:00",
"name": "Tenant Improvement",
"prefix": "CACC",
"company_id": "596927fb670f6eec21c4f409"
},
"no_access": {
"id": "59699cca78cee4f8d07528fb",
"created_at": "2017-07-14T21:40:42.005-07:00",
"updated_at": "2017-07-14T21:40:42.005-07:00",
"name": "Field Operations",
"prefix": "CACC",
"company_id": "596927fb670f6eec21c4f409"
}
}

a coworker and I came up with a solution with a helper, create a helper method that always returns an array:
def present_array(key, data, entity)
d = (data.class.respond_to? :count) ? [data] : data
d = [] if d.nil?
present key, d, entity
end

Related

Design pattern for DRY approach to dynamic serializer fields

Basically, what I want to achieve is to make the list of fields in a serializer be optionally dynamic depending on whether the user has provided the list of fields they are interested in.
Here's my serializer for DRF serializer:
class DynamicFieldsModelSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
fields = self.context['request'].query_params.get('fields')
if fields:
fields = fields.split(',')
allowed = set(fields)
existing = set(self.fields.keys())
for field_name in existing - allowed:
self.fields.pop(field_name)
And my serializer:
class MySerializer(serializer_mixins.DynamicFieldsModelSerializer, serializers.ModelSerializer):
# fields...
This achieves the goal of not including the fields that the user has not mentioned in fields param of the queryset. But! We end up with actual query to the database that fetches the entire set of fields. This issue, in turn, could be solved by just adding the following code to the view:
class Rfs(ListAPIView):
serializer_class = MySerializer
def get_queryset(self):
qs = ...
fields = request.query_params.get('fields')
if fields:
qs = qs.only(*fields.split(','))
return qs
However, fills like two issues issues here:
non-DRY pattern since we have to repeat ourselves both in the view and the serializer
Sometimes it might be the case that the field name inside the queryset does not correspond exactly to the field name of the model.
So maybe there's some more elegant and Django-native solution for this usecase ?
I am using drf_queryfields
In dependence of your query_params your view will be modified
GET http://127.0.0.1:8000/snippets/
[
{
"id": 1,
"title": "",
"code": "foo = \"bar\"\n",
"linenos": false,
"language": "python",
"style": "friendly"
},
{
"id": 2,
"title": "",
"code": "print \"hello, world\"\n",
"linenos": false,
"language": "python",
"style": "friendly"
}
]
GET http://127.0.0.1:8000/snippets/?fields=id,code
[
{
"id": 1,
"code": "foo = \"bar\"\n",
},
{
"id": 2,
"code": "print \"hello, world\"\n",
}
]
I hope that´s it what you would like to achieve.

Nested Image Field in custom Page representation wagtail api

We are building a headless CMS with the wagtail API.
Our main model became very long, to make the representation cleaner and more easily accessible for the Frontend,
I am trying to group the different fields of my PageModel into sections.
But I don't manage to serialize the nested ImageField.
This is my PageModel:
class LandingPage(Page):
…
introduction_headline= models.CharField()
introduction_text = RichTextField()
introduction_icon = models.ForeignKey(
'main.CaptionImage',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name = '+',
)
…
I would like to group those fields into one section in the api, like so:
{
"id": 3,
"meta": {…},
"introduction_section": {
"introduction_headline": "intro head",
"introduction_text": "<p>intro text</p>",
"introduction_image": {
"id": 1,
"meta": {
"type": "main.CaptionImage",
"detail_url": "http://localhost/api/v2/images/1/",
"download_url": "/media/original_images/1.png"
},
"title": "german_design_image.png",
"caption": "Pretty Image"
},
},…
I managed to accomplish this in parts by writing a custom IntroductionSection - serializer:
class LandingPage(Page):
…
api_fields = [
APIField('introduction_section', serializer=IntroductionSectionField(source='*')),
…
]
class IntroductionSectionField(Field):
read_only = True
write_only = False
def to_representation(self, value):
return {
"introduction_headline" : value.introduction_headline,
"introduction_text" : value.introduction_text,
"introduction_image" : ?
}
But I simply can't figure out how to serialize the nested Image Field?
I want the same representation as the standard nested-relation-representation of the page model.
I tried around with get_related_field() method of the PageModel, tried to call the ImageSerializer, and all sorts of other things.

Ruby Active model serializer with jsonapi , how to characterise links

I am using active model serializer and jsonapi format
I need to get :
{
"data": {
"id": "1234",
"type": "search",
"relationships": {
"foo": {
"data": [
{
"id": "12",
"type": "foo"
}
],
"links": "/foo/12"
},
}
},
I have tried several configuration for links but it does not display as above
require 'active_model_serializers'
module test
class SearchSerializer < ActiveModel::Serializer
has_one :foo, data: true, links: {self: true, related: true}
type 'search'
end
end
I want to respect the jsonapi format
Is anybody with a good example of active model serializer and json_api showing "links" as shwon on above json?
At the moment only the following is displayed
{"data": {
"id": "1234",
"type": "search",
"relationships": {
"foo": {
"data": [
{
"id": "12",
"type": "foo"
}
]
}
},
Note also that I am trying to do that outside the rails framework.
Thanks
Sorry to answer now, but if it is still of anyone interests...
It is quite simple really. First of all it's important to notice that JSON:API specification tell us that the related link should be their URL extension and it's better to show that path through the Search(for that case specific) path, for example: http://localhost:3000/searches/:search_id/foo.
So our SearchSerializer should be something like:
class SearchSerializer < ActiveModel::Serializer
# The attributes
attributes :id, :whatever, :something, :another_one
has_one :foo do
link(:related) { contact_foo_url(object.id) }
end
end
Note also that at this point you should include the routes and the controller show method, as similiar to the bellow:
For the routes.rb:
Rails.application.routes.draw do
resources :searches do
resource :foo, only: [:show]
# Also a best practice to dispose a 'relationships' path for this kinda example
resource :foo, only: [:show], path: 'relationships/foo'
end
end
And for the FoosController.rb:
class FoosController < ApplicationController
before_action :set_search
# GET /searches/1/foo
def show
render json: #search.foo
end
private
# Use callbacks to share common setup or constraints between actions.
def set_search
#search = Search.find(params[:search_id])
end
end
Going off of FredyK's answer, JSONAPI-SERIALIZER, is a lightweight no-rails serializer for your ruby objects (even though it does have rails integration if desired) which could be a much simpler solution than using Active Record.
Also if you are not using rails, JSONAPI-SERIALIZER pairs really well with the new gem EASY-JSONAPI, which is a middleware, parser, and response validator for JSON:API requests and responses.
After looking what is available in ruby for JSONAPI without rails, I ended using the gem JSONAPI-serializers, It is much easier to set and lighter to load (less dependencies). This fit better with PORO
My serializer becomes
require_relative ./common_serializer
module JsonSerializer
class SearchSerializer < CommonSerializer
attributes :foo, include_links: true
def relationship_related_link(attribute_name)
nil
end
end
end
This gem is much easier to use as the methods which create the json can be changed in the serializer (or in a commonClass)

How do i serialize objects during enrichment with stream-django and django rest framework?

Im using stream-django with django REST framework and the enriched activities are throwing "not JSON serializable" on the objects returned from enrichment, which is as expected as they have not gone through any serializing.
How do i customize the enrichment process so that it returns a serialized object from my drf serializer and not the object itself?
Some example data, not enriched:
"is_seen": false,
"is_read": false,
"group": "19931_2016-04-04",
"created_at": "2016-04-04T08:53:42.601",
"updated_at": "2016-04-04T11:33:26.140",
"id": "0bc8c85a-fa59-11e5-8080-800005683205",
"verb": "message",
"activities": [
{
"origin": null,
"verb": "message",
"time": "2016-04-04T11:33:26.140",
"id": "0bc8c85a-fa59-11e5-8080-800005683205",
"foreign_id": "chat.Message:6",
"target": null,
"to": [
"notification:1"
],
"actor": "auth.User:1",
"object": "chat.Message:6"
}
The view:
def get(self, request, format=None):
user = request.user
enricher = Enrich()
feed = feed_manager.get_notification_feed(user.id)
notifications = feed.get(limit=5)['results']
enriched_activities=enricher.enrich_aggregated_activities(notifications)
return Response(enriched_activities)
I solved it by doing the following:
property tag on the model that returns the serializer class
#property
def activity_object_serializer_class(self):
from .serializers import FooSerializer
return FooSerializer
Then used this to serialize the enriched activities. Supports nesting.
#staticmethod
def get_serialized_object_or_str(obj):
if hasattr(obj, 'activity_object_serializer_class'):
obj = obj.activity_object_serializer_class(obj).data
else:
obj = str(obj) # Could also raise exception here
return obj
def serialize_activities(self, activities):
for activity in activities:
for a in activity['activities']:
a['object'] = self.get_serialized_object_or_str(a['object'])
# The actor is always a auth.User in our case
a['actor'] = UserSerializer(a['actor']).data
return activities
and the view:
def get(self, request, format=None):
user = request.user
enricher = Enrich()
feed = feed_manager.get_notification_feed(user.id)
notifications = feed.get(limit=5)['results']
enriched_activities = enricher.enrich_aggregated_activities(notifications)
serialized_activities = self.serialize_activities(enriched_activities)
return Response(serialized_activities)
The enrich step replaces string references into full Django model instances.
For example: the string "chat.Message:6" is replaced with an instance of chat.models.Message (same as Message.objects.get(pk=6)).
By default DRF does not know how to serialize Django models and fails with a serialization error. Luckily serializing models is a very simple task when using DRF. There is a built-in serializer class that is specific to Django models (serializers.ModelSerializer).
The documentation of DRF explains this process in detail here: http://www.django-rest-framework.org/api-guide/serializers/#modelserializer.
In your case you probably need to use nested serialization and make the serialization of the object field smart (that field can contain references to different kind of objects).
There is an open issue about this on Github: https://github.com/GetStream/stream-django/issues/38. Ideally this is something the library will provide as helper/example, so any code contribution / input will help making that happen.

Add unrelated collections in rabl

I have an employee controller which includes designation and department. Each one is separate model so i can access each api in the client side to fill each drop down. I need like view model concept in ASP.NET MVC.
For this i read work around like return 2 collections in one json object. But i am confused in the rabl file.
My controller code is below;
def employee_masters
#designations = Designation.all
#departments = Department.all
respond_with #designations
end
and my existing employee_masters.rabl is
collection :#designations
attributes :id, :name
please guide me how to change my rabl and controller code to achieve the behaviour. My expected json is below
{
"designations": [
{
"id": 1,
"name": "Program Manager"
},
{
"id": 2,
"name": "Project Manager"
},
{
"id": 3,
"name": "Tech Lead"
}
],
"departments": [
{
"id": 1,
"name": "IT"
},
{
"id": 2,
"name": "Support"
},
{
"id": 3,
"name": "Finance"
}
]
}
Modify:
I read this and got some idea. I created a class like below
class EmployeeViewModel
attr_accessor :departments, :designations
def initialize(departments, designations)
#departments = departments
#designations = designations
end
end
My controller code is
def employee_masters
#designations = Designation.all
#departments = Department.all
#employee_view_model = EmployeeViewModel.new(#departments, #designations)
respond_with #employee_view_model
end
And my rabl is
object #employee_view_model
child :departments do
extends 'api/v1/departments/show'
end
child :designations do
extends 'api/v1/designations/show'
end
Here shows error as Template::Error (undefined method `departments' for #<#:0x00000003872168>):
But when i change respond_with #employee_view_model to simply render :json => #employee_view_model it worked well and return the json. Please help to rectify rabl error

Resources