View architecture and control flow
Part of Umlaut Technical Overview
Control flow: Points of intervention for a Service
When you call ServiceType.view_data, there is actually a somewhat indirected process that happens. This is to give the Service several points of intervention to customize the process of looking at the actual data stored in a ServiceResponse, and turn it into the conventional key/value pairs for view_data dictionary. There are actually probably more points of intervention than needed, some are legacy. The standard easiest thing to do is to let the ServiceResponse itself -be- that hash-like collection of keys and values, since ServiceResponse implements . But there are several control points where this behavior can be customized.
ServiceType.view_data is actually just a convenience, it really calls service.view_data_from_service_type(self).
Service#view_data_from_service_type(service_type_join) will not normally be overridden by a subclass, although it could be for completely custom handling. But most service adaptor classes inherit the implementation from Service. Service#view_data_from_service_type will first look for a method in the Service adaptor subclass that is named to_[service type name] for the type in question, eg to_fulltext, or to_help. If such a method exists, it will be called, and is expected to return a hash-like object. So this is one place a Service can put custom logic for creating that hash-like object.
But if not found, the default implementation of Service#view_data_from_service_type will instead call Service#response_to_view_data( service_response ) (Note the argument is a ServiceResponse, not a ServiceType). So this is another method an individual service class can override to provide a custom implementation--in this case, a custom implementation that isn't worried about the particular type, since that information isn't available here.
And if that isn't provided, there is still an ultimate default action, which is just to return the ServiceResponse itself, which does implement the  method to be a (duck-typed) hash-like object.
So the point of all this is that an individual Service adaptor doesn't need to implement any of these methods, it can just store a ServiceResponse with the appropriate keys. For the majority of services, that will be sufficient. But if instead, manipulation at the point of display is convenient, there are various control points where the Service can be asked to create the hash-like view data object on the fly, instead of just using the ServiceResponse.
Clicks on service resposnes are sent through the LinkRouterController, with the id of the ServiceType involved.
Again, the default behavior is for LinkRouterController to simply look at the view data under the key :url, and redirect the user to the value found there. But there are again points of intervention where the individual Service sub-class can customize this behavior--perhaps to only calculate the url at the point of need, not at the point of ServiceResponse generation.
The controller actually calls Service#response_url( service_response ). The individual Service subclass can over-ride this method, which should return a url, to provide custom url generation.