doc/book/en/development/vreg/vreg.rst
branchstable
changeset 5147 70181998897f
parent 5146 fe56baf63ecb
child 5148 ec0ea7366066
equal deleted inserted replaced
5146:fe56baf63ecb 5147:70181998897f
     1 .. VRegistry:
       
     2 
       
     3 The `VRegistry`
       
     4 ---------------
       
     5 
       
     6 The `VRegistry` can be seen as a two level dictionary. It contains all objects
       
     7 loaded dynamically to build a |cubicweb| application. Basically:
       
     8 
       
     9 * first level key return a *registry*. This key corresponds to the `__registry__`
       
    10   attribute of application object classes
       
    11 
       
    12 * second level key return a list of application objects which share the same
       
    13   identifier. This key corresponds to the `__regid__` attribute of application
       
    14   object classes.
       
    15 
       
    16 A *registry* hold a specific kind of application objects. You've for instance
       
    17 a registry for entity classes, another for views, etc...
       
    18 
       
    19 The `VRegistry` has two main responsibilities:
       
    20 
       
    21 - being the access point to all registries
       
    22 
       
    23 - handling the registration process at startup time, and during automatic
       
    24   reloading in debug mode.
       
    25 
       
    26 
       
    27 .. _AppObjectRecording:
       
    28 
       
    29 Managing the recording process
       
    30 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       
    31 
       
    32 Details of the recording process
       
    33 ````````````````````````````````
       
    34 
       
    35 .. index::
       
    36    vregistry: registration_callback
       
    37 
       
    38 On startup, |cubicweb| have to load application objects defined in its library
       
    39 and in cubes used by the instance. Application objects from the library are
       
    40 loaded first, then those provided by cubes are loaded in an ordered way (e.g. if
       
    41 your cube depends on an other, objects from the dependancy will be loaded
       
    42 first). Cube's modules or packages where appobject are looked at is explained in
       
    43 :ref:`cubelayout`.
       
    44 
       
    45 For each module:
       
    46 
       
    47 * by default all objects are registered automatically
       
    48 
       
    49 * if some objects have to replace other objects, or be included only if some
       
    50   condition is true, you'll have to define a `registration_callback(vreg)`
       
    51   function in your module and explicitly register **all objects** in this module,
       
    52   using the api defined below.
       
    53 
       
    54 .. Note::
       
    55     Once the function `registration_callback(vreg)` is implemented in a module,
       
    56     all the objects from this module have to be explicitly registered as it
       
    57     disables the automatic objects registration.
       
    58 
       
    59 
       
    60 API for objects registration
       
    61 ````````````````````````````
       
    62 
       
    63 Here are the registration methods that you can use in the `registration_callback`
       
    64 to register your objects to the `VRegistry` instance given as argument (usually
       
    65 named `vreg`):
       
    66 
       
    67 .. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register_all
       
    68 .. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register_and_replace
       
    69 .. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register
       
    70 .. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register_if_interface_found
       
    71 .. automethod:: cubicweb.cwvreg.CubicWebVRegistry.unregister
       
    72 
       
    73 
       
    74 Examples
       
    75 ````````
       
    76 .. sourcecode:: python
       
    77 
       
    78    # web/views/basecomponents.py
       
    79    def registration_callback(vreg):
       
    80       # register everything in the module except SeeAlsoComponent
       
    81       vreg.register_all(globals().values(), __name__, (SeeAlsoVComponent,))
       
    82       # conditionally register SeeAlsoVComponent
       
    83       if 'see_also' in vreg.schema:
       
    84           vreg.register(SeeAlsoVComponent)
       
    85 
       
    86 In this example, we register all application object classes defined in the module
       
    87 except `SeeAlsoVComponent`. This class is then registered only if the 'see_also'
       
    88 relation type is defined in the instance'schema.
       
    89 
       
    90 .. sourcecode:: python
       
    91 
       
    92    # goa/appobjects/sessions.py
       
    93    def registration_callback(vreg):
       
    94       vreg.register(SessionsCleaner)
       
    95       # replace AuthenticationManager by GAEAuthenticationManager
       
    96       vreg.register_and_replace(GAEAuthenticationManager, AuthenticationManager)
       
    97       # replace PersistentSessionManager by GAEPersistentSessionManager
       
    98       vreg.register_and_replace(GAEPersistentSessionManager, PersistentSessionManager)
       
    99 
       
   100 In this example, we explicitly register classes one by one:
       
   101 
       
   102 * the `SessionCleaner` class
       
   103 * the `GAEAuthenticationManager` to replace the `AuthenticationManager`
       
   104 * the `GAEPersistentSessionManager` to replace the `PersistentSessionManager`
       
   105 
       
   106 If at some point we register a new appobject class in this module, it won't be
       
   107 registered at all without modification to the `registration_callback`
       
   108 implementation. The previous example will register it though, thanks to the call
       
   109 to the `register_all` method.
       
   110 
       
   111 .. _Selection:
       
   112 
       
   113 Runtime objects selection
       
   114 ~~~~~~~~~~~~~~~~~~~~~~~~~
       
   115 
       
   116 Now that we've all application objects loaded, the question is : when I want some
       
   117 specific object, for instance the primary view for a given entity, how do I get
       
   118 the proper object ? This is what we call the **selection mechanism**.
       
   119 
       
   120 As explained in the :ref:`Concepts` section:
       
   121 
       
   122 * each application object has a **selector**, defined by its `__select__` class attribute
       
   123 
       
   124 * this selector is responsible to return a **score** for a given context
       
   125 
       
   126   - 0 score means the object doesn't apply to this context
       
   127 
       
   128   - else, the higher the score, the better the object suits the context
       
   129 
       
   130 * the object with the higher score is selected.
       
   131 
       
   132 .. Note::
       
   133 
       
   134   When no score is higher than the others, an exception is raised in development
       
   135   mode to let you know that the engine was not able to identify the view to
       
   136   apply. This error is silenced in production mode and one of the objects with
       
   137   the higher score is picked.
       
   138 
       
   139   In such cases you would need to review your design and make sure your selectors
       
   140   or appobjects are properly defined.
       
   141 
       
   142 For instance, if you are selecting the primary (eg `__regid__ = 'primary'`) view (eg
       
   143 `__registry__ = 'views'`) for a result set containing a `Card` entity, 2 objects
       
   144 will probably be selectable:
       
   145 
       
   146 * the default primary view (`__select__ = implements('Any')`), meaning
       
   147   that the object is selectable for any kind of entity type
       
   148 
       
   149 * the specific `Card` primary view (`__select__ = implements('Card')`,
       
   150   meaning that the object is selectable for Card entities
       
   151 
       
   152 Other primary views specific to other entity types won't be selectable in this
       
   153 case. Among selectable objects, the implements selector will return a higher
       
   154 score than the second view since it's more specific, so it will be selected as
       
   155 expected.
       
   156 
       
   157 .. _SelectionAPI:
       
   158 
       
   159 API for objects selections
       
   160 ``````````````````````````
       
   161 
       
   162 Here is the selection API you'll get on every registry. Some of them, as the
       
   163 'etypes' registry, containing entity classes, extend it. In those methods,
       
   164 `*args, **kwargs` is what we call the **context**. Those arguments are given to
       
   165 selectors that will inspect there content and return a score accordingly.
       
   166 
       
   167 .. automethod:: cubicweb.vregistry.Registry.select
       
   168 
       
   169 .. automethod:: cubicweb.vregistry.Registry.select_or_none
       
   170 
       
   171 .. automethod:: cubicweb.vregistry.Registry.possible_objects
       
   172 
       
   173 .. automethod:: cubicweb.vregistry.Registry.object_by_id
       
   174 
       
   175 
       
   176 .. _Selectors:
       
   177 
       
   178 Selectors
       
   179 ---------
       
   180 
       
   181 Using and combining existant selectors
       
   182 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       
   183 
       
   184 You can combine selectors using the `&`, `|` and `~` operators.
       
   185 
       
   186 When two selectors are combined using the `&` operator (formerly `chainall`), it
       
   187 means that both should return a positive score. On success, the sum of scores is returned.
       
   188 
       
   189 When two selectors are combined using the `|` operator (former `chainfirst`), it
       
   190 means that one of them should return a positive score. On success, the first
       
   191 positive score is returned.
       
   192 
       
   193 You can also "negate" a selector by precedeing it by the `~` unary operator.
       
   194 
       
   195 Of course you can use parens to balance expressions.
       
   196 
       
   197 .. Note:
       
   198   When one chains selectors, the final score is the sum of the score of each
       
   199   individual selector (unless one of them returns 0, in which case the object is
       
   200   non selectable)
       
   201 
       
   202 
       
   203 Example
       
   204 ~~~~~~~
       
   205 
       
   206 The goal: when on a Blog, one wants the RSS link to refer to blog entries, not to
       
   207 the blog entity itself.
       
   208 
       
   209 To do that, one defines a method on entity classes that returns the RSS stream
       
   210 url for a given entity. The default implementation on
       
   211 :class:`~cubicweb.entities.AnyEntity` (the generic entity class used as base for
       
   212 all others) and a specific implementation on Blog will do what we want.
       
   213 
       
   214 But when we have a result set containing several Blog entities (or different
       
   215 entities), we don't know on which entity to call the aforementioned method. In
       
   216 this case, we keep the generic behaviour.
       
   217 
       
   218 Hence we have two cases here, one for a single-entity rsets, the other for
       
   219 multi-entities rsets.
       
   220 
       
   221 In web/views/boxes.py lies the RSSIconBox class. Look at its selector ::
       
   222 
       
   223   class RSSIconBox(ExtResourcesBoxTemplate):
       
   224     """just display the RSS icon on uniform result set"""
       
   225     __select__ = ExtResourcesBoxTemplate.__select__ & non_final_entity()
       
   226 
       
   227 It takes into account:
       
   228 
       
   229 * the inherited selection criteria (one has to look them up in the class
       
   230   hierarchy to know the details)
       
   231 
       
   232 * :class:`~cubicweb.selectors.non_final_entity`, which filters on result sets
       
   233   containing non final entities (a 'final entity' being synonym for entity
       
   234   attributes type, eg `String`, `Int`, etc)
       
   235 
       
   236 This matches our second case. Hence we have to provide a specific component for
       
   237 the first case::
       
   238 
       
   239   class EntityRSSIconBox(RSSIconBox):
       
   240     """just display the RSS icon on uniform result set for a single entity"""
       
   241     __select__ = RSSIconBox.__select__ & one_line_rset()
       
   242 
       
   243 Here, one adds the :class:`~cubicweb.selectors.one_line_rset` selector, which
       
   244 filters result sets of size 1. Thus, on a result set containing multiple
       
   245 entities, :class:`one_line_rset` makes the EntityRSSIconBox class non
       
   246 selectable. However for a result set with one entity, the `EntityRSSIconBox`
       
   247 class will have a higher score than `RSSIconBox`, which is what we wanted.
       
   248 
       
   249 Of course, once this is done, you have to:
       
   250 
       
   251 * fill in the call method of `EntityRSSIconBox`
       
   252 
       
   253 * provide the default implementation of the method returning the RSS stream url
       
   254   on :class:`~cubicweb.entities.AnyEntity`
       
   255 
       
   256 * redefine this method on `Blog`.
       
   257 
       
   258 
       
   259 When to use selectors?
       
   260 ~~~~~~~~~~~~~~~~~~~~~~
       
   261 
       
   262 Selectors are to be used whenever arises the need of dispatching on the shape or
       
   263 content of a result set or whatever else context (value in request form params,
       
   264 authenticated user groups, etc...). That is, almost all the time.
       
   265 
       
   266 .. XXX add and example of a single view w/ big "if" inside splitted into two views with appropriate selectors.
       
   267 
       
   268 
       
   269 .. _CustomSelectors:
       
   270 
       
   271 Defining your own selectors
       
   272 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
       
   273 .. autoclass:: cubicweb.appobject.Selector
       
   274    :members: __call__
       
   275 
       
   276 .. autofunction:: cubicweb.appobject.objectify_selector
       
   277 
       
   278 Selectors __call__ should *always* return a positive integer, and shall never
       
   279 return `None`.
       
   280 
       
   281 Useful abstract base classes for 'entity' selectors:
       
   282 
       
   283 .. autoclass:: cubicweb.selectors.EClassSelector
       
   284 .. autoclass:: cubicweb.selectors.EntitySelector
       
   285 
       
   286 Also, think to use the `lltrace` decorator on your selector class' :meth:`__call__` method
       
   287 or below the :func:`objectify_selector` decorator of your selector function so it gets
       
   288 traceable when :class:`traced_selection` is activated (see :ref:DebuggingSelectors).
       
   289 
       
   290 .. autofunction:: cubicweb.selectors.lltrace
       
   291 
       
   292 
       
   293 .. _DebuggingSelectors:
       
   294 
       
   295 Debugging selection
       
   296 ~~~~~~~~~~~~~~~~~~~
       
   297 
       
   298 Once in a while, one needs to understand why a view (or any AppObject) is, or is
       
   299 not selected appropriately. Looking at which selectors fired (or did not) is the
       
   300 way. There exists a traced_selection context manager to help with that, *if
       
   301 you're running your instance in debug mode*.
       
   302 
       
   303 Here is an example:
       
   304 
       
   305 .. sourcecode:: python
       
   306 
       
   307      from cubicweb.selectors import traced_selection
       
   308      with traced_selection():
       
   309          mycomp = self._cw.vreg['views'].select('wfhistory', self._cw, rset=rset)
       
   310 
       
   311 Don't forget the 'from __future__ import with_statement' at the module
       
   312 top-level if you're using python 2.5.
       
   313 
       
   314 This will yield additional WARNINGs in the logs, like this::
       
   315 
       
   316     2009-01-09 16:43:52 - (cubicweb.selectors) WARNING: selector one_line_rset returned 0 for <class 'cubicweb.web.views.basecomponents.WFHistoryVComponent'>
       
   317 
       
   318 You can also give to traced_selection the registry ids of objects on which to debug
       
   319 you want to debug selection ('wfhistory' in the example above).
       
   320 
       
   321 
       
   322 
       
   323 .. |cubicweb| replace:: *CubicWeb*