Monday, July 02, 2007
Fat Model
After moving parameter validation and transactions into the Model it looks OK. I think it can lead to something enough abstracted to become a CPAN module - but for now I am sure that it'll not be as simple as DBIx::Class::Validation.
Monday, April 23, 2007
Catalyst::Controller::PathArgs
There is still need for the PathPart parameter - for the case when you have paths like "/book/$book_id/edition/$edition_id" and "/book/$book_id/edition", i.e. two paths that differ only in the number of parameters taken by the actions. Since you can have only one "edition" subroutine in the "Book" controller you need to alias a subroutine with a different name to act as if it has the name "edition" - this is what you can do with the PathPart attribute. I am tempted to also rename that one to Alias too - so that it would be more mnemonic.
Anyway the source can be downloaded from it's subdir in the Cat repository. Below is the POD for the Catalyst::Controller::PathArgs controller. And I am waiting for comments before I submit it to CPAN.
NAME
Catalyst::Controller::PathArgs - syntactic sugar for the Catalyst::DispatchType::Chained manpage.
SYNOPSIS
package MyApp::Controller::Root;
use base 'Catalyst::Controller::PathArgs';
__PACKAGE__->config->{namespace} = '';
sub pathargsroot : PathArgs(1) {}
use Catalyst::Controller::PathArgs;
package TestApp::Controller::Pathargsroot; use base 'Catalyst::Controller::PathArgs';
sub pathargsend : PathArgs(1) EndPoint { }
DESCRIPTION
This Catalyst Controller base adds two new action attributes: PathArgs (taking one numerical argument) and EndPoint. This is entirely syntactic sugar over the the Catalyst::DispatchType::Chained manpage full machinery for paths like '/book/$book_id/edition/$edition_id/view' - with PathArgs you can chain the 'book', 'edition' and 'view' methods and declare how many parameters they take. EndPoint is needed to declare an ation as the end of the chain (in theory this should be computable - but for now I don't see any easy way to do that).
This package uses the Class::C3 manpage this means that you cannot use NEXT in classes based on it - but C3 looks like a good replacement for NEXT.
To declare that the book subroutine is the root chained action with one argument you need to declare it in the Root controller with:
sub book : PathArgs(1) { If we had a non chained path with /book/edition - the edition sub would be declared in the 'Book' controller - and this is the same case here - you just add PathArgs(1) to indicate that it is indeed chained and that it takes one parameter. So in the Book controller you add:
sub edition : PathArgs(1) { For the last action in the chain you need to add EndPoint. So in the Book::Edition controller you would need:
sub view : PathArgs(0) EndPoint { You can also mix PathArgs with PathPart (new in Catalyst 5.7007). For example if you wanted to have an action responding for the address ``/book/$book_id/edition'' you would need a subroutine called 'edition' in the Book controller, but there is already one routine called 'edition' in that controller. What you can do in that case is make a sub with a different name and declare that from the outside it's name should be really 'edition':
sub edition_mascarade: PathPart('edition') PathArgs(0) EndPoint { yeah - you need to add EndPoint there as well.
An example is included in the example directory of this distribution.
Internally PathArgs and EndPoint are converted to 'Chained(.)' and appriopriate CaptureArgs or Args attributes. For more sophisticated chaining you might need to use the Catalyst::DispatchType::Chained manpage directly.
create_action
This is the overriden method from Catalyst::Controller used here to compute the new attributes.
BUGS
SUPPORT
AUTHOR
Zbigniew Lukasiak
CPAN ID: ZBY
http://perlalchemy.blogspot.com/
COPYRIGHT
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
The full text of the license can be found in the LICENSE file included with this module.
SEE ALSO
Thursday, April 12, 2007
MatchParams for auto actions
Here is how it would work - using the canonical example for Catalyst::DispatchType::Chained (for the path "/wiki/FooBarPage/rev/23/view"):
sub wiki : PathPart('wiki') Chained('/') CaptureArgs(1) {
my ( $self, $c, $page_name ) = @_;
# load the page named $page_name and put the object
# into the stash
}
sub rev : PathPart('rev') Chained('wiki') CaptureArgs(1) {
my ( $self, $c, $revision_id ) = @_;
# use the page object in the stash to get at its
# revision with number $revision_id
}
sub view : PathPart Chained('rev') Args(0) {
my ( $self, $c ) = @_;
# display the revision in our stash. Another option
# would be to forward a compatible object to the action
# that displays the default wiki pages, unless we want
# a different interface here, for example restore
# functionality.
}
would become
package Wiki;
sub auto : MatchParams ( qr/\w+/ ) {
my ( $self, $c, $page_name ) = @_;
# load the page named $page_name and put the object
# into the stash
}
package Wiki::Rev;
sub auto : MatchParams ( qr/\d+/ ) {
my ( $self, $c, $revision_id ) = @_;
# use the page object in the stash to get at its
# revision with number $revision_id
}
sub view : Local {
my ( $self, $c ) = @_;
# display the revision in our stash. Another option
# would be to forward a compatible object to the action
# that displays the default wiki pages, unless we want
# a different interface here, for example restore
# functionality.
}
Does it not look simpler? Instead of a completely new language for chaining actions it uses the standard directory tree model that we are all accustomed to. And it works in the spirit of the common practice of using auto to put some prerequisites to the stash.
Monday, November 20, 2006
Continuations for Web Development and Leaky Abstractions
Calling something continuation passing when in fact you need to save and then retrieve the whole process memory to and from the disc is such a misleading abstraction. Passing and then calling a continuation, in languages that support it, is a primitive operation - something that translates to just a few machine code instructions - something
O(1) - saving process memory is not. Until this memory saving is completely eliminated I will call Catalyst::Continuation and Jifty::Continuation a leaky abstraction.
Saturday, October 28, 2006
path and args
http://www.domain.com/book?author=Dostoyevskybecomes
http://www.domain.com/book/Dostoyevsky.But what if we add a genre to that search?
http://www.domain.com/book/Dostoyevsky/memoiror
http://www.domain.com/book/memoir/Dostoyevsky?
We need to arbitrary decide about the position of the arguments. But what if both of the arguments are optional? Then we need to reserve some string to play the role of 'undef'. We need to add two unnecessary, arbitrary conventions - complicating the code and also the usage.
Perhaps we should rather do
http://www.domain.com/book/author/Dostoyevsky/genre/memoirwith the semantic that everything after
/book/ is actually the same old key=value pair and that the order of the pairs has no meaning. Does this really buy us something? I don't know - I would rather see: http://www.domain.com/book?author=Dostoyevsky;genre=memoir and know from the start how to manipulate it, and that I don't need to care about the order of the pairs.Why on Earth change all named parameters to positional ones?
For Plugger: Catalyst.
Thursday, October 26, 2006
Tags and search and DBIx::Class
Some time ago I had an idea for a bookmarking site - nothing really revolutionary, but with an effective interface. I've decided that it needs to combine search, browsing by tags and other properties, ordering and jumping to pages. I have really thought much how to make it effective - that is letting the user find some web page with least number of operations assuming that she remembers only random bits from it and that the data we display perhaps reminds her about some new info. These interface ideas are material for another post - here I would like to concentrate on implementation of tagging in DBIx::Class.
I had following requirements:
- tags can be combined together and with other search terms
- all search should use indices
- use database paging of results
- for tag clouds I need a list of tags used by bookmarks matching a search criterium
WHERE ? @@ tags AND ? @@ tags AND ? @@ tags AND THE_OTHER_CRITERIA where the @@ operator is a full text match for PostgreSQL and tags was the name of the aggregated tags column.Unfortunately I did not found any way to meet the fourth requirement with this database schema, so I decided to have separate
bookmark and tag tables. The tag table has two columns bookmark_id and tag_text with indices on both of them. One idea how to make the combined search here could be like:SELECT b.*
FROM bookmark b, tag t
WHERE t.bookmark_id = b.id
AND (t.tag_text IN (?, ?, ?))
GROUP BY b.id
HAVING COUNT( b.id )=3
But that would mean a full scan on the bookmark table and I wanted a solution using indices. So I devised another query for that schema. For a combination of many tags I would do as many joins to the
tag table as there are tags in the query:
WHERE tag_1.bookmark_id = bookmark.id AND
tag_1.tag_text = ? AND
tag_2.bookmark_id = bookmark.id AND
tag_2.tag_text = ? AND
...
For the first glance this looks hard to do in DBIx::Class - but actually it is a lot easier than it seems, the key learning here is:
If the same join is supplied twice, it will be aliased to rel_2 (and similarly for a third time) (from POD for DBIx::Class::ResultSet).I had to build hash with search parameters with the proper key names (
tag, tag_2, tag_3 ...) and values from the @tags array :
my $suffix = '';
my $i = 1;
for my $tag (@tags){
$sqlparams{'tag' . $suffix . '.tag'} = $tag;
$suffix = '_' . ++$i;
}
And then the search:
my $it = $schema->resultset('Bookmark')->search(
\%sqlparams, {
join => [ ('tag') x scalar(@tags), 'usr' ],
order_by => \@order,
page => $page,
rows => $maxrows,
},
);
Tuesday, October 24, 2006
One indirection layer too much?
MyApp::RealModel). This way you can conveniently use the code in batch jobs and command line tools outside of the web environment. But still you need to create a nearly empty MyApp::Model::EmptyModel class just to connect the outside model into the catalyst framework. This might not seem as too much work - but it requires one more unique name and complicates the documentation. And since this EmptyModel does not introduce much new functionality over the MyApp::RealModel, the programmer easily forgets about all differences between them - and eventually falls into confusion.This is one indirection layer too much in my opinion - what I would like instead is to be able to specify in the configuration file (myapp.yml) something like:
model: MyApp::RealModel
Is that change feasible? Don't ask me.
Saturday, October 21, 2006
Customisation by deleting
I think this is one of the factors that makes code generation an efficient tool in some circumstances. When you close the logic in some library, you cannot modify it so easily. This is one of the reasons that in Catalyst::Example::InstantCRUD I finally decided to generate the templates, and also to use a minimum of logic there - so that deleting, and also changing the order of some elements would not break it.
Friday, October 20, 2006
HTML::Widget versus RoR helpers
What I am still not happy with is the widget config - I feel that all the visual things (like the choice between textarea and textfield etc) should be managed in the templates. The Ruby on Rails helpers seem ideal solution - they do all the validation, filling up the values that need to be done in the controller, but let the View manage the actual HTML produced. To reach that we would have be able to produce HTML::Widget::Element without specifying that it is to be a textfield or a radio box and leave the HTML generation to be called in the template, something like:
[% result.field_html('fieldname, 'textarea', cols, 30, rows, 5) %]
This would produce the field html with the proper value and an error mark if there was an error during validation. This looks like too radical step for evolution of HTML::Widget - perhaps what is needed is some completely new module?
Inspired by "Rails-like form helpers" email discussion.
There is a reply at the mailing list.