📜 ⬆️ ⬇️

Rails-based REST providers: a nightmare with views

With the development of browser-based MVC frameworks, Rails has often become mentioned in the context of a convenient framework for REST providers. We also use Rails for this purpose for a long time. There is, however, a very big problem: views. Views that describe the JSON structure for the response.

At first glance, everything is just fine. Nothing but .to_json or RABL , in some complex cases, is required. But then the situation is out of control. And there are endless cycles iterating through JSON builders in search of a better life.

Problem


Let's take for example the banking service. It consists of 30 models. Each model is represented by a CRUD-resource (each with 3-4 extending methods). Each model has 10-12 fields and these are usually long lines. And, of course, they are all connected. Up to 4-5 levels belongs_to .
')
At the same time it is important to remember that in real life JSON response is not just a direct dump of the model structure. It constantly meets the conditions (which attribute should get into the answer? Depends on another attribute) and custom methods.

The problem with views is that the REST service client needs a unique set of model fields for each such model and _for each method of this REST resource. And don't forget about nested entities.


Imagine that you have 4-5 sets of fields for each model. And this is just the beginning. After that, the model includes another. And the parent wants to see only 3 small fields that fully describe their relationship. And then this same model includes another parent who needs 2 other fields. This is about 10 different sets. And in each such set there may be additional conditions for the model to invest in itself.

Pain


The solution we started with is RABL. At first, it looks quite effective, but in practice it is completely unsuitable for complex representations. In reality, RABL is not so far gone from .to_json . We tried a lot of different builders and finally settled on the Jbuilder gem, which allows us to write extremely straightforward and simple code while avoiding syntactic noise.

But it did not help. What do we do in submissions to not dub the code? We use parshaly, correctly. Very soon, this led us to 10-15 parshalam for each model. Multiplying it by 30 models, we get 450 files that are trapped in app/views . This bunch is simply impossible to maintain.

Presenter pattern


A well-known approach to solving such problems is the Presenter pattern. Since our views are just Ruby code, the logical first step was to wrap it in classes.

 # example taken from http://quickleft.com/blog/presenters-as-a-solution-to-asjson-woes-in-rails-apis class Api::V1::ResourcePresenter attr_reader :resource def initialize( resource ) @resource = resource end def as_json( include_root = false ) data_hash = { :attr1 => @resource.attr1, :attr2 => @resource.attr2 } data_hash = { :resource => data_hash } if include_root data_hash end end 


This made it possible to reduce the number of files and group similar sets of fields into one method with input parameters.

Fine. We come to a 1 to 1 ratio for decorators describing sets of model fields. But now another problem has arisen: this code does not look like what is expected of Rails.

The best result allows to achieve gem Draper . With it, we can turn the above code into this:

 # app/decorators/article_decorator.rb class ArticleDecorator < ApplicationDecorator decorates :article def the_very_important_fields_set( include_root = false ) data_hash = { :attr1 => att1, :attr2 => attr2 } data_hash = { :resource => data_hash } if include_root data_hash end end 


But the problems did not end, now on the face of a clear violation of DRY. With a large number of fields, we will be stuck on huge hashes containing the same strings in the keys :foo and self.foo values.

Due to the fact that Draper podderivaet common Application-class is very easy to solve a small method, strapping. However, our goal is to improve the work with Jbuilder. And here it is worth noting that in Jbuilder there is already a method that solves this problem. We do not have to work with hashes, we can collect the answer from a set of strings using Jbuilder directly in our decorators.

At the time of this writing, Jbuilder does not allow inserting raw JSON strings during generation. However, there is another approach that can help achieve the desired result. There is an excellent fork (the pull-request has already been partially confirmed by the author and this functionality will soon fall into Jbuilder itself).

With this fork, we can modify our code as follows:

 # app/decorators/article_decorator.rb class ArticleDecorator < ApplicationDecorator decorates :article def the_very_important_fields_set( include_root = false ) data = Jbuilder.encode do |j| j.(self, :attr1, :attr2) end data = { :resource => data } if include_root end def another_set Jbuilder.encode do |j| j.(self, :attr1, :attr2, :attr3) j.cards card.basic_fields(:include_transactions) end end end 


Perhaps this is not the best example, but practice has shown that using Jbuilder in this way allows you to simplify large decorators very much.

As a result, we have the following structure:



Such a strategy may look slightly redundant. But it works very well for large data layers. This strategy allows REST providers to be accurate (to send exactly the set of fields needed for a particular method for each request), to avoid duplication and to allow maintaining simplicity of support.

PS Security


It seems that this strategy also allows to solve the problem of distribution of different fields for different roles. And indeed it is. However, this is usually not worth it - it can lead to duplication of logic. We need to not only authorize the data distribution, but also the change.

By the time we came to this strategy, we already had a great Heimdallr gem, which allows us to solve this problem much better. But this is a topic for a completely separate article :).

Source: https://habr.com/ru/post/140633/


All Articles