summaryrefslogtreecommitdiff
path: root/posts/2009-05-13-a-simple-feed-aggregator-with-modern-perl-part-4.org
blob: 5d0b0efa7da43bbc0df67c73a994e4730eb03bbf (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
We have the model, the aggregator (and some tests), now we can do a
basic frontend to read our feed. For this I will create a webapp using
[[http://www.catalystframework.org][Catalyst]].

[[http://search.cpan.org/perldoc?Catalyst::Devel][Catalyst::Devel]] is
required for developping catalyst application, so we will install it
first:

#+BEGIN_SRC perl
    % cpan Catalyst::Devel
#+END_SRC

Now we can create our catalyst application using the helper:

#+BEGIN_SRC perl
    % catalyst.pl MyFeedReader
#+END_SRC

This command initialise the framework for our application
*MyFeedReader*. A number of files are created, like the structure of the
MVC directory, some tests, helpers, ...

We start by creating a view, using
[[http://search.cpan.org/perldoc?Catalyst::View::TT][TTSite]]. TTSite
generate some templates for us, and the configuration for this template.
We will also have a basic CSS, a header, footer, etc.

#+BEGIN_EXAMPLE
    cd MyFeedReader
    perl script/myfeedreader_create.pl view TT TTSite
#+END_EXAMPLE

TTSite files are under *root/src* and *root/lib*. A
*MyAggregator/View/TT.pm* file is also created. We edit it to make it
look like this:

#+BEGIN_SRC perl
    __PACKAGE__->config({
        INCLUDE_PATH => [
            MyFeedReader->path_to( 'root', 'src' ),
            MyFeedReader->path_to( 'root', 'lib' )
        ],
        PRE_PROCESS  => 'config/main',
        WRAPPER      => 'site/wrapper',
        ERROR        => 'error.tt2',
        TIMER        => 0,
        TEMPLATE_EXTENSION => '.tt2',
    });
#+END_SRC

Now we create our first template, in *root/src/index.tt2*

#+BEGIN_EXAMPLE
    to <a href="/feed/">your feeds</a>
#+END_EXAMPLE

If you start the application (using
=perl script/myfeedreader_server.pl=) and point your browser on
http://localhost:3000/, this template will be rendered.

We need two models, one for KiokuDB and another one for MyModel:

*lib/MyFeedReader/Model/KiokuDB.pm*

#+BEGIN_SRC perl
    package MyFeedReader::Model::KiokuDB;
    use Moose;
    BEGIN { extends qw(Catalyst::Model::KiokuDB) }
    1;
#+END_SRC

we edit the configuration file (*myfeedreader.conf*), and set the dsn
for our kiokudb backend

#+BEGIN_EXAMPLE
        <Model KiokuDB>
            dsn dbi:SQLite:../MyAggregator/foo.db
        </Model>
#+END_EXAMPLE

*lib/MyFeedReader/Model/MyModel.pm*

#+BEGIN_SRC perl
    package MyFeedReader::Model::MyModel;
    use base qw/Catalyst::Model::DBIC::Schema/;
    1;
#+END_SRC

and the configuration:

#+BEGIN_EXAMPLE
    <Model MyModel>
        connect_info dbi:SQLite:../MyModel/model.db
        schema_class MyModel
    </Model>
#+END_EXAMPLE

We got our view and our model, we can do the code for the controller. We
need 2 controller, one for the feed, and one for the entries. The Feed
controller will list them and display entries titles for a given feed.
The Entry controller will just display them.

*lib/MyFeedReader/Controller/Feed.pm*

#+BEGIN_SRC perl
    package MyFeedReader::Controller::Feed;
    use strict;
    use warnings;
    use parent 'Catalyst::Controller';

    __PACKAGE__->config->{namespace} = 'feed';

    sub index : Path : Args(0) {
        my ( $self, $c ) = @_;
        $c->stash->{feeds}
            = [ $c->model('MyModel')->resultset('Feed')->search() ];
    }

    sub view : Chained('/') : PathPart('feed/view') : Args(1) {
        my ( $self, $c, $id ) = @_;
        $c->stash->{feed}
            = $c->model('MyModel')->resultset('Feed')->find($id);
    }

    1;
#+END_SRC

The function =index= list the feeds, while the function =view= list the
entries for a give feed. We use the chained action mechanism to dispatch
this url, so we can have urls like this */feed/**

We create our 2 templates (for index and view):

*root/src/feed/index.tt2*

#+BEGIN_EXAMPLE
    <ul>
        [% FOREACH feed IN feeds %]
            <li><a href="/feed/view/[% feed.id %]">[% feed.url %]</a></li>
        [% END %]
    </ul>
#+END_EXAMPLE

*root/src/feed/vew.tt2*

#+BEGIN_EXAMPLE
    <h1>[% feed.url %]</h1>

    <h3>entries</h3>
    <ul>
        [% FOREACH entry IN feed.entries %]
            <li><a href="/entry/[% entry.id %]">[% entry.permalink %]</a></li>
        [% END %]
    </ul>
#+END_EXAMPLE

If you point your browser to http://localhost:3000/feed/ you will see
this:

Now the controller for displaying the entries:

#+BEGIN_SRC perl
    package MyFeedReader::Controller::Entry;
    use strict;
    use warnings;
    use MyAggregator::Entry;
    use parent 'Catalyst::Controller';

    __PACKAGE__->config->{namespace} = 'entry';

    sub view : Chained('/') : PathPart('entry') : Args(1) {
        my ( $self, $c, $id ) = @_;
        $c->stash->{entry} = $c->model('KiokuDB')->lookup($id);
    }

    1;
#+END_SRC

The function *view* fetch an entry from the kiokudb backend, and store
it in the stash, so we can use it in our template.

*root/src/entry/view.tt2*

#+BEGIN_EXAMPLE
    <h1><a href="[% entry.permalink %]">[% entry.title %]</a></h1>
    <span>Posted [% entry.date %] by [% entry.author %]</span>
    <div id="content">
        [% entry.content %]
    </div>
#+END_EXAMPLE

If you point your browser to an entry (something like
*http://localhost:3000/entry/somesha256value*), you will see an entry:

Et voila, we are done with a really basic feed reader. You can add
methods to add or delete feed, mark an entry as read, ...

[[http://git.lumberjaph.net/p5-ironman-myfeedreader.git/][The code is
available on my git server]].