aphenine: Teresa and Claire (Default)
aphenine ([personal profile] aphenine) wrote in [site community profile] dw_dev2010-08-01 11:56 am
Entry tags:

Models

I was idly musing on my journal that DW is approaching the MVC model of web development, as it's got Controllers, in the form of functions that get called to handle pages, and Views in the form of the Template Toolkit (or even BML). It even has Routing (which doesn't get a letter in MVC ;) ) to bind the right url to the right Controller. What DW doesn't have is the M part, the Model, which is normally a class that handles access to a table in a database, and I was musing about this because some of the functionality would have been a bit useful for the work I was doing then, even if I do enjoy the fact that not fitting into the MVC concept means DW is a bit more freeform. Anyway, I was wondering if there was any reason that DW didn't have a Model class. [staff profile] denise suggested that I ask, as often something doesn't exist because nobody's done it yet.

Do you think that a general database interface class, like a Model would be useful to DW? Do you think DW should go that way, or are people genuinely happy just hard-coding their SQL queries in various objects, as is done now? Admittedly, the DBI layer does a lot of what Models do, such as quote escaping and input sanitisation, although it doesn't quite do the same job, and it won't error check your SQL query input to check whether you are using the right fields and values, or automatically make your SQL queries for you if you provide it with sensible data and want to do a common operation. If something like a Model is useful, how far should one go with them? Do you find things like behaviours and relationships useful, or do they just get in your way?

Further, if I wrote a simple Model class, would anyone other than me use it? Would anyone know how? Would anyone even care? ;)

#The model fetches all the columns from the database and figures out all the primary keys and datatypes
my $userkeywordstable = Model->new("userkeywords");

#Gets the whole table
my $results = $userkeywordstable->find("all");

#Returns just the keywords column belonging to userid 1
my keywords = $userkeywordstable->find( "all", "keywords", { "userid" => 1 } );

#Return the primary key columns for this table
my $pkeys = $userkeywords->primary_keys;

#Prepare some new tags for a user and add them
#If the userkeywords table was autoincrement, I wouldn't need to work out the keys as the model would do it for me.
for( my $i=0; $i<@newtags; ++$i )
{
push $data, \{ userid=>$user->{userid}, "kwid"=>$maxkw+$i, "keyword"=>$newtags[$i]};
}
$userkeywordstable->insert($data);
if( $userkeywordstable->err )
{
print $userkeywordstable->errmsg;
}

#Update keyword 0, userid 34 with keyword anime
$userkeywordstable->update( { kwid => 0, userid => 34 }, {"keyword"=>"anime"} );

#Produces error, parentkwid isn't in this table!
$userkeywordstable->update( {kwid=>0, userid => 34}, {"parentkwid"=>9} );

#Produces error, didn't you know keyword isn't a number, numbskull!
$userkeywordstable->update( { kwid => 0, userid => 34 }, {"keyword"=>9} );

#I have some data I have worked on that needs updating, figure it out by yourself which fields are the primary keys and update as necessary
my $data = $userkeywordstable->find("all");
... #Work done here
$userkeywordstable->update($data);

#Activate advanced behaviours for this table
$userkeywordstable->behavesAs("user", "threaded");

#Find all tags belonging to user name aphenine and return them in threaded mode
#The user behaviour automatically pulls the userid for a name, which is why userid is no longer in the WHERE clause (third argument), but user is in the options (fourth).
$userkeywordstable->find( "threaded", undef, undef, {"user" => "aphenine"} );

#Get the tag containing the word anime, and all entries which have those tags in them (using relationships).
$userkeywordstable->manyToMany("logtags2", "logkeywords"); #relates to posts (in logtags) via logkeywords table
$userkeywordstable->find("all", undef, {"keyword"=>"anime"}, {"user" => "aphenine", "recurse"=>1}); #return the tag and the entries it appears in
pne: A picture of a plush toy, halfway between a duck and a platypus, with a green body and a yellow bill and feet. (Default)

[personal profile] pne 2010-08-01 01:18 pm (UTC)(link)
push $data, \{ userid=>$user->{userid}, "kwid"=>$maxkw+$i, "keyword"=>$newtags[$i]};


Er, really? A reference to a reference to an anonymous hash? What does the extra \ get you?

Also, doesn't it have to be push @$data, ... or push @data, ...?
kareila: (Default)

[personal profile] kareila 2010-08-01 01:21 pm (UTC)(link)
I think this sort of abstraction is only worth the extra development time if you need to support different database formats within the same application. If we were ever to try to move away from MySQL, we would probably want to look into this.

That said, I have been doing some work recently to make sure our SQL statements are packaged in subroutines. There are some places in the legacy LJ stuff where it's embedded directly into BML files, and that makes me cringe.
damned_colonial: Convicts in Sydney, being spoken to by a guard/soldier (Default)

[personal profile] damned_colonial 2010-08-01 05:45 pm (UTC)(link)
I wrote a blog post a while ago explaining MVCs for beginners. It's here if anyone's interested: http://infotrope.net/blog/2007/07/22/the-mvc-colouring-book/

(It uses colours to explain what's going on, I think it's pretty clear and easy to understand.)
pauamma: Cartooney crab wearing hot pink and acid green facemask holding drink with straw (Default)

[personal profile] pauamma 2010-08-01 06:05 pm (UTC)(link)
DW (and to some extent, LJ before it) uses an OO layer above database tables (eg, LJ::User, LJ::Entry) instead of a monolithic model (or without an additional factory class on top of the existing OO layer - I'm not sure which your code samples are trying to illustrate). I'm not sure what benefits the additional indirection would provide over what we already have. Could you elaborate?
sophie: A cartoon-like representation of a girl standing on a hill, with brown hair, blue eyes, a flowery top, and blue skirt. ☀ (Default)

[personal profile] sophie 2010-08-01 06:12 pm (UTC)(link)
Any Model class would need to understand how our database servers are set up, as it's not what you might be used to.

The best explanation is given by Mark in this tutorial post (which I wasted a few minutes trying to find in Google before remembering DW has its own search now, yay!) It might be worth taking a look and seeing if there's anything there that might get in the way, or otherwise.
allen: (pooka)

I miss Hibernate

[personal profile] allen 2010-08-01 06:47 pm (UTC)(link)
First, in general, yes. I actually had decided that the next time I added a new top-level DB table and object class that I was going to make something like DbObject.pm and pull together all the generic SQL handling code in one place. I mean, how many cut and pastes of absorb_row do we really need? And combine it with Memcacheable and you should be able to create a new cached, DB-backed class with just a 'use base', some table and column name configuration, and a little bit of accessor programming.

That having been said... A lot of LJ/DW really does follow the Model part of MVC ok: LJ::Entry, LJ::User, and even LJ::Userpic are all Model implementations, if maybe not the cleanest examples of them. They're just all hand-coded Model implementations. So they're ugly, and have lots of duplicated code, and in some cases (keywords in particular) try to handle too much at once. But they are valid Models; you can, in a Controller, load up an Entry object, read it, and modify it without actually writing any new SQL. (edit: as [personal profile] pauamma says above.)
mark: A photo of Mark kneeling on top of the Taal Volcano in the Philippines. It was a long hike. (Default)

Re: I miss Hibernate

[staff profile] mark 2010-08-01 07:14 pm (UTC)(link)
I went to a number of Moose talks at OSCON this year, and the urge to rewrite everything as proper OO (which would help enforce proper Models) was really strong.
afuna: Cat under a blanket. Text: "Cats are just little people with Fur and Fangs" (Default)

Re: I miss Hibernate

[personal profile] afuna 2010-08-01 11:25 pm (UTC)(link)
Oh yeah, resisting the same urge. It wouldn't quite abstract things to the extent of the ORM -- we'd still be hand-writing the SQL, but it would take care of a lot of the basic object structure.

Mmm, temptation.
sophie: A cartoon-like representation of a girl standing on a hill, with brown hair, blue eyes, a flowery top, and blue skirt. ☀ (Default)

Re: I miss Hibernate

[personal profile] sophie 2010-08-03 12:07 am (UTC)(link)
As far as I know, LJ::User (the package) doesn't have the functions to load users; on the other hand, LJ/User.pm (the file) does. Yes, the file mixes packages. The actual function to load a user is LJ::load_user(), which is located in the LJ/User.pm file. So really, they need separating for more than Model reasons. ;p But it's going to be a fair bit of work to do that for everything...
mark: A photo of Mark kneeling on top of the Taal Volcano in the Philippines. It was a long hike. (Default)

[staff profile] mark 2010-08-01 07:13 pm (UTC)(link)
The Model you're talking about is an ORM -- Object Relational Mapping. Taking an RDBMS (relational database) and mapping it to an OO system. The "way" to do that in Perl is with DBIx::Class (http://search.cpan.org/dist/DBIx-Class/lib/DBIx/Class.pm), by mst.

I've used this system before in a Catalyst application I built and when I was at Six Apart they used it for things. It certainly has a lot of nice functionality, and you can make it do some interesting things.

I never found it particularly useful, though. It's easier for me to see the raw SQL and know what it's doing.

Now, one thing I did always want to do was to create objects for the various classes of data (models basically) but not use an ORM. The system would not automatically scan the tables for keys and such. I would just define classes that had all of the SQL self-contained. Then the rest of the application would be 100% free of SQL, which would be a boon if we ever wanted to change backends or whatnot.

Eh. Not a useful comment, I suppose. I have vague low-level distrust and dislike of ORMs. I think it's hard to use them well and they're mostly a crutch.
thorfinn: <user name="seedy_girl"> and <user name="thorfinn"> (Default)

[personal profile] thorfinn 2010-08-02 03:09 am (UTC)(link)
In some ways, a DB *is* a Model. :-) A somewhat scrappy and potentially hard to use one, but it is nevertheless.
pauamma: Cartooney crab wearing hot pink and acid green facemask holding drink with straw (Default)

[personal profile] pauamma 2010-08-03 02:52 pm (UTC)(link)
Yannow, that comment, added to the discussion above about how tables map (or don't) to application objects reminded me of the hierarchical and network database models. I think I'm gonna go curl up in a corner and hate you for a while. :-)

[personal profile] jproulx 2010-08-03 04:07 pm (UTC)(link)
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/the.model

That's a pretty good page explaining what a model should encapsulate in MVC paradigms, although it's written from a PHP/Zend Framework perspective -- from the article:

“The mapping of Models to a database or other storage medium does not define the Model. In fact you can add data access to developed Models after the fact and this is not an uncommon approach! The Model includes the business rules, behaviour and constraints of the data it represents, and these may be stored to a database (generally once they are reduced to quantifiable configurable rules) after they have been designed and documented. [...] This should, however, highlight an essential concept to bear in mind: a Model is not merely accessing a database. It encapsulates all the data and behaviour of a specific unit within the application, excluding any presentation related logic.”