<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="http://feeds.feedburner.com/~d/styles/rss2full.xsl" type="text/xsl" media="screen"?><?xml-stylesheet href="http://feeds.feedburner.com/~d/styles/itemcontent.css" type="text/css" media="screen"?><rss 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" version="2.0">

<channel>
	<title>Ramblings</title>
	
	<link>http://ramblings.gibberishcode.net</link>
	<description>about fetching, interpreting, and executing.</description>
	<pubDate>Wed, 05 Nov 2008 03:34:56 +0000</pubDate>
	<generator>http://wordpress.org/?v=2.5.1</generator>
	<language>en</language>
			<atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" href="http://feeds.feedburner.com/gibberishcode/vDzA" type="application/rss+xml" /><item>
		<title>Getting mouse wheel to work with KVM and Ubuntu</title>
		<link>http://ramblings.gibberishcode.net/archives/getting-mouse-wheel-to-work-with-kvm-and-ubuntu/20</link>
		<comments>http://ramblings.gibberishcode.net/archives/getting-mouse-wheel-to-work-with-kvm-and-ubuntu/20#comments</comments>
		<pubDate>Mon, 03 Nov 2008 00:02:10 +0000</pubDate>
		<dc:creator>Michael</dc:creator>
		
		<category><![CDATA[Servers]]></category>

		<category><![CDATA[Systems]]></category>

		<category><![CDATA[kvm]]></category>

		<category><![CDATA[linux]]></category>

		<category><![CDATA[mouse]]></category>

		<category><![CDATA[mouse wheel]]></category>

		<category><![CDATA[ubuntu]]></category>

		<guid isPermaLink="false">http://ramblings.gibberishcode.net/?p=20</guid>
		<description><![CDATA[I have been busy getting a Ubuntu based Network Attached Storage server up and running as well as finally switching my trusty desktop to Ubuntu running my old Windows XP as a VMWare Server image.  All was fine and dandy until I decided to add a KVM to the mix so I could easily switch [...]]]></description>
			<content:encoded><![CDATA[<p>I have been busy getting a Ubuntu based Network Attached Storage server up and running as well as finally switching my trusty desktop to Ubuntu running my old Windows XP as a VMWare Server image.  All was fine and dandy until I decided to add a KVM to the mix so I could easily switch between the NAS and my workstation without having to have two monitors and keyboards.</p>
<p>Turns out the mouse wheel would stop working after a switcharoo.  It took me a little while to track down the solution on the Ubuntu forums.  There was a lot of back and forth in the comments, but this is the dead-simple solution:</p>
<h3>Ensure the psmouse module is loading</h3>
<p>Edit and add the following line to /etc/modules:</p>
<pre class="c:nocontrols">psmouse
</pre>
<p>You&#8217;ll need to use sudo (i.e. <strong>sudo vi /etc/modules</strong>).</p>
<h3>Set the imps option for the module</h3>
<p>As sudo user, edit /etc/modprobe.d/options and append the following:</p>
<pre class="c:nocontrols"> # Make my mouse work with KVM
 options psmouse proto=imps
</pre>
<h3>Reload the mouse module</h3>
<p>Once you have the above, issue the following commands:</p>
<pre class="c:nocontrols"> # sudo modprobe -r psmouse
 # sudo modprobe -a psmouse
</pre>
<p>Once you do this, you should have a functioning mouse wheel.  Test it out before and after switching machines with your KVM hot keys.</p>
<p>Thanks to the contributors of the <a href="http://ubuntuforums.org/archive/index.php/t-205640.html">Ubuntu Forums</a> for this.</p>
]]></content:encoded>
			<wfw:commentRss>http://ramblings.gibberishcode.net/archives/getting-mouse-wheel-to-work-with-kvm-and-ubuntu/20/feed</wfw:commentRss>
		</item>
		<item>
		<title>Jumpstarting your Virtual tour with Oracle VM</title>
		<link>http://ramblings.gibberishcode.net/archives/jumpstarting-your-virtual-tour-with-oracle-vm/19</link>
		<comments>http://ramblings.gibberishcode.net/archives/jumpstarting-your-virtual-tour-with-oracle-vm/19#comments</comments>
		<pubDate>Wed, 29 Oct 2008 23:02:38 +0000</pubDate>
		<dc:creator>Michael</dc:creator>
		
		<category><![CDATA[Servers]]></category>

		<category><![CDATA[Systems]]></category>

		<category><![CDATA[centos]]></category>

		<category><![CDATA[oracle]]></category>

		<category><![CDATA[oracle vm]]></category>

		<category><![CDATA[virtualization]]></category>

		<category><![CDATA[vmware]]></category>

		<category><![CDATA[xen]]></category>

		<guid isPermaLink="false">http://ramblings.gibberishcode.net/?p=19</guid>
		<description><![CDATA[Server virtualization technology has come a long ways in the last 18 months (today being a wonderful October 28, 2008 crisp, cold Fall Sunny day).  Here, I am going to take a look at Oracle VM, one of the relative newcomers to the game and show you how to quickly get up and running with [...]]]></description>
			<content:encoded><![CDATA[<p>Server virtualization technology has come a long ways in the last 18 months (today being a wonderful October 28, 2008 crisp, cold Fall Sunny day).  Here, I am going to take a look at Oracle VM, one of the relative newcomers to the game and show you how to quickly get up and running with a Xen host and virtual server.</p>
<h3>A little background&#8230;</h3>
<p>Our shop has been using virtual technologies for just over two years.  At the time we started taking it seriously, the open source offerings were just on the cusp of being production quality; Citrix had not yet bought into Xen; and VMWare had just rolled out their free VMWare Server option.  We had a mixture of Linux and Windows servers, all physical, and were really feeling some of the budget pince in our rapid growth.  We decided it was time to rethink how we were growing our data-center and that led us to try out VMWare and Xen side-by-side, eventually siding with Xen since the bulk of our servers turned out to be Linux hosts while our predominant application, SQL Server 2005 was not all that conducive to being virtualized without some heavy, heavy investment in hardware purchases.  </p>
<p>The gameplan, get some beefy servers, run Xen on them, with images residing on SAN and migrate all our physical Linux hosts to virtualized environment.  Once converted, we&#8217;d simply reclaim all the excess hardware to fuel the Windows SQL Server hunger.  </p>
<p>So, off we went. We purchased a three of Dell&#8217;s first dual quad-core servers at 1.66ghz and loaded them up with 16gb of RAM and a Dell AX150i iSCSI dual storage processor SAN to run our wonderfully envisioned HA cluster off of.  </p>
<p>Xen had just turned 3.0 at the time and we really went through a lot of permutations in our stress testing and daily use before we finally came up with a stable blending of several offerings from the open source community.  As wild as it may seem, we settled on Oracle VM&#8217;s first release as the Xen server, CentOS 5 repositories to manage packages on the host, Red Hat&#8217;s kickstart project to build the VM&#8217;s and ran a mixed guest environment of CentOS 4, CentOS 5, and Fedora Core 8.  The guest environment&#8217;s heterogeneity had a purpose for existing.  You see, our clients often dictated what version of Red Hat they wanted us to support when we deployed our systems in their data center, so we needed these three items (all relatively free of compulsory licensing fees) to give us the ability to effectively emulate any of our clients&#8217; data center in-house for Q/A, stress/load, and deployment &#8220;dry-run&#8221; testing purposes. </p>
<p>After trying for many moons to effect a HA clustering solution that was rock-solid and backed by the SAN, we sadly had to regress from that vision (for now).  VM images that were once all homed on the SAN and connected via iSCSI to the Xen hosts simply overwhelmed our SAN with I/O dropping low enough to force the filesystems of the guests into read-only state.  We gave up the HA vision (for now) and purchased a bunch of very fast SCSI drives and RAID 5&#8242;d them, filling up each of the Xen Host&#8217;s drive bays and relocated the VM images to each Xen host&#8217;s local volumes.  </p>
<p>This infrastructure has been our &#8220;norm&#8221; for the better part of the year now and we have a rock-solid deployment running about 80 virtualized Linux hosts on just four Dell servers.  The ease with which we can set up a new server or tear one down and dismiss it has had quite an effect on our ability to test widely varying deployment scenarios and automate the deployments themselves. </p>
<h3>Fast forward October 2008</h3>
<p>The newest version of Xen from Red Hat (and hence CentOS for us) is looking rather sharp.  Virtually all the issues we were encountering with just standalone Xen hosts appear to be addressed (in our limited testing).  One of the more critical one, where our Dom0 environment would become unstable and we&#8217;d lose ability to manage it has been fixed (our lone-remaining headache in the above setup).  We have not, however, ventured to test whether our HA woes have been resolved as well.  One other thing we did notice, though was that VM guests shutdown and launch a whole lot faster than they do on our production servers, so that could hint to some very good performance improvements lurking in the newest releases of Xen.,</p>
<p>The downside:  Six, yes count &#8216;em, six CDs must be downloaded to install the CentOS Xen version.  Oracle VM, by comparison clocks in at roughly ONE 500mb CD image plus two companion CDs that give you their VM Manager and some other handy tools that go hand in hand with managing a Virtual Server environment.  Oracle wisely dispenses with all the developer tools and Gnome desktop and office productivity tools that bloat up the RHEL/CentOS distros.  Good security practices recommend only running what you need to run on your server and nothing else!</p>
<p>What follows below is how to get your first Virtualized server up and running by downloading just the one Oracle VM Xen Server CD.  As far as I know, this is about as simple as it gets.  The graphical environment is not used and none of the iSCSI or LVM (logical volume management) tools are employed.  This is just a simple exercise that gets your hands dirty and playing with virtual technologies.</p>
<h3>Step 1 - Install the Xen Server</h3>
<p>Download the Oracle VM Server 32-bit Edition CD from <a href="http://www.oracle.com/virtualization">Oracle</a> and burn the ISO to CD.  Then plop it into your CD drive on the machine you want and boot up.  Follow the installation instructions on the screen (or the <a href="http://download.oracle.com/docs/cd/E11081_01/welcome.html">Online documentation</a>).  Once installed, boot up and log in as root.</p>
<p>Note:  At the time of this writing, we&#8217;re installing Oracle VM 2.1.2</p>
<p>You should now be able to issue:</p>
<pre  name="code" class="ruby:nocontrols">
# xm list
Name                                        ID   Mem VCPUs      State   Time(s)
Domain-0                                     0   543     1     r-----   3036.7
</pre>
<p>And see an output similar to the above.  Congratulations, you have a Xen Server up and running.</p>
<h3>Step 2 - Initial File Preparation for the Guest</h3>
<p>Initialize diskspace for your first Virtual Machine by performing the following commands (as root):</p>
<pre  name="code" class="ruby:nocontrols">
# cd /OVS
# mkdir -p local/numberone &#038;&#038; cd local/numberone
# mkdir numberone
# cd numberone
# dd if=/dev/zero of=numberone_hda1.img bs=1024k count=6000
# dd if=/dev/zero of=numberone_hda2.img bs=1024k count=1500
</pre>
<p>This will set up a &#8220;local/numberone&#8221; folder on the Oracle Clustered File System partition, &#8220;/OVS&#8221; with first file being 6gb and 2nd file being 1.5gb.  These will then become root partition and swap space of the guest VM, which we&#8217;re calling &#8220;numberone&#8221; here (you can name it any valid hostname you desire). Once these files are established, we create the filesystems for them with:</p>
<pre  name="code" class="ruby:nocontrols">
# mkfs.ext3 numberone_hda1.img
# mkswap numberone_hda2.img
</pre>
<h3>Step 3 - Establish the Root partition of the Guest</h3>
<p>Its time to push some files onto the root partition so that we have a valid system that can boot.  Begin by issuing the following statements:</p>
<pre  name="code" class="ruby:nocontrols">
# cd /OVS/local/numberone
# mkdir hda1
# mount -o loop numberone_hda1.img hda1
# rsync  -avH /boot hda1
# rsync  -avH /root hda1
# rsync  -avH /dev hda1
# rsync  -avH /var hda1
# rsync  -avH /etc hda1
# rsync  -avH /usr hda1
# rsync  -avH /bin hda1
# rsync  -avH /sbin hda1
# rsync  -avH /lib hda1
# rsync -avH /selinux hda1
# mkdir hda1/{proc,sys,home,tmp}
# chmod 777 hda1/tmp
# unmount hda1
# rmdir -rf hda1
</pre>
<h3>Step 4 - Configure the Guest</h3>
<p>You really don&#8217;t want the guest OS coming up with the same configuration as your Xen Host, so lets get into the guest&#8217;s /etc folder and do some tweaking:</p>
<p>Edit /OVS/local/numberone/hda1/etc/fstab and set the root and swap partitions</p>
<pre  name="code" class="ruby:nocontrols">
/dev/hda1               /                       ext3    defaults        1 1
/dev/hda2               swap                    swap    defaults        0 0
devpts                  /dev/pts                devpts  gid=5,mode=620  0 0
tmpfs                   /dev/shm                tmpfs   defaults        0 0
proc                    /proc                   proc    defaults        0 0
sysfs                   /sys                    sysfs   defaults        0 0
</pre>
<p>Edit /OVS/local/numberone/hda1/etc/sysconfig/network and change the hostname</p>
<pre  name="code" class="ruby:nocontrols">
NETWORKING=yes
NETWORKING_IPV6=no
HOSTNAME=numberone
GATEWAY=192.168.0.5
</pre>
<p>Note:  Gateway is the firewall on your network providing Internet connectivity.  Mine happens to be 192.168.0.5.</p>
<p>Edit /OVS/local/numberone/hda1/etc/hosts and change the hostname</p>
<pre  name="code" class="ruby:nocontrols">
127.0.0.1               numberone localhost.localdomain localhost
::1             localhost6.localdomain6 localhost6
</pre>
<p>Rename /OVS/local/numberone/hda1/etc/sysconfig/network-scripts/ifcfg-eth0 to /OVS/local/numberone/hda1/etc/sysconfig/network-scripts/ifcfg-eth0.bak so its not used when the guest first boots up.</p>
<p>Similarly, rename /OVS/local/numberone/hda1/lib/tls to /OVS/local/numberone/hda1/lib/tls.disabled</p>
<p>Then unmount the guest&#8217;s root partition:</p>
<pre  name="code" class="ruby:nocontrols">
# cd /OVS/local/numberone
# umount hda1
</pre>
<p>Create a domain configuration file for your new guest in /etc/xen/numberone on the Xen Server (not the guest&#8217;s root partition):</p>
<pre  name="code" class="ruby:nocontrols">
#  -*- mode: python; -*-
#============================================================================
# Python configuration setup for 'xm create'.
# This script sets the parameters used when a domain is created using 'xm create'.
# You use a separate script for each domain you want to create, or
# you can set the parameters for the domain on the xm command line.
#============================================================================

#----------------------------------------------------------------------------
kernel = "/boot/vmlinuz-2.6.18-8.1.15.1.16.el5xen"
ramdisk = "/boot/initrd-2.6.18-8.1.15.1.16.el5xen.img"
memory = 512
name = "numberone"

vif = [ 'bridge=xenbr0' ]

disk = ['file:/OVS/local/gox/gox_hda1.img,hda1,w',
        'file:/OVS/local/gox/gox_hda2.img,hda2,w' ]

root = &#8220;/dev/hda1 ro&#8221;

# Sets runlevel 4.
extra = &#8220;4&#8243;
</pre>
<p>The mac address was left out of the vif definition.  We&#8217;ll boot up the guest to console and see what &#8220;reasonable value&#8221; the Xen host establishes for the server then come back and hard code the value so its not changing all the time.  </p>
<h3>Step 5 - Booting for the first time</h3>
<p>To boot the guest, we&#8217;ll use the command-line tools, specifically &#8220;xm&#8221; to create (boot) the guest and shutdown rather than the Oracle VM Manager.  So lets have a look:</p>
<pre  name="code" class="ruby:nocontrols">
# xm create -c numberone
</pre>
<p>The &#8220;-c&#8221; flag boots you with console view of the guest.  If all went well, you&#8217;ll see a typical Linux boot up process that leads all the way to a login prompt similar to this:</p>
<pre  name="code" class="ruby:nocontrols">
Oracle VM server release 2.1.2
Hypervisor running in UNDETERMINED bit mode (WARNING: XEND IS PROBABLY NOT RUNNING).

Network :
Management Interface  :
If : eth1(Down)  Mac :  IP address : 

Configured Networks and Bridges :
If : eth0	 Mac : 00:16:3E:22:F3:4F

CPU :
cpu family	: 15
model		: 31
model name	: AMD Athlon(tm) 64 Processor 3200+

numberone login:
</pre>
<p>Copy that Mac address to clipboard (or write it down) because you&#8217;re going to need it for the next steps.  Log in as root, same password as the Xen Host (since we copied everything off the host to begin with!).</p>
<p>Edit /etc/sysconfig/network-scripts/ifcfg-eth0:</p>
<pre  name="code" class="ruby:nocontrols">
DEVICE=eth0
BOOTPROTO=static
HWADDR=00:16:3E:22:F3:4F
ONBOOT=yes
IPADDR=192.168.0.150
NETMASK=255.255.255.0
</pre>
<p>Once you&#8217;ve done this, you should be able to start your network and ping your gateway:</p>
<pre  name="code" class="ruby:nocontrols">
# ifup eth0
# ping 192.168.0.5
PING 192.168.0.5 (192.168.0.5) 56(84) bytes of data.
64 bytes from 192.168.0.5: icmp_seq=1 ttl=64 time=3.33 ms
64 bytes from 192.168.0.5: icmp_seq=2 ttl=64 time=1.04 ms
</pre>
<p>If not, check for typos.  Otherwise, you&#8217;re done with configuring the guest.  Continue by logging out of your guest then break from console with <strong>CTRL-]</strong> (control-key plus left-bracket key).  Edit /etc/xen/numberone and change the vif line to read:</p>
<pre  name="code" class="ruby:nocontrols">
vif = [ 'mac=00:16:3E:22:F3:4F, bridge=xenbr0' ]
</pre>
<p>This gels your mac address for the guest VM so the network will be reliably started on reboots for eth0 device.  Now issue the following:</p>
<pre  name="code" class="ruby:nocontrols">
# xm shutdown numberone
# xm top
</pre>
<p>And watch your guest disappear from the listing.  Once its gone, issue the following to bring it back up:</p>
<pre  name="code" class="ruby:nocontrols">
# xm create numberone
</pre>
<p>Finally, if everything&#8217;s gone well, you will be able to ssh to the guest VM with:</p>
<pre  name="code" class="ruby:nocontrols">
# ssh 192.168.0.150 -l root
</pre>
<p>(Hey, I didn&#8217;t say it was uber secure or that we&#8217;re following best practices regarding root accounts!)</p>
<p>If you can&#8217;t ssh into the guest from the xen server, you&#8217;ll have to get back into console and review logs and figure out what went wrong.  You may need to fix typos in your configs or try another xen bridge such as xenbr1.  If you are able to issue &#8220;ifup eth0&#8243; within the guest and can ping your firewall, its not network connectivity, but a typo in your /etc/sysconfig/* files somewhere.  Probably the best place to double-check things is the mac address.</p>
<pre  name="code" class="ruby:nocontrols">
# xm console numberone
</pre>
<p>One last tip.  If you want to install packages that didn&#8217;t come on the Oracle VM install CD, you can add the CentOS5 repository to the VM guest and install various packages using yum thereafter.  To do this, log into your Guest VM, and edit the following new file: /etc/yum.repos.d</p>
<pre  name="code" class="ruby:nocontrols">
[CentOS5 base]
name=CentOS-5-Base
mirrorlist=http://mirrorlist.centos.org/?release=5&#038;arch=$basearch&#038;repo=os
#baseurl=http://mirror.centos.org/centos/$releasever/os/$basearch/
gpgcheck=0
enabled=1
gpgkey=http://mirror.centos.org/centos/RPM-GPG-KEY-CentOS-5

[CentOS5 updates]
name=CentOS-5-Updates
mirrorlist=http://mirrorlist.centos.org/?release=5&#038;arch=$basearch&#038;repo=updates
gpgcheck=0
enabled=1
gpgkey=http://mirror.centos.org/centos/RPM-GPG-KEY-CentOS-5

[CentOS5plus]
name=CentOS-5-Plus
mirrorlist=http://mirrorlist.centos.org/?release=5&#038;arch=$basearch&#038;repo=centosplus
gpgcheck=0
enabled=1
gpgkey=http://mirror.centos.org/centos/RPM-GPG-KEY-CentOS-5
</pre>
<p>So there you have it.  One of the simplest approaches to getting your first Xen guest Virtual Machine up and running and well on your way to experimenting with virtual technologies.  </p>
<p><em><strong>Special Note:</strong>  Much credit must go to my colleague, Manyuan Jin, who spent many, many tireless hours researching early instability issues and treading through the jungle of open source solutions to assemble a working solution for our shop.  </em></p>
]]></content:encoded>
			<wfw:commentRss>http://ramblings.gibberishcode.net/archives/jumpstarting-your-virtual-tour-with-oracle-vm/19/feed</wfw:commentRss>
		</item>
		<item>
		<title>Rails has and belongs to many (habtm) demystified</title>
		<link>http://ramblings.gibberishcode.net/archives/rails-has-and-belongs-to-many-habtm-demystified/17</link>
		<comments>http://ramblings.gibberishcode.net/archives/rails-has-and-belongs-to-many-habtm-demystified/17#comments</comments>
		<pubDate>Mon, 27 Oct 2008 14:49:02 +0000</pubDate>
		<dc:creator>Michael</dc:creator>
		
		<category><![CDATA[CSS]]></category>

		<category><![CDATA[Programming]]></category>

		<category><![CDATA[Ruby Language]]></category>

		<category><![CDATA[html]]></category>

		<category><![CDATA[many-to-many]]></category>

		<category><![CDATA[rails]]></category>

		<guid isPermaLink="false">http://ramblings.gibberishcode.net/?p=17</guid>
		<description><![CDATA[Every time I have to implement a many-to-many relationship between Rails models, I seem to have to figure out how to do it effectively all over again.  Especially as Rails seems to evolve the relational hooks with better support and elegance.  Here, I will show a has_and_belongs_to_many strategy that works well for me.  Along the way, I'll expose a few other minor tricks, such as adding a custom inflector for pluralizing your model or not adding the ID column on a table declaration.]]></description>
			<content:encoded><![CDATA[<p>Every time I have to implement a many-to-many relationship between Rails models, I seem to have to figure out how to do it effectively all over again.  Especially as Rails seems to evolve the relational hooks with better support and elegance.  Here, I will show a has_and_belongs_to_many strategy that works well for me.  Along the way, I&#8217;ll expose a few other minor tricks, such as adding a custom inflector for pluralizing your model or not adding the ID column on a table declaration.</p>
<p>The following Browser Edit form is what we&#8217;re going for.  That is, having a list of Operating Systems to check off while editing a Browser object:</p>
<p><center><br />
<img src="http://ramblings.gibberishcode.net/wp-content/uploads/2008/10/editing_browser.png" alt="Editing Browser View" title="editing_browser" width="266" height="453" class="alignleft size-full wp-image-18" /><br />
</center><br />
First, the models:  What I wanted, was a way to declare browsers (Firefox, Explorer, Opera, etc.) and associate them with one or more operating systems (OS X, Windows, Linux, etc.).  This begats two models, <strong>Os</strong>, and <strong>Browser</strong> with a many-to-many table, <strong>browsers_oses</strong> joining the two.  The migration scripts for these models follow:</p>
<pre  name="code" class="ruby:nocontrols">
class CreateBrowsers < ActiveRecord::Migration
  def self.up
    create_table :browsers do |t|
      t.string :name
      t.string :version
      t.string :short_name
      t.timestamps
    end
  end

  def self.down
    drop_table :browsers
  end
end
</pre>
<pre  name="code" class="ruby:nocontrols">
class CreateOses < ActiveRecord::Migration
  def self.up
    create_table :oses do |t|
      t.string :vendor
      t.string :name
      t.string :version
      t.string :short_name
      t.timestamps
    end
  end

  def self.down
    drop_table :oses
  end
end
</pre>
<p>The Browser and Os table are very straightforward migration scripts.  However, pay attention to the next script as we do a couple of interesting things.  A) we tell the migration not to create the default ID column and B) we use the t.references declaration in defining our columns.</p>
<pre  name="code" class="ruby:nocontrols">
class BrowsersOses < ActiveRecord::Migration
  def self.up
      create_table :browsers_oses, :id => false do |t|
        t.references :browser
        t.references :os
        t.timestamps
      end
    end

    def self.down
      drop_table :browsers_oses
  end
end
</pre>
<p>Note that I used the singular version of the model names.  I originally used the plural form and got <strong>browsers_id</strong> and <strong>oses_id</strong> instead of the expected <strong>browser_id</strong> and <strong>os_id</strong> column names.</p>
<p>Which brings me to another issue I encountered.  &#8220;Os&#8221; didn&#8217;t pluralize properly, so I needed to add a custom inflector to the project&#8217;s <strong>config/initializers/inflections.rb</strong> file:</p>
<pre  name="code" class="ruby:nocontrols">
ActiveSupport::Inflector.inflections do |inflect|
  inflect.irregular 'os', 'oses'
end
</pre>
<p>For simplicity and clarity, I chose the irregular form since I didn&#8217;t figure on needing to match regular expressions against more complex model names.</p>
<p>Since there are many articles dedicated to the model side of things where relationships are concerned, I will skimp on discussing model implementations.  Please see <a href="http://blog.hasmanythrough.com/2006/4/20/many-to-many-dance-off">many-to-many-dance-off</a> by Josh Susser or the <a href="http://wiki.rubyonrails.org/rails/pages/has_and_belongs_to_many">has_and_belongs_to_many</a> page on the Ruby on Rails wiki site for a couple of classics on the topic.</p>
<p>The models are straightforward, as shown below.  Note that the <strong>browsers_oses</strong> table does not get its own model.  Rails simply expects the table to be a concatenation of the two models pluralized and the model names in alphabetical order.</p>
<pre  name="code" class="ruby:nocontrols">
class Os < ActiveRecord::Base
  has_and_belongs_to_many :browsers
end

class Browser < ActiveRecord::Base
  has_and_belongs_to_many :oses
end
</pre>
<p>I&#8217;m not a big fan of the default views the rails scaffolding produces, but I do tend to run <em>script/generate scaffold &#8220;model_name&#8221;</em> to get the ball rolling quickly, anyway.  So fire up a couple of views with:</p>
<pre  name="code" class="ruby:nocontrols">
script/generate scaffold browser
script/generate scaffold os
</pre>
<p><strong>Note:</strong>  I removed all previously generated files and regenerated the <strong>Os</strong> model and views after adding the irregular plural inflection rule described above.</p>
<p>Once I get my models and views, the first thing I do is create a form partial for each:</p>
<p><strong>app/views/oses/_form.html.erb</strong></p>
<pre  name="code" class="html:nocontrols">
<fieldset>
	<legend>OS</legend>
	&lt;p>
		<label for="vendor">Vendor</label>
		<%= text_field :os, :vendor, :value => @os.vendor %>
	&lt;/p>

	&lt;p&gt;
		<label for="name">Name</label>
		<%= text_field :os, :name, :value => @os.name %>
	&lt;/p&gt;

	&lt;p&gt;
		<label for="version">Version</label>
		<%= text_field :os, :version, :value => @os.version %>
	&lt;/p&gt;

	&lt;p&gt;
		<label for="short_name">Short Name</label>
		<%= text_field :os, :short_name, :value => @os.short_name %>
	&lt;/p&gt;
</fieldset>
</pre>
<p>There&#8217;s nothing fancy about the <strong>Os</strong> form as there&#8217;s no references to select drop-downs or the likes.  With the above wrapped in a fieldset with legend of <strong>Os</strong>. I then  tweak <strong>app/views/oses/edit.html.erb</strong> like so:</p>
<pre  name="code" class="html:nocontrols">
<h1>Editing OS</h1>

<% form_for(@os) do |f| %>
  	<%= f.error_messages %>
	<%= render :partial => &#8216;form&#8217; %>

	<fieldset>
    	<%= f.submit "Update" %>
		<%= link_to 'Show', @os %> |
		<%= link_to 'Back', oses_path %>
  	</fieldset>
<% end %>
</pre>
<p>And similarly for <strong>app/views/oses/new.html.erb</strong>:</p>
<pre  name="code" class="html:nocontrols">
<h1>New OS</h1>

<% form_for(@os) do |f| %>
  	<%= f.error_messages %>
	<%= render :partial => &#8216;form&#8217; %>

  	<fieldset>
    	<%= f.submit "Create" %>
		<%= link_to 'Back', oses_path %>
  	</fieldset>
<% end %>
</pre>
<p><strong>app/views/oses/show.html.erb</strong> is nearly a cut-n-paste of the _form.html.erb file with the input tags stripped out:</p>
<pre  name="code" class="html:nocontrols">
<fieldset>
	<legend>OS</legend>
	&lt;p&gt;
		<label for="vendor">Vendor</label>
		<%= @os.vendor %>
	&lt;/p&gt;

	&lt;p&gt;
		<label for="name">Name</label>
		<%= @os.name %>
	&lt;/p&gt;

	&lt;p&gt;
		<label for="version">Version</label>
		<%= @os.version %>
	&lt;/p&gt;

	&lt;p&gt;
		<label for="short_name">Short Name</label>
		<%= @os.short_name %>
	&lt;/p&gt;
</fieldset>

<fieldset>
	<%= link_to 'Edit', edit_os_path(@os) %> |
	<%= link_to 'Back', oses_path %>
</fieldset>
</pre>
<p>It would be nice if the tag fields were action aware and intelligently generated inputs or text rendering according to whether new, edit, or show was the action, wouldn&#8217;t it??</p>
<p>The <strong>OsesController</strong> model is unchanged from the generated scaffolding code, so I won&#8217;t list its code here.  Lets hop over to Browsers model and view as this is the model I do the most interesting things with.  I will be rendering the Browser inputs so that one of the input is a checkbox list of all available OSes.  To do this, I am effectively performing a &#8220;find all&#8221; on the Os model, so I DRY my code by adding the following method to the <strong>BrowsersController</strong>:</p>
<pre  name="code" class="ruby:nocontrols">
  def get_all_oses
    @oses = Os.find(:all, :order => 'vendor, version')
  end
</pre>
<p>Declaring small, single purpose methods is a good practice to be in the habit of.  As it happened, I decided at some point to order the checklist by vendor and version.  Instead of having to change code in four or five methods, I only had to update the one <strong>get_all_oses</strong> method.  A tad bit of extra work up front pays off!</p>
<p>Similarly, compound names get their own method in the model class so I have ability to easily change without hunting down references everywhere in the view.  So the Os model presented above becomes:</p>
<pre  name="code" class="ruby:nocontrols">
class Os < ActiveRecord::Base
  has_and_belongs_to_many :browsers

  def display_name
    "#{vendor} #{name} #{version}"
  end
end
</pre>
<p>And then I reference like so in the Browser view&#8217;s form partial:</p>
<pre  name="code" class="html:nocontrols">
<fieldset>
	<legend>Browser</legend>
	&lt;p&gt;
		<label for="name">Name</label>
		<%= text_field :browser, :name, :value => @browser.name %>
	&lt;/p&gt;

	&lt;p&gt;
		<label for="version">Version</label>
		<%= text_field :browser, :version, :value => @browser.version %>
	&lt;/p&gt;

	&lt;p&gt;
		<label for="oses">Operating Systems</label>
		<% @oses.each do |os| %>
			<%= check_box_tag(
                            "browser[os_list][#{os.id}]",
                            "1",
                            @browser.oses.detect{|d| d == os}) %>
			<%= "#{os.display_name}"%>
		<% end %>
	&lt;/p&gt;

	&lt;p&gt;
		<label for="short_name">Short Name</label>
		<%= text_field :browser, :short_name, :value => @browser.short_name %>
	&lt;/p&gt;
</fieldset>
</pre>
<p>A few important things to note here about the Operating Systems checklist:</p>
<ul>
<li>the @oses variable is a list of all Oses as per the get_all_oses method.</li>
<li>I gave the checkbox name &#8220;browser[os_list][#{os.id}]&#8221; which causes the params to collect all checkboxes into an &#8220;os_list&#8221; hash that is contained in the browser keyword on the params hash.  Keep this in mind because we&#8217;re going to leverage this in a moment.</li>
<li>I don&#8217;t do anything with the values of the checkbox, so the value assigned (&#8221;1&#8243; in this case) is of little concern.</li>
<li>The <strong>@browsers.oses.detect{|d| d == os}</strong> line activates the checkbox if the browser already has the Os associated with it.  (quite important for edit actions).</li>
</ul>
<p>So the Operating Systems checklist is simply an iteration over all OSes, emitting a checkbox input control for each and setting the checked flag accordingly.  So how do we handle this in the controller?  We don&#8217;t!  That&#8217;s right.  Not in the controller, but in the model.  This is &#8220;The Rails Way&#8221; as described in the Skinny Controller, Fat Model approaches championed by <a href="http://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model">Jamis Buck</a> and <a href="http://www.therailsway.com/tags/model">The Rails Way</a> crew. My approach is to add a attribute accessor called &#8220;os_list&#8221; to the Browser model and a callback to the after_save event.  So the Browser model now becomes:</p>
<pre  name="code" class="ruby:nocontrols">
class Browser < ActiveRecord::Base
  has_and_belongs_to_many :oses

  attr_accessor :os_list
  after_save :update_oses

  private 

  def update_oses
    oses.delete_all
    selected_oses = os_list.nil? ? [] : os_list.keys.collect{|id| Os.find_by_id(id)}
    selected_oses.each {|os| self.oses << os}
  end
end
</pre>
<p>So there&#8217;s a bit of magic!  The update_attributes and save methods will assign the os_list hash from the params to our accessor &#8220;os_list&#8221; property automatically and thus provide us convenient access to the hash data in our callback handler.  This is Rails at its best.  <a href="http://paulbarry.com/articles/2007/10/24/has_many-through-checkboxes">Paul Barry</a> gets the credit for being the first to put it together (at least for me).  </p>
<p>What this method does is first clear out all the currently associated Oses.  Then, if there are no selected Oses (in which case, items will be []), nothing happens with the loop, otherwise, each item is pushed onto the browser.oses list.  When I first started writing Ruby on Rails apps, I was a stickler for not hitting the database unnecessarily and I would write lots of conditionals to avoid doing so.  </p>
<p>However, the number of bugs I found in such logic eventually led me down the path of aggressively reducing lines of code in situations where it simply does not matter.  Here, we&#8217;re talking about, at most, about 10 operating systems defined in the app I&#8217;m writing and I am very infrequently going to be adding new browsers to the list (about as often as a major version comes out as a rule of thumb).  Is it really worth the extra code and the extra specs (you are writing test cases, aren&#8217;t you?) to gain so little?  Rails is smart.  It&#8217;ll issue a delete statement only if there are actually Oses in the list and it&#8217;ll only issue one DELETE statement, too!  Check this session history out (for PostgreSQL):</p>
<pre  name="code" class="ruby:nocontrols">
  SQL (0.000153)   BEGIN
  SQL (0.000337)   DELETE FROM "browsers_oses" WHERE browser_id = 14 AND os_id IN (8,9)
  SQL (0.009701)   COMMIT
  SQL (0.000131)   BEGIN
  SQL (0.000194)   INSERT INTO "browsers_oses" ("browser_id", "updated_at", "os_id", "created_at") VALUES (14, '2008-10-24 19:33:43.107494', 8, '2008-10-24 19:33:43.107494')
  SQL (0.000649)   COMMIT
  SQL (0.000094)   BEGIN
  SQL (0.000191)   INSERT INTO "browsers_oses" ("browser_id", "updated_at", "os_id", "created_at") VALUES (14, '2008-10-24 19:34:01.088552', 9, '2008-10-24 19:34:01.088552')
  SQL (0.000570)   COMMIT
  SQL (0.000086)   BEGIN
  SQL (0.000199)   INSERT INTO "browsers_oses" ("browser_id", "updated_at", "os_id", "created_at") VALUES (14, '2008-10-23 20:24:59.809242', 2, '2008-10-23 20:24:59.809242')
  SQL (0.000657)   COMMIT
  SQL (0.000076)   BEGIN
  SQL (0.000110)   COMMIT
Redirected to http://localhost:3000/browsers/14
Completed in 0.08188 (12 reqs/sec) | DB: 0.01585 (19%) | 302 Found [http://localhost/browsers/14]
</pre>
<p>Now, if you&#8217;ve got a situation where 100&#8217;s of users are performing a similar action and the small doses truly add up to something significant in your daily logs, then definitely optimize.  Just be cognizant of your use-case and profile your application for performance before getting fancy with the conditional updates, otherwise you&#8217;re in the trap of premature optimization and not getting something else done you might have wish you were!</p>
<p>Ok, having digressed a bit, lets get back on course.  So what does the Browser Controller now look like?  Well, the only thing I have to add is a call to &#8220;get_all_oses&#8221; just in case of a failure to save and the redirect back to the same page occurs.  So this is what we have:</p>
<pre  name="code" class="ruby:nocontrols">
   def get_all_oses
     @oses = Os.find(:all, :order => 'vendor, version')
   end  

  # POST /browsers
  # POST /browsers.xml
  def create
    @browser = Browser.new(params[:browser])
    get_all_oses

    respond_to do |format|
      if @browser.save
        flash[:notice] = &#8216;Browser was successfully created.&#8217;
        format.html { redirect_to(@browser) }
        format.xml  { render :xml => @browser, :status => :created, :location => @browser }
      else
        format.html { render :action => &#8220;new&#8221; }
        format.xml  { render :xml => @browser.errors, :status => :unprocessable_entity }
      end
    end
  end

  # PUT /browsers/1
  # PUT /browsers/1.xml
  def update
    params[:oses] ||= {}
    @browser = Browser.find(params[:id])
    get_all_oses

    respond_to do |format|
      if @browser.update_attributes(params[:browser])
        flash[:notice] = &#8216;Browser was successfully updated.&#8217;
        format.html { redirect_to(@browser) }
        format.xml  { head :ok }
      else
        format.html { render :action => &#8220;edit&#8221; }
        format.xml  { render :xml => @browser.errors, :status => :unprocessable_entity }
      end
    end
  end
</pre>
<p>One last trick for the eagle eyes:  The form image I posted positions the labels in bold above the inputs and with a trailing colon, but nary a &lt;BR&gt; element (or colons) to be seen in the views.  I accomplished this with simple CSS styling by switching the label element to a block element (rather than inline as is the default) and then providing the colon as additional content:</p>
<pre  name="code" class="css:nocontrols">
label {
	display: block;
	font-weight: bold;
}
label:after {
	content: ':';
}
</pre>
<p>So there you have it.  Leveraging the Rails framework appropriately, you can easily provide a checklist generated from one model on another&#8217;s edit form and have those records maintained in a many-to-many relationship without bloating your views or controllers.  </p>
]]></content:encoded>
			<wfw:commentRss>http://ramblings.gibberishcode.net/archives/rails-has-and-belongs-to-many-habtm-demystified/17/feed</wfw:commentRss>
		</item>
		<item>
		<title>Converting Oddmuse Wiki to Edgewall Trac</title>
		<link>http://ramblings.gibberishcode.net/archives/converting-oddmuse-wiki-to-edgewall-trac/13</link>
		<comments>http://ramblings.gibberishcode.net/archives/converting-oddmuse-wiki-to-edgewall-trac/13#comments</comments>
		<pubDate>Thu, 02 Oct 2008 16:30:38 +0000</pubDate>
		<dc:creator>Michael</dc:creator>
		
		<category><![CDATA[Macs]]></category>

		<category><![CDATA[Programming]]></category>

		<category><![CDATA[Python Language]]></category>

		<category><![CDATA[Ruby Language]]></category>

		<category><![CDATA[SQL]]></category>

		<category><![CDATA[oddmuse]]></category>

		<category><![CDATA[python]]></category>

		<category><![CDATA[trac]]></category>

		<guid isPermaLink="false">http://ramblings.gibberishcode.net/?p=13</guid>
		<description><![CDATA[Our company began long ago with wiki&#8217;s, but we chose the Oddmuse wiki way back when.  These days, we&#8217;re heavy users of Trac wiki because of its integrated ticket support system.  So what to do with all those old wiki&#8217;s that folks have stopped using and reading.  The Oddmuse wikis still hold [...]]]></description>
			<content:encoded><![CDATA[<p>Our company began long ago with wiki&#8217;s, but we chose the Oddmuse wiki way back when.  These days, we&#8217;re heavy users of Trac wiki because of its integrated ticket support system.  So what to do with all those old wiki&#8217;s that folks have stopped using and reading.  The Oddmuse wikis still hold valuable data, but they have since become an administrative overhead to keep around, so I decided to convert them all to Edgewall&#8217;s Trac. </p>
<p>To get started, I needed to know how to get the data into Trac.  The following links gave me an API I could utilize to create trac pages and content:</p>
<p><a href="http://trac.edgewall.org/wiki/TracDev/DataModels">Trac Data Models</a><br />
<a href="http://trac.edgewall.org/browser/trunk/trac/wiki/model.py">The Data model Python code</a></p>
<p>Great!  Except there&#8217;s one small problem.  I don&#8217;t really know python all that well, even though I did do a bit of Zope development, oh, five years ago.  These days, my language of choice is Ruby, so I needed to figure out how to parse the Oddmuse syntax and turn it into Trac syntax.  The strategy:  Parse with Ruby, load cleaned page files with Python.  </p>
<p>This script will take a filename at command line and convert it and save back to file, but different extension (I chose *.om for &#8220;oddmuse&#8221;).  In the script below, I made the command-line optional as I discovered I didn&#8217;t handle every case as I worked on developing this script, so I simply changed the line that pulls the command-line argument to point to a specific file and output such to terminal and this allowed me to debug and fix as I went:</p>
<pre  name="code" class="ruby:nocontrols">
#!/opt/local/bin/ruby
# $FS  = "\xb3";      # The FS character is the RECORD SEPARATOR control char in ASCII
# $FS0 = "\xb3";      # The old FS character is a superscript "3" in Latin-1
# $FS1 = $FS . '1';   # The FS values are used to separate fields
# $FS2 = $FS . '2';   # in stored hashtables and other data structures.
# $FS3 = $FS . '3';   # The FS character is not allowed in user data.

FS = 179.chr

SITE_NAME = "corporate_wiki"
ATTACHMENT_URL_BASE = "https://intranet.example.com/#{SITE_NAME}/attachment/wiki/ConvertedAttachments/"

require 'find'
require 'ftools'

def safe_page_name(pagename)
  result = pagename.gsub("'","")
  result = result.slice(/(\w+)/).first
end

def get_section(page, section)
  page.each_with_index {|e,i| return page[i+1] if e == section}
  return &#8221;
end

def asterick_prefixed(text)
  t = text.match(/(\*+)([^\b]*)/)
  return text if t.nil?

  tokens = t.to_a
  return (&#8217;  &#8216; * tokens[1].size) + &#8216;* &#8216; + tokens[2]
end

def colon_prefixed(text)
  return &#8221;  #{colon_prefixed(text.slice(1,text.size))}&#8221; if text[0] == &#8220;:&#8221;[0]
  return text
end

def pound_prefixed(text)
  return &#8221;  1. #{text.slice(1,text.size)}&#8221; if text[0] == &#8220;#&#8221;[0]
  return text
end

def fix_headers(text)
  t = text.match(/(=+)([^=]+)(=+)/)
  return text if t.nil?

  tokens = t.to_a
  return &#8220;#{tokens[1]} #{tokens[2].strip} #{tokens[1]}&#8221;
end

def replace_a_url(url, text)
  text.gsub!(&#8221;http://#{url}&#8221;, ATTACHMENT_URL_BASE)
  text.gsub!(&#8221;https://#{url}&#8221;, ATTACHMENT_URL_BASE)
  text.gsub!(&#8221;http:/#{url}&#8221;, ATTACHMENT_URL_BASE)
  text.gsub!(&#8221;https:/#{url}&#8221;, ATTACHMENT_URL_BASE)
  return text
end

def replace_wiki_urls(text)
  matches = text.match(/\[http(.)*\]/)
  result = text
  if matches
    result = replace_a_url(&#8221;wiki.example.com/#{SITE_NAME}/wikifiles/&#8221;, result)
    result = replace_a_url(&#8221;pdfreviewfiles/&#8221;, result)
    result = replace_a_url(&#8221;cgi-bin/&#8221;, result)
  end
  result
end

def convert_formats(text)
  result = text.gsub(/\[\[(\w+)\]\]/,&#8221;[wiki:" + '\1' + "]&#8220;)
  result = asterick_prefixed(result)
  result = colon_prefixed(result)
  result = pound_prefixed(result)
  result = replace_wiki_urls(result)
  result = fix_headers(result)
  result
end

def oddmuse_to_trac(text)
  result = &#8221;
  in_pre = false
  text.split(&#8221;\n&#8221;).each do |line|

    # Detect indented lines as these are rendered as PRE html blocks
    trimmed = line.lstrip
    is_prefixed = ((trimmed.size < line.size) and (trimmed[0] != '*'[0]))
    result << "{{{\n" if (is_prefixed and !in_pre)

    result << "}}}\n" if (!is_prefixed and in_pre)
    in_pre = is_prefixed

    # Don't process special characters when in PRE block
    if in_pre
      result << line << "\n"
    else
      result << convert_formats(line) << "\n"
    end
  end
  result += "}}}\n" if in_pre
  result
end

path = ARGV[0] || "page/U/UsingThePhones.db"
raw_page = File.new(path).read
page2 = raw_page.split(FS + '2')
page3 = raw_page.split(FS + '3')

text = get_section(page3, 'text')

pagename = File.basename(path).split('.').first
author = get_section(page2, 'username')
address = get_section(page2, 'ip')
body = oddmuse_to_trac(text)

puts "writing: #{pagename}.om"
outfile = File.new(path.gsub('.db','.om'),'w')
outfile.puts 'pagename:' + pagename
outfile.puts 'author:' + author
outfile.puts 'address:' + address
outfile.puts body
outfile.close

puts body if ARGV[0].nil?
</pre>
<p>Below, comes the Python side of the equation.  Once I did the heavy lifting in Ruby, I needed to generate all the pages.  As you can see, there&#8217;s some parsing happening, but its very simple parsing and I was able to apply what simple Python knowledge I still retained pretty effectively:</p>
<pre  name="code" class="python:nocontrols">
import sys
from trac.env import Environment
from trac.wiki.model import WikiPage

print sys.argv[1]
file = open(sys.argv[1], &#8216;r&#8217;)
pagename = file.readline().split(&#8217;:')[1].split(&#8217;\n&#8217;)[0]
author_name = file.readline().split(&#8217;:')[1].split(&#8217;\n&#8217;)[0]
address = file.readline().split(&#8217;:')[1].split(&#8217;\n&#8217;)[0]

text = file.read()

env = Environment(&#8217;/srv/www/trac/corporate_wiki&#8217;)

# Read an existing or new WikiPage:
page = WikiPage(env, pagename)
if page.text != text:
        page.text = text
        page.save(author=author_name, comment=&#8217;imported from oddmuse wiki&#8217;, remote_addr=address)
</pre>
<p>As you can see, this file simply takes the command line argument, open the file for reading, pull the various variables out with the remainder of the file going to the page content.</p>
<p>During the conversion, I noticed some pages were failing to convert at all, like &#8220;Michael&#8217;sTodoList&#8221;  The culprit, the tick in the page name and thus the filename.  The scripts themselves weren&#8217;t the problem, it was the xargs parameter passing that was the issue (getting to that below).  So I ran the below script to rename all the troublesome filenames and then added similar tick to underscore substitution in the main Ruby script above.  </p>
<pre  name="code" class="ruby:nocontrols">
require 'find'
require 'ftools'

total_size = 0

Find.find('./page') do |path|
  if FileTest.directory?(path)
    if File.basename(path)[0] == ?.
      Find.prune       # Don&#8217;t look any further into this directory.
    else
      next
    end
  else
    if path.match(/[\']/)
      safe_path = path.gsub(&#8221;&#8216;&#8221;, &#8220;_&#8221;)
      puts path + &#8216; to &#8216; + safe_path
      File.move(path, safe_path)
    end
    end
  end
</pre>
<p>A simple up-front execution of the script was all that was needed to now be able to pick up the missing pages:</p>
<pre  name="code" class="ruby:nocontrols">
ruby fix_unsafe_files.rb
</pre>
<p>Finally, to bring it all together, I utilized find to locate all the *.db Oddmuse files which I had located into the various wiki subfolders where these scripts sat, passing the filename to the script via the xargs utility:</p>
<pre  name="code" class="ruby:nocontrols">
find . -name *.db -print0 | xargs -0 -L 1 ruby rdpage.rb
</pre>
<p>I then essentially repeated the above, but for the generated *.om files, passing to the Python mkpage.py script:</p>
<pre  name="code" class="ruby:nocontrols">
find . -name *.om -print0 | xargs -0 -L 1 python mkpage.py
</pre>
<p>One last thing, before we&#8217;re done.  Each wiki had attachments.  So I had to think how to get those attachments referenced and linked to the Trac wiki.  A little investigation revealed that each attachment got a database entry in the attachment table.  My task was a bit complicated by the fact that the users over the years in the Oddmuse environment scattered their attachments everywhere and also used varying syntax to refer to them, hence, if you review the main Ruby parsing script, you&#8217;ll see the &#8220;replace_a_url&#8221; method above that cleans all those funky URLs up.  So the script I used below simply scans through all the directories where we had files stored and attached them to one &#8220;ConvertedAttachments&#8221; page.  Probably not the ideal solution, but again, got the job done quickly.</p>
<pre  name="code" class="ruby:nocontrols">
#  type |          id          |  filename  |  size  |    time    | description | author |      ipnr
# ------+----------------------+------------+--------+------------+-------------+--------+----------------
#  wiki | ConvertedAttachments | gotapi.png | 200272 | 1218381947 |             | michael | 192.168.16.100

path = ARGV[0] || &#8216;generate_attachments.rb&#8217;
filename = File.basename(path)

puts &#8220;insert into attachment values (&#8217;wiki&#8217;, &#8216;ConvertedAttachments&#8217;, &#8216;#{filename}&#8217;, #{FileTest.size(path)}, null, null, &#8216;oddmuse&#8217;,'192.168.1.246&#8242;);&#8221;
</pre>
<p>So, running the above needs to be piped to a SQL fisle and then executed against your Trac database.  In my case, Postgresql.  Since I had many directories to snatch up, I ran it for each folder as appropriate and simply kept appending until I had all the attachment directories represented.</p>
<pre  name="code" class="ruby:nocontrols">
ls | xargs -0 -L 1 >> attachments.sql
psql trac_db < attachments.sql
</pre>
<p>All in all, not too bad of a conversion for a few hour&#8217;s work.  Its about 95% correct.  Some of the page names didn&#8217;t translate to Trac, and could probably do with some regexp search and replace on the page names themselves.  The biggest gain is that we have preserved the information in the old systems by porting it to the new systems we all know and use on a daily basis, so WikiGardening is more likely to keep the content fresher and its one less system for our end-users to figure out how to use.  </p>
<p>I can now retire the server running Oddmuse, stop doing backups and keeping up with documentation and training new system administrators on how to manage Oddmuse AND Trac.</p>
]]></content:encoded>
			<wfw:commentRss>http://ramblings.gibberishcode.net/archives/converting-oddmuse-wiki-to-edgewall-trac/13/feed</wfw:commentRss>
		</item>
		<item>
		<title>Changing ANSI colors in Terminal on Macs</title>
		<link>http://ramblings.gibberishcode.net/archives/changing-ansi-colors-in-terminal-on-macs/16</link>
		<comments>http://ramblings.gibberishcode.net/archives/changing-ansi-colors-in-terminal-on-macs/16#comments</comments>
		<pubDate>Tue, 09 Sep 2008 15:56:34 +0000</pubDate>
		<dc:creator>Michael</dc:creator>
		
		<category><![CDATA[General]]></category>

		<category><![CDATA[Servers]]></category>

		<category><![CDATA[Systems]]></category>

		<category><![CDATA[ansi colors]]></category>

		<category><![CDATA[Macs]]></category>

		<category><![CDATA[terminal colors]]></category>

		<category><![CDATA[vi syntax highlight]]></category>

		<guid isPermaLink="false">http://ramblings.gibberishcode.net/?p=16</guid>
		<description><![CDATA[Probably the most frustrating experience I have had with Macs so far is figuring out how to change the ANSI colors in Terminal (Terminal.app) so that I can read the outputs of man and most especially ls and comments in vi when colorization is activated and I have chosen a dark background theme.
I am using [...]]]></description>
			<content:encoded><![CDATA[<p>Probably the most frustrating experience I have had with Macs so far is figuring out how to change the ANSI colors in Terminal (Terminal.app) so that I can read the outputs of <b>man</b> and most especially <b>ls</b> and comments in <b>vi</b> when colorization is activated and I have chosen a dark background theme.</p>
<p>I am using Mac OS X Leopard 10.5.4 at the time of this writing.  These are the steps I took to fix that most hideous of all colors, dark blue on black background:</p>
<ol>
<li>Set your terminal theme colors to your liking</li>
<li>Set up <b>ls</b> to colorize directory listings by default.  There are two ways to do this:  Either set the CLICOLOR Environment variable or add an alias for ls to include the -G flag.  I chose the CLICOLOR flag:
<pre name="code" class="ruby:nocontrols">
export CLICOLOR=1
</pre>
</li>
<li>Next up, install the ANSI color SIMBL plugin found here: <a href="http://niw.at/articles/2007/11/02/TerminalColoreopard/en">http://niw.at: works of Yoshimasa Niwa</a>.  Make sure Terminal.app is closed while you install the plugin then relaunch Terminal to find under its main menu, &#8220;Color Preferences&#8230;&#8221;</li>
<li>Finally, edit that dark blue away!</li>
</ol>
<p>If you&#8217;re interested in tailoring the output of the colors even further, you can also export the LSCOLORS variable:</p>
<pre name="code" class="ruby:nocontrols">
export LSCOLORS="exfxcxdxbxegedabagacad"
</pre>
<p>And then change according to the manual entry for ls (&#8221;man ls&#8221;):</p>
<table>
<tr>
<th>a</th>
<td>black</td>
</tr>
<tr>
<th>b</th>
<td>red</td>
</tr>
<tr>
<th>c</th>
<td>green</td>
</tr>
<tr>
<th>d</th>
<td>brown</td>
</tr>
<tr>
<th>e</th>
<td>blue</td>
</tr>
<tr>
<th>f</th>
<td>magenta</td>
</tr>
<tr>
<th>g</th>
<td>cyan</td>
</tr>
<tr>
<th>h</th>
<td>light grey</td>
</tr>
<tr>
<th>A</th>
<td>bold black, usually shows up as dark grey</td>
</tr>
<tr>
<th>B</th>
<td>bold red</td>
</tr>
<tr>
<th>C</th>
<td>bold green</td>
</tr>
<tr>
<th>D</th>
<td>bold brown, usually shows up as yellow</td>
</tr>
<tr>
<th>E</th>
<td>bold blue</td>
</tr>
<tr>
<th>F</th>
<td>bold magenta</td>
</tr>
<tr>
<th>G</th>
<td>bold cyan</td>
</tr>
<tr>
<th>H</th>
<td>bold light grey; looks like bright white</td>
</tr>
<tr>
<th>x</th>
<td>default foreground or background</td>
</tr>
</table>
<p>One final trick to get my bash shell exactly like I like it was to set the prompt.  This can likewise be done by editing ~/.profile:</p>
<pre name="code" class="ruby:nocontrols">
PS1="[\u@\h \W] &#8221;
</pre>
]]></content:encoded>
			<wfw:commentRss>http://ramblings.gibberishcode.net/archives/changing-ansi-colors-in-terminal-on-macs/16/feed</wfw:commentRss>
		</item>
		<item>
		<title>Configuring ntpd to hand out time to local servers</title>
		<link>http://ramblings.gibberishcode.net/archives/configuring-ntpd-to-hand-out-time-to-local-servers/14</link>
		<comments>http://ramblings.gibberishcode.net/archives/configuring-ntpd-to-hand-out-time-to-local-servers/14#comments</comments>
		<pubDate>Mon, 11 Aug 2008 16:31:51 +0000</pubDate>
		<dc:creator>Michael</dc:creator>
		
		<category><![CDATA[General]]></category>

		<category><![CDATA[Servers]]></category>

		<category><![CDATA[Systems]]></category>

		<category><![CDATA[ntp client]]></category>

		<category><![CDATA[ntp server]]></category>

		<category><![CDATA[yum]]></category>

		<guid isPermaLink="false">http://ramblings.gibberishcode.net/?p=14</guid>
		<description><![CDATA[Syncing your servers to public time servers is one of the most common ways of keeping server times in sync.  Apparently the less common approach of configuring one server to sync with public servers and it in turn becomes the authoritative time keeper for the rest of your servers is less well-known.  Digging [...]]]></description>
			<content:encoded><![CDATA[<p>Syncing your servers to public time servers is one of the most common ways of keeping server times in sync.  Apparently the less common approach of configuring one server to sync with public servers and it in turn becomes the authoritative time keeper for the rest of your servers is less well-known.  Digging through the copious ntp documentation, I have to say, its some very, very dense documentation mainly because the jargon isn&#8217;t well translated to a layperson&#8217;s &#8220;How do I?&#8221; inquiry.</p>
<p>I combed the documentation for days trying to figure out exactly how to set up a server to hand out time to all my other servers.  Normally, my Google-fu can pierce just about any need, but &#8220;time&#8221; &#8220;server&#8221; &#8220;local&#8221; and so on are all too common terms in both client-and-server and client-only configurations and by far and away, the bulk of the documentation guides you through the latter.  The documentation that guides you through the former is dense with references to peers, stratum, broadcasting, multi-casting, and so on.</p>
<p>So, here&#8217;s how to set up an NTP service as both client and local time server on CentOS 4:</p>
<pre  name="code" class="ruby:nocontrols">
yum install ntp
</pre>
<p>Then edit the /etc/ntp.conf file and set up the public ntp server pools and uncomment the broadcast line and change IP mask to match your network&#8217;s:</p>
<pre  name="code" class="ruby:nocontrols">
server 0.rhel.pool.ntp.org
server 1.rhel.pool.ntp.org
server 2.rhel.pool.ntp.org
broadcast 192.168.1.255
</pre>
<p>Modify your /etc/sysconfig/iptables to allow other servers to connect:</p>
<pre  name="code" class="ruby:nocontrols">
-A RH-Firewall-1-INPUT -p udp -m udp --dport 123 -j ACCEPT
</pre>
<p>Finally, go to the &#8220;client server&#8221; and install ntp on it just as you did with the ntp server, except, this time, you edit your /etc/ntp.conf file to point to your new local time server.  In my case, I set up two servers to give out times and I added ntp.1 and ntp.2 to my local DNS servers, so my config file for the &#8220;client servers&#8221; uses:</p>
<pre  name="code" class="ruby:nocontrols">
server ntp.1.example.com
server ntp.2.example.com
</pre>
<p>Its really that simple!  Its probably not <strong>the most secure</strong> way to do the job as there&#8217;s plenty of information overload in the ntp documentation on configuring securely.  However, with all of my servers sitting in a trusted VLAN and the DNS entries are only served to these servers and NTP port is blocked between this VLAN and others, I reckon it would be fairly hard to hijack the local ntp services.  In other words, the simple approach is good enough, for now.</p>
]]></content:encoded>
			<wfw:commentRss>http://ramblings.gibberishcode.net/archives/configuring-ntpd-to-hand-out-time-to-local-servers/14/feed</wfw:commentRss>
		</item>
		<item>
		<title>Pradipta’s Rolodex</title>
		<link>http://ramblings.gibberishcode.net/archives/pradiptas-rolodex/12</link>
		<comments>http://ramblings.gibberishcode.net/archives/pradiptas-rolodex/12#comments</comments>
		<pubDate>Fri, 18 Jul 2008 14:05:20 +0000</pubDate>
		<dc:creator>Michael</dc:creator>
		
		<category><![CDATA[General]]></category>

		<guid isPermaLink="false">http://ramblings.gibberishcode.net/?p=12</guid>
		<description><![CDATA[Something funny happened last night.  A one, Max Archie from Indiana hired an India-based Call Center to find a couple of Ruby developers.  Well, by and by, the recruiter mass-mails some 400 developers asking if they&#8217;re interested.  Within about 10 mins, I had to 10 emails of somewhat ticked off developers begging [...]]]></description>
			<content:encoded><![CDATA[<p>Something funny happened last night.  A one, Max Archie from Indiana hired an India-based Call Center to find a couple of Ruby developers.  Well, by and by, the recruiter mass-mails some 400 developers asking if they&#8217;re interested.  Within about 10 mins, I had to 10 emails of somewhat ticked off developers begging for people to stop replying!  Unfortunately, it was getting on late and stressed out developers up after midnight tend to get a little cantankerous and start poking real fun at the world in ways only we can do.</p>
<p>Today, we have <a href="http://groups.google.com/group/pradiptas-rolodex">Pradipta&#8217;s Rolodex</a>!<br />
<a href="http://digg.com/programming/recruiter_stupidly_CC_s_400_coders_and_one_replies_all">digg link</a> <a href="http://www.reddit.com/info/6scl4/comments/">Reddit</a>.</p>
<p>To all developers out there unfortunate enough to get caught in this web, thanks, for you have made me laugh out loud harder than I have in a good while!</p>
<p>Cheerios!</p>
]]></content:encoded>
			<wfw:commentRss>http://ramblings.gibberishcode.net/archives/pradiptas-rolodex/12/feed</wfw:commentRss>
		</item>
		<item>
		<title>Geocoded Zipcodes</title>
		<link>http://ramblings.gibberishcode.net/archives/geocoded-zipcodes/11</link>
		<comments>http://ramblings.gibberishcode.net/archives/geocoded-zipcodes/11#comments</comments>
		<pubDate>Mon, 07 Jul 2008 23:20:26 +0000</pubDate>
		<dc:creator>Michael</dc:creator>
		
		<category><![CDATA[Ruby Language]]></category>

		<category><![CDATA[SQL]]></category>

		<category><![CDATA[loading data]]></category>

		<category><![CDATA[mysql]]></category>

		<category><![CDATA[zip codes]]></category>

		<guid isPermaLink="false">http://ramblings.gibberishcode.net/?p=11</guid>
		<description><![CDATA[You would think that loading up a database of zip codes would be an extremely simple case of finding a public database on www.usps.gov, and then loading up with a database bulk load.  It turns out that, while the USPS does offer products for you to purchase, and a rather nice lookup interface for [...]]]></description>
			<content:encoded><![CDATA[<p>You would think that loading up a database of zip codes would be an extremely simple case of finding a public database on www.usps.gov, and then loading up with a database bulk load.  It turns out that, while the USPS does offer products for you to purchase, and a rather nice lookup interface for looking up zip codes and so on, there&#8217;s not really any free data to be had that I could tell.  So the hunt was on to find some zip code data, preferably geocoded (latitude/longitude for GIS) zip codes and then load them into MySQL.</p>
<p>A bit of hunting around turned up <a href="">this list of zip code databases</a>.  The first link to CivicSpace database appears to be defunct (I got a Go Daddy &#8220;this page is parked&#8221; page).  So, the next free one, offered by <a href="http://www.populardata.com/index.html">Popular Data</a> turned out to be exactly what I was looking for.  It may be a &#8220;little old&#8221; as the review site says, but its good enough for me to get started with associating my data with geocoded locations and explore the wonderful world of mapping offered by Google&#8217;s Maps API.  </p>
<p>To load it up in MySQL and PostgreSQL, I used the following Ruby script to generate a bunch of insert statements:</p>
<pre  name="code" class="ruby:nocontrols">
require 'rubygems'
require 'fastercsv'
source_file = 'ZIP_CODES.txt'
outfile = File.new('us_zip_codes.sql','w')

# generate the truncate statement if we want to empty the table
outfile.puts "truncate us_zip_codes;"

# generate the SQL Insert statements
FasterCSV.foreach(source_file, :headers => false) do |cols|
  row = "INSERT INTO us_zip_codes VALUES ("
  row << as_string(cols[0]) << ","
  row << as_number(cols[1]) << ","
  row << as_number(cols[2]) << ","
  row << as_string(cols[3]) << ","
  row << as_string(cols[4]) << ","
  row << as_string(cols[5]) << ","
  row << as_string(cols[6]) << ");"
  outfile.puts row
end
outfile.close
puts "copied #{i} records."
</pre>
<p>Create the zip code table with the following SQL:</p>
<pre  name="code" class="sql:nocontrols">
create table us_zip_codes (
  zip_code char(5),
  latitude float(10,6),
  longitude float(10,6),
  city varchar(255),
  state char(2),
  county varchar(255),
  zip_class varchar(255)
);
</pre>
<p>And finally, with the table in place and the SQL insert statements generated, I loaded the data into MySQL with the following from command line:</p>
<pre  name="code" class="ruby:nocontrols">
mysql target_db_name < us_zip_codes.sql
</pre>
]]></content:encoded>
			<wfw:commentRss>http://ramblings.gibberishcode.net/archives/geocoded-zipcodes/11/feed</wfw:commentRss>
		</item>
		<item>
		<title>DRYing your Views</title>
		<link>http://ramblings.gibberishcode.net/archives/drying-your-views/8</link>
		<comments>http://ramblings.gibberishcode.net/archives/drying-your-views/8#comments</comments>
		<pubDate>Sat, 05 Jul 2008 20:48:27 +0000</pubDate>
		<dc:creator>Michael</dc:creator>
		
		<category><![CDATA[General]]></category>

		<category><![CDATA[Ruby Language]]></category>

		<category><![CDATA[SQL]]></category>

		<category><![CDATA[dry]]></category>

		<category><![CDATA[rails]]></category>

		<category><![CDATA[ruby block]]></category>

		<category><![CDATA[views]]></category>

		<guid isPermaLink="false">http://ramblings.gibberishcode.net/?p=8</guid>
		<description><![CDATA[Let me start out by saying that I am finally beginning to understand a bit about that magical Ruby block notion and how implementing methods through block passing can really empower you as a Ruby developer.  Thanks to, a most excellent Ruby tutorial, I am definitely feeling a good bit more empowered about getting [...]]]></description>
			<content:encoded><![CDATA[<p>Let me start out by saying that I am finally beginning to understand a bit about that magical Ruby block notion and how implementing methods through block passing can really empower you as a Ruby developer.  Thanks to, a most excellent <a href="http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_containers.html">Ruby tutorial</a>, I am definitely feeling a good bit more empowered about getting my Views in Rails all DRY&#8217;d up.</p>
<p>The problem:  I wanted to introduce a property editor metaphor for the website I&#8217;m working on where properties could expose edit forms for just about any situation, much like wordpress&#8217; widget interface does.  </p>
<p>That is, there&#8217;s an area of the current page that shows basic information about the property in a read-only (and preferably compact) form, and user could click an &#8220;edit&#8221; button and exposed an editable version of the properties.  The &#8220;edit&#8221; link becomes a &#8220;cancel&#8221; link, which, when clicked, simply discards the edit form and return the initial compact view state.</p>
<p><a href='http://ramblings.gibberishcode.net/wp-content/uploads/2008/07/click_edit_to_show.png'><img src="http://ramblings.gibberishcode.net/wp-content/uploads/2008/07/click_edit_to_show.png" alt="Clicking EDIT to extend the display for editing" title="click_edit_to_show" width="280" height="206" class="alignleft size-full wp-image-10" /></a></p>
<p>The problem is, Rails doesn&#8217;t really have any good helpers for reusing a design concept in your views.  I found that even with partials, the code was really starting to bog down and become both unmaintainable and unreadable.  I needed a simple mechanism to construct my property editors.  I started out with helper methods.  But that wasn&#8217;t much better as the code just started ending as ruby here-docs and became a maintenance issue of another sort!  Take a look at this embarrassment:</p>
<pre name="code" class="ruby:nocontrols">
result = "&lt;table&gt;&lt;tr&gt;"
result += "&lt;th colspan=2&gt;"
result += button_image_action('Group selected together', 'link.png')
result += button_image_action('Copy selected to this group', 'link_add.png')
result += button_image_action('Move selected to this group', 'link_go.png')
result += button_image_action('copy group', 'page_copy.png')
result += button_image_action('cut group', 'cut.png')
result += button_image_action('paste group', 'page_paste.png')
result += button_image_action('delete group', 'delete.png')
result += "&lt;/th&gt;&lt;/tr&gt;"
result += "&lt;/table&gt;"
</pre>
<p>Ok, I admit it, I chopped out more than half again as many lines as you&#8217;re looking at there, and perhaps worse, this isn&#8217;t actually the original HTML code that led me down this path, but lets take a look at this approach anyway, as I learned something valuable in the process and it <em>was</em> my first attempt at DRYing my views. First thing, first, get the above HTML back into the erb file:</p>
<pre name="code" class="ruby:nocontrols">
<table>
<th colspan=2>
			<%= button_image_action('Group selected together', 'link.png') %>
			<%= button_image_action('Copy selected to this group', 'link_add.png') %>
			<%= button_image_action('Move selected to this group', 'link_go.png') %>
			<%= button_image_action('copy group', 'page_copy.png') %>
			<%= button_image_action('cut group', 'cut.png') %>
			<%= button_image_action('paste group', 'page_paste.png') %>
			<%= button_image_action('delete group', 'delete.png') %>
		</th>
</tr>
</table>
</pre>
<p>Which promptly let me to trying something like this:</p>
<pre name="code" class="ruby:nocontrols">
def button_image_action(title, image_filename, button_text = '', url = '')
    form_for @cube url do |f|
  	  <<-TEXT
  	    <BUTTON alt='#{title}' title='#{title}' type='submit'>#{button_text}
  	    <IMG alt='#{title}' title='#{title}' src='/images/#{image_filename}'>
  	    </BUTTON>
  	  TEXT
     end
  end
</pre>
<p>I thought it was shaping up nicely, but I got completely blindsided when it came to rendering this bit.  You see, the Rails Form Helpers are unavailable in a regular helper!  They require ERB (being inside <% %> and <%= %>) to actually properly render their blocks.</p>
<p>I fiddled around for a bit to try to jump this hurdle, but it just simply ain&#8217;t gonna work without some serious hacking. I&#8217;m happy to report that I got a little wiser and stepped back from the problem and reevaluated my approach in general.  I realized, I wasn&#8217;t getting away from doing HTML code in helpers!  Bottom line:  Helpers are great, but you&#8217;re doing something clearly against &#8220;The Rails Way&#8221; when you start rendering large chunks of HTML code from helpers.  It just isn&#8217;t a great approach and if you have designers on your team, they can get quite lost hunting around for all those HTML snippets. </p>
<p>So, how could I get <em>all</em> of my HTML back into *.erb files where they belonged, yet develop some helpers that would make mincemeat of the coding effort to build these fancy toolbars and forms I had coming?  Fellow colleague, Steven Beales said, &#8220;you need to use Ruby blocks.&#8221;  To which, my first reaction was, &#8220;what are Ruby blocks?&#8221;  To my great benefit, he explained by pointing me straight at <a href="http://www.igvita.com/2007/03/15/block-helpers-and-dry-views-in-rails/ ">Block Helpers and DRY Views in Rails</a>.</p>
<p>Truth be told, I mostly understood this article, but not entirely.  Even so, I dutifully set off to solve my problem with this approach.  This led me to a bit of copying and pasting (and crediting)&#8230;</p>
<pre name="code" class="ruby:nocontrols">
  # From:  http://errtheblog.com/posts/11-block-to-partial
  #
  # captures the output of our passed block using Rails’ capture method and adds
  # it to our options hash under the key of :body. Next it renders our passed
  # partial, sending in our options hash as :locals
  def block_to_partial(partial_name, options = {}, &#038;block)
    options.merge!(:body => capture(&#038;block))
    concat(render(:partial => partial_name, :locals => options), block.binding)
  end
</pre>
<p>Look at that carefully and, if I may be so bold to suggest, go read up on <a href="http://errtheblog.com/posts/11-block-to-partial">ERR the Blog</a> to better understand as this method is key and I daresay was appropriately called &#8220;tomfoolery&#8221; by the wise authors therein.  This bit of code really forced me to finally begin to learn and understand what the heck block passing was all about.  In a nutshell, this little gem lets you pass options that become local variables to the partial and the block is the contents to be rendered within the partial when you <em>yield</em> to the passed block.</p>
<div class="aside">
<h2> An Aside </h2>
<p>There were a couple instances where I was building a repeated elements with pretty much the same resulting HTML (namely toolbar buttons where only the image, caption, alt, title, and onclick events were changing).  Taking inspiration from <a href="http://www.igvita.com/2007/03/15/block-helpers-and-dry-views-in-rails/">Ilya Grigorik&#8217;s rounded box implementation</a>, I felt he had the approach I wanted, but I didn&#8217;t always need to pass in blocks for what I was do in every situation.  Unfortunately, I didn&#8217;t understand blocks!  I had to go back to school and understand them in order to adapt his approach to my situation.  After schooling myself a bit, I came up with this modified version of the block_to_partials to pass those attributes in (no block necessary!).  I named the simplified version &#8220;variables_to_partial&#8221; as follows:</p>
<pre name="code" class="ruby:nocontrols">
  # Calls given partial, injecting the options hash into the partial's local hash
  def variables_to_partial(partial_name, options = {})
    render(:partial => partial_name, :locals => options)
  end
</pre>
</div>
<p>The block_to_partial is a great start, but I wanted to simplify my calls and also enforce a bit of use-pattern on the developer (i.e. me) so that all property editors had essentially the same behavior pattern (and components).  Thus, we introduce the property editor container which in turn can have one or more sections associated with it:</p>
<pre name="code" class="ruby:nocontrols">
  # Generates a division around the given block that is styled with "property-editor"
  # Property editors are intended as a view on data that has both a condensed display and
  # an expanded display that lets you edit the information contained in the property editor.
  def property_editor(id, title, options = {}, &#038;block)
    @id = id
    block_to_partial 'layouts/property_editor', options.merge(:id => id, :title => title), &#038;block
  end
</pre>
<pre name="code" class="ruby:nocontrols">
  # Renders content of a section (provided by block) inside a property editor division
  # The stylesheet contains styling for the following section_id's:
  #   => "show" - the section initially visible
  #   => "edit" - the section made visible when the edit button is clicked
  def property_editor_section(section_id, options = {}, &#038;block)
    block_to_partial('layouts/property_editor_section',
      options.merge(:section_class => "property-editor-#{section_id}",
        :section_id => "#{@id}-#{section_id}"),
        &#038;block)
  end
</pre>
<p>The property editor partial:</p>
<pre name="code" class="ruby:nocontrols">
&lt;div &lt;%= %[id="#{id}"] %&gt; class=&#8221;property-editor&#8221;&gt;
	&lt;div class=&#8221;property-editor-title&#8221;&gt;
		&lt;div style=&#8221;float:right&#8221;&gt;
			&lt;a href=&#8221;javascript: void(0)&#8221; onclick=&#8221;toggle_div(&#8217;&lt;%= id %&gt;-show&#8217;);toggle_div(&#8217;&lt;%= id %&gt;-edit&#8217;);&#8221;&gt;edit&lt;/a&gt;
		&lt;/div&gt;
  		&lt;%= title %&gt;
	&lt;/div&gt;
	&lt;%= body %&gt;
&lt;/div&gt;
</pre>
<p>and the property editor sections are rendered with this partial:</p>
<pre name="code" class="ruby:nocontrols">
&lt;div &lt;%= %[id="#{section_id}"] %&gt; &lt;%= %[class="#{section_class}"] %&gt; &lt;%= %[style="display:none"] if section_id =~ /(.+)?\-edit/  %&gt;&gt;
	&lt;%= body %&gt;
&lt;/div&gt;
</pre>
<p>With these helpers and partials, property editors as a concept is very easy to pull together in my view files.  As you can see, if my section is an &#8220;edit&#8221; section, its hidden at the outset.  The button to toggle between show and edit is in the property editor partial itself in which an onclick javascript calls toggle_div to make this happen.  The Javascript is very straightforward:</p>
<pre name="code" class="javascript:nocontrols">
function toggle_div(div_name) {
	var div = $(div_name);
	div.getStyle('display') == 'none' ? div.setStyle('display', 'block') :	div.setStyle('display', 'none');
}
</pre>
<p>Here&#8217;s just one such property editor operating on my &#8220;cube model&#8221;:</p>
<pre name="code" class="ruby:nocontrols">
&lt;% property_editor 'properties', 'Properties' do %&gt;
	&lt;% property_editor_section 'show' do %&gt;
		&lt;%= @cube.name %&gt;&lt;br/&gt;
		&lt;%= render :partial =&gt; '/cubes/cube_stats' %&gt;
	&lt;% end %&gt;

	&lt;% property_editor_section 'edit' do %&gt;
		&lt;%= render :partial =&gt; 'form' %&gt;
	&lt;% end %&gt;
&lt;% end %&gt;
</pre>
<p>What&#8217;s more, I had a complete set of CSS selectors with which I could control my property editor and its associated sections:  </p>
<pre name="code" class="css:nocontrols">
.property-editor {
	margin-top: 5px;
	border: 3px solid #FDEFE6;
}

.property-editor-title {
	padding: 2px;
	padding-left: 5px;
	padding-right: 5px;
	background-color: #EEE;
	color: #2F6569;
}

.property-editor-show, .property-editor-edit {
	padding: 5px;
}

.property-editor p {
	margin: 0px;
	padding: 0px;
}
</pre>
<p>Well, all in all, that&#8217;s a good bit of rambling, but I hope others can benefit from my experiences with Ruby, blocks, partials, and DRY up those views.</p>
]]></content:encoded>
			<wfw:commentRss>http://ramblings.gibberishcode.net/archives/drying-your-views/8/feed</wfw:commentRss>
		</item>
		<item>
		<title>Cross-browser AJAX updates to table elements</title>
		<link>http://ramblings.gibberishcode.net/archives/cross-browser-ajax-updates-to-table-elements/9</link>
		<comments>http://ramblings.gibberishcode.net/archives/cross-browser-ajax-updates-to-table-elements/9#comments</comments>
		<pubDate>Sun, 29 Jun 2008 23:09:24 +0000</pubDate>
		<dc:creator>Michael</dc:creator>
		
		<category><![CDATA[JavaScript]]></category>

		<category><![CDATA[IE]]></category>

		<category><![CDATA[mootools]]></category>

		<category><![CDATA[tables]]></category>

		<guid isPermaLink="false">http://ramblings.gibberishcode.net/?p=9</guid>
		<description><![CDATA[It seems that one of the toughest Javascript challenge is to get your AJAX code consistently behave between browsers when you&#8217;re dealing with IE&#8217;s handling of table elements vs. Gecko and other engines.  It took quite a bit of finagling to figure out exactly where the issues and trappings are.  Most web developers [...]]]></description>
			<content:encoded><![CDATA[<p>It seems that one of the toughest Javascript challenge is to get your AJAX code consistently behave between browsers when you&#8217;re dealing with IE&#8217;s handling of table elements vs. Gecko and other engines.  It took quite a bit of finagling to figure out exactly where the issues and trappings are.  Most web developers know about IE&#8217;s, ahem, rather unque way of handling tables and the likes, but what&#8217;s a web application to do if (s)he is gonna bring some sanity to the picture?  Well, Mootools 1.2 goes a long ways towards easing your misery in terms of interacting with the DOM and doing some really cool Javascript things in a consistent manner, but you still have to figure out the specifics that you&#8217;re encountering for your application&#8217;s needs.</p>
<p>My needs were simple.  I wanted to use AJAX to pop up an embedded dialog (just some fancy CSS styling to get a DIV hovering over an overlay, really), update some values, and have that info populate back to the table where said information was presently displayed.  I also wanted to be able to add new records and delete existing rows through AJAX calls rather than refreshing the whole page.</p>
<p>This article will walk you through the basic approach I used.  I&#8217;ll leave out the fancy dialogs and all that and just hard-code some buttons so as to keep this article tightly focused on the task at hand:  manipulating tables consistently across all browsers through Mootools 1.2.</p>
<p>This technique was borne of some code and help I got from Jan Kassens http://blog.kassens.net/ on the IRC channel for mootools (#mootools at freenode.net).</p>
<p>First, the HTML page:</p>
<pre name="code" class="html:nocontrols">
&lt;table id="mytable" style="border: 1px solid blue"&gt;
    &lt;tbody id="mytbody"&gt;
    &lt;tr id="row1"&gt;
        &lt;td&gt;r1c1&lt;/td&gt;
        &lt;td&gt;r1c2&lt;/td&gt;
        &lt;td&gt;r1c3&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr id="row2"&gt;
        &lt;td&gt;r2c1&lt;/td&gt;
        &lt;td&gt;r2c2&lt;/td&gt;
        &lt;td&gt;r2c3&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr id="row3"&gt;
        &lt;td&gt;r3c1&lt;/td&gt;
        &lt;td&gt;r3c2&lt;/td&gt;
        &lt;td&gt;r3c3&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr id="row4"&gt;
        &lt;td&gt;r4c1&lt;/td&gt;
        &lt;td&gt;r4c2&lt;/td&gt;
        &lt;td&gt;r4c3&lt;/td&gt;
    &lt;/tr&gt;
    &lt;/tbody&gt;
&lt;/table&gt;
</pre>
<p>Cut and paste that into a more complete HTML document if you&#8217;re gonna play with it.  But as you can probably see, that code generates a fairly simple table with named table row elements and a named tbody element (which is very important for IE).</p>
<p>One of the first things I found out is that you can&#8217;t inject directly on the table element in IE, you have to inject into the TBODY element, otherwise, IE just accepts your injects gleefully without ever presenting them (I know this because I could subsequently select with $(&#8217;new_element&#8217;) and delete the elements I dynamically created!).</p>
<p>So now that we have that first big &#8220;gotcha&#8221; out of the way, lets look at some mootools functions you can use to mess with your tables.  First, be sure you have mootools loading into your HTML document:</p>
<pre name="code" class="html:nocontrols">
&lt;script type="text/javascript" src="scripts/mootools-1.2-core-nc.js" charset="utf-8"&gt;&lt;/script&gt;
</pre>
<p>And now, some javascript to let us do a little table manipulation:</p>
<pre name="code" class="javascript:nocontrols">&lt;script type="text/javascript"&gt;

	function htmlToElements(str){
		return new Element('div', {html: '&lt;table&gt;&lt;tbody&gt;' + str + '&lt;/tbody&gt;&lt;/table&gt;'}).getElement('tr');
	}

	function add_row() {
		var myTBody = $('mytbody');

		newRow = htmlToElements("&lt;tr&gt;&lt;td&gt;foo&lt;/td&gt;&lt;td&gt;bar&lt;/td&gt;&lt;/tr&gt;");
		newRow.inject(myTBody);
	}
	function delete_row() {
		$('row5').dispose();
	}

	function replace_row(row) {
		var newRow = htmlToElements('&lt;tr id="' + row + '"&gt;&lt;td&gt;foo&lt;/td&gt;&lt;td&gt;bar&lt;/td&gt;&lt;td&gt;replaces!&lt;/td&gt;&lt;/tr&gt;');
		newRow.replaces($(row));
	}
&lt;/script&gt;
</pre>
<p>With this, I can put some buttons on the page to click and call these nifty functions accordingly:</p>
<pre name="code" class="javascript:nocontrols">&lt;button onclick="add_row();"&gt;add row&lt;/button&gt;
&lt;button onclick="delete_row();"&gt;delete row&lt;/button&gt;
&lt;button onclick="replace_row('row2');"&gt;replace row 2&lt;/button&gt;
</pre>
<p>So, lets go over this a bit.  <em>delete_row()</em> is the simplest one.  It just shows that you can use mootool&#8217;s dollar function to grab a TR and throw it away.  Works great in FF2 and IE without any tricks.  Next up, <em>add_row()</em>.  This one turned out to be tricky because I was initially trying to add to the &#8220;mytable&#8221; TABLE element and it worked in Firefox, but not Internet Explorer.  I really got frustrated with this one, trying all manner of innerHTML, outerHTML, DOM manipulators (e.g. <em>appendChild</em>) with *no* success until Jan tipped me off that its the tbody you have to work against.</p>
<p>Alright, so with that nasty one out of the way, the next step was getting rows into IE.  It seems that just assigning innerHTML property for a TR was a major no-no and there&#8217;s plenty of blogs and other forum posts to tell you all about this one.  But I get my new data from the server as HTML, not DOM objects.  I needed a simple helper method to get me to DOM elements I could inject.  So Jan whips up what I needed in <em>htmlToElements(str)</em> function in which a a fully valid table is constructed (including the TBODY element IE depends on) in which the TR is selected and returned from it.  With a TR element in hand, it was embarrassingly simple to call mootool&#8217;s inject on the TBODY element.</p>
<p>But wait, there&#8217;s a bonus round:  You can also replace an existing TR just as easily as you can add a new TR to the table and Internet Explorer (or maybe it was mootools!) tickled me pink in happily complying with my wishes.  With this toolset, you can build a fairly comprehensive set of functionality to add, delete, and update your rows in any browser.</p>
]]></content:encoded>
			<wfw:commentRss>http://ramblings.gibberishcode.net/archives/cross-browser-ajax-updates-to-table-elements/9/feed</wfw:commentRss>
		</item>
	</channel>
</rss>
