Posted by Chuck Vose
Tue, 31 Aug 2010 17:48:00 GMT
I’ve now been working professionally in Drupal for a year and have learned a lot about it; I have some patches into contrib but I’ve not really done much with core other than some simpletests I was too shy to commit at Drupalcon and some comments trying to help people out on d.o. Prior to my stint as Drupal programmer I was a hardcore Ruby on Rails developer for about 4 years. Over the last two years in particular I’ve learned a lot about frameworks and I’d like to share an observation about which framework feels right to me for which situations and why.
Administrators vs End-users
At the heart of my observation is this question: Who is putting content into your database and what percentage of the database are they creating? I think it’s fair to assume that there is a database but for those that are in the NoSQL camp and might be using multiple types of database lets just all agree to talk about some mythical database which content comes from.
- At least 80% of my client’s content comes primarily from their staff members and is consumed by the faceless masses on the Internet
- At least 80% of my client’s content comes primarily from logged in users via uploads, comments, content creation, etc.
Nobody is going to argue that making the admin interface of a website easy to use is critical for the first case. If you have people spending their entire days working on the back side of your website it had better be something awesome or they’re all going to stalk you and break your flowers just below the point where they can regrow. Unacceptable of course. Similarly, when it comes right down to it, for me of course, creating a slick admin interface to something like stackoverflow.com would be completely pointless. Probably someone uses it, but the interface needs to be sitting in the front for the users.
As it turns out, this seems to be a great dividing line between Ruby on Rails and Drupal in my mind. I believe that Drupal has one of the greatest admin interfaces of any CMS. Failing that, I don’t think that I’ll get many complaints if I say that it is easy to cajole the Drupal admin into a super usable state if you know what you’re doing (or you just used Drupal 7 to begin with). Drupal manages content exceedingly well, but it is extremely hard to build applications like todo lists in the thing. I’m taking todo lists because they’re an easy concept that doesn’t really require an admin at all.
Ruby on Rails on the other hand, does not come with an admin interface because it is not something that every web application needs. There are some marvelous backend builders for Rails, but it’s not the core competency. Same thing for Sinatra and many of the other Ruby frameworks. What they excel at is being versatile, they excel at doing things that just can’t really be done by the web engines we already understand (Content management and blogging in particular).
What’s crucial to understand here is that for the average website for your average client that just wants to get online, we’ve probably already built an engine that will make them happy. Drupal in particular of course, but Wordpress and Joomla are also excellent at doing the things that everyone else is already doing. A CMS can be done very profitably in Rails, I’ve done it, but it probably would have been much more profitable had I learned Wordpress or Drupal back in the day.
Conclusion / Summary
My point, summarized to one sentence is thus: Do your clients want to be on the Internet, or do your clients want to create some chunk of the Internet. If they want to create awesome content and reap the rewards of it, Drupal is probably your framework of choice. If they want to do something that really doesn’t fit into something that every other website is doing, Ruby on Rails is probably your framework of choice.
The hardest question though, is what do you do when it’s not really so clear cut? When your client wants half of the site to be essentially a perfect candidate for a CMS, but the other half to be a crazy interactive, totally innovative thing? Do you try to use both? Do you strap it all together with Ajax? Or do you try to build the CMS in Ruby and lose out on that time? I don’t know that answer, but I hope that I’ve made you think about your clients and their projects in a slightly different way.
Posted in Rails, Drupal, Programming | Tags admin, data, drupal, model, Rails, rich | 6 comments
Posted by Chuck Vose
Wed, 02 Dec 2009 22:59:00 GMT
Cross posted on the Metal Toad Blog
If you’ve ever worked with a rich data model in drupal you know it can be a pain
to load up all the children and parents of a node within the templating engine.
One method that could save you a lot of time is to load the data recursively
in node_load and save your poor front-end guy some wrist pain (or yourself if you’re
that guy!)
Edit: Please look at the get_metadata() definition towards the bottom or none of this is going to make sense.
Here is the initial bit which loads on details about a node. Here of course you
could load all manner of things like read/write attributes, cck fields, etc.
/**
* Implementation of hook_load().
*/
function wrapper_load($node) {
$metadata = get_metadata($node);
// Load our attributes without children. We'll load children later.
$node = wrapper_load_without_friends($node);
One thing we found was that search would break when trying to index since it was
trying to load all the data from associated nodes as well. One way of dealing with
this is to use hook_nodeapi(‘update index’) to only load a subset of data instead
of the whole shebang. But I didn’t do it like that and I’m not going to put untested
code on the blog (well, aside from slightly edited code).
This is what I did instead. It’s interesting for its hackishness. There must be a
better way though.
$backtrace = debug_backtrace();
if ($backtrace[4]['function'] == 'node_update_index') {
return $node;
}
Here we get to the fun part though. This bit loads up the parents, those that this
node belongs to. Due to performance constraints when doing this you need to pick
a direction to load infinitely. If you choose to load parents recursively you have
easier code and it’s actually a lot faster as far as SQL is concerned. If you choose
to load children recursively, as I will do in a moment, the SQL is a little slower
but on-the-fly SQL is easier to write for a belongs_to relationship. When all the
data necessary is already in the db row you load to build yourself it’s easy to
include your parents too.
So you’ll note that here we load up a collection of parents being careful to make
sure that the recursive function knows who called it by the parent attribute on $obj.
// Load up any parents
if ($metadata['belongs_to']) {
foreach ($metadata['belongs_to'] as $drupal_attr => $legacy_attr) {
// When we load up a child it shouldn't load its parents
if ($node->parent != $drupal_attr) {
$node->{$drupal_attr."_collection"} = array();
// Get all the node attributes for our new object
$obj = db_fetch_object(db_query(
"SELECT * FROM %s as extra WHERE node.nid = %s",
$node->$drupal_attr
));
// This ensures that a child doesn't reverse and load its parent in the
// next call
$obj->parent = $node->type;
// Push our recursively loaded object into the empty collection. Here we
// choose to only load one level but you could use hook_load again to load
// deeper structures. The parent attribute should prevent us from getting
// into loops.
array_push(
$node->{$drupal_attr."_collection"},
wrapper_load_without_friends($obj)
);
}
}
}
Very similarly, here we load up the children. This time we load recursively with
no end condition. This is prone to cycles so you may have to go with a non-recursive
loader here if you have a cyclic loading cycle or some other way of terminating
the recursion.
// Load any children
if ($metadata['has_many'] && $node->nid) {
foreach ($metadata['has_many'] as $drupal_attr => $legacy_attr) {
// Bail out if this node has a parent at all. We just want to keep it
// simple for now.
if (!$node->parent) {
// Get the list of things this object owns
$res = db_query(
"SELECT * FROM $drupal_attr as extra WHERE extra.%s = %s",
$node->type,
$node->nid
);
// node_load all children and drop them in an array
$node->{$drupal_attr."_collection"} = array();
while ($obj = db_fetch_object($res)) {
// Once again, prevent our children from loading us and creating loops.
$obj->parent = $node->type;
// Push our recursively loaded children and their children onto the empty
// collection. This time we go all the way and create a much deeper data
// model. These two could be reversed, loading belongs_to indefinitely
// but I find this way easier.
array_push($node->{$drupal_attr."_collection"}, wrapper_load($obj));
}
}
}
}
return $node;
}
Here’s where we load extra attributes from the node addon table that you see associated
with every custom content-type. These attributes just get added onto the node directly
so there is some concern about columns named with php reserved words. Conflicting
column names like title just need to be carefully considered, they may not actually
be bad a bad idea.
// Hook load that doesn't recursively load children/parents, just the attributes
// of another table
function wrapper_load_without_friends($node) {
$metadata = get_metadata($node);
// Load up extra info from the node addon table
if ($node->nid) {
$extra_attributes = db_fetch_object(db_query(
"SELECT * FROM %s WHERE nid = %s",
$node->type, $node->nid)
);
// Foreach of the linking attributes which will allow us to find children
// load them onto the node directly.
foreach ($extra_attributes as $key => $value) {
$node->$key = $value;
}
}
return $node;
}
This is just a sample of our metadata loader. Naturally you could do this some other
way but it works pretty well for us. There is room for improvement though, using
a different format like YAML could buy some extra win for instance.
// Sample metadata describing has_many and belongs_to relationships as well as
// the read/write attributes each table has.
function get_metadata($node=NULL) {
$metadata = array(
'belongs_to' => array(
'staff' => array(
'office' => 'office_id',
)
),
'has_many' => array(
'office' => array(
'staff' => 'office_id',
)
),
);
$return['belongs_to'] = $metadata['belongs_to'][$node->type];
$return['has_many'] = $metadata['has_many'][$node->type];
return $return;
}
Posted in Drupal, Programming | Tags data, drupal, model, node_load, recursive, rich | no comments