Thread: [Modeling-cvs] ProjectModeling/Modeling/doc/UserGuide CodeRequirements.tex,NONE,1.1 CustomObject.tex
Status: Abandoned
Brought to you by:
sbigaret
Update of /cvsroot/modeling/ProjectModeling/Modeling/doc/UserGuide
In directory sc8-pr-cvs1:/tmp/cvs-serv27433/Modeling/doc/UserGuide
Modified Files:
CustomObject.tex ManipulatingGraphOfObjects.tex
NestedEditingContext.tex
Added Files:
CodeRequirements.tex
Log Message:
documentation: moved the section on KeyValueCoding and
RelationshipManipulation to an 'advanced techniques' part. Added raw
material about fetching and inheritance. Updated to reflect the API
change.
--- NEW FILE: CodeRequirements.tex ---
\chapter{Functionalities for Object Management\label{code-requirements}}
All python objects must inherit from the framework class
\class{CustomObject}, to be thus connected to the framework's core.
The \class{CustomObject} class defines all the necessary logic to make it
possible to ensure the objects' persistency within the RDBMS, as well as
numerous functionalities to help you with your own business logic. For those
purposes, it works with a model, from which the classes are generally
generated.
This chapter presents some important issues that you should not forget when
coding and customizing your classes and logic--particularly important are the
methods \method{willRead} and \method{willChange}. We will also look at other
functionalities you can take advantage of, among which: validation of the
referential constraints and custom validation logic.
Note that, you may start working directly with the generated classes for your
model--adding, modifying, and getting data (as explained in chapter
\ref{editing-context})--and letting the framework do the above-mentioned tasks
transparently. The information here is provided to allow you to better
understand how the framework performs these tasks, and to thus make it easier
to add to, or modify, the framework's default behaviour to better suit your
application needs, e.g. to add custom validation logic.
{\bf Note}: Only a small sampling of the methods and functionalities of the
\class{CustomObject} class, and other supporting classes, are mentioned here.
For a complete picture you will need to look at the source doc strings, for
\class{CustomObject} itself, as well as the doc strings for the interfaces it
implements, namely \class{RelationshipManipulation},
\class{KeyValueCoding} (these two interfaces are exposed in details in
chapter~\ref{custom-object}) and \class{DatabaseObject}.
\section{The python package generated from an XML model \label{basics-CustomObject}}
We take the model \code{AuthorBooks} as an example--you'll find it at:\\
\file{Modeling/tests/testPackages/AuthorBooks/model_AuthorBooks.xml}.
In this model, we have two entities, \class{Writer} and \class{Book}:
\begin{verbatim}
+--------+ +------+
| Writer |<-author----------books->| Book |
|--------| |------|
| | | |
| |<-pygmalion--+ | |
+--------+ | +------+
| |
+-----------------+
\end{verbatim}
When you generate the python code for that model, you get a
\module{AuthorBooks} package.
\begin{itemize}
\item \module{__init__.py} takes care to load the model within the default
\class{ModelSet},
\item the model xml file is dropped within the package,
\item you get two modules, \module{Writer} and \module{Book}, containing,
respectively the classes \class{Book} and \class{Writer}.
\end{itemize}
Let's have a look at the \class{Book} class -- I won't copy it here, go to
\file{Modeling/tests/testPackages/AuthorBooks/Book.py} and keep an eye on
it (or generate your own copy using the ZModelizationTool or the provided
scripts).
Let's take a look at the code. After the initial imports, we get:
\begin{enumerate}
\item class declaration:
\begin{verbatim}
class Book (CustomObject):
\end{verbatim}
i.e.: every object should derive from \class{CustomObject}, which
defines the appropriate methods.
\item Initializer: defines some default values for your attributes.
{\bf Note}: It {\bf must} be possible to call the \method{__init__}
with no arguments at all. If you want to add arguments to
\method{__init__}, each one should have default values. The reason for this is
that the framework relies on a call to \code{__init__()}, without any
arguments, when it needs to create an instance before populating it with data
fetched from the database.
\item \code{def entityName(self)}: this is the way the framework currently
binds an object to its entity. This should be changed (see TODO)
\item Then you get setters and getters for the attributes, whose exact form
depends on their nature (attributes, to-one or to-many relationships).
There is also some validation code ready to be used. See
\ref{customobject-validation}, ``Validation'' for details.
In getters and setters, notice the methods \method{willRead()} and
\method{willChange()}. These methods are defined by \class{CustomObject}.
\begin{itemize}
\item \method{willRead} informs the object that we need the values stored in
the database. This is because objects can be ``{\em faults}'' (ZODB
speaking, they are ghosts). This method's job is to initialize the
object.
\item \method{willChange} informs the object that it is about to change. This
is part of the \class{Observing} interface, and its purpose is to notify
the \class{EditingContext} that an object is about to change; the
\class{EditingContext} needs this to keep track of changes in its graph
of objects. ZODB speaking, this is what the mix-in class
\class{Persistent} does transparently for immutable attributes (see
also: TODO).
Of course, \method{willChange} invokes \method{willRead} when
appropriate.
\begin{notice}[warning]
It is your responsability to call \method{willRead} and
\method{willChange} when you are about to, respectively, access or
change a class attribute corresponding to a Model's Attribute or
Relationship; if you do not, the \class{EditingContext}, which is
responsible for examining the changes and making them persistent, is
likely to do only part of its job, possibly resulting in objects being
partially saved, or not at all.
\end{notice}
\end{itemize}
\end{enumerate}
That's it. You can then start coding your own business logic. Objects are
always inserted, deleted or updated in a database via an instance of the
\class{EditingContext} class (see chapter \ref{editing-context}, ``Working
with your objects: insert, changes, deletion'').
\section{Automatic validation of referential and business-logic constraints
\label {customobject-validation}}
A major part of the checks made at runtime are validation operations --for
example, you want to verify that a zipCode has a valid format, that the
age of a person is a positive integer, etc.
The framework defines an interface, \class{Validation}, which allows you
and the framework as well to trigger these validations when needed, in a
defined scheme. You automatically get these functionalities when your
class inherits from \class{Modeling.CustomObject} and corresponds to an
entity defined in a model.
There is, roughly, two kinds of validation checks. The first one consists
in checking individual properties of an object (such as: check that a
value for a given key is of the proper type, that its width is ok if it is
a 'string', etc.); the second one considers objects as a whole: its
checks are like post-conditions, or consistency checking. We will see in
the following how both can be adjusted and triggered.
\subsection{Integrity constraints derived from the underlying model\label{customobject-validation-integrity}}
When you define a model, you also define a number of constraints that has
to be checked. For example, lower- and upper- bounds of a relationship's
multiplicity, say (min=1, max=3), specify that an object cannot have less
than one object (of a certain type!) in relation with itself, and no more
than three as well. You can also mark an attribute as 'required' to avoid
it being \constant{None}.
The framework naturally verifies these constraints: the checking occurs
automatically when an EditingContext is about to save changes (we will see
hereafter how this can be manually triggered).
The constraints that are enforced are the following:
\begin{itemize}
\item Attributes:
\begin{itemize}
\item type of the stored value,
\item if it is marked as 'required', it shouldn't be \constant{None}
\end{itemize}
\item Relationships:
\begin{itemize}
\item the type of the objects in relation with oneself,
\item the number of objects in relation {\em vs.} the relationship's
multiplicity.
\end{itemize}
\end{itemize}
\subsection{Checking constraints: key by key\label{customobject-validation-by-key}}
To verify a given value for a specific attribute, say on \var{lastName}
for a \class{Writer} object, you use the method
\method{validateValueForKey}:
\begin{verbatim}
aWriter.validateValueForKey('Cleese', 'lastName')
\end{verbatim}
This check that the value \code{'Cleese'} is a valid value for
\var{Writer.lastName}. Note that this value is directly given as a
parameter and is not stored, nor searched, within the object: the
validation can thus be done without actually assigning the value to an
attribute (but the ``global'' checks we'll see hereafter do not work that
way).
\subsubsection{Return code\label{customobject-validation-return-code}}
When the value is valid, \method{validateValueForKey} simply
returns. But if it encounters an error, it raises the exception
\class{Validation.ValidationException}. This exception holds a
dictionary gathering the reasons of failure; this dictionary has the
following characteristics:
\begin{itemize}
\item its keys are names of attributes for which the validation failed,
\item the corresponding values indicates the reason(s) for the failure.
\end{itemize}
For example, suppose we called \code{aWriter.validateValueForKey('',
'lastName')} and that attribute \var{lastName} is marked as 'required',
the raised exception's dictionary is then::
\begin{verbatim}
{'lastName': ['Key is required but value is void',],
}
\end{verbatim}
If \method{validateValueForKey} raises an exception, the dictionary will
only contain one key: the one that was supplied as the parameter 'key'.
Its corresponding value is a list of all observed errors; here, it
corresponds to the constant \constant{Validation.REQUIRED} defined in
the Validation interface. You will find there the complete list of
possible error codes.
\subsubsection{How to define your own validation logic\label{customobject-validation-custom-logic}}
Now suppose that you want to enforce that the value stored in the
\var{lastName} attribute does not begin with a \code{'J'}. This sort of
constraint is not expressed within the model, so you have
to write some code for that, and you want that to be checked
transparently, along with other constraints.
The \method{validateValueForKey} is ready for such a situation: it
expects you to write a method named \method{validateLastName} (note the
capitalized letter in \method{validate{\bf L}astName}). If
it exists, then it gets automatically called. This is how you would
write it:
\begin{verbatim}
from Modeling import Validation
def validateLastName(self, aValue):
"Checks that the provided value does not begin with a 'J'"
if aValue.find('J')==0:
raise Validation.Exception
return
\end{verbatim}
Let's call \code{aWriter.validateValueForKey("Jleese", "lastName")} one
more time, catch the exception, and checks its error dictionary:
\begin{verbatim}
{ 'lastName': ['Custom validation of key failed'],
}
\end{verbatim}
Our own validation method has been taken into account, as expected, and
the value \constant{Validation.CUSTOM_KEY_VALIDATION}, part of the
errors for key \var{lastName}, signals it.
\subsection{\class{Validation.ValidationException}: the list of error-codes \label{customobject-validation-list-errors}}
The values that can be found in the list of reasons of failures for a
given key are the following (extracted from the \class{Validation}
interface):
\begin{verbatim}
REQUIRED="Key is required but value is void"
TYPE_MISMATCH="Wrong type"
CUSTOM_KEY_VALIDATION="Custom validation of key failed"
LOWER_BOUND="Lower bound of key's multiplicity constraint not fulfilled"
UPPER_BOUND="Upper bound of key's multiplicity constraint not fulfilled"
DELETE_DENY_KEY="Key has rule 'DELETE_DENY' but object still holds object(s)"
CUSTOM_OBJECT_VALIDATION="Custom validation of object as a whole failed"
OBJECT_WIDE="Validation of object as a whole failed"
OBJECT_WIDE_KEY='OBJECT_WIDE_VALIDATION'
\end{verbatim}
The last three items occurs when an object is validated as a whole, not
when its individual properties are checked; all other values can be
returned by \method{validateValueForKey}.
\subsection{Validating an object ``as a whole''\label{customobject-validation-invariants}}
We know how specific properties can be checked, but we still need a way to
validate the consistence of the whole object, so that e.g. invariants of
an object are ensured before its data is stored in the database. The
framework defines the following methods for that purpose:
\begin{itemize}
\item \method{validateForInsert()}
\item \method{validateForUpdate()}
\item \method{validateForSave()}
\item \method{validateForDelete()}
\end{itemize}
The two first methods simply calls the third one. The fourth one verifies
a particular set of constraints we'll see in a moment.
\subsubsection{\method{validateForSave()}\label{validate-for-save}}
This method iterates on every key (attribute {\bf and} relation) and
calls \method{validateValueForKey} for each of them, using the stored
value as the parameter 'value'.
\begin{notice}
if you defined your own validation logic for some keys, they are called as
well, as expected and seen above.
\end{notice}
You can also define your own global validation method, like:
\begin{verbatim}
from Modeling import Validation
def validateForSave(self):
"Validate "
error=Validation.Exception()
try:
CustomObject.validateForSave(self)
except Validation.Exception, error:
error.aggregateException(error)
# Your custom bizness logic goes here
if self.getFirstName()=='John': # No John, except the One
if self.getLastName()!='Cleese':
error.aggregateError(Validation.CUSTOM_OBJECT_VALIDATION,
Validation.OBJECT_WIDE_KEY)
error.finalize() # raises, if appropriate
\end{verbatim}
Now suppose that our object \code{aWriter} stores \code{Jleese} for
\var{lastName} and \code{John} for \var{firstName}. The dictionary of
errors stored in the raised exception, after \method{validateForSave} is
called, will be:
\begin{verbatim}
{ 'OBJECT_WIDE_VALIDATION':
['Validation of object as a whole failed',
'Custom validation of object as a whole failed'],
'lastName':
['Custom validation of key failed'],
}
\end{verbatim}
The value \constant{Validation.OBJECT_WIDE_KEY} is used to report global
validation errors. You will find it {\bf as soon as an error has been
detected while validating}. Here you find an other value for that key,
\constant{Validation.CUSTOM_OBJECT_VALIDATION}, indicating that our own
code did not validate the values as well. Note that the validation for
key \var{lastName} has failed as well and is also reported.
\begin{notice}[warning]
{\bf IMPORTANT NOTE} -- contrary to what happens with methods
\method{validate<AttributeName>}, the method \method{validateForSave()}
we defined here overrides the definition inherited from
\class{CustomObject}. Hence, it is very important not to forget calling
the superclass' implementation, as described in the code above.
\end{notice}
\subsubsection{\method{validateForDelete()}\label{validate-for-delete}}
TBD.
\subsection{Misc.\label{customobject-misc}}
A \class{Validation.ValidationException} object defines \method{__str__},
so for the previous example, \code{str(error)} says::
\begin{verbatim}
Validation for key OBJECT_WIDE_VALIDATION failed:
- Validation of object as a whole failed
- Custom validation of object as a whole failed
Validation for key name failed:
- Custom validation of key failed
\end{verbatim}
Index: CustomObject.tex
===================================================================
RCS file: /cvsroot/modeling/ProjectModeling/Modeling/doc/UserGuide/CustomObject.tex,v
retrieving revision 1.4
retrieving revision 1.5
diff -C2 -d -r1.4 -r1.5
*** CustomObject.tex 19 May 2003 16:05:16 -0000 1.4
--- CustomObject.tex 4 Jul 2003 17:06:52 -0000 1.5
***************
*** 1,141 ****
! \chapter{Functionalities for Object Management\label{custom-object}}
!
! All python objects must inherit from the framework class
! \class{CustomObject}, to be thus connected to the framework's core.
! The \class{CustomObject} class defines all the necessary logic to make it
! possible to ensure the objects' persistency within the RDBMS, as well as
! numerous functionalities to help you with your own business logic. For those
! purposes, it works with a model, from which the classes are generally
! generated.
!
! This chapter presents some important issues that you should not forget when
! coding and customizing your classes and logic--particularly important are the
! methods \method{willRead} and \method{willChange}. We will also look at other
! functionalities you can take advantage of, among which: validation of the
! referential constraints, custom validation logic, and dynamic manipulation of
! an object's properties and relationships.
!
! Note that, you may start working directly with the generated classes for your
! model--adding, modifying, and getting data (as explained in chapter
! \ref{editing-context})--and letting the framework do the above-mentioned tasks
! transparently. The information here is provided to allow you to better
! understand how the framework performs these tasks, and to thus make it easier
! to add to, or modify, the framework's default behaviour to better suit your
! application needs, e.g. to add custom validation logic.
!
! {\bf Note}: Only a small sampling of the methods and functionalities of the
! \class{CustomObject} class, and other supporting classes, are mentoined here.
! For a complete picture you will need to look at the source doc strings, for
! \class{CustomObject} itself, as well as the doc strings for the interfaces it
! implements, namely \class{RelationshipManipulation} and
! \class{DatabaseObject}.
!
!
! \section{The python package generated from an XML model \label{basics-CustomObject}}
!
! We take the model \code{AuthorBooks} as an example--you'll find it at:
! \file{Modeling/tests/testPackages/AuthorBooks/model_AuthorBooks.xml}.
!
!
! In this model, we have two entities, \class{Writer} and \class{Book}:
\begin{verbatim}
! +--------+ +------+
! | Writer |<-author----------books->| Book |
! |--------| |------|
! | | | |
! | |<-pygmalion--+ | |
! +--------+ | +------+
! | |
! +-----------------+
\end{verbatim}
! When you generate the python code for that model, you get a
! \module{AuthorBooks} package.
!
! \begin{itemize}
! \item \module{__init__.py} takes care to load the model within the default
! \class{ModelSet},
!
! \item the model xml file is dropped within the package,
!
! \item you get two modules, \module{Writer} and \module{Book}, containing,
! respectively the classes \class{Book} and \class{Writer}.
!
! \end{itemize}
!
! Let's have a look at the \class{Book} class -- I won't copy it here, go to
! \file{Modeling/tests/testPackages/AuthorBooks/Book.py} and keep an eye on
! it (or generate your own copy using the ZModelizationTool or the provided
! scripts).
!
!
! Let's take a look at the code. After the initial imports, we get:
!
! \begin{enumerate}
! \item class declaration:
! \begin{verbatim}
! class Book (CustomObject):
! \end{verbatim}
! i.e.: every object should derive from \class{CustomObject}, which
! defines the appropriate methods.
!
! \item Initializer: defines some default values for your attributes.
! {\bf Note}: It {\bf must} be possible to call the \method{__init__}
! with no arguments at all. If you want to add arguments to
! \method{__init__}, each one should have default values. The reason for this is
! that the framework relies on a call to \code{__init__()}, without any
! arguments, when it needs to create an instance before populating it with data
! fetched from the database.
!
! \item \code{def entityName(self)}: this is the way the framework currently
! binds an object to its entity. This should be changed (see TODO)
! \item Then you get setters and getters for the attributes, whose exact form
! depends on their nature (attributes, to-one or to-many relationships).
! There is also some validation code ready to be used. See
! \ref{customobject-validation}, ``Validation'' for details.
! In getters and setters, notice the methods \method{willRead()} and
! \method{willChange()}. These methods are defined by \class{CustomObject}.
! \begin{itemize}
! \item \method{willRead} informs the object that we need the values stored in
! the database. This is because objects can be ``{\em faults}'' (ZODB
! speaking, they are ghosts). This method's job is to initialize the
! object.
! \item \method{willChange} informs the object that it is about to change. this
! is part of the \class{Observing} interface, and its purpose is to notify
! the \class{EditingContext} that an object is about to change; the
! \class{EditingContext} needs this to keep track of changes in its graph
! of objects. ZODB speaking, this is what the mix-in class
! \class{Persistent} does transparently for immutable attributes (see
! also: TODO).
!
! Of course, \method{willChange} invokes \method{willRead} when
! appropriate.
! \begin{notice}[warning]
! It is your responsability to call \method{willRead} and
! \method{willChange} when you are about to, respectively, access or
! change a class attribute corresponding to a Model's Attribute or
! Relationship; if you do not, the \class{EditingContext}, which is
! responsible for examining the changes and making them persistent, is
! likely to do only part of its job, possibly resulting in objects being
! partially saved, or not at all.
! \end{notice}
! \end{itemize}
! \end{enumerate}
! That's it. You can then start coding your own business logic. Objects are
! always inserted, deleted or updated in a database via an instance of the
! \class{EditingContext} class (see chapter \ref{editing-context}, ``Working
! with your objects: insert, changes, deletion'').
\section{Manipulating objects and their relationships\label{customobject-relationshipmanipulation}}
! With the methods defined by this interface, you can assign an object
\class{Book} to another object \class{Author}, and conversely, without
even knowing if the relationship has some inverse relationship, or even if
--- 1,76 ----
! \chapter{Accessing a model and its properties\label{accessing-model-properties}}
+ Every model required at runtime is loaded into a single \class{ModelSet}, the
+ so-called ``default model set''. It is accessed this way:
\begin{verbatim}
! >>> from Modeling.ModelSet import defaultModelSet
! >>> ms=defaultModelSet()
\end{verbatim}
! Suppose we already imported the two test packages \module{AuthorBooks} and
! \module{StoreEmployees}, then we have:
! \begin{verbatim}
! >>> ms.modelsNames()
! ['AuthorBooks', 'StoreEmployees']
! \end{verbatim}
! The two corresponding models have been correctly imported, as expected.
! Accessing a model in particular, or one of its entities is straightforward:
! \begin{verbatim}
! >>> model_AuthorBooks=ms.modelNamed('AuthorBooks')
! >>> model_AuthorBooks.entitiesNames()
! ['Writer', 'Book']
! >>> model_AuthorBooks.entities()
! (<Entity instance at 8396b50>, <Entity instance at 8337cd0>)
! >>> entity_Book=model_AuthorBooks.entityNamed('Book')
! \end{verbatim}
! Note: you do not need to access a model to get one of its entities; since
! entities share a common namespace inside a \module{ModelSet}, this can be
! asked to the model set as well:
! \begin{verbatim}
! >>> ms.entitiesNames()
! ('Writer', 'Book', 'Store', 'Employee', 'Address', 'SalesClerk',
! 'Executive', 'Mark')
! >>> ms.entityNamed('Book')
! <Entity instance at 8337cd0>
! \end{verbatim}
! Last, given an entity, you can access its attributes and relationships quite
! the same way, for example:
! \begin{verbatim}
! >>> # All attributes' names
! ... entity_Book.attributesNames()
! ('title', 'id', 'price', 'FK_Writer_Id')
! >>> # All relationships' names
! ... entity_Book.relationshipsNames()
! ('author',)
! >>> # Class properties only
! ... entity_Book.classProperties()
! (<Attribute instance at 839bc38>, <Attribute instance at 83c5720>,
! <Attribute instance at 83eaca8>, <SimpleRelationship instance at 840b280>)
! >>> # and their names
! ... entity_Book.classPropertiesNames()
! ['title', 'id', 'price', 'author']
! >>> # dealing with an attribute
! ... book_title=entity_Book.attributeNamed('title')
! >>> book_title.type(), book_title.externalType(), book_title.width()
! ('string', 'VARCHAR', 40)
! \end{verbatim}
! Each of the classes \class{Model}, \class{Entity}, \class{Attribute} and
! \class{Relationship} have more functionalities then that. We invite you to
! look at their respective API for further details.
! \chapter{Generic manipulation of objects\label{custom-object}}
+ In this chapter, we'll see how each object's properties can be dynamically
+ handled.
\section{Manipulating objects and their relationships\label{customobject-relationshipmanipulation}}
! With the methods defined by the interface \class{RelationshipManipulation}
! (implemented by \class{CustomObject}), you can assign an object
\class{Book} to another object \class{Author}, and conversely, without
even knowing if the relationship has some inverse relationship, or even if
***************
*** 154,159 ****
the graph of object remains consistent.
! Of course, you can always do anything by hand, and the first statement is
! strictly equivalent (even if a bit slower) to:
\begin{verbatim}
_author=aBook.getAuthor()
--- 89,93 ----
the graph of object remains consistent.
! Compare this with that you normally do by hand, the explicit way:
\begin{verbatim}
_author=aBook.getAuthor()
***************
*** 164,171 ****
\end{verbatim}
! However, this can come in handy for rapid prototyping, or to make things
! smoother when you are beginning the dev. and that the model can rapidly
! change, or for designing generic algorithms where the manipulated objects
! and relationships are not known at runtime.
Last note: the inverse method is the following one:
--- 98,109 ----
\end{verbatim}
! \method{addObjectToBothSidesOfRelationshipWithKey} does exactly the same
! thing, taking care of all the details for you (even it's a bit slower than
! the explicit statements because it has to examine the model).
!
! This can come in handy for rapid prototyping, or to make things smoother
! when you are beginning the dev. and that the model can rapidly change, or
! for designing generic algorithms where the manipulated objects and
! relationships are not known at runtime.
Last note: the inverse method is the following one:
***************
*** 181,186 ****
(quick notes, needs to be further documented)
! The \class{KeyValueCoding} interface defines methods for accessing and
! setting values to keys. In fact, the \class{RelationshipManipulation}
interface is, somehow, the equivalent of the \class{KeyValueCoding} for
relationships.
--- 119,125 ----
(quick notes, needs to be further documented)
! The \class{KeyValueCoding} interface is also implemented by
! \class{CustomObject}; it defines methods for accessing and setting
! attributes' values to keys. In fact, the \class{RelationshipManipulation}
interface is, somehow, the equivalent of the \class{KeyValueCoding} for
relationships.
***************
*** 224,227 ****
--- 163,188 ----
\end{enumerate}
+ \subsection{The whole API\label{kvc-whole-api}}
+
+ \begin{itemize}
+ \item{\method{valueForKey()}} gets a value for a given attribute's name,
+ \item{\method{takeValueForKey()}} sets a value for a given attribute's name,
+ \item{\method{valueForKeyPath()}} is working the same way than
+ \method{valueForKey}, except that the key can be here expressed by dotted
+ notation to automatically acc...
[truncated message content] |