Thursday, August 28, 2008

One gotcha less!

Looks like one of the anomalies I described in my previous post is fixed now in the svn (see Revision 4773 of DBIC). Thumbs up!

Friday, June 13, 2008

DBIx::Class gotchas


DBIC is a complex beast - and it has it's rather obscure corners where it can burn the inexperienced. Here are two of those, comments with others are welcome.

Don't use $rs->update_or_create(...) (or $rs->find_or_create) on a table with autoincrement primary key



The ->find method requires that all the columns comprising the key used for the search are present in the query or in the resultsource conditions. Otherwise it will return some random record (and emit a warning). This is because currently there is no way to determine if all the needed columns are present. To do this you would have to parse the arbitrary SQL::Abstract query that can be in the resultsource condition and check how the columns are used there. Think for example about:
$new_rs = $cd_rs->search( { -and => [ id => [1, 2], id => { '>' => 1 } ] } )
and then $new_rs->update_or_create( { artist => 1 } ).


But for autoincrement primary keys the ->create method requires that the primary key is not present in the query. If you provide { pk => 'undef' } to ->create, this would generate a query like: INSERT INTO cd ( pk, ...) VALUES ( NULL, ... ) - some databases would accept that but others (PostgreSQL for example) not.


So in summary, for tables with autoincrement primary keys ->find requires that you provide the primary key in the query, and ->create requires that you don't - this means that you cannot pass the same query hash to both methods and this is what ->update_or_create does.


Update: Looks like the 4773 revision fixes the problem described below. That's why I moved this point to the end of the post. I did not check it too deeply though.

Don't call $row->some_relation when the $row has not loaded the columns required to resolve some_relation



Column values in DBIC are kept in a hash - this means that a column can have some defined value, have undefined value or can be completely non-existent - and DBIC interprets all of those states differently. When $row->some_relation is called and ->some_relation uses a foreign key column that is non-existent then the result is currently random (and in the future most probably an exception will be raised).


When this can happen? When you create a row and don't provide the value for the column (or if you retrieve the row from the database and explicitelly omit the column). For example if you do
$cd = $cd_rs->create( { name => "Cisza" } )
(or $cd = $cd_rs->new...). Now the 'artist' column in $cd is non-existent and when you try to call $cd->artist it blows up.
In a library where you don't know how a record was created you need to check first if the relevant columns are loaded before you call a relation on the record.


I thought that a good remedy would be also to always provide undef in the ->create and ->new calls for all the columns that we don't have values for, but allegedly this is not a good strategy (don't ask me why).

Tuesday, May 27, 2008

Installing Test::WWW::Mechanize::Catalyst


It seems that there is some incompatibility between the latest LWP version (5.812) and Test::WWW::Mechanize::Catalyst. The tests die with an:

HTTP::Message content must be bytes at lib/Test/WWW/Mechanize/Catalyst.pm line 88

error message. After downgrading LWP to 5.808 Test::WWW::Mechanize::Catalyst (version 41 - which looks better than 42 at CPAN Testers: Reports for Test-WWW-Mechanize-Catalyst) worked. Looks like this change is the culprit: "Don't allow HTTP::Message content to be set to Unicode strings".


confound - thanks for the hint.

Sunday, May 25, 2008

Interesting articles from the Ruby community

Why coupling is always bad / Cohesion vs. coupling and Why Rails is total overkill and why I love Rack - looks like the Catalyst philosophy is quite close to that of the Rack project (but maybe less articulated).


As a side-note - I cannot comment there (even though I have javascript turned on) - do you get the same problem?

Monday, April 21, 2008

Tuesday, March 04, 2008

Writing Facebook apps in Catalyst

A few weeks ago I've started a howto page for writing Facebook applications in Catalyst at the Catalyst wiki. Now I have accumulated there a few tips. Everyone using Catalyst for Facebook apps is invited to add their problems and solutions.

Wednesday, January 30, 2008

Refreshing a row from the DB - another note to myself

This is another thing that I keep forgetting - how to refresh a row with values from the database.
So here it is:

$row->discard_changes;

It comes from DBIx::Class::PK (and not from DBIx::Class::Row where I usually start the serach).

Update: The core devs say that the new version of documentation will mention discard_changes in DBIx::Class::Row POD. Thanks.

Tuesday, January 29, 2008

Automatic DBIC schema creation from a database

This is something that I keep forgetting and then I have to wade through the docs again. And there are traps in the docs - for example SQLT can do the schema creation - but it does not build the relationships.

So here is the magic invocation for DBIx::Class::Schema::Loader which do create the relationships (if you use the right switch):

perl -MDBIx::Class::Schema::Loader=make_schema_at,dump_to_dir:./lib -e make_schema_at("New::Schema::Name", { relationships => 1 }, [ "dbi:Pg:dbname=foo","postgres" ])’

Saturday, January 12, 2008

A free implementation of Amazon SimpleDB

Here is a tip to Amazon - there should be a free implementation of SimpleDB. I am now considering using SimpleDB for a web application - but I would like to develop it on my desktop computer - without paying Amazon, without even connecting to Amazon. This might be a minor issue - the fee you would pay for developing the application on Amazon web services seem to be very small. But it would be so much more convenient: no fiddling with credit cards, no problems for developers with shaky internet connection and more compatibility with Free Software/Open Source development models where money is always a difficult subject.

For Amazon it would not mean much less income - since as I've said the fee for the development use of the services is rather small - but it would mean many more customers. Perhaps it would also mean some competition - but this would only validate the whole model and make it more suitable for big business which does not like so much dependence on one provider.

Saturday, December 08, 2007

Have you ever forgotten where some subroutine comes from?

Or have you ever been learning a big stack of modules, like DBIx::Class or Catalyst and stumble from time to time over a new method name and you did not know where to look for it's documentation? I have once even wrote a script that generated an index of all methods documented in some code directory tree. It worked for me when learning DBIx::Class - but you needed to be very careful when choosing what you are indexing - you indexed too much and the list just got too long, you indexed too little and it's coverage was too limited.

Until discovering Pod::POM::Web I did not know that what I need is actually a full text search over all the Pod documents - you cut and paste the unknown method name into the search box press Enter and voila you have list of modules that document that method. And if you know that it comes from one of the DBIx::Class submodules you just add DBIx::Class to the search criteria - and don't get all the Class::DBI documentation pages. No problem with too long index lists. Someone should hack it to run against a full CPAN mirror and beat the other CPAN search websites.

Wednesday, September 12, 2007

Catalyst::Example::InstantCRUD updated to newer versions of underlying libraries

Version 0.0.20 released to CPAN. My plan is to port it to use the new HTML::FormFu instead of HTML::Widget and then perhaps make a web wizard to edit the config options.

Saturday, September 08, 2007

Comment spam

Due to commend spam I had to hide comments for some blog posts here. Unfortunately Blogger does not let me do it individually - so I had to hide them all. Authors of the comments please understand. I've switched on comment moderation.

Monday, July 02, 2007

Fat Model

I've decided to try out moving some more functionality from the controller to the model. My theory behind this move is that I can enjoy more freedom in structuring the Model code then I have with the controller (and view) parts, model is the least coupled with the Catalyst code.

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

I was not quite happy with the information efficiency of the solution Matt proposed as an answer to my previous post. It still used three attributes - and simplification of the usage of the library by reducing the number of attributes was my main goal in this quest. But it showed that indeed it needs just a bit of tweaking to get there and with some more help from the core Cat devs I did that tweaking and went down to just to attributes: PathArgs - for declaring the number of parameters to be taken from the path and EndPoint - to declare an action the endpoint of the chain. I am sure that the EndPoint parameter could be computable - but for now I don't see any simple way to do that.

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

the Catalyst::DispatchType::Chained manpage

Thursday, April 12, 2007

MatchParams for auto actions

I had an idea for a new MatchParams attribute applicable for auto actions. I've looked into Catalyst::Dispatcher and unfortunately I don't see how it could be implemented - but I decided post it here anyway - maybe someone with more Catalyst knowledge will help implementing it. I think this would greatly simplify the chaining of actions. Instead of specifying three attributes (Chained, PathPart and CaptureArgs) you would need to specify just one.

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

In the popular online essay The Law of Leaky Abstractions Joel Sopolsky talks about choosing wrong abstractions for describing programing libraries. Abstractions that hide details influencing in important ways the programs using those libraries. Since those details are hidden the programmer have no control over them and in result has no control over important aspects of the resulting programs.

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

There is a drive to convert all the addresses with GET parameters to paths.
http://www.domain.com/book?author=Dostoyevsky
becomes
http://www.domain.com/book/Dostoyevsky.
But what if we add a genre to that search?
http://www.domain.com/book/Dostoyevsky/memoir
or
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/memoir
with 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

Update: Advanced Search in web DBIx::Class based applications (with tags, full text search and searching by location) is a more elaborated version of this article.

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
With only the first three criteria I could use a simple table with tags concatenated in one field and use full text search on it. In fact the first implementation used that technique. This meant tags can be only one word - but that is a reasonable constraint. The searches for a combination of three tags there looked like 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?

It is a common practice to have a model completely outside of Catalyst classes (let's call it 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.