Update of /cvsroot/openinteract/SPOPS/doc/Manual
In directory usw-pr-cvs1:/tmp/cvs-serv23215/doc/Manual
Modified Files:
Configuration.pod Datasource.pod Error.pod Intro.pod
Object.pod ObjectRules.pod Relationships.pod Security.pod
Serialization.pod
Log Message:
lots of doc updates (some just intro to the manual section, some
beefup and moving examples to examples/ dir)
Index: Configuration.pod
===================================================================
RCS file: /cvsroot/openinteract/SPOPS/doc/Manual/Configuration.pod,v
retrieving revision 1.5
retrieving revision 1.6
diff -C2 -d -r1.5 -r1.6
*** Configuration.pod 2001/10/12 05:39:13 1.5
--- Configuration.pod 2001/10/12 19:39:07 1.6
***************
*** 3,6 ****
--- 3,31 ----
SPOPS::Manual::Configuration - Description of variables used in the SPOPS configuration process
+ =head1 DESCRIPTION
+
+ This document aims to answer the questions:
+
+ =over 4
+
+ =item *
+
+ What configuration options are available for SPOPS objects?
+
+ =item *
+
+ How do I modify configuration options?
+
+ =item *
+
+ How can I get to the configuration items once the object class is
+ operational?
+
+ =item *
+
+ How can I add new configuration items?
+
+ =back
+
=head1 GENERAL CONFIGURATION
***************
*** 151,154 ****
--- 176,183 ----
=head1 SPOPS::DBI CONFIGURATION
+ This section describes configuration keys that are used differently by
+ L<SPOPS::DBI|SPOPS::DBI> than the default, as well as new
+ configuration keys used only by L<SPOPS::DBI|SPOPS::DBI>.
+
=head2 General Configuration Fields
***************
*** 235,240 ****
base_dn => 'ou=Equipment,dc=MyCompany,dc=com'
! Note that L<SPOPS::LDAP::MultiDatasource> allows you to specify a
! partial DN on a per-datasource basis.
B<ldap_object_class> (\@)
--- 264,269 ----
base_dn => 'ou=Equipment,dc=MyCompany,dc=com'
! Note that L<SPOPS::LDAP::MultiDatasource|SPOPS::LDAP::MultiDatasource>
! allows you to specify a partial DN on a per-datasource basis.
B<ldap_object_class> (\@)
Index: Datasource.pod
===================================================================
RCS file: /cvsroot/openinteract/SPOPS/doc/Manual/Datasource.pod,v
retrieving revision 1.3
retrieving revision 1.4
diff -C2 -d -r1.3 -r1.4
*** Datasource.pod 2001/10/12 05:39:13 1.3
--- Datasource.pod 2001/10/12 19:39:07 1.4
***************
*** 5,8 ****
--- 5,26 ----
=head1 DESCRIPTION
+ This document aims to answer the questions:
+
+ =over 4
+
+ =item *
+
+ What is a datastore?
+
+ =item *
+
+ How do SPOPS objects access the datastore?
+
+ =item *
+
+ How do I implement a datastore connection manager?
+
+ =back
+
=head1 COPYRIGHT
Index: Error.pod
===================================================================
RCS file: /cvsroot/openinteract/SPOPS/doc/Manual/Error.pod,v
retrieving revision 1.4
retrieving revision 1.5
diff -C2 -d -r1.4 -r1.5
*** Error.pod 2001/10/12 05:39:13 1.4
--- Error.pod 2001/10/12 19:39:07 1.5
***************
*** 73,77 ****
=head1 EXAMPLES
! [% INCLUDE examples/error_fetch %]
=head1 COPYRIGHT
--- 73,77 ----
=head1 EXAMPLES
! [% INCLUDE examples/error_fetch | linenum %]
=head1 COPYRIGHT
Index: Intro.pod
===================================================================
RCS file: /cvsroot/openinteract/SPOPS/doc/Manual/Intro.pod,v
retrieving revision 1.3
retrieving revision 1.4
diff -C2 -d -r1.3 -r1.4
*** Intro.pod 2001/10/12 05:39:13 1.3
--- Intro.pod 2001/10/12 19:39:07 1.4
***************
*** 5,10 ****
=head1 DESCRIPTION
! =head2 Class Hierarchy
SPOPS (Simple Perl Object Persistence with Security) provides a
framework to make your application objects persistent (meaning, you
--- 5,28 ----
=head1 DESCRIPTION
! This document aims to answer the questions:
!
! =over 4
!
! =item *
!
! What needs does SPOPS fill?
!
! =item *
!
! Why would I use SPOPS?
!
! =item *
+ How does everything broadly fit together?
+
+ =back
+
+ =head1 CLASS HIERARCHY
+
SPOPS (Simple Perl Object Persistence with Security) provides a
framework to make your application objects persistent (meaning, you
***************
*** 79,83 ****
=item *
! Persistency State
=back
--- 97,101 ----
=item *
! Persistent State
=back
***************
*** 87,112 ****
the L<tie|tie> mechanism.
! In Persistency State, the object exists in some persistent form, that
! is, it is stored in a database, or written out to a file.
You can control what happens to the object when it gets written to its
persistent form, or when it is deleted, or fetched from its storage
! form, by implementing a simple API: fetch(), save(), remove().
! ------------- save, remove -----------------
! |Runtime State| -------------------> |Persistency State|
! ------------- <------------------ -----------------
fetch
! Around the fetch(), save(), and remove() calls, you can execute helper
! functions (rules in one or more of the following stages: pre_fetch,
! post_fetch, pre_save, post_save, pre_remove, post_remove), in case you
! need to prepare anything or clean up something, according to needs of
! your storage technology. These are pushed on a queue based on a
! search of C<@ISA>, and executed front to end of the queue. If any of
! the calls in a given queue returns a false value, the whole action
! (save, remove, fetch) is short-circuited (that is, a failing method
! bombs out of the action). More information on this is in L<Data
! Manipulation Callbacks: Rulesets> below.
=head1 COPYRIGHT
--- 105,132 ----
the L<tie|tie> mechanism.
! In Persistent State, the object exists in some more permanent form --
! saved in a database, in the filesystem, in a directory, etc.
You can control what happens to the object when it gets written to its
persistent form, or when it is deleted, or fetched from its storage
! form, by implementing a simple API: C<fetch()>, C<save()>,
! C<remove()>.
! ------------- save, remove ----------------
! |Runtime State| -------------------> |Persistent State|
! ------------- <------------------ ----------------
fetch
! Around the C<fetch()>, C<save()>, and C<remove()> calls, you can
! execute helper functions (rules in one or more of the following
! stages: pre_fetch, post_fetch, pre_save, post_save, pre_remove,
! post_remove), in case you need to prepare anything or clean up
! something, according to needs of your storage technology. These are
! pushed on a queue based on a search of C<@ISA>, and executed front to
! end of the queue. If any of the calls in a given queue returns a false
! value, the whole action (save, remove, fetch) is short-circuited (that
! is, a failing method bombs out of the action). See
! L<SPOPS::Manual::ObjectRules|SPOPS::Manual::ObjectRules> for details
! on the process and how to implement your own.
=head1 COPYRIGHT
Index: Object.pod
===================================================================
RCS file: /cvsroot/openinteract/SPOPS/doc/Manual/Object.pod,v
retrieving revision 1.5
retrieving revision 1.6
diff -C2 -d -r1.5 -r1.6
*** Object.pod 2001/10/12 05:39:13 1.5
--- Object.pod 2001/10/12 19:39:07 1.6
***************
*** 18,61 ****
CGI.pm, set the values into a new SPOPS object and save it:
! my $q = new CGI;
! my $obj = MyUserClass->new();
! foreach my $field ( qw( f_name l_name birthdate ) ) {
! $obj->{ $field } = $q->param( $field );
! }
! my $object_id = eval { $obj->save };
! if ( $@ ) {
! ... report error information ...
! }
! else {
! warn " Object saved with ID: $obj->{object_id}\n";
! }
You can then display this object's information from a later request:
! my $q = new CGI;
! my $object_id = $q->param( 'object_id' );
! my $obj = MyUserClass->fetch( $object_id );
! print "First Name: $obj->{f_name}\n",
! "Last Name: $obj->{l_name}\n",
! "Birthday: $obj->{birthdate}\n";
To display other information from the same object, like related
objects:
! my $user_group = $obj->group;
! print "Group Name: $user_group->{name}\n";
And you can fetch batches of objects at once based on arbitrary
criteria:
! my $q = new CGI;
! my $last_name = $q->param( 'last_name' );
! my $user_list = MyUserClass->fetch_group({ where => 'l_name LIKE ?',
! value => [ "%$last_name%" ],
! order => 'birthdate' });
! print "Users with last name having: $last_name\n";
! foreach my $user ( @{ $user_list } ) {
! print " $user->{f_name} $user->{l_name} -- $user->{birthdate}\n";
! }
=head2 Tie Interface
--- 18,36 ----
CGI.pm, set the values into a new SPOPS object and save it:
! [% INCLUDE examples/object_simple_cgi_create | linenum %]
You can then display this object's information from a later request:
! [% INCLUDE examples/object_simple_cgi_display | linenum %]
To display other information from the same object, like related
objects:
! [% INCLUDE examples/object_simple_cgi_related | linenum %]
And you can fetch batches of objects at once based on arbitrary
criteria:
! [% INCLUDE examples/object_simple_cgi_fetchgroup | linenum %]
=head2 Tie Interface
***************
*** 63,67 ****
This version of SPOPS uses a L<tie|tie> interface to get and set the
individual data values. You can also use the more traditional OO
! I<get> and I<set> operators, but most people will likely find the
hashref interface easier to deal with. It also means you can
interpolate data into strings: bonus!
--- 38,42 ----
This version of SPOPS uses a L<tie|tie> interface to get and set the
individual data values. You can also use the more traditional OO
! C<get> and C<set> operators, but most people will likely find the
hashref interface easier to deal with. It also means you can
interpolate data into strings: bonus!
***************
*** 80,84 ****
--- 55,81 ----
was created or fetched. Property values can also be lazy-loaded.
+ =head2 Automatically Created Accessors
+
+ In addition to getting the data for an object through the hashref
+ method, you can also get to the data with accessors named after the
+ fields.
+
+ For example, given the fields:
+
+ $user->{f_name}
+ $user->{l_name}
+ $user->{birthday}
+
+ You can call to retrieve the data:
+ $user->f_name();
+ $user->l_name();
+ $user->birthday();
+
+ Note that this is only to read the data, not to change it. The system
+ does this using AUTOLOAD, and after the first call it automatically
+ creates a subroutine in the namespace of your class which handles
+ future calls so there is no need for AUTOLOAD on the second or future
+ calls.
=head2 Tracking State Changes
***************
*** 114,141 ****
been any modification, the system will save it, otherwise it will not.
- B<Automatically Created Accessors>
-
- In addition to getting the data for an object through the hashref
- method, you can also get to the data with accessors named after the
- fields.
-
- For example, given the fields:
-
- $user->{f_name}
- $user->{l_name}
- $user->{birthday}
-
- You can call to retrieve the data:
-
- $user->f_name();
- $user->l_name();
- $user->birthday();
-
- Note that this is only to read the data, not to change it. The system
- does this using AUTOLOAD, and after the first call it automatically
- creates a subroutine in the namespace of your class which handles
- future calls so there is no need for AUTOLOAD on the second or future
- calls.
-
=head2 Lazy Loading
--- 111,114 ----
***************
*** 157,177 ****
So when we define our object, we define a column group called
! 'listing' which contains the fields we display when listing the objects:
! $spops = {
! html_page => {
! class => 'My::HTMLPage',
! isa => [ qw/ SPOPS::DBI::Pg SPOPS::DBI / ],
! field => [ qw/ page_id location title author content / ],
! column_group => { listing => [ qw/ location title author / ] },
! ...
! },
! };
And when we retrieve the objects for listing, we pass the column group
name we want to use:
! my $page_list = My::HTMLPage->fetch_group({ order => 'location',
! column_group => 'listing' });
Now each object in C<\@page_list> has the fields 'page_id',
--- 130,142 ----
So when we define our object, we define a column group called
! 'listing' which contains the fields we display when listing the
! objects:
! [% INCLUDE examples/object_lazyload_config | linenum %]
And when we retrieve the objects for listing, we pass the column group
name we want to use:
! [% INCLUDE examples/object_lazyload_fetch | linenum %]
Now each object in C<\@page_list> has the fields 'page_id',
***************
*** 180,200 ****
we try to retrieve the 'content' field, SPOPS will load the value for
that field into the object behind the scenes.
-
- foreach my $page ( @{ $page_list } ) {
! # These properties are in the fetched object and are not
! # lazy-loaded
!
! print "Title: $page->{title}\n",
! "Author: $page->{author}\n";
!
! # When we access lazy-loaded properties like 'content', SPOPS goes
! # and retrieves the value for each object property as it's
! # requested.
!
! if ( $title =~ /^OpenInteract/ ) {
! print "Content\n\n$page->{content}\n";
! }
! }
Obviously, you want to make sure you use this wisely, otherwise you
--- 145,150 ----
we try to retrieve the 'content' field, SPOPS will load the value for
that field into the object behind the scenes.
! [% INCLUDE examples/object_lazyload_usage | linenum %]
Obviously, you want to make sure you use this wisely, otherwise you
***************
*** 222,247 ****
could say:
! field_map => { 'last_name' => 'sn',
! 'first_name' => 'givenname',
! 'password' => 'userpassword',
! 'login_name' => 'uid',
! 'email' => 'mail',
! 'user_id' => 'cn', }
So, despite having entirely different schemas, the following would
print out equivalent information:
! sub display_user_data {
! my ( $user ) = @_;
! return <<INFO;
! ID: $user->{user_id}
! Name: $user->{first_name} $user->{last_name}
! Login: $user->{login_name}
! Email: $user->{email}
! INFO
! }
!
! print display_user_data( $my_ldap_user );
! print display_user_data( $my_dbi_user );
Another use might be to represent properties in a different language.
--- 172,181 ----
could say:
! [% INCLUDE examples/object_fieldmap_config | linenum %]
So, despite having entirely different schemas, the following would
print out equivalent information:
! [% INCLUDE examples/object_fieldmap_usage | linenum %]
Another use might be to represent properties in a different language.
***************
*** 264,280 ****
First, you can specify the information in your object configuration:
! my $config = {
! myobject => {
! class => 'My::SPOPS',
! field => [ qw/ my_id my_name my_date / ],
! field_alter => { my_date => "DATE_FORMAT( my_date, '%Y/%m/%d %I:%i %p' )" },
! ...,
! },
! };
Second, you can pass the information in on a per-object basis:
! my $alter = { my_date => "DATE_FORMAT( my_date, '%Y/%m/%d %I:%i %p' )" };
! my $object = My::SPOPS->fetch( $object_id, { field_alter => $alter } );
Both will have exactly the same effect.
--- 198,206 ----
First, you can specify the information in your object configuration:
! [% INCLUDE examples/object_fieldalter_config | linenum %]
Second, you can pass the information in on a per-object basis:
! [% INCLUDE examples/object_fieldalter_inmethod | linenum %]
Both will have exactly the same effect.
***************
*** 282,299 ****
So, how would you do this in Perl and SPOPS? You would likely create
a post_fetch rule that did whatever data manipulation you wanted:
-
- sub ruleset_add {
- my ( $class, $rs_table ) = @_;
- push @{ $rs_table->{post_fetch_action} }, \&manipulate_date;
- return ref $class || $class;
- }
! sub manipulate_date {
! my ( $self, $p ) = @_;
! return 1 unless ( $self->{start_date} );
! my $start_date_object = Class::Date->new( $self->{start_date} );
! local $Class::Date::DATE_FORMAT = '%Y/%m/%d %I:%M %p';
! $self->{start_date} = "$start_date_object";
! }
See L<SPOPS::Manual::ObjectRules|SPOPS::Manual::ObjectRules> for more
--- 208,213 ----
So, how would you do this in Perl and SPOPS? You would likely create
a post_fetch rule that did whatever data manipulation you wanted:
! [% INCLUDE examples/object_fieldalter_asrule | linenum %]
See L<SPOPS::Manual::ObjectRules|SPOPS::Manual::ObjectRules> for more
***************
*** 308,329 ****
should be multivalued:
! multivalue => [ 'field1', 'field2' ]
Thereafter you can access them as below (more examples in
L<SPOPS::Tie|SPOPS::Tie>):
-
- my $object = My::Object->new;
-
- # Set field1 to [ 'a', 'b' ]
- $object->{field1} = [ 'a', 'b' ];
-
- # Replace the value of 'a' with 'z'
- $object->{field1} = { replace => { a => 'z' } };
! # Add the value 'c'
! $object->{field1} = 'c';
!
! # Find only the ones I want
! my @ones = grep { that_i_want( $_ ) } @{ $object->{field1} };
Note that the value returned from a field access to a multivalue field
--- 222,231 ----
should be multivalued:
! [% INCLUDE examples/object_multivalue_config %]
Thereafter you can access them as below (more examples in
L<SPOPS::Tie|SPOPS::Tie>):
! [% INCLUDE examples/object_multivalue_usage %]
Note that the value returned from a field access to a multivalue field
***************
*** 337,450 ****
the configuration option 'strict_field'. For instance:
- $spops = {
- user => {
- class => 'My::User',
- isa => [ qw/ SPOPS::DBI::Pg SPOPS::DBI / ],
- field => [ qw/ first_name last_name login / ],
- strict_field => 1,
- ...
- },
- };
- ...
- my $user = My::User->new;
- $user->{firstname} = 'Chucky';
-
would result in a message to STDERR, something like:
! Error setting value for field (firstname): it is not a valid field
! at my_tie.pl line 9
! since you have misspelled the property, which should be
! 'first_name'. Note that SPOPS will continue working and will not 'die'
! on such an error, just issue a warning.
=head2 More Examples
-
- # Retrieve all themes and print a description
-
- my $themes = eval { $theme_class->fetch_group( { order => 'title' } ) };
- if ( $@ ) { ... report error ... }
- else {
- foreach my $thm ( @{ $themes } ) {
- print "Theme: $thm->{title}\n",
- "Description: $thm->{description}\n";
- }
- }
-
- # Create a new user, set some values and save
-
- my $user = $user_class->new;
- $user->{email} = 'my...@us...';
- $user->{first_name} = 'My';
- $user->{last_name} = 'User';
- my $user_id = eval { $user->save };
- if ( $@ ) {
- print "There was an error: ", $R->error->report(), "\n";
- }
-
- # Retrieve that same user from the database
-
- my $user_id = $cgi->param( 'user_id' );
- my $user = eval { $user_class->fetch( $user_id ) };
- if ( $@ ) { ... report error ... }
- else {
- print "The user's first name is: $user->{first_name}\n";
- }
-
- # Create a new object with initial values, set another value and save
-
- my $data = MyClass->new({ field1 => 'value1',
- field2 => 'value2' });
- print "The value for field2 is: $data->{field2}\n";
- $data->{field3} = 'value3';
- eval { $data->save };
- if ( $@ ) { ... report error ... }
-
- # Remove the object permanently
-
- eval { $data->remove };
- if ( $@ ) { ... report error ... }
-
- # Call arbitrary object methods to get other objects
-
- my $other_obj = eval { $data->call_to_get_other_object() };
- if ( $@ ) { ... report error ... }
-
- # Clone the object with an overridden value and save
! my $new_data = $data->clone({ field1 => 'new value' });
! eval { $new_data->save };
! if ( $@ ) { ... report error ... }
!
! # $new_data is now its own hashref of data --
! # explore the fields/values in it
!
! while ( my ( $k, $v ) = each %{ $new_data } ) {
! print "$k == $v\n";
! }
!
! # Retrieve saved data
!
! my $saved_data = eval { MyClass->fetch( $id ) };
! if ( $@ ) { ... report error ... }
! else {
! while ( my ( $k, $v ) = each %{ $saved_data } ) {
! print "Value for $k with ID $id is $v\n";
! }
! }
! # Retrieve lots of objects, display a value and call a
! # method on each
! my $data_list = eval { MyClass->fetch_group({
! where => "last_name like 'winter%'" }) };
! if ( $@ ) { ... report error ... }
! else {
! foreach my $obj ( @{ $data_list } ) {
! print "Username: $obj->{username}\n";
! $obj->increment_login();
! }
! }
=head1 COPYRIGHT
--- 239,258 ----
the configuration option 'strict_field'. For instance:
+ [% INCLUDE examples/object_strictfield_define | linenum %]
would result in a message to STDERR, something like:
! [% INCLUDE examples/object_strictfield_errormsg | linenum %]
! since you have misspelled the property. Note that SPOPS will continue
! working and will not 'die' on such an error, just issue a warning.
=head2 More Examples
! [% INCLUDE examples/object_example_allthemes %]
! [% INCLUDE examples/object_example_users %]
! [% INCLUDE examples/object_example_misc %]
=head1 COPYRIGHT
***************
*** 457,459 ****
Chris Winters <ch...@cw...>
-
--- 265,266 ----
Index: ObjectRules.pod
===================================================================
RCS file: /cvsroot/openinteract/SPOPS/doc/Manual/ObjectRules.pod,v
retrieving revision 1.3
retrieving revision 1.4
diff -C2 -d -r1.3 -r1.4
*** ObjectRules.pod 2001/10/12 05:39:13 1.3
--- ObjectRules.pod 2001/10/12 19:39:07 1.4
***************
*** 5,18 ****
=head1 DESCRIPTION
! =head1 DATA MANIPULATION CALLBACKS: RULESETS
! When a SPOPS object calls fetch/save/remove, the base class takes care
! of most of the details for retrieving and constructing the
! object. However, sometimes you want to do something more complex or
! different. Each data manipulation method allows you to define two
! methods to accomplish these things. One is called before the action is
! taken (usually at the very beginning of the action) and the other
! after the action has been successfully completed.
What kind of actions might you want to accomplish? Cascading deletes
(when you delete one object, delete a number of dependent objects as
--- 5,41 ----
=head1 DESCRIPTION
! This document aims to answer the questions:
! =over 4
!
! =item *
!
! What is a rule?
!
! =item *
!
! What can a rule do?
!
! =item *
!
! What is a ruleset?
!
! =item *
!
! How do I implement a rule?
!
! =back
!
! =head1 RULES AND RULESETS DEFINED
+ When a SPOPS object calls C<save()>, C<fetch()>, or C<remove()>, the
+ implementing class (e.g., L<SPOPS::DBI|SPOPS::DBI>) takes care of most
+ of the details for retrieving and constructing the object. However,
+ sometimes you want to do something more complex or different. Each
+ data manipulation method allows you to define two methods to
+ accomplish these things. One is called before the action is taken
+ (usually at the very beginning of the action) and the other after the
+ action has been successfully completed.
+
What kind of actions might you want to accomplish? Cascading deletes
(when you delete one object, delete a number of dependent objects as
***************
*** 20,28 ****
component objects as well); implement a consistent data layer (such as
full-text searching) by sending all inserts and updates to a separate
! module or daemon. Whatever.
! Each of these actions is a rule, and together they are rulesets. There
! are some fairly simple guidelines to rules:
=over 4
--- 43,55 ----
component objects as well); implement a consistent data layer (such as
full-text searching) by sending all inserts and updates to a separate
! module or daemon; data validation (by submitting the data in the
! object to a rules engine). Whatever -- it's up to you.
! Each of these actions is a rule, and together they are rulesets.
+ =head2 Rule Guidelines
+
+ There are some fairly simple guidelines to rules:
+
=over 4
***************
*** 50,57 ****
say on whether a particular object is fetched, saved or removed.
=back
! For example, you may want to implement a 'layer' over certain classes
! of data. Perhaps you want to collect how many times users from various
groups visit a set of objects on your website. You can create a fairly
simple class that puts a rule into the ruleset of its children that
--- 77,90 ----
say on whether a particular object is fetched, saved or removed.
+ NOTE: This will probably be dropped in favor of a more flexible scheme
+ that allows non-essential rules to fail without forcing the entire
+ action to fail.
+
=back
! =head2 Rule Uses
!
! Rules enable you to implement a 'layer' over certain classes of
! data. Perhaps you want to collect how many times users from various
groups visit a set of objects on your website. You can create a fairly
simple class that puts a rule into the ruleset of its children that
***************
*** 67,76 ****
data of the row that is being manipulated, whereas a rule should not.
! B<pre_fetch_action( { id =E<gt> $ } )>
Called before a fetch is done, although if an object is retrieved from
! the cache this action is skipped. The only argument is the ID of the
! object you are trying to fetch.
B<post_fetch_action( \% )>
--- 100,124 ----
data of the row that is being manipulated, whereas a rule should not.
! =head2 Rules and Aspects
!
! Another useful way to think of rules is in terms of aspect oriented
! programming (AOP). AOP works in conjunction with other methods of
! programming (object-oriented, procedural, functional) and allows you
! to create joinpoints at which you perform actions with and on
! different types of data.
+ #TODO: Put reference URL here and/or Aspect CPAN link
+
+ =head1 RULESET HOOKS
+
+ B<pre_fetch_action({ id =E<gt> $ })>
+
Called before a fetch is done, although if an object is retrieved from
! the cache this action is skipped. (NOTE: THIS MIGHT NOT BE TRUE -- WE
! NEED TO IMPLEMENT CACHING AND SEE HOW THIS WORKS IN REALITY.) The only
! argument is the ID of the object you are trying to fetch.
+ This hook is generally not used very often.
+
B<post_fetch_action( \% )>
***************
*** 78,87 ****
a positive cache hit.
! B<pre_save_action( { is_add =E<gt>; bool } )>
Called before a save has been attempted. If this is an add operation
(versus an update), we pass in a true value for the 'is_add' parameter.
! B<post_save_action( { is_add =E<gt> bool } )>
Called after a save has been successfully completed. If this object
--- 126,135 ----
a positive cache hit.
! B<pre_save_action( is_add =E<gt>; bool })>
Called before a save has been attempted. If this is an add operation
(versus an update), we pass in a true value for the 'is_add' parameter.
! B<post_save_action({ is_add =E<gt> bool })>
Called after a save has been successfully completed. If this object
***************
*** 97,107 ****
Called after a remove has been successfully completed.
B<ruleset_add( $class, \%class_ruleset )>
! Interface for adding rulesets to a class. The first argument is the
! class to which we want to add the ruleset, the second is the ruleset
! for the class. The ruleset is simply a hash reference with keys as the
! methods named above ('pre_fetch_action', etc.) pointing to an arrayref
! of code references.
This means that every phase named above above ('pre_fetch_action',
--- 145,165 ----
Called after a remove has been successfully completed.
+ =head1 RULE IMPLEMENTATION
+
+ Adding rules to an object class is very simple. You have one simple
+ method to create to add your rule(s) to the ruleset for an object, and
+ then the actual rules.
+
+ =head2 Rule Factory
+
B<ruleset_add( $class, \%class_ruleset )>
! NOTE: THIS METHOD MIGHT BE RENAMED TO 'rule_factory()'
!
! Interface for adding rules to a class. The first argument is the class
! to which we want to add the ruleset, the second is the ruleset for the
! class. The ruleset is simply a hash reference with keys as the methods
! named above ('pre_fetch_action', etc.) pointing to an arrayref of code
! references.
This means that every phase named above above ('pre_fetch_action',
***************
*** 126,132 ****
ensure that the same rule does not get entered multiple times.
B<ruleset_process_action( ($object|$class), $action, \%params )>
! This method executes all the rules in a given ruleset for a give
action. For instance, when called with the action name
'pre_fetch_action' it executes all the rules in that part of the
--- 184,199 ----
ensure that the same rule does not get entered multiple times.
+ NOTE: THIS INTERFACE MAY BE CHANGED TO INSTEAD RETURN THE NAMES
+ (PACKAGE + METHOD) OF ALL ROUTINES ADDED TO THE RULESET.
+
+ =head2 Rule Processor
+
+ You should never have to worry about this since it is implemented in
+ L<SPOPS|SPOPS> and therefore part of every SPOPS class. But it is here
+ for completeness:
+
B<ruleset_process_action( ($object|$class), $action, \%params )>
! This method executes all the rules in a given ruleset for a given
action. For instance, when called with the action name
'pre_fetch_action' it executes all the rules in that part of the
***************
*** 134,137 ****
--- 201,207 ----
Return value is true if all the rules executed ok, false if not.
+
+ NOTE: THIS MAY BE MODIFIED SO THAT EACH RULE CAN REPORT A STATUS WHICH
+ IS AVAILABLE FOR LATER INSPECTION.
=head1 COPYRIGHT
Index: Relationships.pod
===================================================================
RCS file: /cvsroot/openinteract/SPOPS/doc/Manual/Relationships.pod,v
retrieving revision 1.3
retrieving revision 1.4
diff -C2 -d -r1.3 -r1.4
*** Relationships.pod 2001/10/12 05:39:13 1.3
--- Relationships.pod 2001/10/12 19:39:07 1.4
***************
*** 5,11 ****
=head1 DESCRIPTION
! =head1 HAS_A
! =head1 LINKS_TO
=head1 COPYRIGHT
--- 5,17 ----
=head1 DESCRIPTION
! This document aims to answer the following questions:
! =over 4
!
! =item *
!
! How do I relate objects?
!
! =back
=head1 COPYRIGHT
Index: Security.pod
===================================================================
RCS file: /cvsroot/openinteract/SPOPS/doc/Manual/Security.pod,v
retrieving revision 1.4
retrieving revision 1.5
diff -C2 -d -r1.4 -r1.5
*** Security.pod 2001/10/12 05:39:13 1.4
--- Security.pod 2001/10/12 19:39:07 1.5
***************
*** 11,14 ****
--- 11,40 ----
provide some concrete examples.
+ This document should answer the following questions:
+
+ =over 4
+
+ =item *
+
+ How does security work?
+
+ =item *
+
+ How does security use users and groups?
+
+ =item *
+
+ How does SPOPS implement security?
+
+ =item *
+
+ How can I implement security?
+
+ =item *
+
+ How can I customize security?
+
+ =back
+
=head1 OVERVIEW
***************
*** 90,94 ****
------------------------------------------------
! >From this, we can say:
=over 4
--- 116,120 ----
------------------------------------------------
! From this, we can say:
=over 4
***************
*** 136,155 ****
your user object must implement a method that returns the groups that
user belongs to via the method 'group':
-
- # Note that 'login_name' is not required as a parameter; this is just
- # an example
-
- my $user_members = eval { $group->user };
- foreach my $user ( @{ $user_members } ) {
- print "Username is $user->{login_name}\n";
- }
-
- # Note that 'name' is not required as a parameter; this is just an
- # example
! my $groups = eval { $user->group };
! foreach my $group ( @{ $groups } ) {
! print "Group name is $group->{name}\n";
! }
=item *
--- 162,167 ----
your user object must implement a method that returns the groups that
user belongs to via the method 'group':
! [% INCLUDE examples/security_get_users_groups | linenum %]
=item *
Index: Serialization.pod
===================================================================
RCS file: /cvsroot/openinteract/SPOPS/doc/Manual/Serialization.pod,v
retrieving revision 1.5
retrieving revision 1.6
diff -C2 -d -r1.5 -r1.6
*** Serialization.pod 2001/10/12 05:39:13 1.5
--- Serialization.pod 2001/10/12 19:39:07 1.6
***************
*** 5,22 ****
=head1 DESCRIPTION
! =head2 Storable Serialization
! Since the main SPOPS class from which all SPOPS objects derive has
! L<Storable|Storable> as a parent, you can call any of its methods from any
! SPOPS object and have a serialized version of your object. You can
! send it over the network, save it for later -- whatever you like.
! Note that this feature appeared starting version 0.39, so if you have
! any issues with it please e-mail the author. There are some issues to
! be worked out with configuration and the like, but it basically just
! works.
! =head1 FAILED ACTIONS
If an action fails, the 'fail' method associated with that action is
triggered. This can be a notification to an administrator, or saving
--- 5,46 ----
=head1 DESCRIPTION
! This part of the SPOPS manual aims to answer the following questions:
! =over 4
! =item *
! How does serialization work?
!
! =item *
!
! How can I customize serialization?
!
! =item *
!
! How does caching work?
!
! =item *
+ How can I lazy-load my fields?
+
+ =back
+
+ =head1 SERIALIZATION BASICS
+
+ =head1 CUSTOMIZING SERIALIZATION
+
+ =head2 Pre/Post Hooks
+
+ SPOPS allows you to create multiple pre- and post-actions to
+ C<save()>, C<fetch()> and C<remove()>. These actions can be as simple
+ or complex as you like and allow objects to be extremely flexible.
+
+ These the actions implemented in the hooks are called rules, and each
+ collection of rules is called a ruleset. Rulesets are documented in
+ L<SPOPS::Manual::ObjectRules|SPOPS::Manual::ObjectRules>.
+
+ =head2 Failed Actions
+
If an action fails, the 'fail' method associated with that action is
triggered. This can be a notification to an administrator, or saving
***************
*** 37,44 ****
=head1 CACHING
! SPOPS has hooks for object caching. As mentioned above, you will need
! to define a B<global_cache> either in your SPOPS object class one of
! its parents. Typically, you will put the I<stash class> in the @ISA of
! your SPOPS object.
B<pre_cache_fetch()>
--- 61,71 ----
=head1 CACHING
! SPOPS has hooks for object caching. You will need to return a caching
! object via the a C<global_cache()> method implemented either in your
! SPOPS object class one of its parents.
!
! The caching object will have a simple interface so it's easy to wrap
! it around your favorite caching backend. (Who wants to write this
! stuff from scratch themselves?)
B<pre_cache_fetch()>
***************
*** 68,72 ****
Called after an object is successfully removed from the cache.
! =head2 Lazy Loading
This section describes how to implement lazy loading for your objects.
--- 95,99 ----
Called after an object is successfully removed from the cache.
! =head1 LAZY LOADING
This section describes how to implement lazy loading for your objects.
***************
*** 86,94 ****
=back
! Here are the methods to implement lazy loading:
! =over 4
! B<get_lazy_load_sub>
Called by SPOPS when initializing a new object if one or more
--- 113,121 ----
=back
! =head2 What You Need To Do
! Here are the methods you need to create to implement lazy loading:
! B<get_lazy_load_sub()>
Called by SPOPS when initializing a new object if one or more
***************
*** 107,114 ****
includes the ID field and value of the object.
- =back
-
For lazy loading usage, see L<SPOPS::Manual::Object|SPOPS::Manual::Object>.
B<is_loaded( $field )>
--- 134,143 ----
includes the ID field and value of the object.
For lazy loading usage, see L<SPOPS::Manual::Object|SPOPS::Manual::Object>.
+ =head2 What SPOPS does
+
+ These are methods implemented by SPOPS for lazy loading.
+
B<is_loaded( $field )>
***************
*** 135,138 ****
--- 164,193 ----
For an example of how a SPOPS subclass implements lazy-loading, see
L<SPOPS::DBI|SPOPS::DBI>.
+
+ =head1 STORABLE SERIALIZATION
+
+ Since the main SPOPS class from which all SPOPS objects derive has
+ L<Storable|Storable> as a parent, you can call any of the Storable
+ methods from any SPOPS object and have a serialized version of your
+ object. You can send it over the network, save it for later --
+ whatever you like.
+
+ Example:
+
+ [% INCLUDE examples/serial_storable_user | linenum %]
+
+ This has not been extensively tested, particularly with the
+ C<nstore()> option for transporting objects among different
+ architectures. In theory, this shouldn't be an issue at all, as long
+ as when thawing the object the specific SPOPS class has been
+ previously initialized.
+
+ This could be an interesting area of future development, since you
+ could in theory send an object over a network along with a minimal
+ configuration and object definition that, when it reached the
+ C<read_code> section of the code generation, reached out over the
+ network to read in the actual meat of a class. Basically, the thawing
+ routine could check to see if it had processed the SPOPS class and if
+ not, grab it off the network and whip it up.
=head1 COPYRIGHT
|