View architecture and control flow

Revision as of 23:00, 6 February 2008 by Jrochkind (Talk | contribs)

Revision as of 23:00, 6 February 2008 by Jrochkind (Talk | contribs)

Part of [Umlaut Technical Overview]

Accessing response data

So the main interesting thing your View code wants to do is access data from ServiceResponses. There are some convenience methods to deal with the somewhat complicated data structures easily. There are also some abstractions in place to try to preserve encapsulation and loose coupling between views, data, and the Service adaptor code responsible for creating the data and dealing with any idiosyncracies.

Views are almost always interested in dealing with responses on a type-by-type basis, for instance all of the 'fulltext' responses. View code will normally call the ResolveHelper method #get_service_type(service_type). The argument should be the string name of the particular ServiceTypeValue type you are interested in--views are almost always interested in dealing with responses on a type-by-type basis. This will return an array of ServiceType join objects. But this is kind of tricky to deal with. This process does make a db trip with every call, so it's worth storing the results in a temporary variable rather than calling it multiple times.

What you really want is just the collection of key/value properties according to the standard conventions for keys and values in a ServiceType. (See the documentation of conventions [http://umlaut.rubyforge.org/api/classes/ServiceResponse.html ServiceResponse ).

You can get that by calling #view_data on each ServiceType returned. That will return a Hash-like object with those keys and values. "Hash-like" because it may or may not be an actual Hash--in the simplest case, it'll actually be a ServiceResponse, but it'll be something that responds to [] to get the keys and values.

Now the View code can use []on these 'view data' objects to get any properties of interest. We will discuss later how the control chain actually gives Services several points of intervention to customize this view_data dictionary.

When the view code wants to make a link out to that response data (ie, let the user click on the fulltext link to go view the fulltext, it should generate a link to the LinkRouter controller, using the ServiceType id (not the ServiceResponse id) as a parameter. All links are sent back through Umlaut, rather than direct to target, to links to be modified or generated at the point of click (see link_out_filters on the Services page), and to allow the possibility of Umlaut clickthrough statistics.

For example:

<%=
service_type_obj
view_data = service_type_obj.view_data
 link_to(view_data[:display_text], {:controller=>'link_router', 
         :id=>service_type_obj.id}, 
         'target'=>"_blank") 
%>

Control flow: Points of intervention for a Service

view_data generation

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.

url generation

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.