<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>sacha chua :: living an awesome life &#187; drupal</title>
	<atom:link href="http://sachachua.com/blog/category/drupal/feed/" rel="self" type="application/rss+xml" />
	<link>http://sachachua.com/blog</link>
	<description>I help organizations and people learn how to connect and collaborate more effectively using Web 2.0 tools.</description>
	<lastBuildDate>Sat, 26 May 2012 16:19:15 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.2</generator>
		<item>
		<title>Drupal 6: Adding color support to your theme</title>
		<link>http://sachachua.com/blog/2012/02/drupal-6-adding-color-support-theme/</link>
		<comments>http://sachachua.com/blog/2012/02/drupal-6-adding-color-support-theme/#comments</comments>
		<pubDate>Thu, 09 Feb 2012 18:00:23 +0000</pubDate>
		<dc:creator>Sacha Chua</dc:creator>
				<category><![CDATA[drupal]]></category>

		<guid isPermaLink="false">http://sachachua.com/blog/?p=23147</guid>
		<description><![CDATA[We spent a few hours trying to figure out how to use Color to make our custom Drupal 6 theme configurable. Color rewrites your CSS to include the user-configured colours, and adds the resulting stylesheet link to your header. The first trick was to get the colour picker to show up on the theme settings [...]<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2012/02/drupal-6-adding-color-support-theme/">Drupal 6: Adding color support to your theme</a> (Sacha Chua's blog)</p>
]]></description>
			<content:encoded><![CDATA[<p>We spent a few hours trying to figure out how to use Color to make our custom Drupal 6 theme configurable. Color rewrites your CSS to include the user-configured colours, and adds the resulting stylesheet link to your header. </p>
<p> The first trick was to get the colour picker to show up on the theme settings page. The documentation wasn&#8217;t clear, but the easiest way to get started seems to be to copy the color/ directory from the Garland theme into a subdirectory of your theme, and then customize it from there. You will also need to follow the Drupal 6 or Drupal 7-specific instructions for calling the Color module when preprocessing pages. </p>
<p> Color searches your style.css (and imported stylesheets or other stylesheets defined by the &#8216;css&#8217; part of your $info array) for colour definitions. Any colour that exactly matches one of the colours defined in the default scheme is replaced by the colour in the selected scheme, <b>with the caveat</b> that the base colour <b>should not</b> appear in the stylesheet. If the base colour is found in the stylesheet, it will be replaced by an empty string. In your stylesheet, make sure your base colour uses the shortened version (ex: replace #cccccc with #ccc) or use a very similar colour instead (ex: #cbcbcb). </p>
<p> So, the easy way to colourize your theme: </p>
<ol>
<li>Enable Color, if you haven&#8217;t yet. </li>
<li>Copy the color directory from the Garland theme into your theme </li>
</ol>
<p> Color will attempt to figure out unspecified colours based on those colours&#8217; relationship with the base colour. This can lead to interesting combinations. If there are colours you do not want Color to change, put them in a section after a comment like this: </p>
<pre class="example">/*******************************************************************
 * Color Module: Don't touch                                       *
 *******************************************************************/
</pre>
<p> All colours specified after that comment will not be rewritten. </p>
<p> <b>Some gotchas to watch out for:</b> </p>
<ul>
<li>It&#8217;s probably a good idea to add a comment to your style.css   reminding developers to resubmit the colour settings after making   changes to the stylesheet. Color rewrites the stylesheet, so changes   aren&#8217;t picked up until the stylesheet is regenerated. </li>
<li>The Color preview appears to use hardcoded HTML. The gradient is   created by color.js, and there doesn&#8217;t seem to be a way around it.   Our workaround is to use CSS to hide both the preview and the header   above it. Unfortunately, there is no div that encloses both the   header and the preview. </li>
</ul>
<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2012/02/drupal-6-adding-color-support-theme/">Drupal 6: Adding color support to your theme</a> (Sacha Chua's blog)</p>
]]></content:encoded>
			<wfw:commentRss>http://sachachua.com/blog/2012/02/drupal-6-adding-color-support-theme/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>My CSS theming setup</title>
		<link>http://sachachua.com/blog/2012/01/my-css-theming-setup/</link>
		<comments>http://sachachua.com/blog/2012/01/my-css-theming-setup/#comments</comments>
		<pubDate>Tue, 31 Jan 2012 00:07:06 +0000</pubDate>
		<dc:creator>Sacha Chua</dc:creator>
				<category><![CDATA[development]]></category>
		<category><![CDATA[drupal]]></category>
		<category><![CDATA[geek]]></category>
		<category><![CDATA[tips]]></category>
		<category><![CDATA[work]]></category>

		<guid isPermaLink="false">http://sachachua.com/blog/?p=23136</guid>
		<description><![CDATA[“Why is your window transparent?” a coworker asked me when she noticed my screen. I told her about how I do my CSS theming, and she pulled another coworker over and made me repeat the explanation. Since that seems like something other people might find handy, here it is. Sass: Syntactically Awesome Sytlesheets I rarely [...]<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2012/01/my-css-theming-setup/">My CSS theming setup</a> (Sacha Chua's blog)</p>
]]></description>
			<content:encoded><![CDATA[<p>“Why is your window transparent?” a coworker asked me when she noticed my screen. I told her about how I do my CSS theming, and she pulled another coworker over and made me repeat the explanation. Since that seems like something other people might find handy, here it is.</p>
<p><strong>Sass: Syntactically Awesome Sytlesheets</strong></p>
<p>I rarely do CSS/front-end theming work, but when I do, I try to make it as fun and easy as back-end development. I use <a href="http://sass-lang.com/">Sass</a> (Syntactically Awesome Stylesheets) so that I can use nested selectors, variables, and mixins. This makes my code cleaner and easier to write. You’ll need Ruby in order to install Sass, but the tool will give you CSS that you can use on any web platform.</p>
<p><strong>Browser-based tools</strong></p>
<p>I prefer doing the initial tweaking in Google Chrome, because I like the way that the developer tools make it easy to modify the stylesheet. The <a href="https://chrome.google.com/webstore/detail/dnfpcpfijpdhabaoieccoclghgplmpbd">Chrome CSS Reloader extension</a> is handy, too. Most of the time, I make my CSS changes in the text editor, then use the CSS Reloader to reload the stylesheet without refreshing the page. This makes it easy to manually toggle the display of some elements while allowing me to refresh style rules. If I want to figure out the values for a few simple changes, I’ll sometimes make the changes directly in Chrome (you can use arrow keys to adjust values), then copy the values to my Sass source file.</p>
<p><strong>Colors, sizes, and spaces</strong></p>
<p>A second monitor is totally awesome and well worth it.</p>
<p>Designs rarely specify all the colours, sizes, and spacing needed. To quickly get the color of a pixel, I use <a href="http://www.donationcoder.com/Software/Skrommel/index.html#WhatColor">WhatColor</a>. This shows the hex code for colors, and allows me to quickly copy the code with the F12 shortcut key. If you want to change the shortcut key, the source is available as an AutoHotkey script. </p>
<p>To make it easier to match sizes and spaces, I use <a href="http://www.donationcoder.com/Software/Skrommel/index.html#WinWarden">WinWarden</a> to make my browser window 20% translucent. Then I carefully position it over my design reference until the important features match. <a href="http://www.blacksunsoftware.com/screenmagnifier.html">Magnifixer</a> makes it easier to line things up because it can magnify a fixed portion of the screen. By focusing Magnifixer on the part I’m working on, I can tweak CSS without straining my eyes.</p>
<p>When I know I’m going to be making a lot of changes, I use AutoHotkey to map a shortcut so that I can refresh the CSS with one keystroke instead of several. When I happen to have my USB foot pedal handy, I rig it up to refresh my stylesheet.</p>
<p><strong>Regression testing</strong></p>
<p>Sometimes my CSS changes modify other rules. Instead of laboriously checking each page after changes, I’ve figured out how to use <a href="http://seleniumhq.org/">Selenium WebDriver</a> to write a Java program that loads the pages in Mozilla Firefox and Internet Explorer, capturing screenshots and numbering them according to the pages in my design reference. This means that I can run the program in the background or start it before taking a break, and then flip through all the screenshots when I get back.</p>
<p><strong>Cross-browser testing</strong></p>
<p>What’s CSS theming without the requirement of browser compatibility? Someday, when I need to deal with more browsers, I might look into Selenium RC. In the meantime, I develop in Chrome, my Selenium-based program makes it easier to test in Firefox and IE, and it’s easy enough to try the URLs in Safari as well. Virtual machines handle the rest of the requirements.&#160; </p>
<p>So that’s how I’ve been doing CSS theming on this project. What are your favourite tips?</p>
<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2012/01/my-css-theming-setup/">My CSS theming setup</a> (Sacha Chua's blog)</p>
]]></content:encoded>
			<wfw:commentRss>http://sachachua.com/blog/2012/01/my-css-theming-setup/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Drupal: Finding nodes through autocomplete</title>
		<link>http://sachachua.com/blog/2011/09/drupal-finding-nodes-through-autocomplete/</link>
		<comments>http://sachachua.com/blog/2011/09/drupal-finding-nodes-through-autocomplete/#comments</comments>
		<pubDate>Tue, 06 Sep 2011 14:17:00 +0000</pubDate>
		<dc:creator>Sacha Chua</dc:creator>
				<category><![CDATA[drupal]]></category>

		<guid isPermaLink="false">http://sachachua.com/blog/?p=22476</guid>
		<description><![CDATA[The clients wanted a quick way to jump to the latest revision of a node. I was delighted to discover that the Finder module made it easy to create an autocomplete shortcut to nodes and users. It offered way more features than I would&#8217;ve coded myself. Finder lets you match nodes on title, author, CCK [...]<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2011/09/drupal-finding-nodes-through-autocomplete/">Drupal: Finding nodes through autocomplete</a> (Sacha Chua's blog)</p>
]]></description>
			<content:encoded><![CDATA[<p>The clients wanted a quick way to jump to the latest revision of a node. I was delighted to discover that the <a href="http://drupal.org/project/finder">Finder module</a> made it easy to create an autocomplete shortcut to nodes and users. It offered way more features than I would&#8217;ve coded myself. Finder lets you match nodes on title, author, CCK fields, and so on.  </p>
<p> There&#8217;s a simpler module called <a href="http://drupal.org/project/node_quick_find">Node Quick Find</a>, but I&#8217;m going to go with Finder for now because of the options that Finder offers.  </p>
<p> There was one small thing I needed to tweak. Finder Node goes to node/nid, but we&#8217;ve got <a href="http://drupal.org/project/revisioning">Revisioning</a> set up to view the current revision of a node and not the latest. Fortunately, Finder took that into account and provided enough hooks to let me change the behavior. Here&#8217;s what I needed to do: </p>
<pre class="src src-php">function mymodule_finder_goto_alter(&amp;$result, &amp;$finder) {
  $finder-&gt;base_handler['#module'] = 'mymodule';
}

function mymodule_finder_goto($finder, $result) {
  $vid = revisioning_get_latest_revision_id($result-&gt;nid);
  drupal_goto('node/' . $result-&gt;nid . '/revisions/' . $vid . '/view');
}
</pre>
<p> You&#8217;ll want to use more <code>if</code> logic if you&#8217;re working with different kinds of Finders, of course, but this was enough to handle what I needed. Hooray for hooks! </p>
<p> Finder doesn&#8217;t seem to support <a href="http://drupal.org/project/features">Features</a>, so I&#8217;ll need to configure things again once I move to production. No problem! I&#8217;ve added some notes to myself in the issue-tracking system we&#8217;re using, and I&#8217;ve asked the clients to try this new shortcut out. </p>
<p> Drupal: There&#8217;s a module for that. </p>
<p>  <span class="timestamp-wrapper"> <span class="timestamp">2011-09-02 Fri 14:17<br /> </span></span></p>
<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2011/09/drupal-finding-nodes-through-autocomplete/">Drupal: Finding nodes through autocomplete</a> (Sacha Chua's blog)</p>
]]></content:encoded>
			<wfw:commentRss>http://sachachua.com/blog/2011/09/drupal-finding-nodes-through-autocomplete/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Drupal debugging story: Rules defined in multiple Features</title>
		<link>http://sachachua.com/blog/2011/08/drupal-debugging-story-rules-defined-in-multiple-features/</link>
		<comments>http://sachachua.com/blog/2011/08/drupal-debugging-story-rules-defined-in-multiple-features/#comments</comments>
		<pubDate>Mon, 29 Aug 2011 12:00:00 +0000</pubDate>
		<dc:creator>Sacha Chua</dc:creator>
				<category><![CDATA[drupal]]></category>
		<category><![CDATA[geek]]></category>
		<category><![CDATA[story]]></category>

		<guid isPermaLink="false">http://sachachua.com/blog/?p=22446</guid>
		<description><![CDATA[Fatal error: Unsupported operand types in &#8230;../patched/rules/rules/rules.module on line 347 That was the error message Daniel sent me when he asked for my help in debugging. After some digging, I found out that the rules had been defined in two features, so Drupal got thoroughly confused. After I commented out one of the implementations of [...]<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2011/08/drupal-debugging-story-rules-defined-in-multiple-features/">Drupal debugging story: Rules defined in multiple Features</a> (Sacha Chua's blog)</p>
]]></description>
			<content:encoded><![CDATA[<p>Fatal error: Unsupported operand types in &hellip;../patched/rules/rules/rules.module on line 347 </p>
<p> That was the error message Daniel sent me when he asked for my help in debugging. After some digging, I found out that the rules had been defined in two features, so Drupal got thoroughly confused. After I commented out one of the implementations of <code>hook_rules_defaults</code> and deleted the relevant rows from <code>rules_rules</code> in the database, the site worked again. </p>
<p> Daniel wanted to know how I figured out that problem, so here&#8217;s the story. </p>
<p> The line number told me that <code>rules_sort_children</code> was having problems. I added a <code>var_dump</code> to it so that I could find out what kind of unexpected data was causing the errors. </p>
<pre class="src src-php">if (!is_numeric($element[$key]['#weight'])) {
  var_dump($key, $element[$key]['#weight']);
}
</pre>
<p> The output showed that the regular rules were fine, but our custom rules weren&#8217;t &#8211; weight was an array instead of an integer. </p>
<p> I looked at the difference between the features in code and the features in the database. The rules were being duplicated. I tried updating the features to match the information in the database, but the code looked wrong, so I used <code>git</code> to stash the changes. I also tried reverting the features, but that didn&#8217;t solve the problem either. Time to dig deeper. </p>
<p> I backed up the database, then deleted our custom rules from <code>rules_rules</code>. Still problematic. I commented out the rule definitions in our <code>site_structure.features.inc</code>. The rules administration page now successfully displayed &#8211; but mysteriously, the rule definitions were still available.  </p>
<p> I looked at the rule tags to see where else they might be defined, and found that another feature had included the rules. Aha! I commented those out. As expected, the rules disappeared from the administration page. I&#8217;d identified the problem: the rules had been defined in more than one module, which had thoroughly confused Drupal. </p>
<p> Because it made more sense to define the rules in our <code>site_structure</code> feature than in the other feature, I uncommented the <code>site_sitestructure_rules_defaults</code> definitions and left the other feature&#8217;s rules commented. That worked. </p>
<p> I tried restoring the rule customizations from the database, but that gave the same error. The database copy had multiple definitions, and I didn&#8217;t feel up to picking my way through the data or writing a Drush script to manipulate the rows. I re-deleted the customizations to get a clean copy. Just in case the other feature had more recent definitions of the rules, I compared the two. Aside from the extra tag, they were identical, so I didn&#8217;t need to copy any information over. It meant that Daniel would have to make his changes again, though. </p>
<p> Features: When it&#8217;s good, it&#8217;s very very good. When it&#8217;s bad, it results in quirky bugs. Make sure you don&#8217;t define your rulesets in multiple features. Drupal Features still has some rough spots when it comes to Rules. I remember when I created this feature with the rules in it &#8211; it created duplicate rules, so I needed to delete the old ones. Still, it&#8217;s a great way to export rules and other configuration changes to code, even though it takes some getting used to (and the occasional bit of database-diving). </p>
<p> Anyway, that&#8217;s the story of how I identified that issue and worked around it. </p>
<p> When you&#8217;re faced with a fatal error involving unsupported operand types, figure out what kind of operands it expects and print out anything that doesn&#8217;t match. Then you can start figuring out the real problem, which is how that data got in there in the first place. I&#8217;ve used this to find form elements that were mistakenly defined, array elements that were unexpectedly null, and so on. Don&#8217;t be afraid to add debugging code to core or contributed modules, particularly if you can use source code control to restore a clean copy. If you use a runtime debugger like XDebug, you can easily explore the data and the call stack. If not, there&#8217;s always <code>var_dump</code>. </p>
<p> Hope that helps! </p>
<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2011/08/drupal-debugging-story-rules-defined-in-multiple-features/">Drupal debugging story: Rules defined in multiple Features</a> (Sacha Chua's blog)</p>
]]></content:encoded>
			<wfw:commentRss>http://sachachua.com/blog/2011/08/drupal-debugging-story-rules-defined-in-multiple-features/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Lessons learned from project M</title>
		<link>http://sachachua.com/blog/2011/08/lessons-learned-from-project-m/</link>
		<comments>http://sachachua.com/blog/2011/08/lessons-learned-from-project-m/#comments</comments>
		<pubDate>Wed, 10 Aug 2011 21:08:00 +0000</pubDate>
		<dc:creator>Sacha Chua</dc:creator>
				<category><![CDATA[drupal]]></category>
		<category><![CDATA[ibm]]></category>
		<category><![CDATA[kaizen]]></category>
		<category><![CDATA[review]]></category>
		<category><![CDATA[work]]></category>

		<guid isPermaLink="false">http://sachachua.com/blog/2011/08/lessons-learned-from-project-m/</guid>
		<description><![CDATA[I&#8217;m wrapping up a Drupal 6 project which was funded by one of IBM&#8217;s corporate citizenship grants. The Snake Hill folks we&#8217;ve been working with will continue working with the client until they&#8217;re ready to launch. For my part, I&#8217;ve been in user acceptance testing and development mode for almost a month, rolling out new [...]<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2011/08/lessons-learned-from-project-m/">Lessons learned from project M</a> (Sacha Chua's blog)</p>
]]></description>
			<content:encoded><![CDATA[<p>I&#8217;m wrapping up a Drupal 6 project which was funded by one of IBM&#8217;s corporate citizenship grants. The <a href="http://snakehill.net">Snake Hill</a> folks we&#8217;ve been working with will continue working with the client until they&#8217;re ready to launch. For my part, I&#8217;ve been in user acceptance testing and development mode for almost a month, rolling out new features, fixing bugs, and getting feedback.  </p>
<p> The project manager has shuffled some hours around and made sure that I&#8217;ve got some &#8220;support&#8221; hours for follow-up questions after we turn the project over. </p>
<h3>What worked well</h3>
<p> <b>Hey, I can do this stuff after all!</b> I gathered requirements, estimated the effort, negotiated the scope, communicated with the clients and other team members, and generally did other technical-lead-ish stuff. I&#8217;ve done that on other projects, but usually that was just me working by myself and talking to clients. This one was more complex. It was fun figuring out what would fit, how things were prioritized, whether or not we were on track, and how to make things happen. I&#8217;d love to do it again. (And with the way the world works, I will probably get an opportunity to do so shortly!) </p>
<p> <b>Understanding a project deeply:</b> I was on the first phase of this project as well, and the experience really helped. We didn&#8217;t have any disruptions in technical leadership on our part, unlike in the first phase. See, last year, the IBM technical lead who had been talking to the client ended up leaving the country, so we had to repeat a few discussions about requirements. This time, I could draw on my experience from the first phase and our ongoing discussions about the client&#8217;s goals for the project. That was fun. </p>
<p> I&#8217;ll be turning the project over to the other development company, and the client&#8217;s concerned about whether they&#8217;ll be able to pick things up and run with it. I&#8217;ve tried to write down as many notes as I can, and I also invested time in briefing the other developers on the overall goals as well as the specific work items. Hope that works out! </p>
<p>  <b>Externally-accessible issue tracking:</b> In the previous phase of this project, issue tracking consisted of e-mailing spreadsheets around. It was painful. One of the first things I did when we started this phase of development was to set up a <a href="http://www.redmine.org/">Redmine issue tracker</a> on the client&#8217;s server. After we gathered and prioritized requirements, I logged them as features in Redmine and split them up over the different phases. I reviewed our list of outstanding work and filed them as bugs, too. As feedback came in, I tracked bugs. I took advantage of Redmine-Git integration and referred to issue numbers in my commit messages. When people e-mailed me their feedback or posted messages on Basecamp, I created issues and added hyperlinks. </p>
<p> Having an externally-accessible issue tracker helped me worry less about missing critical bugs. I also shared some reporting links with the clients and the project manager so that they could track progress and review the priorities. </p>
<p> On future projects, I would love to get to the point of having clients and testers create issues themselves. Wouldn&#8217;t that be nifty?  </p>
<p> <b>Git for version control:</b> I&#8217;m so glad I used Git to manage and share source code between multiple developers. The other developers were fairly new to Git, but they did okay, and I figured out how to clean up after one of the developers wiped out a bit of code after some commit confusion. Git stash and git branch were really helpful when I was trying lots of experimental code. </p>
<p> <b>Developing with a non-default theme:</b> We had a lot of development items to work on while the <a href="http://noinc.com/">No.Inc</a> creative team got their Drupal theme together. Once No.Inc sent the theme, I layered it on top of the site, fixed the usual problems, and had even more fun working on a site that looked halfway done. Definitely recommend getting a reliable theme in place sooner rather than later. </p>
<p> <b>Mentoring people:</b> I helped a new developer start to get the hang of Drupal. It was a slow process (must raise estimates even more when dealing with newbies), but I hope the investment pays off. I wrote (and updated!) documentation. I identified small tasks that he could work on first. I checked on him every so often. I successfully resisted the urge to just do things myself. Slowly getting there&hellip; </p>
<p> <b>Decision log:</b> I used a wiki to keep track of the design decisions I needed to make, the alternatives I considered, and what I eventually chose. That was helpful for me. I hope it will help future developers, too. </p>
<p> <b>Linux VM on a Microsoft Windows host, XMing, and Plink:</b> I&#8217;ve tried lots of different configurations in the course of this project. Doing my development inside a virtual machine has saved me <span style="text-decoration:underline;">so</span> much time in terms of restoring from backup or being able to tweak my operating environment. I started with a Linux VM on a Windows host, using Samba to access my files and either Eclipse or Emacs to edit them. That was a bit slow. Then I shifted to a Linux VM on a Linux host, SSHing to the VM and using Emacs from the VM itself. That was great for being able to do Linux-y stuff transparently. But then I found myself wanting to be back on Microsoft Windows so that I could use Autodesk Sketchbook Pro (Inkscape and MyPaint aren&#8217;t quite as awesome). I ran XMing to create an X server in my Windows environment, used <code>plink</code> to connect, and then started a graphical Emacs running on my virtual machine. Tada! I could probably make this even better by upgrading to 64-bit Microsoft Windows, adding more RAM, and upgrading to a bigger hard disk. (Alternatively, I could host the VM somewhere else instead of on my laptop&hellip;) </p>
<h3>What I&#8217;m going to work on improving next time</h3>
<p> <b>Better browser testing, including cross-browser:</b> I&#8217;m getting slightly better at testing the actual site, motivating myself with (a) interest in seeing my new code actually work, (b) the remembered embarrassment of gaping bugs, and (c) the idea of slowing down and getting things right. Juggling multiple browsers still doesn&#8217;t make me happy, but maybe I can turn it into a game with myself. Selenium might be useful here as well. </p>
<p> <b>Continuous integration:</b> I <a href="http://sachachua.com/blog/2011/06/drush-simpletest-and-continuous-integration-for-drupal-using-jenkins-previously-hudson/">set up Jenkins for continuous integration testing</a>, but it fell by the wayside as I wasn&#8217;t keeping my tests up to date and I wanted more CPU/memory for development. I ran into a number of embarrassing bugs along the way, though, so it might be worth developing stricter discipline around this. I&#8217;m still envious of one of the Drupal projects I heard about in IBM, which got through UAT without identified defects  thanks to lots of manual and automated testing. If I add more power to my development machine or offload testing to another machine, that might be a good way to stick to this process. </p>
<p> <b>Closer communication with clients and external developers:</b> We set up short daily meetings for the developers, but sometimes people still felt a little lost or out of touch. On future projects, I&#8217;ll make sure the clients have it on their calendar as an optional meeting, and maybe see about getting e-mail from people who can&#8217;t join on the phone. If I&#8217;m the tech lead on a future project, I&#8217;ll sit in on all client status update meetings, too. We found out about some miscommunications only when I handled one of the status calls. Fortunately, it was early enough that we could squeeze in the critical functionality while reprioritizing the others. Tense moment, though! </p>
<p> <b>Better vacation planning:</b> I realized we had a 4-day weekend the week before we had it, and we forgot about some people&#8217;s vacations too. Heh. I should get better at looking at the entire project span and listing the gaps up front. </p>
<p> <b>Earlier pipeline-building:</b> I nudged some project opportunities about a month before our projected end date, but that wasn&#8217;t long enough to deal with the paperwork lag. Oh well! Next time, I&#8217;ll set aside some time each week to do that kind of future pipeline-building, and I&#8217;ll set myself reminders for two months and a month before the project ends. Not a big problem. </p>
<p> My manager&#8217;s been lining up other Drupal and Rails projects for me to work on. Looking forward to learning all sorts of lessons on those as well! </p>
<p> Other Drupal lessons learned: </p>
<ul>
<li><a href="http://sachachua.com/blog/p/6410">June 2009</a> </li>
<li><a href="http://sachachua.com/blog/p/6066">April 2009</a> </li>
<li><a href="http://sachachua.com/blog/category/drupal">Lots of Drupal-related posts</a> </li>
</ul>
<p>  <span class="timestamp-wrapper"> <span class="timestamp">2011-08-10 Wed 17:08</span></span> </p>
<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2011/08/lessons-learned-from-project-m/">Lessons learned from project M</a> (Sacha Chua's blog)</p>
]]></content:encoded>
			<wfw:commentRss>http://sachachua.com/blog/2011/08/lessons-learned-from-project-m/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Drupal: Overriding Drupal autocompletion to pass more parameters</title>
		<link>http://sachachua.com/blog/2011/08/drupal-overriding-drupal-autocompletion-to-pass-more-parameters/</link>
		<comments>http://sachachua.com/blog/2011/08/drupal-overriding-drupal-autocompletion-to-pass-more-parameters/#comments</comments>
		<pubDate>Mon, 08 Aug 2011 23:16:00 +0000</pubDate>
		<dc:creator>Sacha Chua</dc:creator>
				<category><![CDATA[drupal]]></category>
		<category><![CDATA[geek]]></category>

		<guid isPermaLink="false">http://sachachua.com/blog/2011/08/drupal-overriding-drupal-autocompletion-to-pass-more-parameters/</guid>
		<description><![CDATA[Drupal autocompletion is easy &#8211; just add #autocomplete_path to a Form API element, set it to something that returns a JSON hash, and off you go. What if you want to pass form values into your autocompletion function so that you can filter results? Searching, I found some pages that suggested changing the value in [...]<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2011/08/drupal-overriding-drupal-autocompletion-to-pass-more-parameters/">Drupal: Overriding Drupal autocompletion to pass more parameters</a> (Sacha Chua's blog)</p>
]]></description>
			<content:encoded><![CDATA[<p>Drupal autocompletion is easy &#8211; just add <code>#autocomplete_path</code> to a Form API element, set it to something that returns a JSON hash, and off you go.
<p> What if you want to pass form values into your autocompletion function so that you can filter results? </p>
<p> Searching, I found some pages that suggested changing the value in the hidden autocomplete field so that it would go to a different URL. However, that probably doesn&#8217;t handle the autocomplete cache. Here&#8217;s another way to do it: </p>
<pre class="src src-javascript">Drupal.ACDB.prototype.customSearch = function (searchString) {
    searchString = searchString + "/" + $("#otherfield").val();
    return this.search(searchString);
};

Drupal.jsAC.prototype.populatePopup = function () {
  // Show popup
  if (this.popup) {
    $(this.popup).remove();
  }
  this.selected = false;
  this.popup = document.createElement('div');
  this.popup.id = 'autocomplete';
  this.popup.owner = this;
  $(this.popup).css({
    marginTop: this.input.offsetHeight +'px',
    width: (this.input.offsetWidth - 4) +'px',
    display: 'none'
  });
  $(this.input).before(this.popup);

  // Do search
  this.db.owner = this;
  if (this.input.id == 'edit-your-search-field') {
    this.db.customSearch(this.input.value);
  } else {
    this.db.search(this.input.value);
  }
}

Drupal.behaviors.rebindAutocomplete = function(context) {
    // Unbind the behaviors to prevent multiple search handlers
    $("#edit-your-search-field").unbind('keydown').unbind('keyup').unbind('blur').removeClass('autocomplete-processed');
    // Rebind autocompletion with the new code
    Drupal.behaviors.autocomplete(context);
}
</pre>
<p>    You&#8217;ll need to use <code>drupal_add_js</code> to add <code>misc/autocomplete.js</code> before you add the Javascript file for your form.
<p> Hope this helps! </p>
<p>   2011-08-08 Mon 19:16 </p>
<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2011/08/drupal-overriding-drupal-autocompletion-to-pass-more-parameters/">Drupal: Overriding Drupal autocompletion to pass more parameters</a> (Sacha Chua's blog)</p>
]]></content:encoded>
			<wfw:commentRss>http://sachachua.com/blog/2011/08/drupal-overriding-drupal-autocompletion-to-pass-more-parameters/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Drupal, HTML Purifier, and embedding IFRAMES from YouTube</title>
		<link>http://sachachua.com/blog/2011/08/drupal-html-purifier-embedding-iframes-youtube/</link>
		<comments>http://sachachua.com/blog/2011/08/drupal-html-purifier-embedding-iframes-youtube/#comments</comments>
		<pubDate>Fri, 05 Aug 2011 20:34:00 +0000</pubDate>
		<dc:creator>Sacha Chua</dc:creator>
				<category><![CDATA[drupal]]></category>
		<category><![CDATA[geek]]></category>
		<category><![CDATA[work]]></category>

		<guid isPermaLink="false">http://sachachua.com/blog/?p=22404</guid>
		<description><![CDATA[I know, I know. I shouldn&#8217;t allow IFRAMEs at all. But the client&#8217;s prospective users were really excited about images and video, and Drupal&#8217;s Media module wasn&#8217;t going to be quite enough. So I&#8217;ve been fighting with CKEditor, IMCE, and HTML Purifier to figure out how to make it easier. I&#8217;m hoping that this will [...]<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2011/08/drupal-html-purifier-embedding-iframes-youtube/">Drupal, HTML Purifier, and embedding IFRAMES from YouTube</a> (Sacha Chua's blog)</p>
]]></description>
			<content:encoded><![CDATA[<p>I know, I know. I shouldn&#8217;t allow IFRAMEs at all. But the client&#8217;s prospective users were <i>really</i> excited about images and video, and Drupal&#8217;s Media module wasn&#8217;t going to be quite enough. So I&#8217;ve been fighting with CKEditor, IMCE, and HTML Purifier to figure out how to make it easier. I&#8217;m hoping that this will be like practically all my other <a href="http://sachachua.com/blog/category/drupal">Drupal posts</a> and someone will comment with a much better way to do things right after I describe what I&#8217;ve done. =) </p>
<p><strong>First: images.</strong> There doesn&#8217;t seem to be a cleaner way than the &#8220;Browse server&#8221; – &#8220;Upload&#8221; combination using CKEditor and IMCE. I tried using WYSIWYG, TinyMCE and IMCE. I tried ImageBrowser, but I couldn&#8217;t get it to work. I tried FCKEditor, which looked promising, but I got tangled in figuring out how to control other parts of it. I&#8217;m just going to leave it as CKEditor and IMCE at the moment, and we can come back to that if it turns out to be higher priority than all the other things I&#8217;m working on. This is almost certainly my limitation rather than the packages&#8217; limitations, but I don&#8217;t have the time to exhaustively tweak this until it&#8217;s right. Someday I may finally learn how to make a CKEditor plugin, but it will not be in the final week of this Drupal project. </p>
<p><strong>Next: HTMLPurifier and Youtube.</strong> You see, Youtube switched to using IFRAMEs instead of Flash embeds. Allowing IFRAMEs is like allowing people to put arbitrary content on your webpage, because it is. The HTML Purifier folks seem firmly against it because it&#8217;s a bad idea, which it also is. But you’ve got to work around what you&#8217;ve got to workaround. Based on the <a href="http://htmlpurifier.org/phorum/read.php?3,4646">Allow iframes</a> thread in the HTMLPurifier forum, this is what I came up with: </p>
<p> <b>Step 1. Create a custom filter in <code>htmlpurifier/library/myiframe.php</code></b>. </p>
<pre class="src src-php" style="background: #000">&lt;?php
// Iframe filter that does some primitive whitelisting in a
// somewhat recognizable and tweakable way
class HTMLPurifier_Filter_MyIframe extends HTMLPurifier_Filter
{
  public $name = 'MyIframe';
  public function preFilter($html, $config, $context) {
    $html = preg_replace('/&lt;iframe/i', '&lt;img class="MyIframe"', $html);
    $html = preg_replace('#&lt;/iframe&gt;#i', '', $html);
    return $html;
  }
  public function postFilter($html, $config, $context) {
    $post_regex = '#&lt;img class="MyIframe"([^&gt;]+?)&gt;#';
    return preg_replace_callback($post_regex, array($this, 'postFilterCallback'), $html);
  }
  protected function postFilterCallback($matches) {
    // Whitelist the domains we like
    $ok = (preg_match('#src="http://www.youtube.com/#i', $matches[1]));
    if ($ok) {
      return '&lt;iframe ' . $matches[1] . '&gt;&lt;/iframe&gt;';
    } else {
      return '';
    }
  }
}
</pre>
<p> <b>Step 2. Include the filter in <code>HTMLPurifier_DefinitionCache_Drupal.php</code></b>. I don&#8217;t know if this is the right place, but I saw it briefly mentioned somewhere. </p>
<pre class="src src-php" style="background: #000">// ... rest of file
require_once 'myiframe.php';
</pre>
<p> <b>Step 3. Create the HTML Purifier config file.</b> In this case, I was changing the config for &#8220;Filtered HTML&#8221;, which had the input format ID of 1. I copied <code>config/sample.php</code> to <code>config/1.php</code> and set the following: </p>
<pre class="src src-php">function htmlpurifier_config_1($config) {
  $config-&gt;set('HTML.SafeObject', true);
  $config-&gt;set('Output.FlashCompat', true);
  $config-&gt;set('URI.DisableExternalResources', false);
  $config-&gt;set('Filter.Custom', array(new HTMLPurifier_Filter_MyIframe()));
}
</pre>
<p> Now I can switch to the source view in CKEditor, paste in my IFRAME code from Youtube, and view the results. Mostly. I still need to track down why I sometimes need to refresh the page in order to see it, but this is promising. </p>
<p>   2011-08-05 Fri 16:34 </p>
<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2011/08/drupal-html-purifier-embedding-iframes-youtube/">Drupal, HTML Purifier, and embedding IFRAMES from YouTube</a> (Sacha Chua's blog)</p>
]]></content:encoded>
			<wfw:commentRss>http://sachachua.com/blog/2011/08/drupal-html-purifier-embedding-iframes-youtube/feed/</wfw:commentRss>
		<slash:comments>15</slash:comments>
		</item>
		<item>
		<title>Drupal and JQuery 1.5: Fixing the JSON encoding of ampersands</title>
		<link>http://sachachua.com/blog/2011/08/drupal-and-jquery-1-5-fixing-the-json-encoding-of-ampersands/</link>
		<comments>http://sachachua.com/blog/2011/08/drupal-and-jquery-1-5-fixing-the-json-encoding-of-ampersands/#comments</comments>
		<pubDate>Thu, 04 Aug 2011 18:01:00 +0000</pubDate>
		<dc:creator>Sacha Chua</dc:creator>
				<category><![CDATA[drupal]]></category>
		<category><![CDATA[geek]]></category>
		<category><![CDATA[work]]></category>

		<guid isPermaLink="false">http://sachachua.com/blog/2011/08/drupal-and-jquery-1-5-fixing-the-json-encoding-of-ampersands/</guid>
		<description><![CDATA[Drupal 6&#8242;s drupal_json method encodes ampersands incorrectly for JQuery 1.5, causing the rather cryptic error: Uncaught Syntax error, unrecognized expression: ... (If you&#8217;re lucky.) The way to fix this is to borrow the JSON-handling code from Drupal 7. Here&#8217;s something you might be able to use: function yourmodule_json_encode($var) { return str_replace(array('&#60;', '&#62;', '&#38;'), array('\u003c', '\u003e', [...]<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2011/08/drupal-and-jquery-1-5-fixing-the-json-encoding-of-ampersands/">Drupal and JQuery 1.5: Fixing the JSON encoding of ampersands</a> (Sacha Chua's blog)</p>
]]></description>
			<content:encoded><![CDATA[<p>Drupal 6&#8242;s <code>drupal_json</code> method encodes ampersands incorrectly for JQuery 1.5, causing the rather cryptic error: </p>
<pre class="example">Uncaught Syntax error, unrecognized expression: ...
</pre>
<p> (If you&#8217;re lucky.) </p>
<p> The way to fix this is to borrow the JSON-handling code from Drupal 7. Here&#8217;s something you might be able to use: </p>
<pre class="src src-php">function yourmodule_json_encode($var) {
  return str_replace(array('&lt;', '&gt;', '&amp;'), array('\u003c', '\u003e', '\u0026'), $var);
}

// Fix Drupal JSON problems from http://witti.ws/blog/2011/03/14/jquery-15-json-parse-error
function yourmodule_json($var) {
  drupal_set_header('Content-Type: text/javascript; charset=utf-8');
  if (isset($var)) {
    echo yourmodule_json_encode(json_encode($var));
  }
}
</pre>
<p> Use <code>yourmodule_json</code> instead of <code>drupal_json</code> wherever applicable. </p>
<p> Hat tip to <a href="http://witti.ws/blog/2011/03/14/jquery-15-json-parse-error">Greg Payne (Witti)</a> for pointing me in the right direction! </p>
<p>   2011-08-04 Thu 14:01 </p>
<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2011/08/drupal-and-jquery-1-5-fixing-the-json-encoding-of-ampersands/">Drupal and JQuery 1.5: Fixing the JSON encoding of ampersands</a> (Sacha Chua's blog)</p>
]]></content:encoded>
			<wfw:commentRss>http://sachachua.com/blog/2011/08/drupal-and-jquery-1-5-fixing-the-json-encoding-of-ampersands/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Hacking Drupal views and taxonomy: looking for 100% matching of terms</title>
		<link>http://sachachua.com/blog/2011/07/hacking-drupal-views-and-taxonomy-looking-for-100-matching-of-terms/</link>
		<comments>http://sachachua.com/blog/2011/07/hacking-drupal-views-and-taxonomy-looking-for-100-matching-of-terms/#comments</comments>
		<pubDate>Wed, 06 Jul 2011 12:47:03 +0000</pubDate>
		<dc:creator>Sacha Chua</dc:creator>
				<category><![CDATA[drupal]]></category>
		<category><![CDATA[geek]]></category>

		<guid isPermaLink="false">http://sachachua.com/blog/2011/07/hacking-drupal-views-and-taxonomy-looking-for-100-matching-of-terms/</guid>
		<description><![CDATA[I&#8217;m working on a Drupal 6 site that helps match volunteers to speaking opportunities, or sessions. I use Taxonomy to keep track of the qualifications so that I can maintain the qualification hierarchy. Given a list of subject areas that a person is interested in, I need to find all sessions that match any of [...]<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2011/07/hacking-drupal-views-and-taxonomy-looking-for-100-matching-of-terms/">Hacking Drupal views and taxonomy: looking for 100% matching of terms</a> (Sacha Chua's blog)</p>
]]></description>
			<content:encoded><![CDATA[<p>I&#8217;m working on a Drupal 6 site that helps match volunteers to speaking opportunities, or sessions. I use Taxonomy to keep track of the qualifications so that I can maintain the qualification hierarchy. Given a list of subject areas that a person is interested in, I need to find all sessions that match any of those subject areas. The quirk: <b>the session must have at least one of the person&#8217;s terms, and the person must also have all the session&#8217;s terms.</b> </p>
<p> Let&#8217;s say that our volunteer is interested in speaking about biology and physics. I couldn&#8217;t use a straightforward AND search. If I searched for biology AND physics, I wouldn&#8217;t get sessions for just biology. It also means I can&#8217;t use a straightforward OR search, because I shouldn&#8217;t list sessions that require both biology AND another subject the person hadn&#8217;t listed, such as chemistry. </p>
<p> Views didn&#8217;t seem to have a built-in way to do it. I couldn&#8217;t think of a standard-ish way to describe my challenge in order to find relevant posts on drupal.org. <a href="http://groups.drupal.org/node/12347">Content recommendation modules</a> seemed similar, but I wasn&#8217;t familiar with any of them enough to know which one would be the closest to hack for my cross-type comparisons and 100% match requirements. So it was time to hack my Views query. </p>
<p> After several attempts, I settled on the approach of precalculating how many terms were associated with each session node. I created a table with the information and used the following query to populate it in my install file. </p>
<pre class="example">db_query("INSERT INTO {node_term_count}
  SELECT nid, vid, count(tid) AS term_count
  FROM {term_node} GROUP BY nid, vid");
</pre>
<p> I also used <code>hook_nodeapi</code> to update the table on insert, update, and delete operations. </p>
<p> Then I started experimenting through the SQL console. I used COUNT and GROUP BY to find out how many terms the session had in common with the person. Selecting from that MySQL subquery let me filter the list to the nodes where the total number of terms equaled the number of terms the session had. I ended up with a query that looked like this: </p>
<pre class="example">SELECT nid, vid FROM (SELECT tns.nid, tns.vid,
  COUNT(tns.tid) AS match_count,
  c.term_count FROM term_node tns
  INNER JOIN node_term_count c ON tns.vid=c.vid
  WHERE tns.tid in (55, 56, 42, 39, 41)
  GROUP BY tns.vid) AS result
WHERE term_count = match_count;
</pre>
<p> When I was happy with the query, I used <code>hook_views_pre_execute</code> to change my <code>$view-&gt;build_info['query']</code>  and <code>$view-&gt;build_info['count_query']</code>. With all the other filters I needed, it eventually looked like this: </p>
<pre class="example">    $view-&gt;build_info['query'] = "SELECT * FROM (
SELECT tns.nid, tns.vid, count(tns.tid) AS match_count, c.term_count, workflow_node.sid FROM node n
INNER JOIN term_node tns ON (n.vid=tns.vid AND n.nid=tns.nid)
LEFT JOIN workflow_node workflow_node ON n.nid = workflow_node.nid
INNER JOIN node_term_count c ON tns.vid=c.vid
INNER JOIN content_type_session session ON (n.nid=session.nid AND n.vid=session.vid)
INNER JOIN node school_node ON (session.field_session_school_nid=school_node.nid)
INNER JOIN content_type_school school ON (school_node.nid=school.nid AND school_node.vid=school.vid)
INNER JOIN content_field_session_dates date ON (n.nid=date.nid AND n.vid=date.vid AND date.delta=0)
WHERE (n.type in ('%s')
AND workflow_node.sid=%d
AND session.field_session_request_mode_value = '%s'
AND (n.status &lt;&gt; %d)
AND (DATE_FORMAT(ADDTIME(date.field_session_dates_value, SEC_TO_TIME(-14400)), '%Y-%m-%%d') &gt;= '" . date('Y-m-d') . "')
AND school.field_school_district_nid IN ($district_where)
AND tns.tid in ($tid_where))
GROUP BY tns.vid
) as result WHERE term_count = match_count AND match_count &gt; 0";
</pre>
<p> I used variables like <code>$tid_where</code> and <code>$district_where</code> to simplify the query. They use <code>array_fill</code> to create placeholders for the arguments. </p>
<p> Result: I think it works the way it&#8217;s supposed to. It passes my unit tests and manual testing, anyway. If performance becomes an issue, I might precalculate the results and store them in a table. I hope I don&#8217;t have to do that, though. </p>
<p> Views 3 is supposed to have arbitrary data stores that let you write views on top of any sort of query or function, but I&#8217;m going to stay with Views 2 for now. </p>
<p> Whenever I write about stuff we&#8217;re doing with Drupal, I often hear about even awesomer ways to do things. =) Is this one of those times? Is there a little-known module that Does the Right Thing?  </p>
<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2011/07/hacking-drupal-views-and-taxonomy-looking-for-100-matching-of-terms/">Hacking Drupal views and taxonomy: looking for 100% matching of terms</a> (Sacha Chua's blog)</p>
]]></content:encoded>
			<wfw:commentRss>http://sachachua.com/blog/2011/07/hacking-drupal-views-and-taxonomy-looking-for-100-matching-of-terms/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Getting a grip on a large database migration</title>
		<link>http://sachachua.com/blog/2011/07/grip-large-database-migration/</link>
		<comments>http://sachachua.com/blog/2011/07/grip-large-database-migration/#comments</comments>
		<pubDate>Sat, 02 Jul 2011 12:00:00 +0000</pubDate>
		<dc:creator>Sacha Chua</dc:creator>
				<category><![CDATA[drupal]]></category>
		<category><![CDATA[geek]]></category>

		<guid isPermaLink="false">http://sachachua.com/blog/?p=22337</guid>
		<description><![CDATA[Michael is working on migrating a custom website with hundreds of database tables to Drupal, and he wanted to know if I had any advice for keeping track of table mappings and other migration tasks. I&#8217;ve worked on small migration projects before (including migrating my own blog from lots of Planner-mode text files to WordPress!), [...]<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2011/07/grip-large-database-migration/">Getting a grip on a large database migration</a> (Sacha Chua's blog)</p>
]]></description>
			<content:encoded><![CDATA[<p>Michael is working on migrating a custom website with hundreds of database tables to Drupal, and he wanted to know if I had any advice for keeping track of table mappings and other migration tasks.</p>
<p>I&#8217;ve worked on small migration projects before (including migrating my own blog from lots of Planner-mode text files to WordPress!), but no large projects like the ones Michael described. But if I needed to do something like that, here&#8217;s what I&#8217;d probably do. I&#8217;d love to hear your tips!</p>
<p><strong>I&#8217;d list all the tables and start mapping them to entities.</strong> What content types would I need to create? What fields would I need to define? How are the content types related to each other? An entity relationship diagram can help you get an overview of what&#8217;s going on in the database.</p>
<p><strong>Then I&#8217;d start untangling the entities to see which ones I can migrate first.</strong> If you have entities with node references, it makes sense to migrate the data referred to before migrating the data that refers to them. If I can get a slice of the database &#8211; not all the records, just enough to flesh out the different relationships &#8211; that would make testing the migrations faster and easier. I would probably write a custom Drupal module to do the migrations, because it&#8217;s much easier to programmatically create nodes than it is to insert all the right entries into all the right tables.</p>
<p><strong>I&#8217;d commit the custom module to source code control frequently.</strong> I&#8217;d write some code to migrate an entity type or two, test the migration, and commit the source code. As I migrated more and more of the relationships, I&#8217;d probably check them off or colour them differently in the diagram, making note of anything I&#8217;d need to revisit (circular references, etc.).</p>
<p><strong>I might break the custom module up into steps to make it easier to rerun or test.</strong> That way, I&#8217;m not reconstructing the entire database in one request, too.</p>
<p><strong>I&#8217;d take notes on design decisions.</strong> When you migrate data, you&#8217;ll probably come across data that challenges your initial assumptions. This might require redesigning your entities and revising your earlier migration code. When I make design decisions, I often write about the options I&#8217;m considering and the reasons for or against them. This makes those decisions easier to revisit when new data might invalidate my assumptions, because I can see what may need to be changed.</p>
<p>How would you handle a migration project that&#8217;s too large to hold in your head?</p>
<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2011/07/grip-large-database-migration/">Getting a grip on a large database migration</a> (Sacha Chua's blog)</p>
]]></content:encoded>
			<wfw:commentRss>http://sachachua.com/blog/2011/07/grip-large-database-migration/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Context-switching and a four-project day</title>
		<link>http://sachachua.com/blog/2011/06/context-switching-and-a-four-project-day/</link>
		<comments>http://sachachua.com/blog/2011/06/context-switching-and-a-four-project-day/#comments</comments>
		<pubDate>Thu, 30 Jun 2011 20:19:00 +0000</pubDate>
		<dc:creator>Sacha Chua</dc:creator>
				<category><![CDATA[drupal]]></category>
		<category><![CDATA[geek]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[work]]></category>

		<guid isPermaLink="false">http://sachachua.com/blog/2011/06/context-switching-and-a-four-project-day/</guid>
		<description><![CDATA[Context-switching among multiple projects can be tough. I&#8217;m currently: working full-time on one project (a Drupal 6 non-profit website) consulting on another (helping an educational institution with Drupal 7 questions) supporting a third (Ruby on Rails site I built for a local nonprofit, almost done), and trying to wrap up on a fourth (PHP/AJAX dashboard [...]<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2011/06/context-switching-and-a-four-project-day/">Context-switching and a four-project day</a> (Sacha Chua's blog)</p>
]]></description>
			<content:encoded><![CDATA[<p>Context-switching among multiple projects can be tough. I&#8217;m currently: </p>
<ul>
<li>working full-time on one project (a Drupal 6 non-profit website) </li>
<li>consulting on another (helping an educational institution with Drupal 7 questions) </li>
<li>supporting a third (Ruby on Rails site I built for a local nonprofit, almost done), and </li>
<li>trying to wrap up on a fourth (PHP/AJAX dashboard for a call center   in the US). </li>
</ul>
<p> I&#8217;m doing the Drupal 6 development in a virtual machine on my system, with an integration server set up externally. Consulting for the second project is done on-site or through e-mail. The Rails site is on a virtual server. The dashboard project is now on the company&#8217;s servers (IIS6/Microsoft SQL Server), which I can VPN into and use Remote Desktop to access. I&#8217;m glad I have two computers and a standing desk (read: kitchen counter) that makes it easy to use both! </p>
<p> Today was one of those days. I helped my new team member set up his system so that he could start working on our project. He&#8217;s on Mac OS X. It took us some time to figure out some of the quirky behaviour, such as MySQL sockets not being where PHP expected them to be. Still, we got his system sorted out, so now he can explore the code while I&#8217;m on vacation tomorrow. </p>
<p> In between answering his questions, I replied to the consulting client&#8217;s questions about Drupal and the virtual image we set up yesterday. That mainly required remembering what we did and how we set it up. Fortunately, that part was fairly recent, so it was easy to answer her questions. </p>
<p> Then I got an instant message from the person I worked with on the fourth project, the call-center dashboard. He asked me to join a conference call. They were having big problems: the dashboard wasn&#8217;t refreshing, so users couldn&#8217;t mark their calls as completed. It was a little nerve-wracking trying to identify and resolve the problem on the phone. There were two parts to the problem: IIS was unresponsive, and Microsoft SQL Server had stopped replicating. The team told me that there had been some kind of resource-related problem that morning, too, so the lack of system resources might&#8217;ve cascaded into this. After some hurried searching and educated guesses about where to nudge the servers, I got the database replication working again, and I set IIS back to using the shared application pool. I hope that did the trick. I can do a lot of things, but I&#8217;m not as familiar with Microsoft server administration as I am with the Linux/Apache/MySQL or Linux/Apache/PostgreSQL combinations.  </p>
<p> I felt myself starting to stress out, so I deliberately slowed down while I was making the changes, and I took a short nap afterwards to reset myself. (Coding or administering systems while stressed is a great way to give yourself even more work and stress.) </p>
<p> After the nap, I was ready to take on the rest. The client for the Rails project e-mailed me a request to add a column of output to the report. I&#8217;d archived my project-related virtual machine already, so I (very carefully) coded it into the site in a not-completely-flexible manner. I found and fixed two bugs along the way, so it was a good thing I checked. </p>
<p> Context-switching between Drupal 6, Drupal 7, and Rails projects is weird. Even Drupal 6 and Drupal 7 differ significantly in terms of API, and Rails is a whole &#8216;nother kettle of fish. I often look things up, because it&#8217;s faster to do that than to rely on my assumptions and debug them when I&#8217;m wrong. Clients and team members watching me might think I don&#8217;t actually know anything by myself and I&#8217;m looking everything up as I go along. Depending on how scrambled my brain is, I&#8217;d probably suck in one of those trying-to-be-tough job interviews where you have to write working code without the Internet. But it is what it is, and this helps me build things quickly. </p>
<p> On the bright side, it&#8217;s pretty fun working with multiple paradigms. Rails uses one way of thinking, Drupal uses another, and so on. I&#8217;ve even mixed in Java before. There were a few weeks I was switching between enterprise Java, Drupal, Rails, and straight PHP. It&#8217;s not something I regularly do, but when the company needs it, well&hellip; it&#8217;s good exercise. Mental gymnastics. (And scheduling gymnastics, too.) </p>
<p> I like having one-project days. Two-project days are kinda okay too. Four-project days &#8211; particularly ones that involve solving a problem in an unfamiliar area while people are watching! &#8211; are tough, but apparently survivable as long as I remember to breathe. =) </p>
<p> Here are tips that help me deal with all that context-switching. Maybe they&#8217;ll help you! </p>
<p> <b>Look things up.</b> It&#8217;s okay. I find myself looking up even basic things all the time. For example, did you know that Ruby doesn&#8217;t have a straightforward min/max function the way PHP does? The canonical way to do it is to create an array (or other enumerable) and call the min or max member function, like this: <code>[x,y].max</code>. Dealing with little API/language quirks like that is part of the context-switching cost. Likewise, I sometimes find myself wishing I could <span style="text-decoration:underline;">just</span> use something like <code>rails console</code> in my Drupal sites&hellip; =) </p>
<p> <b>Take extensive notes.</b> Even if you&#8217;re fully focused on one project and have no problems remembering it now, you might need to go back to something you thought you already finished. </p>
<p> <b>Slow down and take breaks.</b> Don&#8217;t let stress drag you into making bad decisions. I felt much more refreshed after a quick nap, and I&#8217;m glad I did that instead of trying to force my way through the afternoon. This is one of the benefits of working at home &#8211; it&#8217;s easy to nap in an ergonomic and non-embarrassing way, while still getting tons of stuff done the rest of the day. </p>
<p> <b>Clear your brain and focus on the top priority.</b> It&#8217;s hard to juggle multiple projects. I made sure my new team member had things to work on while I focused on the call center dashboard project so that I wouldn&#8217;t be tempted to switch back and forth. Likewise, I wrote the documentation I promised for that project before moving on to the Rails project. </p>
<p> <b>Breathe.</b> No sense in stressing out and getting overwhelmed. Make one good decision at a time. Work step by step, and you&#8217;ll find that you&#8217;ll get through everything you need to do. Avoid multi-tasking. Single-task and finish as much as you can of your top priority first. </p>
<p> I prefer having one main project, maybe two projects during the transition periods. This isn&#8217;t always possible. Programming competitions helped me learn how to deal with multiple chunks of work under time pressure, and I&#8217;m getting better at it the more that work throws at me. </p>
<p> What are your tips for dealing with simultaneous projects? </p>
<p>  <span class="timestamp-wrapper"> <span class="timestamp">2011-06-30 Thu 16:19</span></span> </p>
<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2011/06/context-switching-and-a-four-project-day/">Context-switching and a four-project day</a> (Sacha Chua's blog)</p>
]]></content:encoded>
			<wfw:commentRss>http://sachachua.com/blog/2011/06/context-switching-and-a-four-project-day/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Drupal notes from helping a client improve her development environment</title>
		<link>http://sachachua.com/blog/2011/06/drupal-notes-from-helping-a-client-improve-her-development-environment/</link>
		<comments>http://sachachua.com/blog/2011/06/drupal-notes-from-helping-a-client-improve-her-development-environment/#comments</comments>
		<pubDate>Tue, 28 Jun 2011 12:55:00 +0000</pubDate>
		<dc:creator>Sacha Chua</dc:creator>
				<category><![CDATA[drupal]]></category>
		<category><![CDATA[geek]]></category>

		<guid isPermaLink="false">http://sachachua.com/blog/2011/06/drupal-notes-from-helping-a-client-improve-her-development-environment/</guid>
		<description><![CDATA[Keeping a to-do list helps you keep sane. If you don&#8217;t have a full-scale issue tracker, use a wiki page, text file, or something like that. It&#8217;s really useful to be able to get the list of things you&#8217;re working on or waiting for out of your head and into a form you can review. [...]<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2011/06/drupal-notes-from-helping-a-client-improve-her-development-environment/">Drupal notes from helping a client improve her development environment</a> (Sacha Chua's blog)</p>
]]></description>
			<content:encoded><![CDATA[<p><b>Keeping a to-do list helps you keep sane.</b> If you don&#8217;t have a  full-scale issue tracker, use a wiki page, text file, or something  like that. It&#8217;s really useful to be able to get the list of things  you&#8217;re working on or waiting for out of your head and into a form you  can review. </p>
<p> *<a href="http://drupal.org/project/features">Drupal Features</a> help you export configuration into code.* This is much better than creating an installation profile because you can update your features with new settings and apply them to existing sites. Invaluable when working with multi-sites that may need to be updated. You may need to clear your Drupal cache before you see changes applied. </p>
<p> <b>Version control is really handy even when you&#8217;re working on your own.</b> The ability to go back in time to a working setup (code + database) can help you experiment more freely and avoid late nights spent recovering from mistakes. </p>
<p> *<a href="http://drupal.org/project/drush">Drush</a> (Drupal shell) is awesome.* It&#8217;s a big timesaver. We use it to download and enable modules (dl and en), clear the cache (cc), run database updates (updatedb), launch a SQL console (sqlc), execute PHP (php-eval), run tests, and so on. I use it a lot because I hate clicking around. </p>
<p> Even more powerful with a little bit of xargs magic so that it&#8217;s easy to run a drush command against all the sites. Like this: </p>
<pre class="example">cat sites.txt | xargs -n 1 -I {} drush -l {} somecommand
</pre>
<p> <b>Design decisions:</b> Multisite without shared tables; services or syndication for sharing content between sites; central authentication for admin users&hellip; </p>
<p> <b>Bash script to create or clone multisites</b> makes tedious things a  little bit simpler. Tasks: </p>
<ul>
<li>Create a database and give access to a user. </li>
<li>Create the site and files directory. </li>
<li>Create the settings.php with the database settings. </li>
<li>Copy the base database into the new database. </li>
<li>Create a symbolic link. </li>
</ul>
<p>  <span class="timestamp-wrapper"> <span class="timestamp">2011-06-28 Tue 08:55</span></span> </p>
<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2011/06/drupal-notes-from-helping-a-client-improve-her-development-environment/">Drupal notes from helping a client improve her development environment</a> (Sacha Chua's blog)</p>
]]></content:encoded>
			<wfw:commentRss>http://sachachua.com/blog/2011/06/drupal-notes-from-helping-a-client-improve-her-development-environment/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>Managing configuration changes in Drupal</title>
		<link>http://sachachua.com/blog/2011/06/managing-configuration-changes-in-drupal/</link>
		<comments>http://sachachua.com/blog/2011/06/managing-configuration-changes-in-drupal/#comments</comments>
		<pubDate>Fri, 10 Jun 2011 15:00:00 +0000</pubDate>
		<dc:creator>Sacha Chua</dc:creator>
				<category><![CDATA[development]]></category>
		<category><![CDATA[drupal]]></category>
		<category><![CDATA[geek]]></category>
		<category><![CDATA[work]]></category>

		<guid isPermaLink="false">http://sachachua.com/blog/2011/06/managing-configuration-changes-in-drupal/</guid>
		<description><![CDATA[One of our clients asked if we had any tips for documenting and managing Drupal configuration, modules, versions, settings, and so on. She wrote, &#8220;It&#8217;s getting difficult to keep track of what we&#8217;ve changed, when, for that reason, and what settings are in that need to be moved to production versus what settings are there [...]<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2011/06/managing-configuration-changes-in-drupal/">Managing configuration changes in Drupal</a> (Sacha Chua's blog)</p>
]]></description>
			<content:encoded><![CDATA[<p>One of our clients asked if we had any tips for documenting and managing Drupal configuration, modules, versions, settings, and so on. She wrote, &#8220;It&#8217;s getting difficult to keep track of what we&#8217;ve changed, when, for that reason, and what settings are in that need to be moved to production versus what settings are there for testing purposes.&#8221; Here&#8217;s what works for us. </p>
<p> <b>Version control:</b> A good distributed version control system is key. This allows you to save and log versions of your source code, merge changes from multiple developers, review differences, and roll back to a specified version. I use Git whenever I can because it allows much more flexibility in managing changes. I like the way it makes it easy to branch code, too, so I can start working on something experimental without interfering with the rest of the code. </p>
<p> <b>Issue tracking:</b> Use a structured issue-tracking or trouble-ticketing system to manage your to-dos. That way, you can see the status of different items, refer to specific issues in your version control log entries, and make sure that nothing gets forgotten. Better yet, set up an issue tracker that&#8217;s integrated with your version control system, so you can see the changes that are associated with an issue. I&#8217;ve started using <a href="http://www.redmine.org/">Redmine</a>, but there are plenty of options. Find one that works well with the way your team works. </p>
<p> <b>Local development environments and an integration server:</b> Developers should be able to experiment and test locally before they share their changes, and they shouldn&#8217;t have to deal with interference from other people&#8217;s changes. They should also be able to refer to a common integration server that will be used as the basis for production code. </p>
<p> I typically set up a local development environment using a Linux-based virtual machine so that I can isolate all the items for a specific project. When I&#8217;m happy with the changes I&#8217;ve made to my local environment, I convert them to code (see Features below) and commit the changes to the source code repository. Then I update the integration server with the new code and confrm that my changes work there. I periodically load other developers&#8217; changes and a backup of the integration server database into my local environment, so that I&#8217;m sure I&#8217;m working with the latest copy. </p>
<p> <b>Database backups:</b> I use <a href="http://drupal.org/project/backup_migrate">Backup and Migrate</a> for automatic trimmed-down backups of the integration server database. These are regularly committed to the version control repository so that we can load the changes in our local development environment or go back to a specific point in time. </p>
<p> <b>Turning configuration into code:</b> You can use the Features module to convert most Drupal configuration changes into code that you can commit to your version control repository.  </p>
<p> There are some quirks to watch out for: </p>
<ul>
<li>Features aren&#8217;t automatically enabled, so you may want to have one   overall feature that depends on any sub-features you create. If you   are using Features to manage the configuration of a site and you   don&#8217;t care about breaking Features into smaller reusable components,   you might consider putting all of your changes into one big Feature. </li>
<li>Variables are under the somewhat unintuitively named category of Strongarm. </li>
<li>Features doesn&#8217;t handle deletion of fields well, so delete fields   directly on the integration server. </li>
<li>Some changes are not exportable, such as nodequeue. Make those changes directly on the integration server. </li>
</ul>
<p> You want your integration server to be at the default state for all features. On your local system, make the changes you want, then create or update features to encapsulate those changes. Commit the features to your version control repository. You can check if you&#8217;ve captured all the changes by reverting your database to the server copy and verifying your functionality (make a manual backup of your local database first!). When you&#8217;re happy with the changes, push the changes to the integration server. </p>
<p> Using Features with your local development environment should minimize the number of changes you need to directly make on the server. </p>
<p> <b>Documenting specific versions or module sources:</b> You can use <a href="http://drupal.org/project/drush_make">Drush Make</a> to document the specific versions or sources you use for your Drupal modules. </p>
<p> <b>Testing:</b> In development, there are few things as frustrating as finding you&#8217;ve broken something that was working before. Save yourself lots of time and hassle by investing in automated tests. You can use Simpletest to test Drupal sites, and you can also use external testing tools such as Selenium. Tests can help you quickly find and compare working and non-working versions of your code so that you can figure out what went wrong. </p>
<p> What are your practices and tips? </p>
<p>  <span class="timestamp-wrapper"> <span class="timestamp">2011-06-09 Thu 12:25</span></span> </p>
<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2011/06/managing-configuration-changes-in-drupal/">Managing configuration changes in Drupal</a> (Sacha Chua's blog)</p>
]]></content:encoded>
			<wfw:commentRss>http://sachachua.com/blog/2011/06/managing-configuration-changes-in-drupal/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Drush, Simpletest, and continuous integration for Drupal using Jenkins (previously Hudson)</title>
		<link>http://sachachua.com/blog/2011/06/drush-simpletest-and-continuous-integration-for-drupal-using-jenkins-previously-hudson/</link>
		<comments>http://sachachua.com/blog/2011/06/drush-simpletest-and-continuous-integration-for-drupal-using-jenkins-previously-hudson/#comments</comments>
		<pubDate>Thu, 09 Jun 2011 13:51:00 +0000</pubDate>
		<dc:creator>Sacha Chua</dc:creator>
				<category><![CDATA[automation]]></category>
		<category><![CDATA[drupal]]></category>
		<category><![CDATA[geek]]></category>

		<guid isPermaLink="false">http://sachachua.com/blog/2011/06/drush-simpletest-and-continuous-integration-for-drupal-using-jenkins-previously-hudson/</guid>
		<description><![CDATA[One of my development goals is to learn how to set up continuous integration so that I&#8217;ll always remember to run my automated tests. I picked up the inspiration to use Hudson from Stuart Robertson, with whom I had the pleasure of working on a Drupal project before he moved to BMO. He had set [...]<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2011/06/drush-simpletest-and-continuous-integration-for-drupal-using-jenkins-previously-hudson/">Drush, Simpletest, and continuous integration for Drupal using Jenkins (previously Hudson)</a> (Sacha Chua's blog)</p>
]]></description>
			<content:encoded><![CDATA[<p>One of my development goals is to learn how to set up continuous integration so that I&#8217;ll always remember to run my automated tests. I picked up the inspiration to use Hudson from Stuart Robertson, with whom I had the pleasure of working on a Drupal project before he moved to BMO. He had set up continuous integration testing with Hudson and Selenium on another project he&#8217;d worked on, and they completed user acceptance testing without any defects. That&#8217;s pretty cool. =) </p>
<p> I&#8217;m a big fan of automated testing because I hate doing repetitive work. Automated tests also let me turn software development into a game, with clearly defined goalposts and a way to keep score. Automated tests can be a handy way of creating lots of data so that I can manually test a site set up the way I want it to be. I like doing test-driven development: write the test first, then write the code that passes it. </p>
<p> Testing was even better with Rails. I love the Cucumber testing framework because I could define high-level tests in English. The Drupal equivalent (Drucumber?) isn&#8217;t quite there yet. I could actually use Cucumber to test my Drupal site, but it would only be able to test the web interface, not the code, and I like to write unit tests in addition to integration tests. Still, some automated testing is better than no testing, and I&#8217;m comfortable creating Simpletest classes. </p>
<p> <a href="http://jenkins-ci.org">Jenkins</a> (previously known as Hudson) is a continuous integration server that can build and test your application whenever you change the code. I set it up on my local development image by following <a href="https://wiki.jenkins-ci.org/display/JENKINS/Installing+Jenkins">Jenkins&#8217; installation instructions</a>. I enabled the Git plugin (Manage Jenkins &#8211; Manage Plugins &#8211; Available). </p>
<p> Then I set up a project with my local git repository. I started with a placeholder build step of <b>Execute shell</b> and <code>pwd</code>, just to see where I was. When I built the project, Hudson checked out my source code and ran the command. I then went into the Hudson workspace directory, configured my Drupal settings.php to use the database and URL I created for the integration site, and configured permissions and Apache with a name-based virtual host so that I could run web tests. </p>
<p> For build steps, I used <b>Execute shell</b> with the following settings: </p>
<pre class="example">mysql -u integration integration &lt; sites/default/files/backup_migrate/scheduled/site-backup.mysql
/var/drush/drush test PopulateTestUsersTest
/var/drush/drush test PopulateTestSessionsTest
/var/drush/drush testre MyProjectName --error-on-fail
</pre>
<p> This loads the backup file created by <a href="http://drupal.org/project/backup_migrate">Backup and Migrate</a>, sets up my test content, and then uses my custom testre command. </p>
<p> Code below (c) 2011 Sacha Chua (sacha@sachachua.com), available under GNU General Public License v2.0 (yes, I should submit this as a patch, but there&#8217;s a bit of paperwork for direct contributions, and it&#8217;s easier to just get my manager&#8217;s OK to blog about something&hellip;) </p>
<pre class="example">// A Drush command callback.
function drush_simpletest_test_regular_expression($test_re='') {
  global $verbose, $color;
  $verbose = is_null(drush_get_option('detail')) ? FALSE : TRUE;
  $color = is_null(drush_get_option('color')) ? FALSE : TRUE;
  $error_on_fail = is_null(drush_get_option('error-on-fail')) ? FALSE : TRUE;
  if (!preg_match("/^\/.*\//", $test_re)) {
    $test_re = "/$test_re/";
  }
  // call this method rather than simpletest_test_get_all() in order to bypass internal cache
  $all_test_classes = simpletest_test_get_all_classes();

  // Check that the test class parameter has been set.
  if (empty($test_re)) {
    drush_print("\nAvailable test groups &amp; classes");
    drush_print("-------------------------------");
    $current_group = '';
    foreach ($all_test_classes as $class =&gt; $details) {
      if (class_exists($class) &amp;&amp; method_exists($class, 'getInfo')) {
        $info = call_user_func(array($class, 'getInfo'));
        if ($info['group'] != $current_group) {
          $current_group = $info['group'];
          drush_print('[' . $current_group . ']');
        }
        drush_print("\t" . $class . ' - ' . $info['name']);
      }
    }
    return;
  }

  // Find test classes that match
  foreach ($all_test_classes as $class =&gt; $details) {
    if (class_exists($class) &amp;&amp; method_exists($class, 'getInfo')) {
      if (preg_match($test_re, $class)) {
        $info = call_user_func(array($class, 'getInfo'));
        $matching_classes[$class] = $info;
      }
    }
  }

  // Sort matching classes by weight
  uasort($matching_classes, '_simpletest_drush_compare_weight');

  foreach ($matching_classes as $class =&gt; $info) {
    $main_verbose = $verbose;
    $results[$class] = drush_simpletest_run_single_test($class, $error_on_fail);
    $verbose = $main_verbose;
  }

  $failures = $successes = 0;
  foreach ($results as $class =&gt; $status) {
    print $status . "\t" . $class . "\n";
    if ($status == 'fail') {
      $failures++;
    } else {
      $successes++;
    }
  }
  print "Failed: " . $failures . "/" . ($failures + $successes) . "\n";
  print "Succeeded: " . $successes . "/" . ($failures + $successes) . "\n";
  if ($failures &gt; 0) {
    return 1;
  }
}
</pre>
<p> I didn&#8217;t bother hacking Simpletest output to match the Ant/JUnit output so that Jenkins could understand it better. I just wanted a pass/fail status, as I could always look at the results to find out which test failed. </p>
<p> What does it gain me over running the tests from the command-line? I like having the build history and being able to remember the last successful build. </p>
<p> I&#8217;m going to keep this as a local build server instead of setting up a remote continuous integration server on our public machine, because it involves installing quite a number of additional packages. Maybe the other developers might be inspired to set up something similar, though! </p>
<p>  <span class="timestamp-wrapper"> <span class="timestamp">2011-06-09 Thu 09:51</span></span> </p>
<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2011/06/drush-simpletest-and-continuous-integration-for-drupal-using-jenkins-previously-hudson/">Drush, Simpletest, and continuous integration for Drupal using Jenkins (previously Hudson)</a> (Sacha Chua's blog)</p>
]]></content:encoded>
			<wfw:commentRss>http://sachachua.com/blog/2011/06/drush-simpletest-and-continuous-integration-for-drupal-using-jenkins-previously-hudson/feed/</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>VMWare, Samba, Eclipse, and XDebug: Mixing a virtual Linux environment with a Microsoft Windows development environment</title>
		<link>http://sachachua.com/blog/2011/06/vmware-samba-eclipse-xdebug-mixing-virtual-linux-environment-microsoft-windows-development-environment/</link>
		<comments>http://sachachua.com/blog/2011/06/vmware-samba-eclipse-xdebug-mixing-virtual-linux-environment-microsoft-windows-development-environment/#comments</comments>
		<pubDate>Wed, 01 Jun 2011 12:00:00 +0000</pubDate>
		<dc:creator>Sacha Chua</dc:creator>
				<category><![CDATA[development]]></category>
		<category><![CDATA[drupal]]></category>
		<category><![CDATA[geek]]></category>

		<guid isPermaLink="false">http://sachachua.com/blog/?p=22270</guid>
		<description><![CDATA[I&#8217;m starting the second phase of a Drupal development project, which means I get to write about all sorts of geeky things again. Hooray! So I&#8217;m investing some time into improving my environment set-up, and taking notes along the way. This time, I&#8217;m going to try developing code in Eclipse instead of Emacs, although I&#8217;ll [...]<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2011/06/vmware-samba-eclipse-xdebug-mixing-virtual-linux-environment-microsoft-windows-development-environment/">VMWare, Samba, Eclipse, and XDebug: Mixing a virtual Linux environment with a Microsoft Windows development environment</a> (Sacha Chua's blog)</p>
]]></description>
			<content:encoded><![CDATA[<p>I&#8217;m starting the second phase of a Drupal development project, which means I get to write about all sorts of geeky things again. Hooray! So I&#8217;m investing some time into improving my environment set-up, and taking notes along the way. </p>
<p> This time, I&#8217;m going to try developing code in Eclipse instead of Emacs, although I&#8217;ll dip into Emacs occasionally if I need to do anything involving keyboard macros or custom automation. Setting up a good Eclipse environment will help me use XDebug for line-by-line debugging. var_dump> can only take me so far, and I still haven&#8217;t figured out how to properly use XDebug under Emacs. Configuring Eclipse will also help me help my coworkers, who tend to not be big Emacs fans. (Sigh.) </p>
<p> So here&#8217;s my current setup: </p>
<ul>
<li>A Linux server environment in VMWare, so that I can use all the Unix   tools I like and so that I don&#8217;t have to fuss about with a WAMP   stack </li>
<li>Samba for sharing the source code between the Linux VM image and my Microsoft Windows laptop </li>
<li>XDebug for debugging </li>
<li>Eclipse and PDT for development </li>
</ul>
<p> I like this because it allows me to edit files in Microsoft Windows or in Linux, and I can use step-by-step debugging instead of relying on <code>var_dump</code>. </p>
<p> <b>Setting up Samba</b> </p>
<p> Samba allows you to share folders on the network. Edit your <code>smb.conf</code> (mine&#8217;s in <code>/etc/samba/</code>) and uncomment/edit the following lines: </p>
<pre class="example">security = user
</pre>
<p> &hellip; </p>
<pre class="example">[homes]
   comment = Home Directories
   browseable = no
   read only = no
   valid users = %S
</pre>
<p> You may also need to use <code>smbpasswd</code> to set the user&#8217;s password. </p>
<p> <b>Xdebug</b> </p>
<p> Install <code>php5-xdebug</code> or whatever the Xdebug package is for PHP on your system.  Edit <code>xdebug.ini</code> (mine&#8217;s in <code>/etc/php5/conf.d</code>) and add the following lines to the end: </p>
<pre class="example">[Xdebug]
xdebug.remote_enable=on
xdebug.remote_port=9000
xdebug.remote_handler=dbgp
xdebug.remote_autostart=1
xdebug.remote_connect_back=1
</pre>
<p> Warning: this allows debugging access from any computer that connects to it. Use this only on your development image. If you want to limit debugging access to a specific computer, remove the line that refers to <code>remote_connect_back</code> and replace it with this: </p>
<pre class="example">xdebug.remote_host=YOUR.IP.ADDRESS.HERE
</pre>
<p> <b>Eclipse and PDT</b> </p>
<p> I downloaded the all-in-one PHP Development Toolkit (PDT) from <a href="http://www.eclipse.org/pdt/">http://www.eclipse.org/pdt/</a>, unpacked it, and imported my project. After struggling with Javascript and HTML validation, I ended up disabling most of those warnings. Then I set up a debug configuration that used Xdebug and the server in the VM image, and voila! Line by line debugging with the ability to look in variables. Hooray! </p>
<p>  <span class="timestamp-wrapper"> <span class="timestamp">2011-05-31 Tue 17:37</span></span> </p>
<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2011/06/vmware-samba-eclipse-xdebug-mixing-virtual-linux-environment-microsoft-windows-development-environment/">VMWare, Samba, Eclipse, and XDebug: Mixing a virtual Linux environment with a Microsoft Windows development environment</a> (Sacha Chua's blog)</p>
]]></content:encoded>
			<wfw:commentRss>http://sachachua.com/blog/2011/06/vmware-samba-eclipse-xdebug-mixing-virtual-linux-environment-microsoft-windows-development-environment/feed/</wfw:commentRss>
		<slash:comments>14</slash:comments>
		</item>
		<item>
		<title>Drupal fixes: Modifying the entries in Calendar</title>
		<link>http://sachachua.com/blog/2010/12/drupal-fixes-modifying-entries-calendar/</link>
		<comments>http://sachachua.com/blog/2010/12/drupal-fixes-modifying-entries-calendar/#comments</comments>
		<pubDate>Sun, 26 Dec 2010 13:00:00 +0000</pubDate>
		<dc:creator>Sacha Chua</dc:creator>
				<category><![CDATA[drupal]]></category>
		<category><![CDATA[geek]]></category>

		<guid isPermaLink="false">http://sachachua.com/blog/?p=22014</guid>
		<description><![CDATA[The Drupal Calendar module is great. You can show view results in a decent-looking calendar easily. However, if you want to show more than what&#8217;s provided out of the box, you need to do a bit of undocumented and confusing hacking. Let&#8217;s say that instead of displaying the items, you want to display the number [...]<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2010/12/drupal-fixes-modifying-entries-calendar/">Drupal fixes: Modifying the entries in Calendar</a> (Sacha Chua's blog)</p>
]]></description>
			<content:encoded><![CDATA[<p>The Drupal <a href="http://drupal.org/project/calendar">Calendar</a> module is great. You can show view results in a decent-looking calendar easily. However, if you want to show more than what&#8217;s provided out of the box, you need to do a bit of undocumented and confusing hacking. </p>
<p> Let&#8217;s say that instead of displaying the items, you want to display the number of items. You need to implement <code>hook_calendar_add_items</code>, which, despite its name, actually overrides the items displayed. Your <code>hook_calendar_add_items($view)</code> should return an array of an array of items, which is also confusing, because the receiving function (<code>template_preprocess_calendar</code> in <code>calendar/theme/theme.inc</code>) doesn&#8217;t actually do anything with the outermost array, or indeed with multiple entries &#8211; it simply takes the last array and sets the items to it.  </p>
<p> Each item should be something like this: </p>
<pre class="src src-php">$item = new stdClass();
$item-&gt;node_title = t('Hello world');
$item-&gt;calendar_start_date = $date;
$item-&gt;calendar_end_date = $date;
$items[] = $item;
</pre>
<p> and at the end, you do this: </p>
<pre class="src src-php">return array($items);
</pre>
<p> Your <code>hook_calendar_add_items</code> gets the <code>$view</code>, but not the existing items, so you&#8217;ll have to recalculate things using <code>$view-&gt;result</code>. Remember: if you return anything, your results will replace the items instead of being added to them. </p>
<p> Because <code>hook_calendar_add_items</code> doesn&#8217;t actually do what it advertises, there are probably bugs in this area of code, and this behaviour might change in future releases of Calendar. Be careful when using it. However, it seems to be the easiest way to change Calendar behavior. Caveat developer. </p>
<p> Also useful: <a href="http://drupal.org/node/412418">http://drupal.org/node/412418</a> </p>
<p> <span class="timestamp-wrapper"> <span class="timestamp">2010-12-24 Fri 10:19 </p>
<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2010/12/drupal-fixes-modifying-entries-calendar/">Drupal fixes: Modifying the entries in Calendar</a> (Sacha Chua's blog)</p>
]]></content:encoded>
			<wfw:commentRss>http://sachachua.com/blog/2010/12/drupal-fixes-modifying-entries-calendar/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Reflections on mentoring new developers in Drupal</title>
		<link>http://sachachua.com/blog/2010/12/reflections-mentoring-developers-drupal/</link>
		<comments>http://sachachua.com/blog/2010/12/reflections-mentoring-developers-drupal/#comments</comments>
		<pubDate>Fri, 24 Dec 2010 13:00:00 +0000</pubDate>
		<dc:creator>Sacha Chua</dc:creator>
				<category><![CDATA[drupal]]></category>
		<category><![CDATA[geek]]></category>
		<category><![CDATA[mentoring]]></category>

		<guid isPermaLink="false">http://sachachua.com/blog/?p=22012</guid>
		<description><![CDATA[UPDATE: Fixed formatting. Thanks, Brock! Two developers recently joined our team. Johnny has worked with Drupal before, and needs a little help getting used to Drupal 6 and Views 2. Elena is an IT architect who is new to both IBM and Drupal. She needs a lot more help getting started, because she doesn&#8217;t know [...]<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2010/12/reflections-mentoring-developers-drupal/">Reflections on mentoring new developers in Drupal</a> (Sacha Chua's blog)</p>
]]></description>
			<content:encoded><![CDATA[<p>UPDATE: Fixed formatting. Thanks, Brock!
</p>
<p>
Two developers recently joined our team. Johnny has worked with Drupal before, and needs a little help getting used to Drupal 6 and Views 2. Elena is an IT architect who is new to both IBM and Drupal. She needs a lot more help getting started, because she doesn&#8217;t know what things are called yet and she isn&#8217;t yet accustomed to the Drupal way of doing things. For my part, I work on Workflow, node access, and other requirements that require deep Drupal hacking.
</p>
<p>
I&#8217;m learning to check on Elena more frequently and to help her break down tasks. Otherwise, she might get lost or stuck, because she might not yet know where things are or whether she&#8217;s getting closer to an answer. I&#8217;ve made good progress on the things we&#8217;ve planned for this iteration, and I can invest the time into helping our new team members be more productive and learn more effectively.
</p>
<p>
Both Elena and Johnny have set up their debuggers in Eclipse, so they don&#8217;t have to figure out the right places to insert <code>var_dumps</code>. Instead, they can trace through the relevant pieces of code, learning more about the structures and the flow of Drupal websites along the way.
</p>
<p>
Although I occasionally struggle to explain things I take for granted, I enjoy helping someone who&#8217;s new to an area. It helps me remember the things people need to learn. For example, Elena&#8217;s work on surveys requires her to learn about nodes, getting values from the <code>$_REQUEST</code>, loading nodes, working with CCK, altering forms, adding new form fields using the Form API, and using Drupal functions for links and text. We broke down the task into the following steps:
</p>
<ol>
<li>Create a CCK node type.</li>
<li>Use <code>hook_form_alter</code> to add some text to the form.</li>
<li>Load a node and fill the information in the form.</li>
<li>Get the extra node ID from the URL.</li>
<li>Adapt <code>form_alter</code> for the case where you&#8217;re editing the node.</li>
</ol>
<p>
We&#8217;ve managed our planning well, so I don&#8217;t feel overcommitted or stretched with the additional mentoring I&#8217;ve taken on. The time is an investment that will pay off both in the short-term as well as the long-term. If I can slow down and write more, then the investment can benefit to other people too.
</p>
<p>
I like this. It&#8217;s certainly much better than leaving developers to flounder and work things out on their own, and I learn a lot in the process of helping. Maybe that will be one of my specialties: projects where other people are learning a lot on the fly.</p>
<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2010/12/reflections-mentoring-developers-drupal/">Reflections on mentoring new developers in Drupal</a> (Sacha Chua's blog)</p>
]]></content:encoded>
			<wfw:commentRss>http://sachachua.com/blog/2010/12/reflections-mentoring-developers-drupal/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>Using Simpletest and spreadsheets to populate Drupal with data</title>
		<link>http://sachachua.com/blog/2010/12/simpletest-spreadsheets-populate-drupal-data/</link>
		<comments>http://sachachua.com/blog/2010/12/simpletest-spreadsheets-populate-drupal-data/#comments</comments>
		<pubDate>Thu, 23 Dec 2010 13:00:00 +0000</pubDate>
		<dc:creator>Sacha Chua</dc:creator>
				<category><![CDATA[drupal]]></category>
		<category><![CDATA[geek]]></category>
		<category><![CDATA[highlight]]></category>
		<category><![CDATA[simpletest]]></category>

		<guid isPermaLink="false">http://sachachua.com/blog/?p=22011</guid>
		<description><![CDATA[One of the challenges of testing views or custom Drupal code is generating the right kind of data. Devel can generate hundreds of random nodes, but you might need more custom data than that. For example, on our project, we need to have test users, their content profiles, and nodes that follow a certain node [...]<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2010/12/simpletest-spreadsheets-populate-drupal-data/">Using Simpletest and spreadsheets to populate Drupal with data</a> (Sacha Chua's blog)</p>
]]></description>
			<content:encoded><![CDATA[<p> One of the challenges of testing views or custom Drupal code is generating the right kind of data. <a href="http://drupal.org/project/devel">Devel</a> can generate hundreds of random nodes, but you might need more custom data than that. For example, on our project, we need to have test users, their content profiles, and nodes that follow a certain node reference structure. By creating a class that extends DrupalWebTestCase and provides convenience functions on top of drupalCreateNode, we can easily create test data as part of our test cases. Copying the code from drupalCreateUser and making our own version that uses roles and content profiles helps us set up the right users, too.  </p>
<p> We wanted our tests and changes to use the same database tables used by the web interface, so we <a href="http://www.trellon.com/content/blog/forcing-simpletest-use-live-database">overrode the setUp methods to use the actual database</a>. This not only makes the tests faster, it also makes them more useful for the web testing and demos. </p>
<p> Many of our test cases create the data they need. However, some test cases need even more complex structures that are similar from one test to another. Instead of creating and recreating them on each test, I&#8217;ve written another test case for populating the data. For example, PopulateTestUsers sets up about 30 users with different characteristics. I can then write other tests that assume PopulateTestUsers has been run and the sample users and nodes are available. </p>
<p> How do we generate the users and nodes without getting tangled in lots of PHP? Here&#8217;s a technique I picked up from Stuart Robertson, an IT architect with lots of good ideas. He fills in a spreadsheet with the values he wants test data to have. He then uses other columns to generate PHP that set the individual fields, and another column that generates PHP based on the generated PHP. For example, a formula to set a CCK value might look like this: </p>
<pre class="example">=IF(B3&lt;&gt;"",CONCATENATE("'field_widget_doohickey' =&gt; array(array('value' =&gt; '",B3,"')),"),"")
</pre>
<p> which turns a value of &#8220;foo&#8221; in B3 to  </p>
<pre class="example">'field_widget_doohickey' =&gt; array(array('value' =&gt; 'foo'))
</pre>
<p> which is then something you can pass to the node creation function. To figure out the syntax for other node attributes, use <code>var_dump</code> or your favourite debugging tool to look at a node that has the information you want. </p>
<p> You might have the final generation like this: </p>
<pre class="example">=CONCATENATE("$this-&gt;createWidget('",A3,"',array(",E3,"));")
</pre>
<p> where createWidget is a function you&#8217;ve defined to make things more readable. It would be a wrapper around drupalCreateNode that sets the type and does other things. </p>
<p> This spreadsheet makes it so much easier for us to work with our test data because we can refer to it to find test data matching criteria when designing our tests or trying things out using the web interface. Adding new test items is easy: just fill in the rows, copy the equations, and then copy the generated code and paste it into the test case. </p>
<p> Naming tip: Using a different naming convention makes it easy for me to use our custom-coded Drush testre command (run tests matching a regular expression) to run just the tests that populate data, or just the tests that assume data is there. Likewise, test cases that use SimpleTest&#8217;s web client ($this-&gt;get, etc.) have a different naming convention so that I can avoid running these slower tests when I just want to do a quick check. </p>
<p> Simpletest is a powerful tool. I&#8217;ve used it on every Drupal project I&#8217;ve worked on, even when I was the only one writing tests. Combined with a spreadsheet for generating structured test data, it&#8217;s a great help for development and demonstration, because we can set up or refresh complicated sets of users and nodes in little time. Well worth investing time to learn and use.  </p>
<p> <span class="timestamp-wrapper"><span class="timestamp-kwd">SCHEDULED: </span> <span class="timestamp">2010-12-23 Thu 08:00</span></span> </p>
<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2010/12/simpletest-spreadsheets-populate-drupal-data/">Using Simpletest and spreadsheets to populate Drupal with data</a> (Sacha Chua's blog)</p>
]]></content:encoded>
			<wfw:commentRss>http://sachachua.com/blog/2010/12/simpletest-spreadsheets-populate-drupal-data/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Test-driven development and happiness</title>
		<link>http://sachachua.com/blog/2010/12/test-driven-development-and-happiness/</link>
		<comments>http://sachachua.com/blog/2010/12/test-driven-development-and-happiness/#comments</comments>
		<pubDate>Fri, 17 Dec 2010 13:00:00 +0000</pubDate>
		<dc:creator>Sacha Chua</dc:creator>
				<category><![CDATA[coding]]></category>
		<category><![CDATA[development]]></category>
		<category><![CDATA[drupal]]></category>
		<category><![CDATA[geek]]></category>

		<guid isPermaLink="false">http://sachachua.com/blog/2010/12/test-driven-development-and-happiness/</guid>
		<description><![CDATA[Me: Happiness is a test suite that passes. @philiph: Do you practice test-driven development for your happiness? Me: Why, yes, actually, I do. It gives me a tangible sense of accomplishment and minimizes my mouse-clicking. =) Developers find their own balance of how much project structure works with them. Some people like seat-of-their-pants coding. Others [...]<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2010/12/test-driven-development-and-happiness/">Test-driven development and happiness</a> (Sacha Chua's blog)</p>
]]></description>
			<content:encoded><![CDATA[<blockquote><p>Me: Happiness is a test suite that passes.     <br />@philiph: Do you practice test-driven development for your happiness?      <br />Me: Why, yes, actually, I do. It gives me a tangible sense of accomplishment and minimizes my mouse-clicking. =)</p>
</blockquote>
<p>Developers find their own balance of how much project structure works with them. Some people like seat-of-their-pants coding. Others want detailed architecture diagrams. Me, I’m getting the hang of agile development practices, and I really enjoy using them.</p>
<p>Test-driven development, for example. Yes, I could just plunge ahead and write Drupal code, and I could test it by clicking on buttons and typing into forms. I don’t particularly like using the mouse or doing repetitive actions, so I write tests for functionality and occasionally for web interaction. Tests also mean that&#160; I can check small pieces of functionality before I have to build a web interface. And when something breaks – not if, but when – tests help me narrow down the error. </p>
<p>It’s so satisfying to see the tests pass, too.</p>
<p>There are tests that exercise functionality, and tests that set up test data just the way we like it so that we can demonstrate features or try things out using the web interface. One of my team members showed me a wonderful technique for repeatable, well-structured test data by using a spreadsheet to generate PHP code. I’ve been extending the pattern for other things.</p>
<p>Drupal + Simpletest is awesome. It can’t handle everything, but it makes my Drupal life better. Happy developers write happy code!</p>
<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2010/12/test-driven-development-and-happiness/">Test-driven development and happiness</a> (Sacha Chua's blog)</p>
]]></content:encoded>
			<wfw:commentRss>http://sachachua.com/blog/2010/12/test-driven-development-and-happiness/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Drupal, SimpleTest, and the node access API</title>
		<link>http://sachachua.com/blog/2010/11/drupal-simpletest-and-the-node-access-api/</link>
		<comments>http://sachachua.com/blog/2010/11/drupal-simpletest-and-the-node-access-api/#comments</comments>
		<pubDate>Thu, 11 Nov 2010 13:00:00 +0000</pubDate>
		<dc:creator>Sacha Chua</dc:creator>
				<category><![CDATA[drupal]]></category>
		<category><![CDATA[drush]]></category>
		<category><![CDATA[tips]]></category>
		<category><![CDATA[work]]></category>

		<guid isPermaLink="false">http://sachachua.com/blog/2010/11/drupal-simpletest-and-the-node-access-api/</guid>
		<description><![CDATA[Setting up Simpletest and Drush on Drupal 6.x: Download and enable Simpletest with drush dl simpletest; drush en -y simpletest Download simpletest.drush.inc to your ~/.drush/drush_extras directory. This version allows you to run a single test from the command-line. Create a custom module with a tests/ subdirectory, and write your tests in it. (See this Lullabot [...]<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2010/11/drupal-simpletest-and-the-node-access-api/">Drupal, SimpleTest, and the node access API</a> (Sacha Chua's blog)</p>
]]></description>
			<content:encoded><![CDATA[<p> Setting up Simpletest and Drush on Drupal 6.x: </p>
<ol>
<li> Download and enable Simpletest with <code>drush dl simpletest; drush en -y simpletest</code> </li>
<li> Download <a href="https://github.com/hiddentao/drush_simpletest_command">simpletest.drush.inc</a> to your <code>~/.drush/drush_extras</code> directory. This version allows you to run a single test from the command-line. </li>
<li> Create a custom module with a <code>tests/</code> subdirectory, and write your tests in it. (See this <a href="http://www.lullabot.com/articles/drupal-module-developer-guide-simpletest">Lullabot Simpletest tutorial</a>.)  </li>
</ol>
<p>We&#8217;re starting another Drupal project. While the IT architect is working on clarifying the requirements, I volunteered to implement the risky parts so that we could get a better sense of what we needed to do. </p>
<p> <b>The first major chunk of risk was fine-grained access control.</b> Some users needed to be able to edit the nodes associated with other users, and some users needed to have partial access to nodes depending on how they were referenced by the node. Because there were many cases, I decided to start by writing unit tests. </p>
<p> SimpleTest was not as straightforward in Drupal 6.x as it was in Drupal 5.x. There were a few things that confused me before I figured things out.  </p>
<p> <b>I wondered why my queries were running off different table prefixes.</b> I didn&#8217;t have some of the data I expected to have. It turns out that Simpletest now works on a separate Drupal instance by default, using a unique table prefix so that it doesn&#8217;t mess around with your regular database. I&#8217;m doing this on a test server and I want to be able to easily look up details using SQL, so I needed to add this to my test case: </p>
<pre class="example">class ExampleTestCase extends DrupalWebTestCase {
  function setUp() {
    global $base_url;
    $this-&gt;originalPrefix = $GLOBALS['db_prefix'];
  }
  function tearDown() { }
}
</pre>
<p> I also didn&#8217;t like how the built-in <code>$this-&gt;drupalCreateUser</code> took permissions instead of roles, and how it created custom roles each time. I created a function that looked up the role IDs using the <code>{role}</code> table, then added the role IDs and roles to the <code>$edit['roles']</code> array before creating the user.  </p>
<p> Lastly, I needed to add the Content Profile operations to my custom user creation function. I based this code on <code>content_profile.test</code>. </p>
<pre class="example">$this-&gt;drupalLogin($account);
// create a content_profile node
$edit = array(
  'title' =&gt; $account-&gt;name,
  'body'  =&gt; $this-&gt;randomName(),
);
$this-&gt;drupalGet('node/add');
$this-&gt;drupalPost('node/add/' . str_replace(' ', '-', $role), $edit, t('Save'));
</pre>
<p> It would&#8217;ve been even better to do this without going through the web interface, but it was fine for a quick hack. </p>
<p> I had the setup I wanted for writing test cases that checked user permissions. I wrote functions for checking if the user could accept an invitation (must be invited, must not already have accepted, and must be able to fit). SimpleTest made it easy to test each of the functions, allowing me to build and test blocks that I could then put together. </p>
<p> The code in <code>content_permission.module</code> turned out to be a good starting point for my field-level permissions, while the Drupal node access API made it easy to handle the user-association-related permissions even though I used node references instead of user references. </p>
<p> It was a good day of hacking. I wrote tests, then I wrote code, then I argued with the computer until my tests passed. ;) It was fun seeing my progress and knowing I wasn&#8217;t screwing up things I&#8217;d already solved.  </p>
<p> If you&#8217;re writing Drupal code, I strongly recommend giving SimpleTest a try. Implementing <code>hook_node_access_records</code> and <code>hook_node_grants</code> is much easier when you can write a test to make sure the right records are showing up. (With the occasional use of <code>node_access_acquire_grants</code> to recalculate&hellip;) Otherwise-invisible Drupal code becomes easy to verify. The time you invest into writing tests will pay off throughout the project, and during future work as well. Have fun! </p>
<p>Read the original or check out the comments on: <a href="http://sachachua.com/blog/2010/11/drupal-simpletest-and-the-node-access-api/">Drupal, SimpleTest, and the node access API</a> (Sacha Chua's blog)</p>
]]></content:encoded>
			<wfw:commentRss>http://sachachua.com/blog/2010/11/drupal-simpletest-and-the-node-access-api/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
	</channel>
</rss>

<!-- Performance optimized by W3 Total Cache. Learn more: http://www.w3-edge.com/wordpress-plugins/

Page Caching using disk: basic (User agent is rejected)
Database Caching 16/27 queries in 0.018 seconds using disk: basic

Served from: sachachua.com @ 2012-05-27 03:02:30 -->
