<?xml version='1.0' encoding='UTF-8'?><rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearchrss/1.0/" xmlns:blogger="http://schemas.google.com/blogger/2008" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" version="2.0"><channel><atom:id>tag:blogger.com,1999:blog-37855474</atom:id><lastBuildDate>Fri, 20 Mar 2026 23:57:31 +0000</lastBuildDate><category>rails</category><category>activerecord</category><category>composite_primary_keys</category><category>git</category><category>monitoring</category><category>ocaml</category><category>services</category><category>subversion</category><category>tools</category><category>ubuntu</category><category>zabbix</category><category>blinksale</category><category>capistrano</category><category>file_uploads</category><category>github development</category><category>howto</category><category>lrug</category><category>nginx</category><category>osx</category><category>rspec</category><category>ruby itunes xbmc</category><category>tdd</category><category>tutorial</category><category>ubuntu rails</category><category>vim</category><category>vim shoulda</category><category>vmware</category><title>Ronin on Rails</title><description>&lt;p&gt;&lt;strong&gt;Ruby on Rails, Development, Freelancing&lt;/strong&gt;&lt;/p&gt;&#xa;&lt;p align=&quot;right&quot;&gt;Give a man a fish, and he&#39;ll eat for a day. Give a fish a man, and he&#39;ll eat for weeks&lt;/p&gt;</description><link>http://roninonrails.blogspot.com/</link><managingEditor>noreply@blogger.com (digitalronin)</managingEditor><generator>Blogger</generator><openSearch:totalResults>38</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><item><guid isPermaLink="false">tag:blogger.com,1999:blog-37855474.post-7018690419733791886</guid><pubDate>Thu, 14 Oct 2010 11:00:00 +0000</pubDate><atom:updated>2010-10-14T12:00:04.373+01:00</atom:updated><title>Madame Pain&#39;s Boudoir Circus</title><description>Nothing at all to do with computers, this time.&lt;br /&gt;
&lt;br /&gt;
Some people at LRUG asked about the circus show I&#39;m in. So, here are the details.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;border-collapse: collapse; font-family: arial, sans-serif; font-size: 13px;&quot;&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div style=&quot;text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000066;&quot;&gt;&lt;span style=&quot;font-size: xx-large;&quot;&gt;&lt;span style=&quot;font-family: garamond, serif;&quot;&gt;MADAME PAIN’S BOUDOIR CIRCUS&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;&lt;div style=&quot;min-height: 13px; text-align: center;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: large;&quot;&gt;Alice Cooper’s Favourite Freak Chrisalys is to wreak madness as ‘Madame Pain’ in AirCraft Circus’ critically acclaimed side-show spectacular ‘Madame Pain&#39;s Boudoir Circus.’&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;br /&gt;
&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;&lt;div style=&quot;color: #0070c0; text-align: center;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;a href=&quot;http://www.madamepainsboudoircircus.com/&quot; style=&quot;color: #2a5db0;&quot; target=&quot;_blank&quot;&gt;www.madamepainsboudoircircus.&lt;wbr&gt;&lt;/wbr&gt;com&lt;/a&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;color: #0070c0; text-align: center;&quot;&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;a href=&quot;http://www.chrisalys-circus.com/&quot;&gt;Chrisalys&lt;/a&gt; - TimeOut’s favourite circus freak an&lt;/span&gt;&lt;span style=&quot;font-size: large;&quot;&gt;d&amp;nbsp;&lt;/span&gt;&lt;i&gt;&lt;span style=&quot;font-size: large;&quot;&gt;‘stand out’&lt;/span&gt;&lt;/i&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&amp;nbsp;star of Alice Cooper’s upcoming Roundhouse show&amp;nbsp;&lt;/span&gt;&lt;i&gt;&lt;span style=&quot;font-size: large;&quot;&gt;Halloween Night of Fear&lt;/span&gt;&lt;/i&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&amp;nbsp;(as featured in Bizarre Magazine) is to revisit the shadows of the cult hit spectacular&amp;nbsp;&lt;/span&gt;&lt;i&gt;&lt;span style=&quot;font-size: large;&quot;&gt;Madame Pain’s Boudoir Circus&lt;/span&gt;&lt;/i&gt;&lt;span style=&quot;font-size: large;&quot;&gt;.&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;min-height: 13px; text-align: center;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;br /&gt;
&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;Previously only available as an underground sneak peak to the elite few, the doors are unbolting and the lights are lifting on London’s most diverse troupe of aerial acrobats and sideshow miscreants. Telling the tale of the world’s greatest serial killer, shrouded in an underworld of forgotten freaks and Parisian burlesque dancers.&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;min-height: 13px; text-align: center;&quot;&gt;&lt;span style=&quot;text-decoration: underline;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;i&gt;&lt;/i&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;br /&gt;
&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;text-decoration: underline;&quot;&gt;&lt;i&gt;&lt;span style=&quot;font-size: large;&quot;&gt;“Despite having seen some of the jaw dropping spectacles of the Moscow state circus and the Berlin circus, I still have not seen a performance that matches the fusion of ideas, spectacle, visual image and musical extravagance of Madame Pain’s Boudoir Circus”&lt;/span&gt;&lt;/i&gt;&lt;/span&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&amp;nbsp;– James Underwood&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;min-height: 13px; text-align: center;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;br /&gt;
&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;Witness an outrageous aerial display from a cast of 35 swinging trapeze, pole dancing, fire eating, extreme sports and shocking sideshow acts with live music and DJ sets, whilst raising funds for the ‘Heat the Hangar’ project, in aid of the Hangar Arts Trust, home of Aircraft Circus.&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;min-height: 13px; text-align: center;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;br /&gt;
&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: large;&quot;&gt;See the&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;a href=&quot;http://www.youtube.com/watch?v=9Y3vNETU9cQ&quot; style=&quot;color: #2a5db0;&quot; target=&quot;_blank&quot;&gt;&lt;span style=&quot;color: blue; text-decoration: underline;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: large;&quot;&gt;Showreel&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/a&gt;&lt;b&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&amp;nbsp;on YouTube!&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;&lt;div style=&quot;min-height: 13px; text-align: center;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;br /&gt;
&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: large;&quot;&gt;For press enquiries contact&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;font-size: large;&quot;&gt;:&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;Moira Campbell Press Manager on 07976 618163&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;mailto:moira@aircraftcircus.com&quot; style=&quot;color: #2a5db0;&quot; target=&quot;_blank&quot;&gt;&lt;span style=&quot;color: #0070c0;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;moira@aircraftcircus.com&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;Mike Wells Press and Marketing Assistant&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;mailto:mike@aircraftcircus.com&quot; style=&quot;color: #2a5db0;&quot; target=&quot;_blank&quot;&gt;&lt;span style=&quot;color: #0070c0;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;mike@aircraftcircus.com&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style=&quot;min-height: 13px; text-align: center;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;min-height: 13px; text-align: center;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: large;&quot;&gt;Listing Information&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;Tickets and information&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://www.madamepainsboudoircircus.com/&quot; style=&quot;color: #2a5db0;&quot; target=&quot;_blank&quot;&gt;&lt;span style=&quot;color: #0070c0;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;www.madamepainsboudoircircus.&lt;wbr&gt;&lt;/wbr&gt;com&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&amp;nbsp;020 8317 8401&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;min-height: 13px; text-align: center;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;br /&gt;
&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;The Hangar Arts Trust&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;Unit 7A, Mellish House&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;Harrington Way&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;Charlton&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;SE18 5NR&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;min-height: 13px; text-align: center;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;br /&gt;
&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;b&gt;Performances:&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;min-height: 13px; text-align: center;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;b&gt;&lt;br /&gt;
&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;b&gt;Friday 19th @ 8pm + opening night party with guest DJ’s&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;b&gt;Saturday 20th @ 8pm&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;b&gt;Sunday 21st @ 7pm&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;min-height: 13px; text-align: center;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;b&gt;&lt;br /&gt;
&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;b&gt;Friday 26th @ 8pm&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;b&gt;Saturday 27th @ 8pm&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;b&gt;Sunday 28th @ 7pm + after show party&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;min-height: 15px; text-align: center;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;br /&gt;
&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;This event is for over 16&#39;s only, due to adult themes.&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;Tickets are £10.00 early bird and £12.50 from 31st October and on the door.&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;Running time 1hr 15min (no interval)&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;Early booking is advised.&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;br /&gt;
&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;span style=&quot;font-size: small;&quot;&gt;&lt;div&gt;&lt;span style=&quot;font-size: large;&quot;&gt;Hope to see you there.&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;</description><link>http://roninonrails.blogspot.com/2010/10/madame-pains-boudoir-circus.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-37855474.post-1611522394903795327</guid><pubDate>Wed, 16 Jun 2010 19:36:00 +0000</pubDate><atom:updated>2010-06-20T17:55:31.423+01:00</atom:updated><title>Using Ipredator on OS X</title><description>[UPDATE] There seems to be a bug in Snow Leopard where changes made to the Airport interface&#39;s MTU do not persist - it just jumps back to 1500 immediately. To make the change stick, you need to do this in a terminal window;&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;$ sudo ifconfig en1 mtu 576&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre style=&quot;font-family: inherit;&quot;&gt;(Assuming en1 is your airport interface, and 576 is the MTU value you want)&lt;/pre&gt;&lt;pre style=&quot;font-family: inherit;&quot;&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre style=&quot;font-family: inherit;&quot;&gt;Sadly, the change will not persist through a reboot, so you&#39;ll need to run the command whenever you boot up your mac. &lt;/pre&gt;&lt;pre&gt;&lt;/pre&gt;&lt;pre&gt;______________________________ &lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
I&#39;m not a big fan of being &lt;a href=&quot;http://www.telegraph.co.uk/news/uknews/law-and-order/6533107/Every-phone-call-email-and-internet-click-stored-by-state-spying-databases.html&quot;&gt;spied on&lt;/a&gt;, so I&#39;ve signed up to &lt;a href=&quot;https://www.ipredator.se/?lang=en&quot;&gt;IPredator&lt;/a&gt;, the VPN service run by the people who brought us The Pirate Bay.&lt;br /&gt;
&lt;br /&gt;
Setting it up on OS X is pretty trivial, and I haven&#39;t noticed much, if any, difference in network speeds. But, I did find myself unable to access certain websites. Notably my &lt;a href=&quot;http://basecamphq.com/&quot;&gt;Basecamp&lt;/a&gt; site, which is quite inconvenient. I&#39;ve got HTTPS turned on, and when using my VPN connection, I couldn&#39;t load the login page - it fails as soon as it tries to do the TLS handshake. Turn off the VPN, and everything is just fine. Some other sites behaved in a similar way - even some sites that weren&#39;t using HTTPS (although digging into the page content suggests that it is https elements on the page that were causing the browser to hang).&lt;br /&gt;
&lt;br /&gt;
37Signals support, sadly, were no help in this case. But, I did find &lt;a href=&quot;http://ask-leo.com/i_cant_access_some_websites_why.html&quot;&gt;this page&lt;/a&gt; which suggested changing the MTU (Maximum Transmission Unit) setting of my TCP/IP connection.&lt;br /&gt;
&lt;br /&gt;
That page talks about how to do this on Windo$e, but I found that the problem went away if I made the corresponding change on OS X. So, I&#39;m posting this here in case anyone else finds it helpful.&lt;br /&gt;
&lt;br /&gt;
First of all, in &quot;Network Preferences&quot;, select your main network connection (I&#39;m using wired ethernet), and click &quot;Advanced&quot;.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;http://3.bp.blogspot.com/_ODXdwca8eVM/TBknHo8N-KI/AAAAAAAAAFg/xltWLkqPud4/s1600/Screen+shot+2010-06-16+at+20.31.55.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;br /&gt;
&lt;/a&gt;&lt;a href=&quot;http://4.bp.blogspot.com/_ODXdwca8eVM/TBknLylVEcI/AAAAAAAAAFw/sq-GmrgAlgY/s1600/Screen+shot+2010-06-16+at+20.25.59.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;http://4.bp.blogspot.com/_ODXdwca8eVM/TBknLylVEcI/AAAAAAAAAFw/sq-GmrgAlgY/s320/Screen+shot+2010-06-16+at+20.25.59.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
In the advanced options, click &quot;Ethernet&quot; and look for the MTU setting. Change it from &quot;Standard (1500)&quot; to &quot;Custom&quot;, and enter a lower value. In my case, I&#39;m using 576.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;br /&gt;
&lt;/div&gt;&lt;a href=&quot;http://4.bp.blogspot.com/_ODXdwca8eVM/TBknKADgpmI/AAAAAAAAAFo/A2zl7EFiodw/s1600/Screen+shot+2010-06-16+at+20.26.22.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;http://4.bp.blogspot.com/_ODXdwca8eVM/TBknKADgpmI/AAAAAAAAAFo/A2zl7EFiodw/s320/Screen+shot+2010-06-16+at+20.26.22.png&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;http://3.bp.blogspot.com/_ODXdwca8eVM/TBknHo8N-KI/AAAAAAAAAFg/xltWLkqPud4/s1600/Screen+shot+2010-06-16+at+20.31.55.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;http://3.bp.blogspot.com/_ODXdwca8eVM/TBknHo8N-KI/AAAAAAAAAFg/xltWLkqPud4/s320/Screen+shot+2010-06-16+at+20.31.55.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
Click &quot;OK&quot;, click &quot;Apply&quot; and then disconnect and reconnect your VPN connection.&lt;br /&gt;
&lt;br /&gt;
That&#39;s it. I can now access my Basecamp site via my IPredator VPN connection. I hope this helps someone else.&lt;br /&gt;
&lt;br /&gt;
Have a happy &amp;amp; private Internet!</description><link>http://roninonrails.blogspot.com/2010/06/using-ipredator-on-os-x.html</link><author>noreply@blogger.com (Anonymous)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/_ODXdwca8eVM/TBknLylVEcI/AAAAAAAAAFw/sq-GmrgAlgY/s72-c/Screen+shot+2010-06-16+at+20.25.59.png" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-37855474.post-6647412690296461984</guid><pubDate>Sat, 08 May 2010 10:01:00 +0000</pubDate><atom:updated>2010-05-13T07:48:22.425+01:00</atom:updated><title>Book review: Zabbix 1.8 Network Monitoring</title><description>Packt publishing asked me to review &quot;&lt;a href=&quot;https://www.packtpub.com/zabbix-1-8-network-monitoring/book&quot;&gt;Zabbix 1.8 Network Monitoring&lt;/a&gt;&quot; (Disclosure: They gave me a copy of the book. Other than that, I have not been compensated for this review in any way).&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;http://1.bp.blogspot.com/_ODXdwca8eVM/S-U2NZDIWBI/AAAAAAAAAFQ/2XN-FB05kPE/s1600/ZabbixBook.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;http://1.bp.blogspot.com/_ODXdwca8eVM/S-U2NZDIWBI/AAAAAAAAAFQ/2XN-FB05kPE/s320/ZabbixBook.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
I&#39;ve been using &lt;a href=&quot;http://www.zabbix.com/&quot;&gt;Zabbix&lt;/a&gt; for a few years to monitor a variety of systems. I like it for its small (initial) learning curve and the out-of-the-box graphs and reports. But, the kindest word I can use to describe the provided documentation is &quot;minimal&quot;. So, I was pleased to see a book that tries to address this problem.&lt;br /&gt;
&lt;br /&gt;
At just under 400 pages, there&#39;s a lot of material. The book takes a tutorial approach, going from installation through to fairly advanced monitoring ideas including distributed monitoring with Zabbix proxies.&lt;br /&gt;
&lt;br /&gt;
The book&#39;s target audience is sysadmins (aka &quot;devops&quot;) and developers who have to do their own monitoring. So, the level of assumed technical ability is quite high. As usual with technical books, this can lead to over-explanation of some steps and under-explanation in others. But, I found the tone and level of detail struck quite a nice balance. YMMV, of course.&lt;br /&gt;
&lt;br /&gt;
As well as covering the detail of the steps you need to take to setup Zabbix and start monitoring, there are good explanations of the high-level architecture of how the various Zabbix components fit together and communicate, which I found very helpful.&lt;br /&gt;
&lt;br /&gt;
The author, Richards Olups, works with Zabbix SIA (who develop Zabbix), and his in-depth knowledge of the system is obvious throughout the book.&lt;br /&gt;
&lt;br /&gt;
For example, I was pleased to see a section about digging into the underlying database Zabbix uses to store configuration and historical values. Zabbix&#39;s PHP front-end is functional, but can be a bit quirky. IMO, it&#39;s ripe for replacement/augmentation with something a bit slicker - maybe a Rack application using some Flash-based graphing tools. Having a guide through the database structure will make it a lot easier to create something like that.&lt;br /&gt;
&lt;br /&gt;
Also nice are some tips about using the unix command-line tools to help troubleshoot monitoring problems. e.g. using telnet to connect to the zabbix-agent process to confirm it&#39;s listening correctly for connections from the server. This is basic stuff, but a worrying number of developers (and even some sysadmins) I&#39;ve met don&#39;t seem to have much awareness of the toolbox unix provides.&lt;br /&gt;
&lt;br /&gt;
The section on how to upgrade Zabbix and patch its database is also a nice inclusion. A quick glance at the Zabbix support forums shows that that is often a problem area, so I like the fact that this has been addressed upfront. Using Zabbix to monitor its own health is also covered, and the section contains lots of advice that would have saved me at least one weekend spent rebuilding my monitoring from scratch when an earlier version of Zabbix decided to eat its database, one day!&lt;br /&gt;
&lt;br /&gt;
As you can probably tell, I like this book a lot, which is just as well since it appears to be the only book on Zabbix available right now.&lt;br /&gt;
&lt;br /&gt;
There are a few nitpicks, perhaps. The step-by-step tutorial approach makes the book far more suited to being read through rather than as a reference, but since this is the first book available (AFAIK), I think that&#39;s probably a good choice. I would have liked to see Ubuntu covered in the installation section (the author covers compilation/installation on SuSe and Slackware), although the book&#39;s target audience shouldn&#39;t have any trouble adapting the instructions for their distro of choice. The writing and grammar are a little quirky, and a glossary would have been useful. But, these are really minor points.&lt;br /&gt;
&lt;br /&gt;
Overall, I think the book has a lot to offer anyone who is using, or thinking of using Zabbix as their monitoring solution. If I hadn&#39;t been given a copy, I&#39;d probably buy it.</description><link>http://roninonrails.blogspot.com/2010/05/book-review-zabbix-18-network.html</link><author>noreply@blogger.com (Anonymous)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/_ODXdwca8eVM/S-U2NZDIWBI/AAAAAAAAAFQ/2XN-FB05kPE/s72-c/ZabbixBook.png" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-37855474.post-172719794445426720</guid><pubDate>Mon, 21 Dec 2009 15:59:00 +0000</pubDate><atom:updated>2009-12-21T16:02:01.190+00:00</atom:updated><title>Incremental dump of a mysql database</title><description>I&#39;ve just written a couple of simple ruby classes to help dump a mysql database without locking tables for extended periods. &lt;br /&gt;&lt;br /&gt;In particular, some of my tables contain lots of date-based data which is not changed after the day it was created. The classes can be told to dump such data one day at a time, and won&#39;t dump a day that has already been dumped. &lt;br /&gt;&lt;br /&gt;Check it out on github;&lt;br /&gt;&lt;br /&gt;    http://github.com/digitalronin/DbDumper&lt;br /&gt;&lt;br /&gt;I hope people find it useful.&lt;br /&gt;&lt;br /&gt;David</description><link>http://roninonrails.blogspot.com/2009/12/incremental-dump-of-mysql-database.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-37855474.post-3695404543011522937</guid><pubDate>Thu, 12 Nov 2009 14:33:00 +0000</pubDate><atom:updated>2009-11-12T15:10:45.797+00:00</atom:updated><title>Introduction to Hive on Amazon Elastic Map Reduce</title><description>&lt;span style=&quot;font-weight: bold;&quot;&gt;Background&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Scaling an application usually involves adding processing nodes. This means you end up with valuable data (e.g. server logs) existing on multiple different machines. Very often, we want to mine those logs for useful information.&lt;br /&gt;&lt;br /&gt;One way to do this would be to put all the logs in one place and run some kind of computation over all the data. This is relatively simple, but it really doesn&#39;t scale. Pretty soon, you reach the point where the machine which is analysing a day&#39;s worth of log data is taking more than a day to do it.&lt;br /&gt;&lt;br /&gt;Another way is to let each processing node analyse its own logs, and then have some way to collate the analysis from each node to find the overall answer you&#39;re looking for. This is a more practical solution for the long term, but it still has a couple of problems;&lt;br /&gt;&lt;br /&gt;1. A processing node should be processing - i.e. doing the task for which it was created. Ideally, you wouldn&#39;t want to bog it down with other responsibilities. If you do, you probably need to make the node more powerful than it needs to be in order to carry out its primary task, so you&#39;re paying for extra capacity just in case you want to run a log analysis task.&lt;br /&gt;&lt;br /&gt;2. In the case of log data in particular, keeping the logs on the node which created them generally means you have to keep the node around too. This makes it awkward to remove nodes if you want to scale down, or replace nodes with better nodes, because you have to worry about copying the logs off the node and keeping them somewhere.&lt;br /&gt;&lt;br /&gt;It would be nice if we could have each node push its logs into something like Amazon S3 for storage, and spin up a distributed computing task whenever we want to run some analysis. Amazon Elastic Map Reduce (EMR) is designed to work in exactly this way, but the learning curve for writing map/reduce job flows is pretty steep - particularly if you&#39;re used to writing simple scripts to get useful information out of log data.&lt;br /&gt;&lt;br /&gt;As of October 1st 2009, &lt;a href=&quot;http://developer.amazonwebservices.com/connect/ann.jspa?annID=508&quot;&gt;Amazon EMR supports Apache Hive&lt;/a&gt;, which makes things a lot easier.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;What is Hive?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The proper answer is &lt;a href=&quot;http://wiki.apache.org/hadoop/Hive&quot;&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The way I think of Hive is that it lets you pretend that a whole mess of semi-structured log files are actually big database tables, and then helps you run SQL-like queries over those tables. All this without having to actually insert the data into any kind of table, and without having to know how to write distrubuted map/reduce tasks.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;Using Hive with Amazon EMR&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;This is a very basic introduction to working with Hive on Amazon EMR. Very basic because I&#39;ve only just started looking into this myself.&lt;br /&gt;&lt;br /&gt;You will need to be signed up for Amazon Web Services, including S3 and Elastic Map Reduce.&lt;br /&gt;&lt;br /&gt;I&#39;m going to go through part of an exercise from the &lt;a href=&quot;http://www.cloudera.com/hadoop-training-hive-introduction&quot;&gt;Cloudera Introduction to Hive&lt;/a&gt;, which I strongly recommend working through. That training exercise uses a Cloudera VMWare virtual appliance running Hive. Here is how to I did a similar task using Hive on Amazon EMR.&lt;br /&gt;&lt;br /&gt;For this exercise, we&#39;re going to take a data file consisting of words and the frequency of occurrence of those words within the complete works of William Shakespeare. The file consists of a number of lines like this;&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;25848   the&lt;br /&gt;23031   I&lt;br /&gt;19671   and&lt;br /&gt;18038   to&lt;br /&gt;16700   of&lt;br /&gt;14170   a&lt;br /&gt;12702   you&lt;br /&gt;11297   my&lt;br /&gt;10797   in&lt;/pre&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;The first value is an integer saying how many times the word occurs, then a tab character, then the word. This file is generated by an earlier exercise in one of the Cloudera Hadoop tutorials. If you don&#39;t feel like running through those exercises, just generate a file containing a bunch of numbers and words, separated by a tab character, and use that.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;Upload the data to S3&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Before we can analyse the data, we need it to be available in S3. Create a bucket called &quot;hive-emr&quot;, and upload your data file into it using the key &quot;/tables/wordfreqs/whatever&quot;. In my case, I have the tab-delimited text file in;&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;s3://hive-emr/tables/wordfreqs/shakespeare.txt&lt;/pre&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;NB: The S3 path &quot;hive-emr/tables/wordfreqs&quot; is going to be our Hive table. If you&#39;re unfamiliar with S3, &quot;hive-emr&quot; is the name of our bucket, and &#39;tables/wordfreqs/shakespeare.txt&#39; is the key whose value is the contents of our &quot;shakespeare.txt&quot; file.&lt;br /&gt;&lt;br /&gt;Everything in the &#39;directory&#39; &quot;tables/wordfreqs/&quot; (which isn&#39;t really a directory, but we can pretend it is) must be parseable as data for our table, so don&#39;t put any other types of file in there. You could, if you wanted, have more than one tab-delimited text file though, and all of the data in all of those files would become records in the same Hive table.&lt;br /&gt;&lt;br /&gt;It&#39;s also important not to have any underscores in the S3 bucket or key. S3 will happily let you create and upload files to buckets/keys with underscores, but you&#39;ll get an S3 URI error when you try to create the table in Hive.&lt;br /&gt;&lt;br /&gt;I&#39;m using &lt;a href=&quot;http://s3sync.net/wiki&quot;&gt;s3sync&lt;/a&gt; to upload the data files, but you can use anything you want provided you get the data into S3 with the correct bucket and key name.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;Generating an EC2 Key Pair&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;We need a key pair to enable us to SSH onto our Hive cluster, when we&#39;ve started it. If you don&#39;t have a suitable key pair already, sign in to the Amazon Web Services console and go to the Amazon EC2 tab. Near the bottom of the left-hand column, use the &quot;Key Pairs&quot; function to generate a key pair and save the secret key to your local machine.&lt;br /&gt;&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;http://3.bp.blogspot.com/_ODXdwca8eVM/SvwgeMiILQI/AAAAAAAAAFA/OAXh9XdVU74/s1600-h/Picture+2.png&quot;&gt;&lt;img style=&quot;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 196px;&quot; src=&quot;http://3.bp.blogspot.com/_ODXdwca8eVM/SvwgeMiILQI/AAAAAAAAAFA/OAXh9XdVU74/s320/Picture+2.png&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5403229356070284546&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Be aware of the &quot;Region&quot; you&#39;re using - key pairs will only work for servers of the same region. I&#39;m using &quot;EU-West&quot;, but it doesn&#39;t matter which you use, as long as you&#39;re consistent.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;Starting Hive&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Sign in to the Amazon Web Services console and go to the Amazon Elastic MapReduce tab (you won&#39;t see the tab if you haven&#39;t signed up to the service, so make sure you do that first).&lt;br /&gt;&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;http://4.bp.blogspot.com/_ODXdwca8eVM/SvwggpuTeqI/AAAAAAAAAFI/CjTy1B27bkE/s1600-h/Picture+1.png&quot;&gt;&lt;img style=&quot;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 196px;&quot; src=&quot;http://4.bp.blogspot.com/_ODXdwca8eVM/SvwggpuTeqI/AAAAAAAAAFI/CjTy1B27bkE/s320/Picture+1.png&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5403229398265723554&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Click &quot;Create New Job Flow&quot;. Make sure you&#39;re using the same region you used when you generated your key pair.&lt;br /&gt;&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;http://3.bp.blogspot.com/_ODXdwca8eVM/Svwga3fpOeI/AAAAAAAAAE4/euXuTHjcTM8/s1600-h/Picture+3.png&quot;&gt;&lt;img style=&quot;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 213px;&quot; src=&quot;http://3.bp.blogspot.com/_ODXdwca8eVM/Svwga3fpOeI/AAAAAAAAAE4/euXuTHjcTM8/s320/Picture+3.png&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5403229298883115490&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Give the job flow a name, and select &quot;Hive Program&quot; for the job type.&lt;br /&gt;&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;http://3.bp.blogspot.com/_ODXdwca8eVM/SvwgX1QBhrI/AAAAAAAAAEw/nBEv0YzMJ_k/s1600-h/Picture+4.png&quot;&gt;&lt;img style=&quot;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 214px;&quot; src=&quot;http://3.bp.blogspot.com/_ODXdwca8eVM/SvwgX1QBhrI/AAAAAAAAAEw/nBEv0YzMJ_k/s320/Picture+4.png&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5403229246741120690&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;On the next screen, choose &quot;Start an Interactive Hive Session&quot;.&lt;br /&gt;&lt;br /&gt;On the next screen, we choose the number and size of the machines we want to comprise our cluster. In real life use, using a lot of big machines will make things go faster. For the purpose of this exercise, one small instance will do. We&#39;re not doing anything heavyweight here, and we only have one data file, so there isn&#39;t much point spending the extra money to run lots of large machines.&lt;br /&gt;&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;http://2.bp.blogspot.com/_ODXdwca8eVM/SvwgUDwbNXI/AAAAAAAAAEo/bN1ko73Bq_k/s1600-h/Picture+5.png&quot;&gt;&lt;img style=&quot;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 213px;&quot; src=&quot;http://2.bp.blogspot.com/_ODXdwca8eVM/SvwgUDwbNXI/AAAAAAAAAEo/bN1ko73Bq_k/s320/Picture+5.png&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5403229181915641202&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Select the key pair you generated earlier, and start the job flow. Don&#39;t forget to terminate the job flow when you&#39;ve finished, otherwise you&#39;ll be paying to keep an idle cluster going.&lt;br /&gt;&lt;br /&gt;Now we have to wait for the cluster to start up and reach the point where it&#39;s ready to do some work. This usually takes a few minutes.&lt;br /&gt;&lt;br /&gt;When the job flow status is &quot;WAITING&quot;, click on the job flow and scroll down in the lower pane to get the &quot;Master Public DNS Name&quot; assigned to your cluster so that we can SSH to it.&lt;br /&gt;&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;http://1.bp.blogspot.com/_ODXdwca8eVM/SvwgNfDljqI/AAAAAAAAAEg/UFZNM7cSmZI/s1600-h/Picture+9.png&quot;&gt;&lt;img style=&quot;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 184px;&quot; src=&quot;http://1.bp.blogspot.com/_ODXdwca8eVM/SvwgNfDljqI/AAAAAAAAAEg/UFZNM7cSmZI/s320/Picture+9.png&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5403229068984684194&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;From a terminal window, ssh onto your cluster like this;&lt;br /&gt;&lt;br /&gt;ssh -i key/hive.pem hadoop@ec2-79-125-30-42.eu-west-1.compute.amazonaws.com&lt;br /&gt;&lt;br /&gt;Replace key/hive.pem with the location and filename of the secret key you created and saved earlier.&lt;br /&gt;&lt;br /&gt;Replace &quot;ec2-79-125-30-42.eu-west-1.compute.amazonaws.com&quot; with the Master Public DNS Name of your cluster. The username &#39;hadoop&#39; is required.&lt;br /&gt;&lt;br /&gt;You should now have a terminal prompt like this;&lt;br /&gt;&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;http://3.bp.blogspot.com/_ODXdwca8eVM/SvwgF6zR7pI/AAAAAAAAAEY/27Ba5bnQCyk/s1600-h/Picture+10.png&quot;&gt;&lt;img style=&quot;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 134px;&quot; src=&quot;http://3.bp.blogspot.com/_ODXdwca8eVM/SvwgF6zR7pI/AAAAAAAAAEY/27Ba5bnQCyk/s320/Picture+10.png&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5403228938993528466&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Type &quot;hive&quot; to get to the hive console. This is an interactive shell that works in a similar way to the &quot;mysql&quot; command-line client.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;Creating a table&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;We&#39;re almost ready to start querying our data. First, we have to tell Hive where it is, and what kind of data is contained in our file.&lt;br /&gt;&lt;br /&gt;Type these lines into the hive shell;&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;hive&gt; create external table wordfreqs (freq int, word string)&lt;br /&gt;  &gt; row format delimited fields terminated by &#39;\t&#39;&lt;br /&gt;  &gt; stored as textfile&lt;br /&gt;  &gt; location &#39;s3://hive-emr/tables/wordfreqs&#39;;&lt;br /&gt;OK&lt;br /&gt;Time taken: 1.29 seconds&lt;/pre&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;Note that we didn&#39;t need to put &quot;shakespeare.txt&quot; as part of the location. Hive will look at the location we gave it and, provided all the &quot;files&quot; in that &quot;directory&quot; have the right kind of contents (lines consisting of an integer, a tab character and a string), all of their contents will be accessible in the &#39;wordfreqs&#39; table.&lt;br /&gt;&lt;br /&gt;Now that we&#39;ve told Hive how to find and parse our data, we can start asking questions in almost the same way as we would do if it were in a mysql table.&lt;br /&gt;&lt;br /&gt;Try this;&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;hive&gt; select * from wordfreqs limit 5;&lt;br /&gt;OK&lt;br /&gt;25848   the&lt;br /&gt;23031   I&lt;br /&gt;19671   and&lt;br /&gt;18038   to&lt;br /&gt;16700   of&lt;br /&gt;Time taken: 4.868 seconds&lt;br /&gt;hive&gt;&lt;/pre&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;So far, so good - even though that&#39;s a long time to take for a very simple query. Let&#39;s try something a little more interesting;&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;hive&gt; select count(word) from wordfreqs;&lt;/pre&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;Here is the output I got from this;&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;&lt;br /&gt;Total MapReduce jobs = 1&lt;br /&gt;Number of reduce tasks determined at compile time: 1&lt;br /&gt;In order to change the average load for a reducer (in bytes):&lt;br /&gt;set hive.exec.reducers.bytes.per.reducer=&lt;number&gt;&lt;br /&gt;In order to limit the maximum number of reducers:&lt;br /&gt;set hive.exec.reducers.max=&lt;number&gt;&lt;br /&gt;In order to set a constant number of reducers:&lt;br /&gt;set mapred.reduce.tasks=&lt;number&gt;&lt;br /&gt;Starting Job = job_200911121319_0001, Tracking URL = http://ip-10-227-111-150.eu-west-1.compute.internal:9100/jobdetails.jsp?jobid=job_200911121319_0001&lt;br /&gt;Kill Command = /home/hadoop/bin/../bin/hadoop job  -Dmapred.job.tracker=ip-10-227-111-150.eu-west-1.compute.internal:9001 -kill job_200911121319_0001&lt;br /&gt;2009-11-12 01:37:33,004 map = 0%,  reduce =0%&lt;br /&gt;2009-11-12 01:37:46,653 map = 50%,  reduce =0%&lt;br /&gt;2009-11-12 01:37:47,665 map = 100%,  reduce =0%&lt;br /&gt;2009-11-12 01:37:55,709 map = 100%,  reduce =100%&lt;br /&gt;Ended Job = job_200911121319_0001&lt;br /&gt;OK&lt;br /&gt;31809&lt;br /&gt;Time taken: 28.455 seconds&lt;br /&gt;hive&gt;&lt;/pre&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;All of that is Hive translating our sql-like query into MapReduce jobs that are then farmed out to our cluster. Since we&#39;re using a single, small instance, and since we only have one data file, there isn&#39;t any parallelisation happening, and the whole thing runs quite slowly. But, in principle, we could have terabytes of data files in our S3 bucket, and be using more and much larger machines in our cluster. Under those circumstances, we should see major gains from using Hive.&lt;br /&gt;&lt;br /&gt;FYI, the reason the first query didn&#39;t have this kind of output is that Hive is smart enough to figure out that no MapReduce trickery is necessary for this request - it can just read a few lines from the file to satisfy the query.&lt;br /&gt;&lt;br /&gt;This has been a very quick and simple introduction to Hive on Amazon EMR. I hope you found it useful. I plan to go into more advanced, and hopefully more useful, territory in future posts.&lt;br /&gt;&lt;br /&gt;PS: Don&#39;t forget to terminate your Hive cluster!&lt;br /&gt;&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;http://4.bp.blogspot.com/_ODXdwca8eVM/SvwdnZwcGKI/AAAAAAAAAEQ/ZbjMPneziu4/s1600-h/Picture+1.png&quot;&gt;&lt;img style=&quot;margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 83px;&quot; src=&quot;http://4.bp.blogspot.com/_ODXdwca8eVM/SvwdnZwcGKI/AAAAAAAAAEQ/ZbjMPneziu4/s320/Picture+1.png&quot; alt=&quot;&quot; id=&quot;BLOGGER_PHOTO_ID_5403226215703910562&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;</description><link>http://roninonrails.blogspot.com/2009/11/introduction-to-hive-on-amazon-elastic.html</link><author>noreply@blogger.com (Anonymous)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/_ODXdwca8eVM/SvwgeMiILQI/AAAAAAAAAFA/OAXh9XdVU74/s72-c/Picture+2.png" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-37855474.post-1379379997872245222</guid><pubDate>Wed, 04 Nov 2009 11:24:00 +0000</pubDate><atom:updated>2009-11-08T12:44:25.278+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">git</category><title>Setting up a remote git project</title><description>I often find myself working on a quick hack, using a local git repository. Eventually, whatever I&#39;ve hacked up becomes something I need to keep, and I want it in my remote git repository, with the local copy tracking the remote.&lt;br /&gt;&lt;br /&gt;After setting this up manually several times, I finally got around to scripting it.&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;http://gist.github.com/225970&quot;&gt;http://gist.github.com/225970&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Assuming you&#39;ve got a local git repo called &#39;myproject&#39;, and your current working directory is something like &#39;/home/me/projects/myproject&#39;, then running this script will create a directory called &#39;myproject.git&#39; on your remote git server, push your code to the remote repo and set the local copy to track it.&lt;br /&gt;&lt;br /&gt;Don&#39;t forget to edit the script first to set the correct server name and main git directory, below which all your projects live.&lt;br /&gt;&lt;br /&gt;The script assumes you&#39;re using SSH as the transport layer for git.</description><link>http://roninonrails.blogspot.com/2009/11/setting-up-remote-git-project.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-37855474.post-2176602590207217389</guid><pubDate>Wed, 21 Oct 2009 18:06:00 +0000</pubDate><atom:updated>2009-11-08T12:53:45.908+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">ocaml</category><title>OCaml for the impatient - part 2, reading standard input</title><description>Now that &quot;Hello, world&quot; is out of the way, let&#39;s look at the next step in writing our log processing script. We want to be able to read lines from standard input.&lt;br /&gt;OCaml has an &quot;input_line&quot; function, which takes a channel as a parameter. Standard input is available without doing any extra work as the channel &#39;stdin&#39;. So, to read a line of text from standard input, we just need to call;&lt;br /&gt;&lt;br /&gt;&lt;code&gt;input_line stdin&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;In OCaml, parameters to functions are not enclosed with braces. There are plenty of places you do need to use braces, but surrounding parameters is not one of them.&lt;br /&gt;To do something useful with our line of text, we&#39;ll need to assign it to a variable. OCaml uses the &quot;let&quot; keyword for that, but we&#39;ll also need to declare a scope for our variable, using &quot;in&quot;. So, the code we want is something like this;&lt;br /&gt;&lt;br /&gt;&lt;code&gt;let line = input_line stdin in&lt;br /&gt;   ... a block of code ...&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;To read all the lines of text from standard input, until we run out, we&#39;ll need a loop of some kind. OCaml does allow us to write code in an imperative style, so we can just use a while loop. While loops are pretty basic in OCaml (and in functional languages in general), because you&#39;re meant to do much cleverer things with recursion.&lt;br /&gt;Our loop will need to terminate when we run out of lines to read. The simplest way to do that in OCaml is to catch the &quot;End_of_file&quot; exception. I&#39;m not a big fan of using exceptions for normal control flow, but we can live with it for now.&lt;br /&gt;So, a simple program to read lines from standard input and echo them to standard output might look like this;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;try&lt;br /&gt; while true do&lt;br /&gt;   let line = input_line stdin in&lt;br /&gt;   Printf.printf &quot;%s\n&quot; line&lt;br /&gt; done;&lt;br /&gt; None&lt;br /&gt;with&lt;br /&gt; End_of_file -&gt; None&lt;br /&gt;;;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;There are a few points to note here. The semi-colon after &quot;done&quot; is necessary to tell OCaml that it should evaluate everything before the semi-colon first, and then evaluate the stuff after it. Without the semi-colon, you&#39;ll get a syntax error. It needs to be &quot;;&quot; and not &quot;;;&quot; because we&#39;re not terminating a block of code.&lt;br /&gt;We&#39;re using &quot;End_of_file -&gt; None&quot; to discard the exception we get when &quot;input_line&quot; tries to read a line that isn&#39;t there. &quot;None&quot; is a bit like &quot;nil&quot; in Ruby or &quot;undef&quot; in Perl.&lt;br /&gt;The &quot;None&quot; at the end of the block is required to keep the return type consistent. OCaml, like Perl or Ruby, returns whatever is the last thing evaluated in the block. OCaml requires that the &lt;span style=&quot;font-style: italic;&quot;&gt;try&lt;/span&gt; block return the same type of value as we will return if we catch an exception and end up in the &lt;span style=&quot;font-style: italic;&quot;&gt;with&lt;/span&gt; block. If you try running the code without the &quot;None&quot; before with, you&#39;ll get an error saying &quot;&lt;span style=&quot;font-style: italic;&quot;&gt;This expression has type &#39;a option but is here used with type unit&lt;/span&gt;&quot; (OCaml error messages are translated from French, so they&#39;re a little idiosyncratic).&lt;br /&gt;The type &quot;unit&quot; is the empty type, like void in Java. Our &lt;span style=&quot;font-style: italic;&quot;&gt;with&lt;/span&gt; block is returning &quot;None&quot;, so it&#39;s return type is &lt;span style=&quot;font-style: italic;&quot;&gt;unit&lt;/span&gt;, and the &lt;span style=&quot;font-style: italic;&quot;&gt;try&lt;/span&gt; block must return the same type.&lt;br /&gt;If we change the with to say;&lt;br /&gt;&lt;br /&gt;&lt;code&gt;End_of_file -&gt; &quot;whatever&quot;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Then the error becomes &quot;&lt;span style=&quot;font-style: italic;&quot;&gt;This expression has type string but is here used with type &#39;a option&lt;/span&gt;&quot;. So, we can make it go away by replacing the earlier &lt;span style=&quot;font-style: italic;&quot;&gt;None&lt;/span&gt; with any string constant (like &quot;hello&quot; - try it).&lt;br /&gt;The last thing we&#39;re going to do is to take our inline &quot;Printf.printf&quot; statement and turn it into a function call, so that we can do something more interesting with &lt;span style=&quot;font-style: italic;&quot;&gt;line&lt;/span&gt; later.&lt;br /&gt;In OCaml, functions are values we can assign to variables. So, to define a function, we use the same &lt;span style=&quot;font-style: italic;&quot;&gt;let&lt;/span&gt; statement as we used to define &lt;span style=&quot;font-style: italic;&quot;&gt;line&lt;/span&gt;. Here&#39;s a function to print out our line;&lt;br /&gt;&lt;br /&gt;&lt;code&gt;let out = Printf.printf &quot;%s\n&quot;;;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Notice that we terminated the statement without specifying what is supposed to be printed. If you type the code above into the interactive &lt;span style=&quot;font-style: italic;&quot;&gt;ocaml&lt;/span&gt; interpreter, you get this;&lt;br /&gt;&lt;br /&gt;&lt;code&gt;# let out = Printf.printf &quot;%s\n&quot;;;&lt;br /&gt;val out : string -&gt; unit = &lt;fun&gt;&lt;/fun&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;That&#39;s saying &quot;&lt;span style=&quot;font-style: italic;&quot;&gt;the value out is a function which takes a single string and doesn&#39;t return anything&lt;/span&gt;&quot;. OCaml decided we were defining a function because we didn&#39;t specify all the arguments. If we had, it would have simply evaluated it and assigned the &lt;span style=&quot;font-style: italic;&quot;&gt;result&lt;/span&gt; to &#39;out&#39;.&lt;br /&gt;Now, we can simplify our program a little;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;let out = Printf.printf &quot;%s\n&quot;;;&lt;br /&gt;&lt;br /&gt;try&lt;br /&gt;  while true do&lt;br /&gt;    let line = input_line stdin in&lt;br /&gt;    out line&lt;br /&gt;  done;&lt;br /&gt;  None&lt;br /&gt;with&lt;br /&gt;  End_of_file -&gt; None&lt;br /&gt;;;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Try running the program like this &quot;&lt;span style=&quot;font-style: italic;&quot;&gt;ls | ocaml foo.ml&lt;/span&gt;&quot;, or by compiling it as shown in part 1.&lt;br /&gt;So far, we haven&#39;t done anything very useful overall, but we&#39;ve covered reading from standard input and writing to standard output, looping over all the available input, assigning each line to a variable and calling a function with that variable.&lt;br /&gt;In part 3, we&#39;ll actually do something!</description><link>http://roninonrails.blogspot.com/2009/10/ocaml-for-impatient-part-2-reading.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-37855474.post-1375364727964724287</guid><pubDate>Sat, 17 Oct 2009 16:55:00 +0000</pubDate><atom:updated>2009-11-08T12:45:10.697+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">ocaml</category><title>OCaml for the impatient - part 1, &quot;Hello, world!&quot;</title><description>After an inspiring &lt;a href=&quot;http://skillsmatter.com/podcast/ajax-ria/enumerators&quot;&gt;presentation by Tom Stuart&lt;/a&gt; at LRUG earlier this week, I decided to have a go at learning a functional programming language. I picked &lt;a href=&quot;http://en.wikipedia.org/wiki/Ocaml&quot;&gt;OCaml&lt;/a&gt;, partly on recommendation from Tom, although he&#39;s since changed his advice to recommend &lt;a href=&quot;http://en.wikipedia.org/wiki/Clojure&quot;&gt;Clojure&lt;/a&gt; because of it&#39;s &quot;smart and comprehensive treatment of mutable state&quot;.&lt;br /&gt;&lt;br /&gt;I found quite a few &lt;a href=&quot;http://wiki.cocan.org/training&quot;&gt;introductions to OCaml&lt;/a&gt;, but many of them seem quite theoretical and a bit dry. Plus, I&#39;m really impatient. I tend to learn best by just diving in and trying to perform a real-world task. So, I decided to start by writing a basic filter to take apache access log lines and spit out some fields in CSV form.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;I know this is not necessarily the best task for OCaml, and I&#39;m sure my coding style is missing the point and doing many things the &#39;wrong&#39; way. But, at least it&#39;s a practical way to get my hands dirty with the language.&lt;br /&gt;&lt;br /&gt;First of all, let&#39;s do the traditional &quot;Hello, world!&quot; exercise;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;1. Installing OCaml&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Lots of instructions for various platforms here;&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;http://wiki.cocan.org/getting_started_with_ocaml&quot;&gt;http://wiki.cocan.org/getting_started_with_ocaml&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;I used the INRIA binary on Mac OS X.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;2. Hello World&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;A basic hello world program in OCaml. Using a text editor (preferably vim, but I&#39;ve heard that other programs sort of work), put the following into a file called &quot;hello.ml&quot;&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;Printf.printf &quot;Hello, world!\n&quot;;;&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Some points to note;&lt;br /&gt;&lt;br /&gt;1. The &#39;printf&#39; function comes from the &#39;Printf&#39; module. This module is available to all programs, so there&#39;s no need to do anything special to gain access to it.&lt;br /&gt;&lt;br /&gt;2. &quot;;;&quot; denotes the end of a chunk of code.&lt;br /&gt;&lt;br /&gt;You can run this code interactively using the &quot;ocaml toplevel&quot;&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&lt;br /&gt;$ ocaml&lt;br /&gt;Objective Caml version 3.10.1&lt;br /&gt;&lt;br /&gt;# Printf.printf &quot;Hello, world!\n&quot;;;  &lt;--- type this and press enter    Hello, world! - : unit = ()   # &lt;-------- Ctrl-D to exit  $    &lt;/code&gt;&lt;br /&gt;&lt;br /&gt;To run the program from the command-line there are several options.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;Option 1&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;$ ocaml hello.ml&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;This is the easiest way, using the ocaml interpreter.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;Option 2&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;$ ocamlc hello.ml -o hello&lt;br /&gt;$ ./hello&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;This creates an executable &quot;hello&quot; file of OCaml bytecode, which should run on any machine with the OCaml bytecode interpreter, &quot;ocamlrun&quot; installed. It also creates a &quot;hello.cmo&quot; and &quot;hello.cmi&quot; file, which are OCaml object and interface files, respectively. This seems to be an intermediate step, since &quot;hello&quot; runs just fine if you delete them.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;Option 3&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;$ ocamlopt hello.ml -o hello&lt;br /&gt;$ ./hello&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;This time, &quot;hello&quot; is a compiled binary which can run standalone. There will also be the &quot;hello.cmi&quot; file, as well as &quot;hello.cmx&quot; (OCaml *native* object file), and &quot;hello.o&quot; (object file for your OS).&lt;br /&gt;&lt;br /&gt;The interactive toplevel is great for noodling around, and option 1 is what I spend most of my time doing. Option 3 is what I will use if and when I write something that I want to use in production.&lt;br /&gt;&lt;br /&gt;So, that&#39;s &quot;Hello, world&quot; out of the way. In the next part, we&#39;ll look at reading from standard input and writing to standard output.&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;http://roninonrails.blogspot.com/2009/10/ocaml-for-impatient-part-2-reading.html&quot;&gt;OCaml for the impatient - part 2, reading standard input&lt;/a&gt;</description><link>http://roninonrails.blogspot.com/2009/10/ocaml-for-impatient-part-1-hello-world.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-37855474.post-688472074743683701</guid><pubDate>Sat, 30 May 2009 11:59:00 +0000</pubDate><atom:updated>2009-11-08T12:44:46.053+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">rails</category><title>Quick DB check script without loading Rails</title><description>I use &lt;a href=&quot;http://zabbix.com/&quot;&gt;Zabbix&lt;/a&gt; to monitor my servers. It works by having a central zabbix server make calls to zabbix-agent processes on the monitored servers. The zabbix-agent can invoke a user-created script on the server and return whatever one-line output the script provides.&lt;br /&gt;&lt;br /&gt;I needed a quick script to check the server to see if a set of database records exist, matching a given set of conditions. In this case, I&#39;m checking to see if some log data representing yesterday&#39;s web traffic has been imported correctly, on the monitored server.&lt;br /&gt;&lt;br /&gt;To start with, I used something like this;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;#!/usr/bin/env ruby&lt;br /&gt;&lt;br /&gt;ENV[&#39;RAILS_ENV&#39;] = &#39;production&#39;&lt;br /&gt;&lt;br /&gt;require File.dirname(__FILE__) + &#39;/../config/environment&#39;&lt;br /&gt;&lt;br /&gt;day = 1.day.ago.to_date&lt;br /&gt;hostname = `hostname`.chomp&lt;br /&gt;&lt;br /&gt;puts LogEntry.all(:conditions =&gt; {:day =&gt; day, :hostname =&gt; hostname}, :limit =&gt; 1).size&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;There could be a whole lot of log_entries records for yesterday, for this hostname. All I really want is to confirm that the importer script ran OK, so checking for the existence of one record that matches these criteria is sufficient and much faster than doing an aggregate query like a count.&lt;br /&gt;&lt;br /&gt;This script outputs a &#39;1&#39; if everything is OK, and a &#39;0&#39; if not.&lt;br /&gt;&lt;br /&gt;Hooking up a zabbix agent check to the script is easy enough, but whenever the server tries to call it, the check times out. The problem is that loading the entire Rails stack takes too long. Besides, it&#39;s overkill when all I want to do is run a single database query.&lt;br /&gt;&lt;br /&gt;So, with help from &lt;a href=&quot;http://www.dmathieu.com/2009/03/09/ruby-utiliser-active-record-sans-rails/&quot;&gt;here&lt;/a&gt; I changed it to this;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;#!/usr/bin/env ruby&lt;br /&gt;&lt;br /&gt;RAILS_ROOT = File.dirname(__FILE__) + &#39;/..&#39;&lt;br /&gt;require &#39;rubygems&#39;&lt;br /&gt;require RAILS_ROOT + &#39;/vendor/rails/activerecord/lib/active_record&#39;&lt;br /&gt;yaml_file = RAILS_ROOT + &#39;/config/database.yml&#39;&lt;br /&gt;db = YAML::load(File.read(yaml_file))[&#39;production&#39;]&lt;br /&gt;ActiveRecord::Base.establish_connection(db)&lt;br /&gt;&lt;br /&gt;class LogEntry &lt; ActiveRecord::Base; end &lt;br /&gt;&lt;br /&gt;hostname  = `hostname`.chomp&lt;br /&gt;yesterday = 1.day.ago.to_date &lt;br /&gt;&lt;br /&gt;puts LogEntry.all(:conditions =&gt; {:day =&gt; day, :hostname =&gt; hostname}, :limit =&gt; 1).size&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;If you&#39;ve got Rails installed as a gem on your server, you don&#39;t need to jump through all the &#39;RAILS_ROOT&#39; hoops. But, I vendor everything, so I need to tell ruby where to find active_record.&lt;br /&gt;&lt;br /&gt;Now the script is fast enough to respond to zabbix checks. If I needed it to be even faster, I could use lower-level ruby database code. But, this is quick enough, and importing ActiveRecord gives us the &quot;1.day.ago&quot; stuff too, keeping the code clean.</description><link>http://roninonrails.blogspot.com/2009/05/quick-db-check-script-without-loading.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-37855474.post-385524158439949771</guid><pubDate>Sat, 25 Apr 2009 11:24:00 +0000</pubDate><atom:updated>2009-04-29T12:36:11.624+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">rails</category><category domain="http://www.blogger.com/atom/ns#">ubuntu</category><title>Setting up Ubuntu 9.04 for Ruby on Rails development</title><description>I&#39;ve just installed Ubuntu 9.04 &quot;Jaunty Jackalope&quot;, and so far I&#39;m really impressed. It&#39;s slick and fast, with some really nice little touches (e.g. my laptop wakes up when I open the lid - I used to have to hit the power button to wake it up from suspend).&lt;br /&gt;&lt;br /&gt;Here are the steps I went through to set it up for Rails development;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Launch a terminal and become root&lt;/li&gt;&lt;li&gt;aptitude install build-essential&lt;/li&gt;&lt;/ul&gt;This gives you a c compiler and a bunch of other stuff essential to allow you to build other packages (hence the name)&lt;br /&gt;&lt;ul&gt;&lt;li&gt;aptitude install git-core&lt;/li&gt;&lt;/ul&gt;I use git for version control. Of course, there are a bunch of subversion and other packages available too. In Ubuntu 9.04, the version of git is 1.6.0.4, which is good enough for me.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;aptitude install phpunit php5-curl&lt;/li&gt;&lt;/ul&gt;I do some PHP development too. You can ignore this and the php mysql package later on, if you don&#39;t.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;aptitude install ttf-mscorefonts-installer&lt;/li&gt;&lt;/ul&gt;This makes fonts look a lot nicer in firefox.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;aptitude install rails vim-rails ruby1.8-dev&lt;/li&gt;&lt;/ul&gt;This will install rails 2.1.0. A later version would be nicer, but all my projects have their own version of rails in the vendor directory, so this is good enough.&lt;br /&gt;&lt;br /&gt;I use gvim as my editor. You can skip the vim-rails if you don&#39;t.&lt;br /&gt;&lt;br /&gt;The &quot;ruby1.8-dev&quot; package is important. Without this, you&#39;ll get failures loading &quot;mkmf&quot; whenever you try to install gems.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;aptitude install mysql-server-5.0 php5-mysql mysql-client-5.0 libmysqlclient15-dev&lt;/li&gt;&lt;/ul&gt;Ubuntu 9.04 comes with mysql 5.1, but a lot of my projects are still using 5.0, so I want to stick to that for now.&lt;br /&gt;&lt;br /&gt;&quot;libmysqlclient15-dev&quot; is required when we want to switch to a native mysql library for ruby. If you don&#39;t do this, you&#39;ll get deprecation warnings because the non-native, pure ruby library is going to be dropped soon.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;gem install ruby-debug hpricot mongrel&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;So far, those have been the only gems I needed to install to get to the point where my specs would run.&lt;br /&gt;&lt;br /&gt;Happy installing, and please add a comment if you&#39;ve got any suggestions.</description><link>http://roninonrails.blogspot.com/2009/04/setting-up-ubuntu-904-for-ruby-on-rails.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-37855474.post-3642519150895724014</guid><pubDate>Mon, 06 Apr 2009 08:17:00 +0000</pubDate><atom:updated>2009-04-06T10:49:47.900+01:00</atom:updated><title>Private web browsing via SOCKS proxy</title><description>So, &lt;a href=&quot;http://en.wikipedia.org/wiki/Her_Majesty%27s_Government&quot;&gt;Big Brother&lt;/a&gt; &lt;a href=&quot;http://www.independent.co.uk/news/uk/home-news/personal-web-data-to-be-stored-for-a-year-1662237.html&quot;&gt;is now requiring ISPs to keep a log of all your emails, internet phone calls and web browsing activity&lt;/a&gt;. This is, of course, to keep us safe from terrorists. How we stay safe from Big Brother is less clear.&lt;br /&gt;&lt;br /&gt;Anyway, with this in mind, here is a quick guide to using a &lt;a href=&quot;http://en.wikipedia.org/wiki/SOCKS&quot;&gt;SOCKS&lt;/a&gt; proxy to keep your web browsing private by routing all your activity via an encrypted &lt;a href=&quot;http://en.wikipedia.org/wiki/Ssh&quot;&gt;ssh&lt;/a&gt; tunnel to a server elsewhere.&lt;br /&gt;&lt;br /&gt;When you browse the internet normally, e.g. to www.google.com, connections go like this;&lt;br /&gt;&lt;br /&gt;Your machine -&gt; www.google.com&lt;br /&gt;&lt;br /&gt;Your ISP provides the connection in between, so they can log everything for those lovely government people. When you use a SOCKS proxy, it works like this;&lt;br /&gt;&lt;br /&gt;Your machine (1)-&gt; Your proxy (2)-&gt; www.google.com&lt;br /&gt;&lt;br /&gt;Connection (1) still goes via your ISP, but it&#39;s only a connection from your machine to your proxy, so they can&#39;t log where you&#39;re browsing to (in this case, www.google.com). All they can see is that you made a connection from your machine to your proxy. The connection is via SSH, so they have no way of knowing what information is travelling up and down the pipe.&lt;br /&gt;&lt;br /&gt;Connection (2) is from your proxy to www.google.com Whichever ISP connects your proxy to the Internet &lt;span style=&quot;font-weight: bold;&quot;&gt;can&lt;/span&gt; log your browsing activity. This is why it&#39;s important to have your proxy in a country where they won&#39;t do that.&lt;br /&gt;&lt;br /&gt;Let&#39;s get started. You will need;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;A server, located somewhere with a less &lt;a href=&quot;http://en.wikipedia.org/wiki/Stalinist&quot;&gt;Stalinist&lt;/a&gt; government. I use a virtual server in the US, which is quite ironic, really. This machine needs to be running an SSH server.&lt;/li&gt;&lt;/ul&gt;&lt;ul&gt;&lt;li&gt;You need to be able to ssh onto this server from the machine you want to browse from. I&#39;ve tried this from Mac OSX and Linux machines, where it&#39;s easy enough. If you&#39;re unlucky enough to be using Windows, it&#39;s probably possible to do this via &lt;a href=&quot;http://www.cygwin.com/&quot;&gt;cygwin&lt;/a&gt;.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Optionally, the &quot;&lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/125&quot;&gt;SwitchProxy&lt;/a&gt;&quot; add-on for Firefox makes it easy to switch from browsing via your SSH tunnel to browsing normally.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;First of all, you need to have things set up so that you can access your remote server va SSH. i.e. you can start a terminal window and type &quot;ssh yourserver&quot; and you&#39;ve got a terminal session on your remote server. If you can&#39;t do this, you&#39;re screwed, and Big Brother knows about all the &lt;a href=&quot;http://www.hasbro.com/mylittlepony/&quot;&gt;nasty websites&lt;/a&gt; you visit.&lt;br /&gt;&lt;br /&gt;Now, open up a terminal and type;&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;ssh -CND 9999 yourserver&lt;/blockquote&gt;&lt;br /&gt;&quot;-N&quot; tells SSH that we don&#39;t want to run any commands on the remote box (such as a login shell).&lt;br /&gt;&lt;br /&gt;&quot;-D 9999&quot; says &quot;listen on local port 9999 and forward whatever connections come in to that port, using the appropriate protocol, via the remote server. So, if you use local port 9999 to try to connect to www.google.com on port 80, you actually end up connecting to your remote server via SSH and then *from there* to port 80 on www.google.com&lt;br /&gt;&lt;br /&gt;Currently, the -D option to SSH only supports running as a SOCKS proxy, but since that&#39;s all we need, that&#39;s fine.&lt;br /&gt;&lt;br /&gt;&quot;-C&quot; compresses all data sent over the SSH tunnel. Browsing via a remote proxy server is a bit slower than browsing direct, so compression may help speed things up a bit.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;You will need to have a terminal window open, running this command, whenever you browse the net.&lt;/span&gt; There are clever ways to make this automatic, but I prefer to keep things simple.&lt;br /&gt;&lt;br /&gt;Now we just need to tell our web browser to connect via our new SOCKS proxy, instead of going straight to the target website.&lt;br /&gt;&lt;br /&gt;In Firefox, use the Edit menu and select;&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;Edit -&gt; Preferences -&gt; Advanced -&gt; Network -&gt; Settings&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;http://3.bp.blogspot.com/_ODXdwca8eVM/SdnFkGqbemI/AAAAAAAAAEI/TRJ_4zhFajI/s1600-h/Screenshot.png&quot;&gt;&lt;img style=&quot;margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 318px; height: 320px;&quot; src=&quot;http://3.bp.blogspot.com/_ODXdwca8eVM/SdnFkGqbemI/AAAAAAAAAEI/TRJ_4zhFajI/s320/Screenshot.png&quot; alt=&quot;&quot; id=&quot;BLOGGER_PHOTO_ID_5321501658769488482&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;Configure the settings shown. i.e. a SOCKS host on localhost port 9999. You can use more or less any port you like, but any port number below 1024 can only be forwarded by root, so it&#39;s easier to use a higher number. Whatever number you used in the &quot;ssh -ND ...&quot; command is the port you need to put here.&lt;br /&gt;&lt;br /&gt;For any sites where you don&#39;t want to use the SOCKS proxy, add them to the &quot;No proxy for&quot; section. For example, if you use BBC iPlayer, it won&#39;t work via a US host, so you need to not use the proxy for bbc.co.uk&lt;br /&gt;&lt;br /&gt;There are similar options for other browsers such as Safari and, if you really have to use it, Internet Explorer.&lt;br /&gt;&lt;br /&gt;Once you&#39;ve configured your proxy settings, you&#39;re good to go. Try browsing to a website and it should be going via your SSH tunnel. To confirm, try closing the terminal window and refreshing the page - you should see an error saying, &quot;Proxy Server Refused Connection&quot;. Restart the tunnel and refresh again and it should work.&lt;br /&gt;&lt;br /&gt;The &lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/125&quot;&gt;SwitchProxy&lt;/a&gt; add-on for Firefox makes it really easy to switch proxy configurations - hence the name - via a new entry in your &quot;Tools&quot; menu.&lt;br /&gt;&lt;br /&gt;Happy private web-browsing.</description><link>http://roninonrails.blogspot.com/2009/04/private-web-browsing-via-socks-proxy.html</link><author>noreply@blogger.com (Anonymous)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/_ODXdwca8eVM/SdnFkGqbemI/AAAAAAAAAEI/TRJ_4zhFajI/s72-c/Screenshot.png" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-37855474.post-4183049951117427979</guid><pubDate>Sat, 28 Mar 2009 13:44:00 +0000</pubDate><atom:updated>2009-11-08T12:45:25.401+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">nginx</category><title>Redundancy using Nginx for failover</title><description>&lt;p&gt;&lt;br /&gt;I&#39;ve got a website running on two servers, for redundancy, with an nginx load-balancer in front of them. If one server goes down, all requests will be sent to the other using this stanza in the nginx config;&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;upstream webservers {&lt;br /&gt;  server 1.2.3.4;  # web1&lt;br /&gt;  server 2.3.4.5;  # web2&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;This uses a round-robin system to send alternate requests to alternate servers. That&#39;s great for spreading the load around, but not exactly what I want.&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;The webservers are not heavily loaded, but they do run some quite complex reports against the database, the results of which are cached on the webserver&#39;s local filesystem. The cache is expired periodically.&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;Using a round-robin algorithm, we end up calculating each report twice per period - i.e. once on each webserver. What I really want is for all requests to go to web1, so that it&#39;s using its cache as efficiently as possible. web2 should only start receiving traffic if web1 goes down.&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;I don&#39;t know of a way to make nginx do exactly this, but you can use the &#39;weight&#39; parameter to fake it, like this;&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;upstream webservers {&lt;br /&gt;  server 1.2.3.4   weight=100000;  # web1&lt;br /&gt;  server 2.3.4.5;  # web2&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;Now, only one in every 100,000 web requests will go to web2, so the cache on web1 is being utilised to a much greater extent. If web1 goes down, web2 gets all the traffic. &lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;Redundancy plus efficient caching.&lt;br /&gt;&lt;/p&gt;</description><link>http://roninonrails.blogspot.com/2009/03/redundancy-using-nginx-for-failover.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-37855474.post-7370473102239840011</guid><pubDate>Sun, 08 Mar 2009 20:04:00 +0000</pubDate><atom:updated>2009-03-08T20:10:47.543+00:00</atom:updated><title>Rewired State</title><description>&lt;p&gt;I went to the &lt;a href=&quot;http://rewiredstate.org/&quot;&gt;Rewired State&lt;/a&gt; hack day yesterday.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;A brilliant time was had by all. Photos &lt;a href=&quot;http://www.flickr.com/photos/tags/rewiredstate/&quot;&gt;here&lt;/a&gt;, video &lt;a href=&quot;http://rewiredstate.blip.tv/&quot;&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;p&gt;Our hack was &lt;a href=&quot;http://whatsontheirminds.com/&quot;&gt;What&#39;s On Their Minds?&lt;/a&gt; (if that URL doesn&#39;t work, try &lt;a href=&quot;http://67.23.7.159/&quot;&gt;this link&lt;/a&gt;).&lt;/p&gt;</description><link>http://roninonrails.blogspot.com/2009/03/rewired-state.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-37855474.post-6851264087985377448</guid><pubDate>Sun, 30 Nov 2008 11:48:00 +0000</pubDate><atom:updated>2008-11-30T12:09:55.775+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">ruby itunes xbmc</category><title>Converting iTunes playlists with ruby</title><description>Just posting this here in case anyone else has a similar setup.&lt;br /&gt;&lt;br /&gt;I&#39;ve got an xbox running &lt;a href=&quot;http://xbmc.org/&quot; target=&quot;_blank&quot;&gt;XBMC&lt;/a&gt; hooked up to my home stereo system, but all my music is stored and organised in iTunes. The folder containing the music is shared so the xbox can see it, so all my music is available in XBMC with no problems. The one thing I was missing is all my iTunes playlists.&lt;br /&gt;&lt;br /&gt;Along with virtually every other music player, XBMC uses a &lt;a href=&quot;http://pastie.org/327008&quot; target=&quot;_blank&quot;&gt;standard .m3u text file&lt;/a&gt; format to define playlists. I think the &#39;#EXT...&#39; stuff is specific to XBMC, but I&#39;m not sure.&lt;br /&gt;&lt;br /&gt;Needless to say, iTunes is not compatible with this at all. But, it is possible to export a playlist from iTunes, in &lt;a href=&quot;http://pastie.org/327007&quot; target=&quot;_blank&quot;&gt;its own bizarre XML format&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;One &lt;a href=&quot;http://pastie.org/327009&quot; target=&quot;_blank&quot;&gt;quick ruby script&lt;/a&gt; later, and I can convert the iTunes XML to M3U, including a remapping of file locations because iTunes has a different name for the root music folder (in fact, it has 2 different names for it because I moved the folder once, but the older playlists seem to still use the original location, somehow. Hence the alternatives in the ITUNES_ROOT regexp).&lt;br /&gt;&lt;br /&gt;Finally, &lt;a href=&quot;http://pastie.org/327010&quot; target=&quot;_blank&quot;&gt;a noddy bash script&lt;/a&gt; to convert all the .xml playlist files in a directory to .m3u and transfer them to the xbox using &lt;a href=&quot;http://www.ncftp.com/&quot; target=&quot;_blank&quot;&gt;ncftp&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;I still have to right-click on my playlists in iTunes and choose &#39;Export Song List&#39; for each one, but that&#39;s not too arduous. I could probably write an AppleScript or something to do that for me, but in my case it&#39;s not worth the extra effort.&lt;br /&gt;&lt;br /&gt;If you find any of this stuff useful, please drop a note in the comments.&lt;br /&gt;&lt;br /&gt;Cheers&lt;br /&gt;&lt;br /&gt;David</description><link>http://roninonrails.blogspot.com/2008/11/converting-itunes-playlists-with-ruby.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-37855474.post-488828670696196927</guid><pubDate>Wed, 12 Nov 2008 21:20:00 +0000</pubDate><atom:updated>2008-11-19T21:04:58.663+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">github development</category><title>Client collaboration via github</title><description>&lt;div&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;UPDATE: The post-receive URL on github has now been moved to a new &quot;Services&quot; tab on the project edit page.&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;hr/&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;  It can be a hassle setting up and maintaining development environments&lt;br /&gt;  for clients; particularly if they&#39;re not very technical, or if they&lt;br /&gt;  use Windows.&lt;br /&gt;  &lt;br /&gt;&lt;br /&gt;  I usually set up a separate VPS for each client&#39;s project as a&lt;br /&gt;  development/staging server, and it would be great if they could&lt;br /&gt;  just edit the source files on there.  Then they could play around&lt;br /&gt;  and see the effect of their changes in realtime.  The trouble is&lt;br /&gt;  that this requires some kind of remote editing or file-syncing setup&lt;br /&gt;  (e.g. SSH+vi), which can be troublesome to set up.&lt;br /&gt;  &lt;br /&gt;&lt;br /&gt;  So, I&#39;m trying out a combination of github&#39;s in-browser source&lt;br /&gt;  editing, plus a post-receive URL pointing to the client&#39;s development&lt;br /&gt;  VPS. Here&#39;s how it should work.&lt;br /&gt;  &lt;ul&gt;&lt;br /&gt;    &lt;li&gt;      I set up the client&#39;s project as a github repository, and add the client as a collaborator.    &lt;/li&gt;&lt;br /&gt;    &lt;li&gt;     The client makes changes to the view/css files using github&#39;s in-browser editor.    &lt;/li&gt;&lt;br /&gt;    &lt;li&gt;      When they commit their changes, github posts to a listener on the client&#39;s development VPS (using a post-receive hook)    &lt;/li&gt;&lt;br /&gt;    &lt;li&gt;      The listener on the VPS pulls the client&#39;s changes    &lt;/li&gt;&lt;br /&gt;    &lt;li&gt;      The client views their development site to see the changes in (nearly) realtime    &lt;/li&gt;&lt;br /&gt;  &lt;/ul&gt;&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;  This is surprisingly easy to set up. I&#39;ll walk you through setting this up for a dummy &quot;Hello, world&quot; rails application. The ingredients we&#39;ll be using are;&lt;br /&gt;  &lt;ul&gt;&lt;br /&gt;    &lt;li&gt;      github - a free account will do fine, for this demo    &lt;/li&gt;&lt;br /&gt;    &lt;li&gt;      A development server - github will be posting to a URL on this server, so you need to be able to access it from the internet   &lt;/li&gt;&lt;br /&gt;    &lt;li&gt;     A listener to pull changes from github, whenever there&#39;s a new commit. I&#39;m going to use a very simple sinatra application, running via a command-line mongrel instance.    &lt;/li&gt;&lt;br /&gt;    &lt;li&gt;     An application to act as our super-important client project  &lt;/li&gt;&lt;br /&gt;  &lt;/ul&gt;&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;  Setup a simple rails project as a github repository. You can just use a&lt;br /&gt;  bare &quot;rails foobar&quot; project - no need to set up databases.&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;  Now, ssh to your development server (the one github will be posting to when your client makes changes). Setup a clone of the github repo.&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;  For this demo, use the &quot;public&quot; clone url (the one that starts with &quot;git://github.com&quot;).&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;  You can use this technique for a private repo - otherwise it would be a bit pointless - but that takes a bit more SSH setup than I want to get into in this post.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;  I&#39;m using &quot;screen&quot; so that I can start the demo application running&lt;br /&gt;  in a terminal and have it keep running after I log out. If you don&#39;t&lt;br /&gt;  know about the unix screen command, go learn about it right now.&lt;br /&gt;  After SSH it&#39;s one of the most useful tools to have in your toolbox.&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;  screen&lt;br /&gt;  cd dstest&lt;br /&gt;  script/server&lt;br /&gt;  [Ctrl-A D to detach from screen]&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;  So, now the application is running, and you can open a browser window to;&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;  http://your.development.server:3000/&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;  and see the &quot;Welcome to Ruby on Rails&quot; page.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;  Now for the listener which will pull our client&#39;s changes.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;  If you haven&#39;t already got them, you will need sinatra and mongrel on your development server;&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;  sudo gem install sinatra&lt;br /&gt;  sudo gem install mongrel&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;  Now, put the following into a file called &quot;gitmon.rb&quot;&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;  require &#39;rubygems&#39;&lt;br /&gt;  require &#39;sinatra&#39;&lt;br /&gt;  post &#39;/update_site&#39; do&lt;br /&gt;  dir = &#39;/home/david/dstest&#39;&lt;br /&gt;  system &quot;cd #{dir}; git pull&quot;&lt;br /&gt;  &quot;Site updated\n&quot;&lt;br /&gt;  end&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;  You will need to change the &quot;dir&quot; line to&lt;br /&gt;  be the full path to your rails project.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;  Now use screen again to start a listener that will continue to run after you log out;&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;  screen&lt;br /&gt;  ruby gitmon.rb&lt;br /&gt;  [Ctrl-A D to detach from screen]&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;  The listener will start up on Sinatra&#39;s&lt;br /&gt;  default port, 4567. You can test it by&lt;br /&gt;  visiting&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;  http://your.development.server:4567&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;  You should see &quot;Sinatra doesn&#39;t know this diddy.&quot;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;  Now we just need to tell github to post to the listener whenever it gets new commits.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;  Login to your github account and go to&lt;br /&gt;  your repository&#39;s edit page.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;http://2.bp.blogspot.com/_ODXdwca8eVM/SR29kd4Jf0I/AAAAAAAAAC0/PGQDGT7YSwk/s1600-h/Picture+2.png&quot;&gt;&lt;img style=&quot;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 98px;&quot; src=&quot;http://2.bp.blogspot.com/_ODXdwca8eVM/SR29kd4Jf0I/AAAAAAAAAC0/PGQDGT7YSwk/s320/Picture+2.png&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5268575573286420290&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;  Setup the &quot;Post-receive URL&quot; as;&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;  http://your.development.server:4567/update_site&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;br/&gt;&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;http://2.bp.blogspot.com/_ODXdwca8eVM/SR29nW9p2bI/AAAAAAAAAC8/jqVGL_BLxMQ/s1600-h/Picture+3.png&quot;&gt;&lt;img style=&quot;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 155px;&quot; src=&quot;http://2.bp.blogspot.com/_ODXdwca8eVM/SR29nW9p2bI/AAAAAAAAAC8/jqVGL_BLxMQ/s320/Picture+3.png&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5268575622970071474&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;  That should do it.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;  Now, edit a file (e.g. public/index.html) using the github in-browser editor (if you&#39;re not logged-in, you won&#39;t see the edit link). Commit your change.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;http://4.bp.blogspot.com/_ODXdwca8eVM/SR29dlXSyMI/AAAAAAAAACs/6GcYwM9oBuo/s1600-h/Picture+1.png&quot;&gt;&lt;img style=&quot;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 69px;&quot; src=&quot;http://4.bp.blogspot.com/_ODXdwca8eVM/SR29dlXSyMI/AAAAAAAAACs/6GcYwM9oBuo/s320/Picture+1.png&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5268575455037016258&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;  Refresh the browser window you&#39;ve got open to;&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;  http://your.development.server:3000/&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;  and you should see your changes (it can take a few seconds before your site updates).&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;  This is just the tip of the iceberg, and there are a lot of ways this technique could be extended (e.g. hooks to run migrations after a &quot;git pull&quot; on the development server, or getting the sinatra listener to post to Campfire whenever a client commits something).&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;  Also, there is a lot of information in the POST from github, which we&#39;re just ignoring here.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;  I hope you find this useful. I&#39;ll be setting it up for one of my clients very soon, so I&#39;m keen to see how it works out.&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;/div&gt;</description><link>http://roninonrails.blogspot.com/2008/11/client-collaboration-via-github.html</link><author>noreply@blogger.com (Anonymous)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/_ODXdwca8eVM/SR29kd4Jf0I/AAAAAAAAAC0/PGQDGT7YSwk/s72-c/Picture+2.png" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-37855474.post-8067265011880906612</guid><pubDate>Sat, 01 Nov 2008 14:07:00 +0000</pubDate><atom:updated>2009-03-21T13:54:43.227+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">ubuntu rails</category><title>Setting up Ubuntu 8.10 for Ruby on Rails development</title><description>Here&#39;s how I&#39;ve set up my laptop, running Ubuntu 8.10 (Intrepid Ibex) for Ruby on Rails development. You&#39;ll need to run the following commands from the command-line as root. These commands worked for me, but I take no responsibility if you trash your box by doing this ;)&lt;br /&gt;&lt;br /&gt;There is some duplication here, because I sometimes setup servers with different sub-sets of the software I use on my development laptop. Don&#39;t worry about this. It does no harm if you tell apt-get or aptitude to install the same package twice - it will just ignore you the second time.&lt;br /&gt;&lt;br /&gt;Not all of the gems here are required for RoR development, but I use cucumber for integration testing, and it has some pre-requisites.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;# General/Pre-requisite packages&lt;br /&gt;aptitude install \&lt;br /&gt;  build-essential \&lt;br /&gt;  screen \&lt;br /&gt;  subversion \&lt;br /&gt;  mysql-client \&lt;br /&gt;  telnet \&lt;br /&gt;  meld \&lt;br /&gt;  vim \&lt;br /&gt;  vim-gnome \&lt;br /&gt;  exuberant-ctags \&lt;br /&gt;  tk8.5 \&lt;br /&gt;  apache2-prefork-dev \&lt;br /&gt;  rcov&lt;br /&gt;&lt;br /&gt;# mysql server&lt;br /&gt;# use apt-get to avoid installing exim4&lt;br /&gt;DEBIAN_FRONTEND=noninteractive apt-get install --assume-yes \&lt;br /&gt;  mysql-server mysql-client \&lt;br /&gt;  libmysqlclient15-dev libmysql-ruby1.8&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;# git&lt;br /&gt;mkdir gitcore&lt;br /&gt;cd gitcore&lt;br /&gt;wget http://kernel.org/pub/software/scm/git/git-1.6.0.3.tar.gz&lt;br /&gt;apt-get build-dep git-core --assume-yes&lt;br /&gt;tar xzvf git-1.6.0.3.tar.gz&lt;br /&gt;cd git-1.6.0.3/&lt;br /&gt;./configure&lt;br /&gt;make&lt;br /&gt;make install&lt;br /&gt;cd&lt;br /&gt;&lt;br /&gt;# Ruby&lt;br /&gt;aptitude --assume-yes install ruby1.8-dev ruby1.8 ri1.8 rdoc1.8 \&lt;br /&gt;  irb1.8 libreadline-ruby1.8 libruby1.8 libopenssl-ruby&lt;br /&gt;ln -s /usr/bin/ruby1.8 /usr/local/bin/ruby;&lt;br /&gt;ln -s /usr/bin/ri1.8 /usr/local/bin/ri;&lt;br /&gt;ln -s /usr/bin/rdoc1.8 /usr/local/bin/rdoc;&lt;br /&gt;ln -s /usr/bin/irb1.8 /usr/local/bin/irb;&lt;br /&gt;ln -s /usr/local/bin/ruby /usr/bin/ruby&lt;br /&gt;&lt;br /&gt;# Rubygems. You REALLY don&#39;t want to let aptitude install rubygems.&lt;br /&gt;wget http://rubyforge.org/frs/download.php/45905/rubygems-1.3.1.tgz&lt;br /&gt;tar xzf rubygems-1.3.1.tgz&lt;br /&gt;cd rubygems-1.3.1&lt;br /&gt;ruby setup.rb&lt;br /&gt;ln -s /usr/bin/gem1.8 /usr/bin/gem&lt;br /&gt;for g in rails rake capistrano capistrano-ext hpricot treetop ruby-debug term-ansicolor mongrel cheat passenger annotate-models rak; do gem install $g; done&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Have fun.&lt;br /&gt;&lt;br /&gt;David</description><link>http://roninonrails.blogspot.com/2008/11/setting-up-ubuntu-810-for-ruby-on-rails.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-37855474.post-6617167306092382193</guid><pubDate>Wed, 23 Jul 2008 09:10:00 +0000</pubDate><atom:updated>2008-07-23T10:19:41.623+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">vim</category><title>Finding files in vim</title><description>Some of my vim-challenged colleagues, when they try switching to vim, miss the Cmd-T function that lets them type parts of a filename and open the corresponding file.&lt;br /&gt;&lt;br /&gt;After a quick search of the tips on vim.org, it turns out that someone has implemented exactly this function; &lt;br /&gt;&lt;br /&gt;http://www.vim.org/tips/tip.php?tip_id=1432&lt;br /&gt;&lt;br /&gt;This has the drawback that it will find blah.rb.svn-base when you look for blah.rb. So, I&#39;ve tweaked it a little to skip any svn files, and also not to look in the &#39;vendor&#39; directory, so it doesn&#39;t find rails source files.&lt;br /&gt;&lt;br /&gt;http://gist.github.com/1625&lt;br /&gt;&lt;br /&gt;Have fun</description><link>http://roninonrails.blogspot.com/2008/07/finding-files-in-vim.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-37855474.post-7953755297863605153</guid><pubDate>Tue, 22 Jul 2008 13:40:00 +0000</pubDate><atom:updated>2008-07-23T10:20:00.403+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">git</category><title>Automatically &quot;git rm&quot; all deleted files</title><description>Love git. Hate having to &quot;git rm&quot; individual files whenever I delete a bunch of stuff (yes, I know I can &quot;git rm foo/*&quot;, but what if I didn&#39;t remove *all* the files in foo/ ?).&lt;br /&gt;&lt;br /&gt;So, a quick ruby script: &lt;a href=&quot;http://gist.github.com/948&quot;&gt;gitrmall.rb&lt;/a&gt;</description><link>http://roninonrails.blogspot.com/2008/07/automatically-git-rm-all-deleted-files.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-37855474.post-8816547933240001690</guid><pubDate>Tue, 15 Jul 2008 22:31:00 +0000</pubDate><atom:updated>2008-07-15T23:51:42.687+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">vim shoulda</category><title>Disabling tests in Shoulda</title><description>I&#39;m using &lt;a href=&quot;http://www.thoughtbot.com/projects/shoulda&quot;&gt;Shoulda&lt;/a&gt; on a client project, although I prefer &lt;a href=&quot;http://rspec.info/&quot;&gt;rspec&lt;/a&gt; myself.&lt;br /&gt;&lt;br /&gt;One of the things I miss from rspec is the ability to turn off a test, temporarily, by changing;&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;it &quot;should do something&quot;&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;...to;&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;xit &quot;should do something&quot;&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Tests which are disabled in this way show up as &quot;Example disabled: it should do something&quot;, when the specs are executed.&lt;br /&gt;&lt;br /&gt;Using this with &lt;a href=&quot;http://www.zenspider.com/ZSS/Products/ZenTest/&quot;&gt;autotest&lt;/a&gt; and a couple of quick vim macros, my workflow goes something like this;&lt;br /&gt;&lt;br /&gt;1. See that there is a failure in a particular test&lt;br /&gt;2. Position the cursor in that test and hit &quot;,ofs&quot; this triggers my macro to disable all specs except the current one, and save the file so that autotest runs it again.&lt;br /&gt;3. Hack, hack, hack to fix the bug, or develop the feature that the failing test relates to.&lt;br /&gt;4. Hit &quot;,ons&quot; to turn all the specs back on and save the file again.&lt;br /&gt;&lt;br /&gt;In Shoulda, renaming a test from;&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;should &quot;do something&quot;&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;...to;&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;xshould &quot;do something&quot;&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Gives you a nice NoMethodError.&lt;br /&gt;&lt;br /&gt;But, this is ruby, so we just add this to our test/test_helper.rb&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;class ActiveSupport::TestCase&lt;br /&gt;  def self.xshould(name, &amp;amp;block)&lt;br /&gt;    puts &quot;disabled test: #{name}&quot;&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Now, we get pretty much the same behaviour as with &#39;xit&#39; in rspec.&lt;br /&gt;&lt;br /&gt;For any vim users in the audience, here are the macros (actually, abbreviations) I use to turn tests, specs and shouldas on and off. Just add to your .gvimrc (or .vimrc) file;&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&quot;rspec&lt;br /&gt;map ,ofs :%s/ it / xit &lt;CR&gt;&#39;&#39;?xit&lt;CR&gt;x:w&lt;CR&gt;&lt;br /&gt;map ,ons :%s/ xit / it /&lt;CR&gt;&#39;&#39;:w&lt;CR&gt;&lt;br /&gt;&lt;br /&gt;&quot;shoulda&lt;br /&gt;map ,ofh :%s/ should / xshould &lt;CR&gt;&#39;&#39;?xshould&lt;CR&gt;x:w&lt;CR&gt;&lt;br /&gt;map ,onh :%s/ xshould / should /&lt;CR&gt;&#39;&#39;:w&lt;CR&gt;&lt;br /&gt;&lt;br /&gt;&quot;test/unit&lt;br /&gt;map ,oft :%s/def test/def xtest&lt;CR&gt;&#39;&#39;?xtest&lt;CR&gt;x:w&lt;CR&gt;&lt;br /&gt;map ,ont :%s/def xtest/def test/&lt;CR&gt;&#39;&#39;:w&lt;CR&gt;&lt;br /&gt;&lt;/code&gt;</description><link>http://roninonrails.blogspot.com/2008/07/disabling-tests-in-shoulda.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-37855474.post-5525138293926795563</guid><pubDate>Tue, 01 Jul 2008 22:52:00 +0000</pubDate><atom:updated>2008-11-27T08:30:03.569+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">activerecord</category><category domain="http://www.blogger.com/atom/ns#">rails</category><title>ActiveRecord commit timing</title><description>I&#39;m building an application where we&#39;re using a message queue.&lt;br /&gt;&lt;br /&gt;New objects add their IDs to a &lt;a href=&quot;http://xph.us/software/beanstalkd/&quot;&gt;beanstalkd&lt;/a&gt; queue via an after_create callback. Some daemons monitor beanstalk, and grab objects to process, as soon as they hit the queue.&lt;br /&gt;&lt;br /&gt;We were getting errors from the daemons, saying &quot;record with ID &#39;N&#39; not found&quot;, but when we looked for object N via script/console, or the database, there it was.&lt;br /&gt;&lt;br /&gt;It turns out that the after_create callback (along with all the other object creation callbacks) occurs *inside* a transaction. So, this is what was happening;&lt;br /&gt;&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;http://1.bp.blogspot.com/_ODXdwca8eVM/SGq5dYSjfFI/AAAAAAAAACk/nTSBcVH5sJM/s1600-h/Diagram1.png&quot;&gt;&lt;img style=&quot;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;&quot; src=&quot;http://1.bp.blogspot.com/_ODXdwca8eVM/SGq5dYSjfFI/AAAAAAAAACk/nTSBcVH5sJM/s320/Diagram1.png&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5218187032649759826&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;There are a couple of ways around this;&lt;br /&gt;&lt;br /&gt;Call &quot;self.class.connection.commit_db_transaction&quot; in the model, after the after_create method. This works, but it smells really bad.&lt;br /&gt;&lt;br /&gt;A nicer way is to add an &quot;after_commit&quot; callback, like &lt;a href=&quot;http://elimiller.blogspot.com/2007/06/proper-cache-expiry-with-aftercommit.html&quot;&gt;this&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;http://freelancing-gods.com/&quot;&gt;Pat Allan&lt;/a&gt; has &lt;a href=&quot;http://github.com/freelancing-god/thinking-sphinx/tree/master/lib/thinking_sphinx/active_record/delta.rb&quot;&gt;some modifications to this&lt;/a&gt;, to make it play nicer with Rails 2.0 and 2.1&lt;br /&gt;&lt;br /&gt;Or, there is a version &lt;a href=&quot;http://github.com/GUI/after_commit/tree/master&quot;&gt;here&lt;/a&gt; which handles nested transactions.&lt;br /&gt;&lt;br /&gt;Many thanks to Pat for showing me the after_commit stuff.</description><link>http://roninonrails.blogspot.com/2008/07/activerecord-commit-timing.html</link><author>noreply@blogger.com (Anonymous)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/_ODXdwca8eVM/SGq5dYSjfFI/AAAAAAAAACk/nTSBcVH5sJM/s72-c/Diagram1.png" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-37855474.post-9058364755168575038</guid><pubDate>Sat, 07 Jun 2008 10:49:00 +0000</pubDate><atom:updated>2008-06-07T20:23:50.941+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">activerecord</category><category domain="http://www.blogger.com/atom/ns#">composite_primary_keys</category><title>Using non-standard primary keys with ActiveRecord in Ruby on Rails</title><description>A Rails application I&#39;m currently working on has a &#39;User&#39; model. For the purposes of this app. a &#39;user&#39; is uniquely identified by their mobile phone number - a user must have a mobile phone number, and may only have a single one. A second mobile phone number must be a second user.&lt;br /&gt;&lt;br /&gt;I&#39;m going to need the mobile phone number in other models - e.g. &quot;User has_many :messages&quot;, and I&#39;d like to have a &quot;mobile_phone_number&quot; attribute in my Message model, so that I can say &quot;message.mobile_phone_number&quot; without having to join the users table all the time, like this; &quot;message.user.mobile_phone_number&quot;.&lt;br /&gt;&lt;br /&gt;I can get what I want if the mobile_phone_number is the primary key of the User model. Let&#39;s try it.&lt;br /&gt;&lt;br /&gt;Here is the migration (this is specific to mysql);&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;class CreateUsers &lt; ActiveRecord::Migration&lt;br /&gt;  def self.up&lt;br /&gt;    create_table :users, :id =&gt; false do |t|&lt;br /&gt;      t.integer :mobile_phone_number&lt;br /&gt;    end&lt;br /&gt;    execute &quot;alter table users modify column mobile_phone_number bigint unsigned primary key&quot;&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def self.down&lt;br /&gt;    drop_table :users&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;(The alter table statement is a bit of a hack. Another way to achieve the same result would be to use the &lt;a href=&quot;http://agilewebdevelopment.com/plugins/mysql_bigint&quot;&gt;mysql_bigint&lt;/a&gt; gem)&lt;br /&gt;&lt;br /&gt;Here is the model;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;class User &lt; ActiveRecord::Base&lt;br /&gt;  set_primary_key :mobile_phone_number&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Looks OK, but this is what happens when you try to create a User;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt;&gt; u = User.create :mobile_phone_number =&gt; 447123456789&lt;br /&gt;=&gt; #&amp;lt;User mobile_phone_number: 0&gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Where did that &#39;0&#39; come from, and what happened to the mobile_phone_number?&lt;br /&gt;&lt;br /&gt;A quick look in the logs shows this;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;  WARNING: Can&#39;t mass-assign these protected attributes: mobile_phone_number&lt;br /&gt;  SQL (0.000146)   BEGIN&lt;br /&gt;  User Create (0.000222)   INSERT INTO `users` VALUES(DEFAULT)&lt;br /&gt;  SQL (0.000422)   COMMIT&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;So, Rails is not sending the primary key column value in the SQL create statement. This would make sense if, as usual, our primary key was an autoincrement column. The database would supply that value for us, so we don&#39;t want to send it. But, in our case, we want to set the primary key ourselves, so this behaviour is wrong. (The 0 comes from the default value of the column)&lt;br /&gt;&lt;br /&gt;You can try using &#39;attr_accessible&#39; to allow you to assign to the mobile_phone_number, but it doesn&#39;t work (presumably because it&#39;s the primary key that we&#39;re trying to assign to).&lt;br /&gt;&lt;br /&gt;We need some way to override the standard Rails primary key behaviour so it does what we want. That sounds a lot like the &lt;a href=&quot;http://compositekeys.rubyforge.org/&quot;&gt;composite_primary_keys gem&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;So, after installing the gem (in my case, after installing &lt;a href=&quot;http://svn.techno-weenie.net/projects/plugins/gems/&quot;&gt;Rick Olson&#39;s &#39;gems&#39; plugin&lt;/a&gt; so that I can put the composite_primary_keys gem in my project&#39;s vendor directory), here is the updated model;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;require &#39;composite_primary_keys&#39;&lt;br /&gt;class User &lt; ActiveRecord::Base&lt;br /&gt;  set_primary_keys :mobile_phone_number&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Note that &#39;set_primary_key&#39; has changed to &#39;set_primary_keys&#39;. &lt;br /&gt;Now, we get this;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt;&gt; u = User.create :mobile_phone_number =&gt; 447123456789&lt;br /&gt;=&gt; #&amp;lt;User mobile_phone_number: 447123456789&gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Much better. Even though we&#39;re not using a composite_primary_key, the gem is overriding the default Rails behaviour so that the key we want is being set by the create statement. &lt;br /&gt;&lt;br /&gt;If you know a more elegant way to do this, please let me know.</description><link>http://roninonrails.blogspot.com/2008/06/using-non-standard-primary-keys-with.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>1</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-37855474.post-6443418550602153554</guid><pubDate>Sat, 26 Apr 2008 17:40:00 +0000</pubDate><atom:updated>2008-06-07T20:23:44.752+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">composite_primary_keys</category><category domain="http://www.blogger.com/atom/ns#">rails</category><title>Gotcha: composite_primary_keys gem</title><description>&lt;a href=&quot;http://compositekeys.rubyforge.org/&quot;&gt;Dr. Nic&#39;s composite_primary_keys gem&lt;/a&gt; is incredibly helpful if you&#39;re writing rails code against a legacy database, or in any other situation where you can&#39;t or won&#39;t follow the rails convention of having a single field as your model&#39;s primary key.&lt;br /&gt;After doing the usual;&lt;br /&gt;&lt;blockquote&gt;sudo gem install composite_primary_keys&lt;/blockquote&gt;...and requiring it in you environment.rb file, you can define a model like this;&lt;br /&gt;&lt;blockquote&gt;class Membership &lt; ActiveRecord::Base&lt;br /&gt;  set_primary_keys :user_id, :group_id&lt;br /&gt;  ...&lt;br /&gt;end&lt;/blockquote&gt;Brilliant.&lt;br /&gt;But, there is one subtlety you need to be aware of. If your model mixes in any modules, you might end up writing something like this;&lt;br /&gt;&lt;blockquote&gt;class Membership &lt; ActiveRecord::Base&lt;br /&gt;  include MyAwesomeModule&lt;br /&gt;  set_primary_keys :user_id, :group_id&lt;br /&gt;  ...&lt;br /&gt;end&lt;/blockquote&gt;Looks fine, doesn&#39;t it? Unfortunately, your tests will now break with lots of errors like this;&lt;br /&gt;&lt;blockquote&gt;ActiveRecord::StatementInvalid in &#39;Membership should foobar&#39;&lt;br /&gt;Mysql::Error: Column count doesn&#39;t match value count at row 1: INSERT INTO memberships (`user_id`, `group_id`, ... , id) VALUES (...whatever...)&lt;br /&gt;/Users/david/myproj/vendor/composite_primary_keys-0.9.90/lib/composite_primary_keys/base.rb:106:in `create_without_callbacks&#39;&lt;/blockquote&gt;See that last &lt;span style=&quot;font-weight: bold;&quot;&gt;id&lt;/span&gt;, just before &lt;span style=&quot;font-weight: bold;&quot;&gt;VALUES&lt;/span&gt;? It shouldn&#39;t be there. Something weird is going on, because composite_primary_keys doesn&#39;t seem to be doing its thing.&lt;br /&gt;The solution is to make sure the call to &quot;set_primary_keys&quot; is the &lt;span style=&quot;font-weight: bold;&quot;&gt;first&lt;/span&gt; thing executed in your model;&lt;br /&gt;&lt;blockquote&gt;class Membership &lt; ActiveRecord::Base&lt;br /&gt;  set_primary_keys :user_id, :group_id&lt;br /&gt;   include MyAwesomeModule&lt;br /&gt;    ...&lt;br /&gt; end&lt;/blockquote&gt; Remember this, and everything works fine. Forget it, and you&#39;ll have lots of fun with the debugger.&lt;br /&gt;If I were braver, smarter and kinder, I would dive headfirst into the code and try to fix it. But, I&#39;ve got work to do.</description><link>http://roninonrails.blogspot.com/2008/04/gotcha-compositeprimarykeys-gem.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-37855474.post-9009467333144361927</guid><pubDate>Sun, 13 Apr 2008 16:40:00 +0000</pubDate><atom:updated>2008-06-07T20:23:24.876+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">file_uploads</category><category domain="http://www.blogger.com/atom/ns#">rails</category><category domain="http://www.blogger.com/atom/ns#">rspec</category><title>Testing file uploads with RSpec on Rails</title><description>I was writing specs for a controller that processes uploaded images, and came up with this way of mocking a file upload control. So, I thought I&#39;d share it.&lt;br /&gt;Say you have a WibbleController which can replace the image belonging to a particular wibble. In your wibble/edit view you probably have something like this;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;% form_for(@wibble, :html =&gt; { :multipart =&gt; true }) do |f| %&gt;&lt;br /&gt;...&lt;br /&gt;Replace with new image:&lt;br /&gt;&lt;%= f.file_field :new_file %&gt;&lt;br /&gt;...&lt;br /&gt;&lt;% end -%&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;In your controller, there will be something that reads the streamed file data that the browser posts when you choose and submit a file.&lt;br /&gt;In your spec, you can create an ActionController::UploadedStringIO object (which is what your controller will see), but it won&#39;t have access to the file data. So, we need to monkey patch it a bit.&lt;br /&gt;The two methods we need to override are &quot;read&quot;, which will return the file contents, and &quot;size&quot; (guess what that does). So, define a method in your spec file like so;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;def mock_uploader(file, type = &#39;image/png&#39;)&lt;br /&gt;filename = &quot;%s/%s&quot; % [ File.dirname(__FILE__), file ]&lt;br /&gt;uploader = ActionController::UploadedStringIO.new&lt;br /&gt;uploader.original_path = filename&lt;br /&gt;uploader.content_type = type&lt;br /&gt;def uploader.read&lt;br /&gt;  File.read(original_path)&lt;br /&gt;end&lt;br /&gt;def uploader.size&lt;br /&gt;  File.stat(original_path).size&lt;br /&gt;end&lt;br /&gt;uploader&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Then, you could write a spec like this (&#39;foo.png&#39; should be a suitable image file in your spec/controllers directory);&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;it &quot;should upload an image&quot; do&lt;br /&gt; Image.delete_all&lt;br /&gt; uploader = mock_uploader &#39;foo.png&#39;&lt;br /&gt; post :update, {&lt;br /&gt;   :id =&gt; @object.id,&lt;br /&gt;   :wibble =&gt; { :image_file =&gt; uploader }&lt;br /&gt; }&lt;br /&gt; response.should be_success&lt;br /&gt; Image.count.should == 1&lt;br /&gt; i = Image.find(:first)&lt;br /&gt; i.filename.should == uploader.original_path&lt;br /&gt; i.contents.length.should == uploader.size&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;There may well be a better way to do this, in which case please let me know via the comments.</description><link>http://roninonrails.blogspot.com/2008/04/testing-file-uploads-with-rspec-on.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-37855474.post-568424492678431227</guid><pubDate>Tue, 08 Apr 2008 12:17:00 +0000</pubDate><atom:updated>2009-11-08T12:45:48.296+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">rails</category><title>Auto-rotate Rails log files</title><description>I&#39;ve been searching for a way to make my rails apps rotate their log files automatically, the way apache does it. In my case, I get logs like this;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;access_log.20080407&lt;/li&gt;&lt;li&gt;error_log.20080407&lt;/li&gt;&lt;/ul&gt;i.e. a log file per day, with the date as the suffix of the filename.&lt;br /&gt;When a new day rolls around, a new file is started. Simple.&lt;br /&gt;Hunting around on the web, most people seem to be writing a /etc/logrotate.conf file to achieve something like this. This has a couple of problems;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;After switching to a new log file, all your mongrel/fastcgi processes need to be restarted. Otherwise, you get a segfault when they try to log to a file that&#39;s not there anymore.&lt;/li&gt;&lt;li&gt;The /etc/logrotate.conf file is yet another file whose deployment needs to be managed (potentially along with a cron entry to kick off logrotate at the appropriate time).&lt;/li&gt;&lt;/ul&gt;So, I kept digging and eventually found &lt;a href=&quot;http://blog.playlouder.com/index.php/2007/07/23/log-rotation-with-rails-and-cronolog&quot;&gt;this post&lt;/a&gt;&lt;br /&gt;So, Rails can be told to log to a pipe, just like apache. I&#39;m not familiar with cronolog, so this is what I ended up putting in my config/environments/production.rb file;&lt;br /&gt;&lt;blockquote&gt;config.active_record.colorize_logging = false&lt;br /&gt;&lt;br /&gt;log_pipe = IO.popen(&quot;/usr/sbin/rotatelogs #{RAILS_ROOT}/log/production_log.%Y%m%d 86400&quot;, &#39;a&#39;)&lt;br /&gt;&lt;br /&gt;config.logger = Logger.new(log_pipe)&lt;/blockquote&gt;[the second entry is supposed to be a single line from &#39;log_pipe = &#39; to &#39;)&#39; ]&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Turn off colorized logs, because they irritate me.&lt;/li&gt;&lt;li&gt;Open a pipe to rotatelogs (I use &#39;a&#39; for &#39;append&#39;, instead of &#39;w&#39; for &#39;write&#39;, but it doesn&#39;t seem to make any difference on my system - the file is appended to when I restart rails, whichever I choose).&lt;/li&gt;&lt;li&gt;Point the rails logger to the pipe.&lt;/li&gt;&lt;/ol&gt;This seems to do exactly what I want, with minimal mucking about.</description><link>http://roninonrails.blogspot.com/2008/04/auto-rotate-rails-log-files.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-37855474.post-1320280381792181213</guid><pubDate>Tue, 18 Mar 2008 13:59:00 +0000</pubDate><atom:updated>2009-11-08T12:46:11.002+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">osx</category><category domain="http://www.blogger.com/atom/ns#">ubuntu</category><category domain="http://www.blogger.com/atom/ns#">vmware</category><title>Cloning Ubuntu Gutsy virtual machines on OSX with VMWare Fusion</title><description>I use &lt;a href=&quot;http://www.vmware.com/products/fusion/&quot;&gt;VMWare Fusion&lt;/a&gt; on my Mac to create virtual machines so that I can see exactly how my code will behave when it&#39;s deployed on &lt;a href=&quot;https://wiki.ubuntu.com/GutsyGibbon&quot;&gt;Ubuntu Gutsy&lt;/a&gt; server.&lt;br /&gt;I want to setup a few servers, so that I can experiment with different load-balancing solutions. So, I took a virtual machine that I had already configured, and copied it using Finder. When I launched the VM, it asked me if I had moved it or copied it. I clicked &quot;I copied it&quot;, and the machine carried on booting.&lt;br /&gt;&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;http://3.bp.blogspot.com/_ODXdwca8eVM/R9_VP_DePCI/AAAAAAAAACc/_moBnj8pus0/s1600-h/Picture+2.png&quot;&gt;&lt;img style=&quot;margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;&quot; src=&quot;http://3.bp.blogspot.com/_ODXdwca8eVM/R9_VP_DePCI/AAAAAAAAACc/_moBnj8pus0/s320/Picture+2.png&quot; alt=&quot;&quot; id=&quot;BLOGGER_PHOTO_ID_5179092567100111906&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;Unfortunately, the networking was broken - the machine kept insisting that eth0 was not present. After a bit of research, I found &lt;a href=&quot;http://communities.vmware.com/thread/46069&quot;&gt;this thread&lt;/a&gt; on the VMWare forums.&lt;br /&gt;The problem is that VMWare assigns a new MAC address to the machine when you copy it - which is sensible, since you can&#39;t have two instances of the same MAC address on the same network. But, Linux stores the MAC address of the card when the network interface is configured, and VMWare has no way of telling Linux about the new MAC address. So, Linux thinks it&#39;s original network card has disappeared, and that a new card with a different MAC address has been installed, and gets very confused.&lt;br /&gt;The solution, for Ubuntu 7.10 (Gutsy Gibbon), is to edit this file as root;&lt;br /&gt;&lt;blockquote&gt;/etc/udev/rules.d/70-persistent-net.rules&lt;/blockquote&gt;Here is the file before editing;&lt;br /&gt;&lt;blockquote&gt;# This file was automatically generated by the /lib/udev/write_net_rules&lt;br /&gt;# program, probably run by the persistent-net-generator.rules rules file.&lt;br /&gt;#&lt;br /&gt;# You can modify it, as long as you keep each rule on a single line.&lt;br /&gt;&lt;br /&gt;# PCI device 0x1022:0x2000 (pcnet32)&lt;br /&gt;SUBSYSTEM==&quot;net&quot;, DRIVERS==&quot;?*&quot;, ATTRS{address}==&quot;&lt;span style=&quot;color: rgb(255, 0, 0);&quot;&gt;00:0c:29:ba:e7:7a&lt;/span&gt;&quot;, NAME=&quot;eth0&quot;&lt;br /&gt;&lt;br /&gt;# PCI device 0x1022:0x2000 (pcnet32)&lt;br /&gt;SUBSYSTEM==&quot;net&quot;, DRIVERS==&quot;?*&quot;, ATTRS{address}==&quot;&lt;span style=&quot;color: rgb(51, 255, 51);&quot;&gt;00:0c:29:e4:ee:d4&lt;/span&gt;&quot;, NAME=&quot;eth1&quot;&lt;br /&gt;&lt;/blockquote&gt;(The SUBSYSTEM ... NAME stuff is all on a single line, but the blog theme causes it to wrap)&lt;br /&gt;The red MAC address is the original one, the same address as in the original virtual machine we created the copy from. As far as the Linux server is concerned, this network card has just vanished, because it can&#39;t see any card installed with that MAC address.&lt;br /&gt;The green address is the new MAC address that VMWare created. The Linux server thinks it has a network card plugged in, with this MAC address (because VMWare is telling it so), but it&#39;s not configured.&lt;br /&gt;All we need to do is to edit the file so that the card which is installed is known as &quot;eth0&quot;, and get rid of the old MAC address that our original source VM is still using. i.e. we delete the eth0 line and rename eth1 to eth0.&lt;br /&gt;So, after editing, the file looks like this;&lt;br /&gt;&lt;blockquote&gt;# This file was automatically generated by the /lib/udev/write_net_rules&lt;br /&gt;# program, probably run by the persistent-net-generator.rules rules file.&lt;br /&gt;#&lt;br /&gt;# You can modify it, as long as you keep each rule on a single line.&lt;br /&gt;&lt;br /&gt;# PCI device 0x1022:0x2000 (pcnet32)&lt;br /&gt;SUBSYSTEM==&quot;net&quot;, DRIVERS==&quot;?*&quot;, ATTRS{address}==&quot;00:0c:29:e4:ee:d4&quot;, NAME=&quot;eth0&quot;&lt;/blockquote&gt;&lt;br /&gt;Now, reboot the VM (/etc/init.d/networking restart is &lt;span style=&quot;font-weight: bold;&quot;&gt;not&lt;/span&gt; enough - you need to reboot), and all should be fine. Rinse and repeat for as many instances of your server as you need.</description><link>http://roninonrails.blogspot.com/2008/03/cloning-ubuntu-gutsy-virtual-machines.html</link><author>noreply@blogger.com (Anonymous)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/_ODXdwca8eVM/R9_VP_DePCI/AAAAAAAAACc/_moBnj8pus0/s72-c/Picture+2.png" height="72" width="72"/><thr:total>0</thr:total></item></channel></rss>