[Jifty-commit] r5199 - in jifty/trunk: . lib/Jifty/Plugin lib/Jifty/Plugin/Comment lib/Jifty/Plugin/Comment/Action lib/Jifty/Plugin/Comment/Mixin lib/Jifty/Plugin/Comment/Mixin/Model lib/Jifty/Plugin/Comment/Model lib/Jifty/Plugin/Comment/Notification t/TestApp-Plugin-Comments t/TestApp-Plugin-Comments/bin t/TestApp-Plugin-Comments/doc t/TestApp-Plugin-Comments/etc t/TestApp-Plugin-Comments/lib/TestApp t/TestApp-Plugin-Comments/lib/TestApp/Plugin t/TestApp-Plugin-Comments/lib/TestApp/Plugin/Comments t/TestApp-Plugin-Comments/lib/TestApp/Plugin/Comments/Action t/TestApp-Plugin-Comments/lib/TestApp/Plugin/Comments/Model t/TestApp-Plugin-Comments/log t/TestApp-Plugin-Comments/share t/TestApp-Plugin-Comments/share/po t/TestApp-Plugin-Comments/share/web t/TestApp-Plugin-Comments/share/web/static t/TestApp-Plugin-Comments/share/web/templates t/TestApp-Plugin-Comments/t t/TestApp-Plugin-Comments/var t/TestApp-Plugin-Comments/var/mason

Jifty commits jifty-commit at lists.jifty.org
Sun Mar 9 17:03:19 EDT 2008


Author: sterling
Date: Sun Mar  9 17:03:17 2008
New Revision: 5199

Added:
   jifty/trunk/lib/Jifty/Plugin/Comment/
   jifty/trunk/lib/Jifty/Plugin/Comment.pm
   jifty/trunk/lib/Jifty/Plugin/Comment/Action/
   jifty/trunk/lib/Jifty/Plugin/Comment/Action/CreateComment.pm
   jifty/trunk/lib/Jifty/Plugin/Comment/Dispatcher.pm
   jifty/trunk/lib/Jifty/Plugin/Comment/Mixin/
   jifty/trunk/lib/Jifty/Plugin/Comment/Mixin/Model/
   jifty/trunk/lib/Jifty/Plugin/Comment/Mixin/Model/Commented.pm
   jifty/trunk/lib/Jifty/Plugin/Comment/Model/
   jifty/trunk/lib/Jifty/Plugin/Comment/Model/Comment.pm
   jifty/trunk/lib/Jifty/Plugin/Comment/Notification/
   jifty/trunk/lib/Jifty/Plugin/Comment/Notification/CommentNeedsModeration.pm
   jifty/trunk/lib/Jifty/Plugin/Comment/Notification/CommentPublished.pm
   jifty/trunk/lib/Jifty/Plugin/Comment/View.pm
   jifty/trunk/t/TestApp-Plugin-Comments/
   jifty/trunk/t/TestApp-Plugin-Comments/Makefile.PL
   jifty/trunk/t/TestApp-Plugin-Comments/bin/
   jifty/trunk/t/TestApp-Plugin-Comments/bin/jifty   (contents, props changed)
   jifty/trunk/t/TestApp-Plugin-Comments/doc/
   jifty/trunk/t/TestApp-Plugin-Comments/etc/
   jifty/trunk/t/TestApp-Plugin-Comments/etc/config.yml
   jifty/trunk/t/TestApp-Plugin-Comments/lib/
   jifty/trunk/t/TestApp-Plugin-Comments/lib/TestApp/
   jifty/trunk/t/TestApp-Plugin-Comments/lib/TestApp/Plugin/
   jifty/trunk/t/TestApp-Plugin-Comments/lib/TestApp/Plugin/Comments/
   jifty/trunk/t/TestApp-Plugin-Comments/lib/TestApp/Plugin/Comments/Action/
   jifty/trunk/t/TestApp-Plugin-Comments/lib/TestApp/Plugin/Comments/Dispatcher.pm
   jifty/trunk/t/TestApp-Plugin-Comments/lib/TestApp/Plugin/Comments/Model/
   jifty/trunk/t/TestApp-Plugin-Comments/lib/TestApp/Plugin/Comments/Model/BlogPost.pm
   jifty/trunk/t/TestApp-Plugin-Comments/lib/TestApp/Plugin/Comments/View.pm
   jifty/trunk/t/TestApp-Plugin-Comments/log/
   jifty/trunk/t/TestApp-Plugin-Comments/share/
   jifty/trunk/t/TestApp-Plugin-Comments/share/po/
   jifty/trunk/t/TestApp-Plugin-Comments/share/web/
   jifty/trunk/t/TestApp-Plugin-Comments/share/web/static/
   jifty/trunk/t/TestApp-Plugin-Comments/share/web/templates/
   jifty/trunk/t/TestApp-Plugin-Comments/t/
   jifty/trunk/t/TestApp-Plugin-Comments/t/00-model-BlogPost.t
   jifty/trunk/t/TestApp-Plugin-Comments/var/
   jifty/trunk/t/TestApp-Plugin-Comments/var/mason/
Modified:
   jifty/trunk/   (props changed)

Log:
 r15577 at dynpc145:  andrew | 2008-03-09 16:02:57 -0500
 Add blog-style comments to just about any model.


Added: jifty/trunk/lib/Jifty/Plugin/Comment.pm
==============================================================================
--- (empty file)
+++ jifty/trunk/lib/Jifty/Plugin/Comment.pm	Sun Mar  9 17:03:17 2008
@@ -0,0 +1,207 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Comment;
+use base qw/ Jifty::Plugin /;
+
+__PACKAGE__->mk_accessors( qw/ akismet scrubber scrub_message / );
+
+use HTML::Scrubber;
+
+=head1 NAME
+
+Jifty::Plugin::Comment - Add comments to any record
+
+=head1 SYNOPSIS
+
+Setup the F<config.yml>
+
+  Plugins:
+    - Comment:
+      
+      # Set this if you want spam checking by Net::Akismet
+      Akismet:
+        Key: 1234567890a
+        Url: http://example.com
+
+      # Set this if you want to customize the HTML scrubbing of comments
+      Scrubber:
+        message: "Comments may only contain <strong>, <em>, and <a> tags."
+        allow:
+          - strong
+          - em
+          - a
+        default:
+          - 0
+          - 
+            '*': 0
+            href: !!perl/regexp:
+              REGEXP: '^(?!(?:java)?script)'
+              MODIFIERS: i
+
+Setup a model that has comments:
+
+  package App::Model::Fooble;
+  
+  use Jifty::DBI::Schema;
+  use App::Record schema {
+      column scribble => type is 'text';
+      column wobble => type is 'int';
+  };
+
+  use Jifty::Plugin::Comment::Mixin::Model::Commented;
+
+  App::Model::FoobleComment->add_trigger( before_access => sub {
+      my $self = shift;
+      my ($right, %args) = @_;
+
+      if ($right eq 'create') {
+          return 1 if $self->current_user->id;
+      }
+
+      if ($right eq 'read') {
+          return 1;
+      }
+
+      return $self->App::Model::FoobleComment::current_user_can(@_);
+  });
+
+Setup a view for creating, viewing, and managing the comments:
+
+  # assuming $fooble here isa App::Action::UpdateFooble object
+  template 'fooble/view' => page {
+      my $fooble = get 'fooble';
+
+      render_action $fooble, undef, { render_mode => 'read' };
+
+      render_region
+          name     => 'fooble-comments',
+          path     => '__comment/list_and_add',
+          defaults => { 
+              comment_upon  => $fooble->record->for_commenting,
+              initial_title => 'Re: '.substr($fooble->scribble, 0, 20).'...',
+          },
+          ;
+  };
+
+=head1 DESCRIPTION
+
+This plugin allows you to attach comments to any model. You do this using the three steps listed in the synopsis. For variations on these steps, see the other classes that handle the individual parts.
+
+=head1 COMMENTED RECORDS
+
+To set up a commented model, you will need to do the following:
+
+=over
+
+=item 1 Add ths plugin to your project by modifying your F<config.yml>.
+
+=item 1 Add the L<Jifty::Plugin::Comment::Mixin::Model::Commented> mixin to the model or models that you want to have comments attached to. See that class for details on how it works. You may also want to examine L<Jifty::Plugin::Comment::Model::Comment> on how to customize that class for your application.
+
+=item 1 Create a view that appends a comment editor to your edit form (or on a separate page or wherever you feel like comments work best in your application). You should be able to use these views from either L<Template::Declare> or L<HTML::Mason> templates. See L<Jifty::Plugin::Comment::View> for additional details on what views are available.
+
+=back
+
+=head1 METHODS
+
+=head2 init
+
+Called during initialization. This will setup the L<Net::Akismet> object if it is configured and available.
+
+=cut
+
+sub init {
+    my $self = shift;
+
+    $self->_init_akismet(@_);
+    $self->_init_scrubber(@_);
+}
+
+sub _init_akismet {
+    my $self = shift;
+    my %args = @_;
+
+    # Stop now if we don't have the Akismet thing
+    return unless defined $args{Akismet};
+
+    # Check for the Akismet options
+    my $key = $args{Akismet}{Key};
+    my $url = $args{Akismet}{Url};
+
+    # Don't go forward unless we have a key and a URL configured
+    return unless $key and $url;
+
+    # Try to load Akismet first...
+    eval "use Net::Akismet";
+    if ($@) {
+        Jifty->log->error("Failed to load Net::Akismet. Your comments will not be checked for link spam and the like. $@");
+        return;
+    }
+
+    # Now get our object
+    my $akismet = Net::Akismet->new( KEY => $key, URL => $url );
+
+    unless ($akismet) {
+        Jifty->log->error("Failed to verify your Akismet key. Your comments will not be checked for link spam and the like.");
+        return;
+    }
+
+    $self->akismet($akismet);
+}
+
+sub _init_scrubber {
+    my $self = shift;
+    my %args = @_;
+
+    my $scrubber_args = $args{Scrubber};
+    if (not defined $scrubber_args) {
+        $scrubber_args = {
+            message => 'Comments may only contain <strong>, <em>, and <a> tags.'
+                      .' Anything else will be removed.',
+            allow   => [ qw/ strong em a / ],
+            default => [ 0, { '*' => 0, 'href' => qr{^(?!(?:java)?script)}i } ],
+        };
+    }
+
+    my $scrub_message = delete $scrubber_args->{message};
+    $scrub_message = 'The text you have given will be cleaned up.'
+        unless $scrub_message;
+
+    $self->scrub_message($scrub_message);
+
+    my $scrubber = HTML::Scrubber->new( %$scrubber_args );
+
+    $self->scrubber($scrubber);
+}
+
+=head2 akismet
+
+This returns an instance of L<Net::Akismet> that is used to check to see if a new comment posted contains spam. No such checking is performed if this returns C<undef>, which indicates that C<Net::Akismet> is unavailable, wasn't configured, or there was an error configuring it (e.g., the Akismet server was unavailable during Jifty startup).
+
+=head2 scrubber
+
+This returns an instance of L<HTML::Scrubber> that is used to clean up HTML submitted in comments.
+
+=head1 TO DO
+
+Right now the module depends directly upon L<HTML::Scrubber> to do the work of cleaning up the text. You might want to use something else to do this. It also provides no mechanism for customizing any other aspect of the formatting. For example, your application might want to use Markdown, or BBCode, or just turn line breaks in the BR-tags, or anything else to format the comment text.
+
+In the future, I'd like to consider something like L<Text::Pipe> or a similar API to allow these formats to be customized more easily.
+
+=head1 SEE ALSO
+
+L<Net::Akismet>, L<HTML::Scrubber>
+
+=head1 AUTHOR
+
+Andrew Sterling Hanenkamp, C<< <hanenkamp at cpan.org> >>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007 Boomer Consulting, Inc. All Rights Reserved.
+
+This program is free software and may be modified and distributed under the same terms as Perl itself.
+
+=cut
+
+1;

Added: jifty/trunk/lib/Jifty/Plugin/Comment/Action/CreateComment.pm
==============================================================================
--- (empty file)
+++ jifty/trunk/lib/Jifty/Plugin/Comment/Action/CreateComment.pm	Sun Mar  9 17:03:17 2008
@@ -0,0 +1,297 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Comment::Action::CreateComment;
+use base qw/ Jifty::Action::Record::Create /;
+
+use Jifty::Param::Schema;
+use Jifty::Action::Record::Create schema {
+    param parent_class =>
+        type is 'hidden',
+        is mandatory,
+        order is 1,
+        ;
+
+    param parent_id =>
+        type is 'hidden',
+        is mandatory,
+        order is 2,
+        ;
+
+    param title =>
+        label is 'Title',
+        is mandatory,
+        ajax validates,
+        is focus,
+        order is 3,
+        ;
+
+    param your_name =>
+        label is 'Your name',
+        default is defer { from_cookie(0) },
+        # TODO This is canonicalizing at the wrong time, I need another way.
+#        ajax canonicalizes,
+        order is 4,
+        ;
+
+    param web_site =>
+        label is 'Web site',
+        default is defer { from_cookie(1) },
+        ajax validates,
+        order is 5,
+        ;
+
+    param email =>
+        label is 'Email address',
+        default is defer { from_cookie(2) },
+        ajax validates,
+        order is 6,
+        ;
+
+#    param remember_me =>
+#        type is 'checkbox',
+#        label is 'Remember me',
+#        hints is 'Check this box for this site to store a cookie on your browser that is used to automatically fill in your name, email address, and web site the next time you make a comment.',
+#        ;
+
+    param body =>
+        type is 'textarea',
+        label is 'Comment',
+        is mandatory,
+        ajax validates,
+        order is 7,
+        ;
+
+    param published =>
+        type is 'unrendered',
+        render as 'unrendered',
+        mandatory is 0,
+        ;
+
+    param created_on =>
+        type is 'unrendered',
+        render as 'unrendered',
+        ;
+
+    param status =>
+        type is 'unrendered',
+        render as 'unrendered',
+        ;
+
+    param http_referer =>
+        type is 'unrendered',
+        render as 'unrendered',
+        ;
+
+    param http_user_agent =>
+        type is 'unrendered',
+        render as 'unrendered',
+        ;
+
+    param ip_addr =>
+        type is 'unrendered',
+        render as 'unrendered',
+        ;
+};
+
+use CGI::Cookie;
+use MIME::Base64::URLSafe;
+#use Contentment::Notification::CommentPublished;
+#use Contentment::Notification::CommentNeedsModeration;
+#use Contentment::Util;
+use Regexp::Common qw/ Email::Address URI /;
+
+sub record_class { Jifty->app_class('Model', 'Comment') }
+
+sub parent {
+    my $self = shift;
+
+    my $parent_class = $self->argument_value('parent_class');
+    my $parent_id    = $self->argument_value('parent_id');
+
+    my $parent = $parent_class->new;
+    $parent->load($parent_id);
+
+    return $parent;
+}
+
+sub take_action {
+    my $self = shift;
+
+    if ($self->argument_value('submit')) {
+        my $your_name     = urlsafe_b64encode($self->argument_value('your_name'));
+        my $web_site      = urlsafe_b64encode($self->argument_value('web_site'));
+        my $email = urlsafe_b64encode(
+            $self->argument_value('email'));
+
+        my $cookie = CGI::Cookie->new(
+            -path    => '/',
+            -name    => 'COMMENT_REMEMBORY',
+            -value   => join('.', $your_name, $web_site, $email),
+            -expires => '+3M',
+        );
+
+        Jifty->web->response->add_header( 'Set-Cookie' => $cookie->as_string );
+
+        $self->SUPER::take_action(@_);
+
+        # On success, create the extra link record
+        if ($self->result->success) {
+            my $parent_class = $self->argument_value('parent_class');
+            my $link = $parent_class->comment_record_class->new;
+            $link->create(
+                commented_upon => $self->parent,
+                the_comment    => $self->record,
+            );
+
+            # Link failed?
+            unless ($link->id) {
+                $self->log->error("Failed to create the comment and linking record required for comment #@{[$self->record->id]} and parent record class @{[$self->argument_value('parent_class')]} #@{[$self->argument_value('parent_id')]}");
+                $self->result->message(undef);
+                $self->result->error("Failed to create the comment and linking record required. This error has been logged.");
+            }
+
+            # Link succeeded! This comment's training is complete!
+            else {
+                # Send notification by email to the site owners
+                my $notification_class 
+                    = $self->record->published ? 'CommentPublished'
+                    :                            'CommentNeedsModeration';
+                $notification_class 
+                    = Jifty->app_class('Notification', $notification_class);
+                my $notification = $notification_class->new( 
+                    comment => $self->record, 
+                    parent  => $self->parent,
+                );
+                $notification->send;
+            }
+        }
+    }
+    else {
+        $self->result->message(qq{Previewing your comment titled "@{[$self->argument_value('title') || 'Untitled']}"});
+        $self->result->failure(1);
+    }
+}
+
+sub report_success {
+    my $self = shift;
+    $self->result->message(_("Your comment has been added. If it does not immediately appear, it may have been flagged for moderation and should appear shortly."));
+}
+
+my $comment_cookie;
+sub fetch_comment_cookie {
+    return $comment_cookie if defined $comment_cookie;
+
+    my %cookies = CGI::Cookie->fetch;
+    $comment_cookie 
+        = $cookies{'COMMENT_REMEMBORY'} ? $cookies{'COMMENT_REMEMBORY'} : '';
+
+    return $comment_cookie;
+}
+
+sub from_cookie {
+    my $pos = shift;
+
+    if (Jifty->web->current_user->id) {
+        return Jifty->web->escape(
+               $pos == 0 ? Jifty->web->current_user->user_object->name
+             : $pos == 1 ? Jifty->config->framework('Web')->{'BaseURL'}
+             : $pos == 2 ? Jifty->web->current_user->user_object->email
+             :             ''
+        );
+    }
+
+    elsif (my $value = eval { fetch_comment_cookie()->value }) {
+        my @fields = split /\./, $value;
+
+        if (defined $fields[ $pos ]) {
+            return Jifty->web->escape(urlsafe_b64decode($fields[ $pos ]));
+        }
+        else {
+            return '';
+        }
+    }
+
+    else {
+        return '';
+    }
+}
+
+sub validate_title {
+    my $self = shift;
+    my $title = shift;
+
+    if (!$title || $title =~ /^\s*$/) {
+        return $self->validation_error(title => 'You must give a title.');
+    }
+
+    return $self->validation_ok('title');
+}
+
+#sub canonicalize_your_name {
+#    my $self = shift;
+#    my $your_name = shift;
+#
+#    if (!$your_name || $your_name =~ /^\s*$/ || $your_name =~ /\banonymous\b/i) {
+#        $self->canonicalization_note( your_name => 'Afraid to stand behind your words? Any malicious or evil comments by an Anonymous Coward (or anyone) will be unpublished.' );
+#        return 'Anonymous Coward';
+#    }
+#
+#    return $your_name;
+#}
+
+sub validate_web_site {
+    my $self = shift;
+    my $web_site = shift;
+
+    if (!$web_site || $web_site =~ /^\s*$/) {
+        return $self->validation_ok('web_site');
+    }
+
+    unless ($web_site =~ /^$RE{URI}{HTTP}$/) {
+        return $self->validation_error(
+            web_site => 'This does not look like a proper URL. Make sure it starts with http:// or https://'
+        );
+    }
+
+    return $self->validation_ok('web_site');
+}
+
+sub validate_email {
+    my $self = shift;
+    my $email = shift;
+
+    if (!$email || $email =~ /^\s*$/) {
+        return $self->validation_ok('email');
+    }
+
+    unless ($email =~ /^$RE{Email}{Address}$/) {
+        return $self->validation_error(
+            email => 'This does not look like a proper e-mail address.',
+        );
+    }
+
+    return $self->validation_ok('email');
+}
+
+sub validate_body {
+    my $self = shift;
+    my $body = shift;
+
+    if (!$body || $body =~ /^\s*$/) {
+        return $self->validation_error(body => 'You must type a comment.');
+    }
+
+    my $plugin = Jifty->find_plugin('Jifty::Plugin::Comment');
+    my $scrubber = $plugin->scrubber;
+    my $message  = $plugin->scrub_message;
+
+    my $clean_body = $scrubber->scrub($body);
+    if ($clean_body ne $body) {
+        return $self->validation_warning(body => Jifty->web->escape($message));
+    }
+
+    return $self->validation_ok('body');
+}
+
+1;

Added: jifty/trunk/lib/Jifty/Plugin/Comment/Dispatcher.pm
==============================================================================
--- (empty file)
+++ jifty/trunk/lib/Jifty/Plugin/Comment/Dispatcher.pm	Sun Mar  9 17:03:17 2008
@@ -0,0 +1,96 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Comment::Dispatcher;
+use Jifty::Dispatcher -base;
+
+use DateTime::Format::Mail;
+use DateTime::Format::W3CDTF;
+use Jifty::DateTime;
+use Scalar::Util qw/ blessed looks_like_number /;
+
+sub setup_parent_object() {
+    my $parent;
+    unless (get 'parent') {
+        my ($parent_class, $parent_id) = @_;
+        if (get 'comment_upon') {
+            my $comment_upon = get 'comment_upon';
+            ($parent_class, $parent_id) = $comment_upon =~ /^([\w:]+)-(\d)$/;
+        }
+        else {
+            $parent_class = get 'parent_class';
+            $parent_id    = get 'parent_id';
+        }
+
+        abort 404 unless $parent_class and $parent_id;
+        abort 500 unless $parent_class =~ /^[\w:]+$/;
+        abort 500 unless eval { $parent_class->isa('Jifty::Record') };
+        abort 500 unless looks_like_number($parent_id);
+
+        $parent = $parent_class->new;
+        $parent->load($parent_id);
+
+        set parent => $parent;
+    }
+
+    else {
+        $parent = get 'parent';
+    }
+
+    abort 500 unless eval { $parent->isa('Jifty::Record') };
+    abort 500 unless eval { $parent->can('comments') };
+    abort 404 unless eval { $parent->id };
+
+}
+
+on 'comment/list' => run {
+    setup_parent_object();
+    show '/comment/list';
+};
+
+on 'comment/add' => run {
+    setup_parent_object();
+
+    my $parent = get 'parent';
+
+    my $action = Jifty->web->new_action( 
+        class => 'CreateComment',
+        moniker => 'add-comment-'.$parent->id,
+        arguments => {
+            parent_class => blessed $parent,
+            parent_id    => $parent->id,
+        },
+    );
+    $action->argument_value( title => get('title') || '')
+        unless $action->argument_value('title');
+    set action => $action;
+
+    show '/comment/add';
+};
+
+on 'comment/display' => run {
+    my $id = get 'id';
+
+    my $comment = Jifty->app_class('Model', 'Comment')->new;
+    $comment->load($id);
+
+    if (get 'update_status') {
+        my $action = $comment->as_update_action;
+        $action->argument_value( status => get 'update_status' );
+        $action->run;
+
+        Jifty->web->response->result( $action->moniker => $action->result );
+    }
+
+    if (defined get 'update_published') {
+        my $action = $comment->as_update_action;
+        $action->argument_value( published => get 'update_published' );
+        $action->run;
+
+        Jifty->web->response->result( $action->moniker => $action->result );
+    }
+
+    set comment => $comment;
+};
+
+1;

Added: jifty/trunk/lib/Jifty/Plugin/Comment/Mixin/Model/Commented.pm
==============================================================================
--- (empty file)
+++ jifty/trunk/lib/Jifty/Plugin/Comment/Mixin/Model/Commented.pm	Sun Mar  9 17:03:17 2008
@@ -0,0 +1,187 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Comment::Mixin::Model::Commented;
+use base qw/ Jifty::DBI::Record::Plugin /;
+
+our @EXPORT = qw( comments comment_record_class for_commenting );
+
+use Scalar::Util qw/ blessed /;
+
+use Jifty::DBI::Schema;
+use Jifty::Record schema {
+};
+
+=head1 NAME
+
+Jifty::Plugin::Comment::Mixin::Model::Commented - add comments to a model
+
+=head1 SYNOPSIS
+
+  package App::Model::Fooble;
+
+  use Jifty::DBI::Schema;
+  use App::Record schema {
+      column scribble => type is 'text';
+      column wobble => type is 'int';
+  };
+
+  use Jifty::Plugin::Comment::Mixin::Model::Commented;
+
+=head1 DESCRIPTION
+
+Add this mixin to a model if you'd like to attach comments to it. Comments can be used to allow users of your system to comment upon and discuss the record to which they are attached.
+
+=head1 METHODS
+
+=head2 import
+
+This method performs some rather devious magic to make everything work easily. It automatically generates an additional model for your application. This model will look something like this:
+
+  use strict;
+  use warnings;
+  
+  package App::Model::FoobleComment;
+  use Jifty::DBI::Schema;
+  
+  use Jifty::Record schema {
+      column commented_upon =>
+          references App::Model::Fooble,
+          label is 'Commented upon',
+          is mandatory,
+          is immutable,
+          ;
+  
+      column the_comment =>
+          references App::Model::Comment,
+          label is 'Comment',
+          is mandatory,
+          is immutable,
+          is distinct,
+          ;
+  };
+
+  App::Model::FoobleComment->add_trigger( before_access => sub {
+      my $self = shift;
+      my ($right, %args) = @_;
+
+      if ($right eq 'create') {
+          return 'allow' if $self->current_user->id;
+      }
+
+      if ($right eq 'read') {
+          return 'allow';
+      }
+
+      return $self->App::Model::FoobleComment::current_user_can(@_);
+  });
+  
+You will need to define an C<before_access> trigger for this class if you want it to be useful.
+
+=cut
+
+sub import {
+    my $my_class      = caller;
+    my $comment_class = $my_class.'Comment';
+    my $app_class     = Jifty->app_class;
+
+    eval "
+use strict;
+use warnings;
+
+package ${comment_class};
+use Jifty::DBI::Schema;
+
+use Jifty::Record schema {
+    column commented_upon =>
+        references ${my_class},
+        label is 'Commented upon',
+        is mandatory,
+        is immutable,
+        ;
+
+    column the_comment =>
+        references ${app_class}::Model::Comment,
+        label is 'Comment',
+        is mandatory,
+        is immutable,
+        is distinct,
+        ;
+};
+
+1;
+";
+
+    die "Failed to create comment link model ${comment_class}: $@" if $@;
+
+    Jifty->class_loader->_require_model_related_classes($comment_class);
+
+    my $comment_filename = $comment_class;
+    $comment_filename =~ s{::}{/}g;
+    $comment_filename .= '.pm';
+    $INC{ $comment_filename } = 'autogenerated';
+
+    goto &Jifty::DBI::Record::Plugin::import;
+}
+
+=head2 for_commenting
+
+Returns a value to be used with the comment views. It's basically just a string identifying the class name and ID of the record.
+
+=cut
+
+sub for_commenting {
+    my $self = shift;
+    return blessed($self) . '-' . $self->id;
+}
+
+=head2 comments
+
+Returns a collection of L<Jifty::Plugin::Comment::Model::Comment> objects that have been attached to the current record. (Actually, it returns the a collection of the local application class, e.g. C<App::Model::CommentCollection>.)
+
+=cut
+
+sub comments {
+    my $self = shift;
+
+    my $comments = Jifty->app_class('Model', 'CommentCollection')->new;
+    my $link_alias = $comments->join(
+        column1 => 'id',
+        table2  => $self->comment_record_class->table,
+        column2 => 'the_comment',
+    );
+
+    $comments->limit(
+        alias  => $link_alias,
+        column => 'commented_upon',
+        value  => $self->id,
+    );
+
+    return $comments;
+}
+
+=head2 comment_record_class
+
+This is the name of the linking class that was created during L</import>.
+
+=cut
+
+sub comment_record_class {
+    my $self = shift;
+    return (ref $self || $self).'Comment';
+}
+
+=head1 AUTHOR
+
+Andrew Sterling Hanenkamp C<< <hanenkamp at cpan.com> >>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007 Boomer Consulting, Inc. All Rights Reserved.
+
+This program is free software and may be modified and distributed under the same terms as Perl itself.
+
+=cut
+
+1;
+

Added: jifty/trunk/lib/Jifty/Plugin/Comment/Model/Comment.pm
==============================================================================
--- (empty file)
+++ jifty/trunk/lib/Jifty/Plugin/Comment/Model/Comment.pm	Sun Mar  9 17:03:17 2008
@@ -0,0 +1,321 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Comment::Model::Comment;
+use Jifty::DBI::Schema;
+
+use constant CLASS_UUID => '7B703CCA-544E-11DC-9227-2E96D84604BE';
+
+=head1 NAME
+
+Jifty::Plugin::Comment::Model::Comment - comments attached to anything
+
+=head1 SYNOPSIS
+
+  # to customize...
+  package App::Model::Comment;
+  use base qw/ Jifty::Plugin::Comment::Model::Comment /;
+
+  use Jifty::DBI::Schema;
+  use App::Record schema {
+      # Add a reference to the current user that creates this comment
+      column by_user =>
+          references App::Model::User,
+          ;
+  };
+
+  # make it so that any logged user can comment, but anyone can view, except
+  # that we don't really want everyone seeing all those personal bits...
+  sub current_user_can {
+      my $self = shift;
+      my ($right, %args) = @_;
+
+      if ($right eq 'create') {
+          return 1 if $self->current_user->id;
+      }
+
+      if ($right eq 'read') {
+          return $self->published
+              unless $args{column} =~ /^ 
+                  (?: email
+                    | status
+                    | ip_addr
+                    | http_refer
+                    | http_user_agent ) $/x;
+      }
+
+      # otherwise only superuser gets in
+      return $self->SUPER::current_user_can(@_);
+  }
+
+=head1 DESCRIPTION
+
+This model is the repository for all comments in your application, if you use the L<Jifty::Plugin::Comment> plugin.
+
+=head1 SCHEMA
+
+=head2 title
+
+This is the title of the comment.
+
+=head2 body
+
+This is the body of the comment.
+
+=head2 created_on
+
+This is the timestamp of when the comment was created.
+
+=head2 your_name
+
+This is the name the author of the comment has claimed.
+
+=head2 web_site
+
+This is the name of the web site the author claims as her own.
+
+=head2 email
+
+This is the email address the author is claiming.
+
+=head2 published
+
+This is a boolean flag indicating whether the comment should be shown or not when viewed.
+
+=head2 status
+
+This is a flag containing one of two values: "spam" or "ham". It indicates whether the comment has been evaluated as spam or not by L<Net::Akismet>.
+
+=head2 ip_addr
+
+This is the IP address of the remote client of the author that made the comment.
+
+=head2 http_referer
+
+This is the HTTP referer that was sent by the browser when the author made the comment.
+
+=head2 http_user_agent
+
+This is the HTTP user agent that was sent by the browser when the author made the comment.
+
+=cut
+
+use Jifty::Record schema {
+    column title =>
+        type is 'text',
+        label is 'Title',
+        is mandatory,
+        ;
+
+    column body =>
+        type is 'text',
+        label is 'Body',
+        is mandatory,
+        render as 'Textarea',
+        ;
+
+    column created_on =>
+        type is 'timestamp',
+        label is 'Created on',
+        filters are qw/ Jifty::DBI::Filter::DateTime /,
+        ;
+
+    column your_name =>
+        type is 'text',
+        label is 'Your name',
+        is mandatory,
+        ;
+
+    column web_site =>
+        type is 'text',
+        label is 'Web site',
+        ;
+
+    column email =>
+        type is 'text',
+        label is 'Email address',
+        ;
+
+    column published =>
+        type is 'boolean',
+        label is 'Published?',
+        is mandatory,
+        default is 1,
+        ;
+
+    column status =>
+        type is 'varchar(4)',
+        label is 'Status',
+        valid_values are qw/ spam ham /,
+        default is 'ham',
+        ;
+
+    column ip_addr =>
+        type is 'text',
+        label is 'IP Address',
+        ;
+
+    column http_referer =>
+        type is 'text',
+        label is 'HTTP Referer',
+        ;
+
+    column http_user_agent =>
+        type is 'text',
+        label is 'HTTP User Agent',
+        ;
+};
+
+use DateTime;
+use HTML::Scrubber;
+
+=head1 METHODS
+
+=head2 table
+
+Returns the database table name for the comments table.
+
+=cut
+
+sub table { 'comment_comments' }
+
+=head2 before_create
+
+It is assumed that your comments will be made available for create with very little restriction. This trigger is used to perform aggressive cleanup on the data stored and will attempt to check to see if the comment is spam by using L<Net::Akismet>.
+
+=cut
+
+sub before_create {
+    my $self = shift;
+    my $args = shift;
+
+    # Clean up stuff added by Jifty::Plugin::Comment::Action::CreateComment
+    delete $args->{parent_class};
+    delete $args->{parent_id};
+
+    my $plugin   = Jifty->find_plugin('Jifty::Plugin::Comment');
+    my $scrubber = $plugin->scrubber;
+
+    # Store safe fields
+    $args->{'title'}           = Jifty->web->escape($args->{'title'});
+    $args->{'your_name'}       = Jifty->web->escape($args->{'your_name'});
+    $args->{'web_site'}        = Jifty->web->escape($args->{'web_site'});
+    $args->{'email'}           = Jifty->web->escape($args->{'email'});
+    $args->{'body'}            = $scrubber->scrub($args->{'body'});
+
+    $args->{'created_on'}      = DateTime->now;
+
+    $args->{'ip_addr'}         = $ENV{'REMOTE_ADDR'};
+    $args->{'http_user_agent'} = $ENV{'HTTP_USER_AGENT'};
+    $args->{'http_referer'}    = $ENV{'HTTP_REFERER'};
+
+    # Prep for Akismet check or stop
+    my $akismet = $plugin->akismet or return 1;
+
+    # Check to see if it's classified as spam
+    my $verdict = $akismet->check(
+        USER_IP              => $args->{'ip_addr'},
+        USER_AGENT           => $args->{'http_user_agent'},
+        REFERER              => $args->{'http_referer'},
+        COMMENT_CONTENT      => $args->{'title'}."\n\n".$args->{'body'},
+        COMMENT_AUTHOR       => $args->{'your_name'},
+        COMMENT_AUTHOR_EMAIL => $args->{'email'},
+        COMMENT_AUTHOR_URL   => $args->{'web_site'},
+    );
+
+    # I have no idea what it is... mark it spam just in case...
+    # TODO the default no verdict action should configurable
+    if (!$verdict) {
+        $args->{published} = 0;
+        $args->{status}    = 'spam';
+
+        warn "Failed to determine whether new comment is spam or not.";
+        return 1;
+    }
+
+    # Naughty, naughty... mark as spam
+    if ($verdict eq 'true') {
+        warn "A new comment is detected as spam.";
+
+        $args->{published} = 0;
+        $args->{status}    = 'spam';
+
+        return 1;
+    }
+
+    # Excellent, post that ham
+    else {
+        $args->{published} = 1;
+        $args->{status}    = 'ham';
+
+        return 1;
+    }
+}
+
+=head2 before_set_status
+
+This trigger is called when changing the status of the message. If L<Net::Akismet> is in use, this trigger will notify Akismet that this message is being marked as spam or as ham, depending upon the new value.
+
+=cut
+
+sub before_set_status {
+    my $self = shift;
+    my $args = shift;
+
+    my $plugin  = Jifty->find_plugin('Jifty::Plugin::Comment');
+    my $akismet = $plugin->akismet or return 1;
+
+    my %akismet_report = (
+        USER_IP              => $self->ip_addr,
+        USER_AGENT           => $self->http_user_agent,
+        REFERER              => $self->http_referer,
+        COMMENT_CONTENT      => $self->title."\n\n".$self->body,
+        COMMENT_AUTHOR       => $self->your_name,
+        COMMENT_AUTHOR_EMAIL => $self->email,
+        COMMENT_AUTHOR_URL   => $self->web_site,
+    );
+
+    if ($self->status eq 'spam' && $args->{value} eq 'ham') {
+        if ($akismet->ham( %akismet_report )) {
+            Jifty->log->info("Reported that comment ".$self->id." is HAM to Akismet.");
+        }
+        else {
+            # Not the end of the world, just that Akismet doesn't know...
+            Jifty->log->info("FAILED to report that comment ".$self->id." is HAM to Akismet.");
+        }
+    }
+
+    elsif ($self->status eq 'ham' && $args->{value} eq 'spam') {
+        if ($akismet->spam( %akismet_report )) {
+            Jifty->log->info("Reported that comment ".$self->id." is SPAM to Akismet.");
+        }
+        else {
+            # Not the end of the world, just that Akismet doesn't know...
+            Jifty->log->info("FAILED to report that comment ".$self->id." is SPAM to Akismet.");
+        }
+    }
+
+    return 1;
+}
+
+=head2 current_user_can
+
+This method is not actually implemented by this class, but you will either want to implementt this method in your application or add a C<before_access> trigger that grants access. Otherwise, your comments won't be very interesting to anyone but a superuser.
+
+See the L</SYNOPSIS> for a recommended implementation.
+
+=head1 AUTHOR
+
+Andrew Sterling Hanenkamp C<< <hanenkamp at cpan.org> >>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007 Boomer Consulting, Inc. All Rights Reserved.
+
+This program is free software and may be modified and distributed under the same terms as Perl itself.
+
+=cut
+
+1;
+
+

Added: jifty/trunk/lib/Jifty/Plugin/Comment/Notification/CommentNeedsModeration.pm
==============================================================================
--- (empty file)
+++ jifty/trunk/lib/Jifty/Plugin/Comment/Notification/CommentNeedsModeration.pm	Sun Mar  9 17:03:17 2008
@@ -0,0 +1,73 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Comment::Notification::CommentNeedsModeration;
+use base qw/ Jifty::Notification /;
+
+__PACKAGE__->mk_accessors(qw/ comment parent /);
+
+=head1 DESCRIPTION
+
+If you want to receive these notifications, you must override L</setup> to set your the C<to_list> for your application.
+
+  use strict;
+  use warnings;
+
+  package MyApp::Notification::CommentNeedsModeration;
+  use base qw/ Jifty::Plugin::Comment::Notification::CommentNeedsModeration /;
+
+  sub setup {
+      my $self = shift;
+
+      # Limit to users that have a "moderator" column set to 1
+      my $users = MyApp::Model::UserCollection->new;
+      $users->limit( column => 'moderator', value => 1 );
+      $self->to_list(@{ $users->items_array_ref });
+
+      $self->SUPER::setup(@_);
+  }
+
+  1;
+
+=cut
+
+sub setup {
+    my $self = shift;
+
+    my $appname = Jifty->config->framework('ApplicationName');
+    my $comment = $self->comment;
+
+    my $from = $comment->your_name || 'Anonymous Coward';
+    $from .= ' <'.$comment->email.'>' if $comment->email;
+    $from .= ' ('.$comment->web_site.')'      if $comment->web_site;
+
+    my $url = $self->url;
+
+    $self->subject(_("[%1] Moderate comment: %2", $appname, $comment->title));
+    $self->body(_(q{
+The following comment has not been published. If you would like to publish it, please visit the link below and click on the "publish" link. If it has been marked as spam and should not have been you should also click on the "mark as ham" link.
+
+View Comment: %1
+
+On Post: %2
+Subject: %3
+From: %4
+Date: %5
+
+%6
+}, 
+        $url, 
+        $self->parent->title,
+        $comment->title, 
+        $from, 
+        $comment->created_on->strftime('%A, %B %d, %Y @ %H:%M%P'), 
+        $comment->body
+    ));
+}
+
+sub url {
+    my $self = shift;
+    return Jifty->config->framework('Web')->{'BaseURL'};
+}
+
+1;

Added: jifty/trunk/lib/Jifty/Plugin/Comment/Notification/CommentPublished.pm
==============================================================================
--- (empty file)
+++ jifty/trunk/lib/Jifty/Plugin/Comment/Notification/CommentPublished.pm	Sun Mar  9 17:03:17 2008
@@ -0,0 +1,69 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Comment::Notification::CommentPublished;
+use base qw/ Jifty::Notification /;
+
+__PACKAGE__->mk_accessors(qw/ comment parent /);
+
+=head1 DESCRIPTION
+
+If you want to receive these notifications, you must override L</setup> to set your the C<to_list> for your application.
+
+  use strict;
+  use warnings;
+
+  package MyApp::Notification::CommentPublished;
+  use base qw/ Jifty::Plugin::Comment::Notification::CommentPublished /;
+
+  sub setup {
+      my $self = shift;
+
+      # Send to the author of the post
+      $self->to_list($self->parent->author);
+
+      $self->SUPER::setup(@_);
+  }
+
+  1;
+
+=cut
+
+sub setup {
+    my $self = shift;
+
+    my $appname = Jifty->config->framework('ApplicationName');
+    my $comment = $self->comment;
+
+    my $from = $comment->your_name || 'Anonymous Coward';
+    $from .= ' <'.$comment->email.'>' if $comment->email;
+    $from .= ' ('.$comment->web_site.')'      if $comment->web_site;
+
+    my $url = $self->url;
+
+    $self->subject(_("[%1] New comment: %2", $appname, $comment->title));
+    $self->body(_("
+View Comment: %1
+
+On Post: %2
+Subject: %3
+From: %4
+Date: %5
+
+%6
+", 
+        $url,
+        $self->parent->title,
+        $comment->title, 
+        $from, 
+        $comment->created_on->strftime('%A, %B %d, %Y @ %H:%M%P'), 
+        $comment->body
+    ));
+}
+
+sub url {
+    my $self = shift;
+    return Jifty->config->framework('Web')->{'BaseURL'};
+}
+
+1;

Added: jifty/trunk/lib/Jifty/Plugin/Comment/View.pm
==============================================================================
--- (empty file)
+++ jifty/trunk/lib/Jifty/Plugin/Comment/View.pm	Sun Mar  9 17:03:17 2008
@@ -0,0 +1,277 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Comment::View;
+use Jifty::View::Declare -base;
+
+use Jifty::DateTime;
+
+sub scrub_html($) {
+    my $text = shift;
+
+    my $plugin = Jifty->find_plugin('Jifty::Plugin::Comment');
+    return $plugin->scrubber->scrub($text);
+}
+
+template 'comment/view' => page {
+    my $comment = get 'comment';
+
+    { title is _( $comment->title ); }
+
+    div {
+        { class is 'column span-21 first' }
+
+        render_region
+            name => 'comment-'.$comment->id,
+            path => '/comment/display',
+            defaults => {
+                id  => $comment->id,
+                top => 1,
+            },
+            ;
+    };
+
+    show '/advertisement';
+};
+
+template 'comment/display' => sub {
+    my $comment = get 'comment';
+    my $top     = get 'top';
+
+    div {
+        my $class = 'comment';
+        $class .= $comment->status eq 'ham' ? ' ham' : ' spam'
+            if Jifty->web->current_user->id;
+        $class .= $comment->published ? ' published' : ' unpublished';
+
+        { class is $class }
+
+        div {
+            { class is 'actions' }
+
+            if (Jifty->web->current_user->id) {
+
+                for my $status (qw( ham spam )) {
+                    if ($comment->status ne $status) {
+                        hyperlink
+                            label => _('mark as %1', $status),
+                            onclick => {
+                                args  => {
+                                    update_status => $status,
+                                },
+                            },
+                            ;
+                    }
+                }
+
+                for my $published (0, 1) {
+                    if ($comment->published ne $published) {
+                        hyperlink
+                            label => _($published ? 'publish' : 'unpublish'),
+                            onclick => {
+                                args  => {
+                                    update_published => $published,
+                                },
+                            },
+                            ;
+                    }
+                }
+            }
+
+            '';
+        };
+
+        unless ($top) {
+            h5 { 
+                a {
+                    attr { name => 'comment-'.$comment->id };
+                    $comment->title 
+                };
+            };
+        }
+
+        div {
+            { class is 'comment-info' }
+
+            my $poster = $comment->your_name || 'Anonymous Coward';
+            $poster = Jifty->web->escape($poster);
+            $poster = qq{<a href="@{[$comment->web_site]}">$poster</a>}
+                if $comment->web_site;
+
+            my $created_on = Jifty::DateTime->now;
+
+            p { 
+                outs_raw _('By %1 %2', 
+                    $poster, 
+                    $created_on->strftime('%A, %B %d, %Y @ %H:%M%P')
+                ) 
+            };
+        };
+
+        div {
+            outs_raw scrub_html($comment->body);
+        };
+
+    };
+};
+
+template 'comment/add' => sub {
+    my $collapsed = get 'collapsed';
+    my $region    = get 'region';
+
+    if ($collapsed) {
+        p {
+            hyperlink
+                label => _('Add a comment'),
+                onclick => {
+                    refresh_self => 1,
+                    args => { collapsed => 0 },
+                },
+                ;
+        };
+    }
+
+    else {
+        my $action = get 'action';
+
+        if (get 'preview') {
+            div {
+                { class is 'preview comment' }
+
+                h5 { $action->argument_value('title') || 'No Title' };
+
+                div {
+                    { class is 'comment-info' }
+
+                    my $poster = $action->argument_value('your_name') 
+                              || 'Anonymous Coward';
+                    $poster = Jifty->web->escape($poster);
+                    $poster = qq{<a href="@{[$action->argument_value('web_site')]}">$poster</a>}
+                        if $action->argument_value('web_site');
+
+                    my $created_on = Jifty::DateTime->now;
+
+                    p { 
+                        outs_raw _('By %1 %2', 
+                            $poster, 
+                            $created_on->strftime('%A, %B %d, %Y @ %H:%M%P')
+                        ) 
+                    };
+                };
+
+                div {
+                    my $body = $action->argument_value('body')
+                            || 'No Body';
+
+                    outs_raw scrub_html($body);
+                };
+            };
+        };
+
+        div {
+            { class is 'edit comment' }
+
+            form {
+                render_action $action;
+
+                div {
+                    { class is 'submit-buttons' }
+
+                    form_submit
+                        label => _('Preview'),
+                        name => 'op',
+                        class => 'first',
+                        onclick => {
+                            refresh_self => 1,
+                            submit => { 
+                                action => $action, 
+                                arguments => { submit => 0 },
+                            },
+                            args => { preview => 1 },
+                        },
+                        ;
+
+                    if (get 'preview') {
+                        form_submit
+                            label => _('Submit'),
+                            onclick => [ 
+                                {
+                                    refresh_self => 1,
+                                    submit => {
+                                        action => $action,
+                                        arguments => { submit => 1 },
+                                    },
+                                    args => {
+                                        preview => 0,
+                                        collapsed => 1,
+                                    },
+                                },
+                                {
+                                    element => $region->parent->get_element('div.list'),
+                                    append  => '/comment/display',
+                                    args    => {
+                                        id  => { result => $action, name => 'id' },
+                                        top => 0,
+                                    },
+                                },
+                            ],
+                            ;
+                    }
+                };
+            };
+        };
+    }
+};
+
+template 'comment/list' => sub {
+    my $parent   = get 'parent';
+    my $title    = get 'initial_title';
+    my $comments = $parent->comments;
+
+    if (!Jifty->web->current_user->id) {
+        $comments->limit(
+            column => 'status',
+            value  => 'ham',
+        );
+    }
+
+    div {
+        { class is 'list' }
+
+        if ($comments->count) {
+            while (my $comment = $comments->next) {
+                render_region
+                    name => 'comment-'.$comment->id,
+                    path => '/comment/display',
+                    defaults => {
+                        id  => $comment->id,
+                        top => 0,
+                    },
+                    ;
+            }
+        }
+
+        else {
+            p {
+                { class is 'none' }
+
+                _('No one has made a comment yet.');
+            };
+        }
+    };
+
+    unless (get 'no_add') {
+        render_region
+            name     => 'comment-add-'.$parent->id,
+            path     => '/comment/add',
+            defaults => {
+                parent_class => ref $parent,
+                parent_id    => $parent->id,
+                collapsed    => 1,
+                title        => $title,
+            },
+            ;
+    }
+};
+
+1;

Added: jifty/trunk/t/TestApp-Plugin-Comments/Makefile.PL
==============================================================================
--- (empty file)
+++ jifty/trunk/t/TestApp-Plugin-Comments/Makefile.PL	Sun Mar  9 17:03:17 2008
@@ -0,0 +1,7 @@
+use inc::Module::Install;
+
+name        'TestApp::Plugin::Comments';
+version     '0.01';
+requires    'Jifty' => '0.71129';
+
+WriteAll;

Added: jifty/trunk/t/TestApp-Plugin-Comments/bin/jifty
==============================================================================
--- (empty file)
+++ jifty/trunk/t/TestApp-Plugin-Comments/bin/jifty	Sun Mar  9 17:03:17 2008
@@ -0,0 +1,11 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+use File::Basename qw(dirname); 
+use UNIVERSAL::require;
+
+use Jifty;
+use Jifty::Script;
+
+local $SIG{INT} = sub { warn "Stopped\n"; exit; };
+Jifty::Script->dispatch();

Added: jifty/trunk/t/TestApp-Plugin-Comments/etc/config.yml
==============================================================================
--- (empty file)
+++ jifty/trunk/t/TestApp-Plugin-Comments/etc/config.yml	Sun Mar  9 17:03:17 2008
@@ -0,0 +1,56 @@
+--- 
+framework: 
+  AdminMode: 1
+  ApplicationClass: TestApp::Plugin::Comments
+  ApplicationName: TestApp::Plugin::Comments
+  ApplicationUUID: E6A5E652-A2D2-11DC-B489-8901F3F60BF3
+  ConfigFileVersion: 2
+  Database: 
+    AutoUpgrade: 1
+    CheckSchema: 1
+    Database: testapp::plugin::comments
+    Driver: SQLite
+    Host: localhost
+    Password: ''
+    RecordBaseClass: Jifty::DBI::Record::Cachable
+    RecordUUIDs: active
+    User: ''
+    Version: 0.0.1
+  DevelMode: 1
+  L10N: 
+    PoDir: share/po
+  LogLevel: INFO
+  Mailer: Sendmail
+  MailerArgs: []
+
+  Plugins: 
+    - LetMe: {}
+    - SkeletonApp: {}
+    - REST: {}
+    - Halo: {}
+    - ErrorTemplates: {}
+    - OnlineDocs: {}
+    - CompressedCSSandJS: {}
+    - AdminUI: {}
+
+    - Comment: {}
+
+  PubSub: 
+    Backend: Memcached
+    Enable: ~
+  SkipAccessControl: 0
+  TemplateClass: TestApp::Plugin::Comments::View
+  Web: 
+    BaseURL: http://localhost
+    DataDir: var/mason
+    Globals: []
+
+    MasonConfig: 
+      autoflush: 0
+      default_escape_flags: h
+      error_format: text
+      error_mode: fatal
+    Port: 8888
+    ServeStaticFiles: 1
+    StaticRoot: share/web/static
+    TemplateRoot: share/web/templates

Added: jifty/trunk/t/TestApp-Plugin-Comments/lib/TestApp/Plugin/Comments/Dispatcher.pm
==============================================================================
--- (empty file)
+++ jifty/trunk/t/TestApp-Plugin-Comments/lib/TestApp/Plugin/Comments/Dispatcher.pm	Sun Mar  9 17:03:17 2008
@@ -0,0 +1,15 @@
+use strict;
+use warnings;
+
+package TestApp::Plugin::Comments::Dispatcher;
+use Jifty::Dispatcher -base;
+
+on 'view/#' => run {
+    my $blog = TestApp::Plugin::Comments::Model::BlogPost->new;
+    $blog->load($1);
+
+    set blog => $blog;
+    show 'view';
+};
+
+1;

Added: jifty/trunk/t/TestApp-Plugin-Comments/lib/TestApp/Plugin/Comments/Model/BlogPost.pm
==============================================================================
--- (empty file)
+++ jifty/trunk/t/TestApp-Plugin-Comments/lib/TestApp/Plugin/Comments/Model/BlogPost.pm	Sun Mar  9 17:03:17 2008
@@ -0,0 +1,21 @@
+use strict;
+use warnings;
+
+package TestApp::Plugin::Comments::Model::BlogPost;
+use Jifty::DBI::Schema;
+
+use constant CLASS_UUID => '21EC717C-A2D3-11DC-BDD3-A201F3F60BF3';
+
+use TestApp::Plugin::Comments::Record schema {
+    column title => type is 'text';
+    column body => type is 'text';
+    column author => type is 'text';
+    column posted => type is 'text';
+};
+
+use Jifty::Plugin::Comment::Mixin::Model::Commented;
+
+# Your model-specific methods go here.
+
+1;
+

Added: jifty/trunk/t/TestApp-Plugin-Comments/lib/TestApp/Plugin/Comments/View.pm
==============================================================================
--- (empty file)
+++ jifty/trunk/t/TestApp-Plugin-Comments/lib/TestApp/Plugin/Comments/View.pm	Sun Mar  9 17:03:17 2008
@@ -0,0 +1,39 @@
+use strict;
+use warnings;
+
+package TestApp::Plugin::Comments::View;
+use Jifty::View::Declare -base;
+
+template 'post' => page {
+    { title is 'New Post' }
+
+    form {
+        my $post_blog = new_action class => 'CreateBlogPost';
+        render_action $post_blog;
+        form_submit label => _('Post'), submit => $post_blog;
+    };
+};
+
+template 'view' => page {
+    my $blog = get 'blog';
+
+    { title is $blog->title }
+
+    p { _('By %1', $blog->author) };
+
+    p { $blog->body };
+
+    hr { };
+
+    render_region
+        name      => 'comments',
+        path      => '/comment/list',
+        arguments => {
+            collapsed    => 1,
+            parent_class => Jifty->app_class('Model', 'BlogPost'),
+            parent_id    => $blog->id,
+        },
+        ;
+};
+
+1;

Added: jifty/trunk/t/TestApp-Plugin-Comments/t/00-model-BlogPost.t
==============================================================================
--- (empty file)
+++ jifty/trunk/t/TestApp-Plugin-Comments/t/00-model-BlogPost.t	Sun Mar  9 17:03:17 2008
@@ -0,0 +1,76 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+A basic test harness for the BlogPost model.
+
+=cut
+
+use lib 't/lib';
+use Jifty::SubTest;
+
+use Jifty::Test tests => 16;
+
+# Make sure we can load the model
+use_ok('TestApp::Plugin::Comments::Model::BlogPost');
+
+# Grab a system user
+my $system_user = TestApp::Plugin::Comments::CurrentUser->superuser;
+ok($system_user, "Found a system user");
+
+# Try testing a create
+my $o = TestApp::Plugin::Comments::Model::BlogPost->new(current_user => $system_user);
+my ($id) = $o->create();
+ok($id, "BlogPost create returned success");
+ok($o->id, "New BlogPost has valid id set");
+is($o->id, $id, "Create returned the right id");
+
+my $c_list = $o->comments;
+is($c_list->count, 0, 'We have zippo comments');
+
+# Add a comment
+my $c = TestApp::Plugin::Comments::Model::Comment->new(current_user => $system_user);
+$c->create(
+    title      => 'Jifty is da bomb',
+    body       => 'And other overused clichés...',
+    created_on => DateTime->now,
+    your_name  => 'Sterling',
+);
+ok($c->id, 'Created a comment');
+
+my $bpc = TestApp::Plugin::Comments::Model::BlogPostComment->new(current_user => $system_user);
+$bpc->create(
+    commented_upon => $o->id,
+    the_comment    => $c->id,
+);
+ok($bpc->id, 'Created a comment link');
+
+$c_list->redo_search;
+is($c_list->count, 1, 'We have a comment!');
+is($c_list->count_all, 1, 'We have a comment somewhere!');
+
+# And another
+$o->create();
+ok($o->id, "BlogPost create returned another value");
+isnt($o->id, $id, "And it is different from the previous one");
+
+# Searches in general
+my $collection =  TestApp::Plugin::Comments::Model::BlogPostCollection->new(current_user => $system_user);
+$collection->unlimit;
+is($collection->count, 2, "Finds two records");
+
+# Searches in specific
+$collection->limit(column => 'id', value => $o->id);
+is($collection->count, 1, "Finds one record with specific id");
+
+# Delete one of them
+$o->delete;
+$collection->redo_search;
+is($collection->count, 0, "Deleted row is gone");
+
+# And the other one is still there
+$collection->unlimit;
+is($collection->count, 1, "Still one left");
+


More information about the Jifty-commit mailing list