Feed InformerRubyConf 2005 blog digestRubyConf 2005 blog digestRespective post owners and feed distributors2013-07-09T07:15:29ZFeed Informer http://feed.informer.com/http://feed.informer.com/widgets/75FTX0S2THPSA: Getting Microsoft Outlook (or any other Applescript-able app)'s dictionary<div class="content" tabindex="0"><div class="ac-container ac-adaptiveCard" streaming=""><div class="ac-textBlock"><p>So you’re on a Mac and you need to use Microsoft Outlook. Maybe it’s not the best, but luckily, Outlook is scriptable via OSX automation (AppleScript/Javascript/Objective C). Unfortunately, you may have discovered that there are hardly any resources on the internet for AppleScript and Outlook. Sure, there are some examples out there (old ones) and some questions on Stack Overflow and similar sites, but you might find that they don’t cover the things you want to do. In my case, I just wanted to set the importance of an outgoing email I already constructed with AppleScript to high.</p><p>If only there were some sort of reference. Once upon a time, there was an official Microsoft Office 2004 (yes, that old) Reference, but that is nowhere to be found. You’ll find a number of posts of people failing to get that reference, in many cases reaching out to Microsoft support, who also cannot provide any answers.</p><p>This was me, for years. Every now and then, I would look again, hoping to find the right magic keywords to finally get an answer. I’ve also tried various AI chatbots, like Bard, Bing, ChatGPT, but they have all failed to give me the answer to my questions or references that still exist. Yes, even the mighty “Generative AI that will change everything” failed me here.</p><p>Luckily, today I stumbled upon this article [<a href="https://stackoverflow.com/questions/64445534/where-is-the-ozfficial-documentation-applescript-commands-for-powerpoint" target="_blank">where is the official documentation applescript commands for powerpoint</a>], which finally gave me what I was looking for. Gotta give credit where credit is due!</p><p>I learned that you can add the application and then get access to its AppleScript reference via the Script Editor library. Search has in the past pointed me to the Script Editor’s library, but if you open it up, you’ll find it only has entries for Apple-made software.</p><p>Let me save you a few years of looking and get you on your way to automating Outlook (or any other application not made by Apple that can be scripted)</p><ol><li>Open Script Editor</li><li>Go to Window > Library<div class="separator" style="clear: both; text-align: center;"> <a href="https://blogger.googleusercontent.com/img/a/AVvXsEjX99ObleRGofZ9wyPjkZwOAWjM9CWbWpjRquCp4oRG4tGndtywTaYb5Umrqvi-zjlw597IadM9Rdj8jZosfqAWrdA4OeRSkVz2qFfooZq3dC3k0sewuSTyjdvAeVZ_QEFMUBeeKfSFCQ_CP2fIqx4OGOhVfPnj--aRoQUA0JmM80IxBtbeTFfKdg" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="356" data-original-width="752" height="151" src="https://blogger.googleusercontent.com/img/a/AVvXsEjX99ObleRGofZ9wyPjkZwOAWjM9CWbWpjRquCp4oRG4tGndtywTaYb5Umrqvi-zjlw597IadM9Rdj8jZosfqAWrdA4OeRSkVz2qFfooZq3dC3k0sewuSTyjdvAeVZ_QEFMUBeeKfSFCQ_CP2fIqx4OGOhVfPnj--aRoQUA0JmM80IxBtbeTFfKdg" width="320" /></a></div><br /></li><li>Click the + button on the Library <div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEibWw_lR18eQ30wFvv6HsM7x7IiuD6kicrLt86leA8l0Ce3qJfW71_v11NNZuEzhK2aqy7JnApbD8_bHIUhkF9askBv4go1OkZSRebRLPnz_O9vpcISkqGNMSpwkfbZbu6LVjRjUJBQL-xFvrAURIffNPTX6M5HoeO8AUDpGDXklAinRqw2S2YZPg" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="113" data-original-width="234" height="155" src="https://blogger.googleusercontent.com/img/a/AVvXsEibWw_lR18eQ30wFvv6HsM7x7IiuD6kicrLt86leA8l0Ce3qJfW71_v11NNZuEzhK2aqy7JnApbD8_bHIUhkF9askBv4go1OkZSRebRLPnz_O9vpcISkqGNMSpwkfbZbu6LVjRjUJBQL-xFvrAURIffNPTX6M5HoeO8AUDpGDXklAinRqw2S2YZPg" width="320" /></a></div><br /></li><li>Navigate to your app (e.g., /Applications/Microsoft Outlook in my case) and Open it <div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEileI4TtcsnSVzoyNtPyfL9n6MIGg1hJZ62jDV5LRu_4CpOPpO-660jJDxlOCgHnI4lVelf54hx_ibLgbxs7yg8cvoeYdrjz6fx6gVPBn_pLevEV-g8UjKOLYmcQap2ah2XD5UH6aFZSJYB8ZWZLJYW4oclGrM36jb27CdYwImJPUV0lu7t_ALwRw" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="454" data-original-width="716" height="203" src="https://blogger.googleusercontent.com/img/a/AVvXsEileI4TtcsnSVzoyNtPyfL9n6MIGg1hJZ62jDV5LRu_4CpOPpO-660jJDxlOCgHnI4lVelf54hx_ibLgbxs7yg8cvoeYdrjz6fx6gVPBn_pLevEV-g8UjKOLYmcQap2ah2XD5UH6aFZSJYB8ZWZLJYW4oclGrM36jb27CdYwImJPUV0lu7t_ALwRw" width="320" /></a></div><br /></li><li>Open the reference from the library</li></ol><p>That’s it! Now you can explore the AppleScript reference for Outlook and automate your tasks with ease. I hope this saves you some time and frustration. Happy scripting!</p><p><br /></p><p>P.S. The answer to my question is <span class="s1" style="font-family: Verdana; font-size: 12px; font-variant-ligatures: no-common-ligatures; text-indent: -124.9px;"><b>set</b> </span><span class="s2" style="color: #6c05d3; font-family: Verdana; font-size: 12px; font-variant-ligatures: no-common-ligatures; text-indent: -124.9px;">priority</span><span class="s1" style="font-family: Verdana; font-size: 12px; font-variant-ligatures: no-common-ligatures; text-indent: -124.9px;"> <b>to</b> </span><span class="s2" style="color: #6c05d3; font-family: Verdana; font-size: 12px; font-variant-ligatures: no-common-ligatures; text-indent: -124.9px;"><i>priority high </i></span>that's how you set the priority to high in Microsoft Outlook!</p></div></div><cib-overlay></cib-overlay></div>2023-10-13T23:01:36Zurn:uuid:8b0b126d-782f-9a94-70e3-ae7a6740b900PSA: Require login for access to uploaded files in refinerycmsSo you're using <a href="https://www.refinerycms.com/">refinery cms</a> and <a href="https://github.com/heartcombo/devise">devise</a> together in your <a href="https://rubyonrails.org/">Rails</a> app and you have a before_filter in ApplicationController that requires login to access pages on your site, yet your uploaded resources do not require login and are publically accessible. What gives? <br /><br />The simple answer is that <a href="https://markevans.github.io/dragonfly/">Dragonfly</a>, which is used by refinerycms works as Rack middleware. As such, it ignores your before_filter. So a solution is to insert <a href="https://github.com/wardencommunity/warden">warden</a> authentication via the before_serve mechanism in Dragonfly, which I learned to do from <a href="https://github.com/markevans/dragonfly/wiki/Example:-Authentication">https://github.com/markevans/dragonfly/wiki/Example:-Authentication</a>. Conveniently, refinerycms-dragonfly, the plugin that adds Dragonfly support to refinerycms, allows one to configure a dragonfly_before_serve callback to do such a thing. <br />You will need 2 things <br /><ul class="org-ul"><li>refinerycms-dragonfly version 1.0.2, which at the time of writing is not available in rubygems, You can access it via git until that happens with the following line in your Gemfile</li></ul><div class="org-src-container"><pre class="src src-ruby">gem <span style="color: #cc9393;">'refinerycms-dragonfly'</span>, <span style="color: #cc9393;">'1.0.2'</span>, <br /><span style="color: #bfebbf;"> git</span>: <span style="color: #cc9393;">'https://github.com/refinery/refinerycms'</span>, <br /><span style="color: #bfebbf;"> branch</span>: <span style="color: #cc9393;">'master'</span>, <span style="color: #bfebbf;">ref</span>: <span style="color: #cc9393;">'c3879931612d8cdceb2a635d5b9f8fcfd56e09b6'</span><br /></pre></div><ul class="org-ul"><li>overriding the dragonfly_before_serve in your config (for example config/initializers/refinery/resources.rb - depending on how you have Dragonfly/S3 configured)</li></ul><div class="org-src-container"><pre class="src src-ruby">config.dragonfly_before_serve = <span style="color: #dcdccc; font-weight: bold;">lambda</span> { |job, env| <br /> user = env[<span style="color: #cc9393;">'warden'</span>].user <span style="color: #bfebbf;">:user</span> <span style="color: #5f7f5f;"># </span><span style="color: #7f9f7f;">get devise user from scope 'user'</span><br /> <span style="color: #dcdccc; font-weight: bold;">throw</span> <span style="color: #bfebbf;">:halt</span>, [401, {<span style="color: #cc9393;">"Content-Type"</span> => <span style="color: #cc9393;">"text/plain"</span>}, [<span style="color: #cc9393;">"Unauthorized"</span>]] <span style="color: #f0dfaf; font-weight: bold;">unless</span> user<br />}<br /></pre></div>And that's it! Happy private file-ing. <img src="http://feeds.feedburner.com/~r/PuttingTheFunIntoFunkworks/~4/DLcH312qQjc" height="1" width="1" alt=""/>2020-02-27T22:44:34Zurn:uuid:4eb50ac0-1c1b-d549-16c3-a8b6d7ae9b22PSA: Using Pro display monitors with the Macbook Exec, er ProPossibly the best computing accessory I have is the wonderful <a href="https://www.dell.com/en-us/shop/dell-ultrasharp-34-curved-ultrawide-monitor-u3415w/apd/210-adtr/monitors-monitor-accessories" target="_blank">Dell U3415W </a>it's a 34 inch curved widescreen monitor that sports a whopping 3440x1440 resolution. One can lay 3 screens ( browser, editor, console side by side, which is invaluable for a Web developer. It also has crisp clean text and images. It's a joy to work with and forced me to buy a similarly spec'd monitor at home because I found I could not live without it.<br /><div><br /></div><div>Getting a new computer should also be a joyous occasion. We just got brand new <i>Macbook <strike>Execs</strike> Pros</i><b>** </b>and the necessary "<i>Eff you, Dongle!</i>"<b>***</b> to connect our various pro equipment to them. Unfortunately, to my dismay, the Macbook was unable recognize the Dell's full resolution and gave us half the resolution.<br /><div><div><br /></div><div><a href="http://4.bp.blogspot.com/-_dt3WSf-i28/XGH0FcLbBYI/AAAAAAAA6RQ/y-MeV2oNOJkcPf02MkUJOGDtLFV9K_-twCK4BGAYYCw/s1600/Photo_-_Google_Photos.png" imageanchor="1"><img border="0" height="236" src="https://4.bp.blogspot.com/-_dt3WSf-i28/XGH0FcLbBYI/AAAAAAAA6RQ/y-MeV2oNOJkcPf02MkUJOGDtLFV9K_-twCK4BGAYYCw/s640/Photo_-_Google_Photos.png" width="640" /></a></div><div><br /></div><div>Fortunately, the aforementioned "Eff you, Dongle" supports HDMI, so we tried that. Success, except, it only supports 30HZ and 50HZ and not the full 60HZ. Hmm, we kind of like it when our fairly decked out (has to last for years) nearly $4000 laptops "Just work." That used to be Apple's motto, but has not been for a while. Ironically the 2013 Macbook Pro that is getting replaced handles this monitor fine, without a dongle. I guess that's just a feature of the Macbook Execs - we get the privilege to pay extra to get parity the old hardware. Unfortunately, it still didn't work.</div><div><br /></div><div>After a bit of research and experimenting apparently there are 2 solutions to get this working with the Dell. </div><div><ol><li>Usb-c to displayport cables are purported to work</li><li>One can turn off DP 1.2 on the Dell. This gives us the full resolution at 60HZ but loses the ability to daisy chain monitors via displayport which the Dell supports. The way you do this in the Dell U3415W is: Menu -> Input Source -> DP -> Hold check for 8 sec -> Disable.</li></ol><div>I chose option 2 as I had the dongle already.</div></div></div></div><div><br /></div><div>TLDR; If you're expensive widescreen monitor is not working on your Macbook Pro (cough) through a mini display port dongle, turning off DP 1.2 may solve this for you. Interestingly my LG 3440x1440 monitor does work - I'll have to check if it supports daisy chaining.</div><div><br /></div><div><b>** Macbook Exec</b>: is what I'm calling the Macbook Pros made starting with the touch bars. As a pro, I want the most powerful/capable/best hardware I can buy, which Apple used to provide. The latest Macbook Pros seem to be targeting NOT the pro market, but more of an "Exec" - someone who wants something expensive, thin and good looking - a prestige type item to go along the the C that's starts the initials of the job title. It's not how it works, but how it looks, and Dahling, it looks mahvelous!</div><div><br /></div><div><img src="https://i18.photobucket.com/albums/b127/swapmeetlouie/billy_crystal.jpg" /></div><div></div><div>(Photo from Dreamstime.com)</div><div><br /></div><div><b></b><br /><div><b><b><br /></b></b></div><b><div><b><br /></b></div><div><b><br /></b></div><div><b><br /></b></div>** Eff you, Dongle!:</b> Is what I've named Apple's development policy for a good number of years. It started with removing the headphone jacks on iPhones, which unfortunately has contaminated the Android market now too, but goes hand in hand with the Macbook Exec philosophy. If you want to do anything with your laptop that's requires more than a keyboard, trackpad, microphone and camera you need a dongle. It's infuriating to me because it totally ignores the investment that the user put in, that already works with their older products. One is forced to buy dongles, or new equipment. It's a money grab move that ignores what the users actually want.</div><div><br /></div><div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://thumbs.dreamstime.com/z/arrogant-executive-3395738.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="https://thumbs.dreamstime.com/z/arrogant-executive-3395738.jpg" width="472" /></a></div><div><br /></div><div><br /></div><div>Real life "Eff you, Dongle!" scenarios</div></div><div><br /></div><div><b>Pro user</b>: I want to use my laptop to make music. I already have expensive, non USB C equipment that work fine with my old MBP.</div><div><b>Apple</b>: Eff you, Dongle!</div><div><br /></div><div><b>Pro User</b>: I want to do photography on my MBP. I have expensive cameras, can plug in my cable, or SD cards into my old MBP!</div><div><b>Apple</b>: Eff you, Dongle! </div><div><br /></div><div><b>Pro User</b>: I use my laptop as a workstation and am on it all day. I have high quality, often ergonomic keyboard and pointing devices as well as super large expensive monitors, that all can work without a dongle on my old MBP</div><div><b>Apple</b>: Eff you, Dongle! </div><div><br /></div><div><b>Pro User</b>: I have $400 headphones and I'd like to use it with phone, and since I listen a lot I'd like to use my nice headphones and charge at the same time.</div><div><b>Apple</b>: Eff you, Dongle! Or buy new headphones.</div><div><br /></div><img src="http://feeds.feedburner.com/~r/PuttingTheFunIntoFunkworks/~4/TiufNZvjxXU" height="1" width="1" alt=""/>2019-02-11T23:17:30Zurn:uuid:6c914cdd-fdef-7337-b504-2e0bdd0199c4PSA - Restarting your MacbookPro camera without rebootingI've been encountering this issue lately where my camera would stop working. I regularly do a lot of video meetings, so this is a hindrance. I had been rebooting my machine, but that's a bit of a diversion, esp. when one is busy and/or has a lot of things in progress. I <a href="https://www.quora.com/How-do-I-activate-the-camera-on-my-MacBook-Pro" target="_blank">discovered</a> today one only needs to kill VDCAssistant (it will restart) and your camera functionality will be restored.<img src="http://feeds.feedburner.com/~r/PuttingTheFunIntoFunkworks/~4/GLj9kyyd6CE" height="1" width="1" alt=""/>2018-08-28T15:54:55Zurn:uuid:0558ac34-c8cf-5ee0-b5fc-a7c52ff60b17PSA - Fixing Omniauth Rails app with Twitter Oauth started failing - OAuth::Unauthorized 403 Forbidden ErrorJust a a PSA for those with Rails apps using Omniauth-Twitter who may have noticed that <i>Sign in with Twitter</i> authentication now fails with a 403. I found the answer via this <a href="https://stackoverflow.com/questions/50826742/omniauth-twitter-with-rails-5-stopped-working-oauthunauthorized-403-forbidden" target="_blank">stackoverflow post</a>. For my app in particular it was due to <a href="https://twittercommunity.com/t/action-required-sign-in-with-twitter-users-must-whitelist-callback-urls/105342" target="_blank">Twitter's decision to only allow apps to whitelist callback URLs</a>. If one follows many typical omniauth-twitter tutorials, one leverages the ability of the apps to override the callback URL - i.e you can test with localhost:3000 but deploy to whatever real URL and the app override's the callback URL. Per the stackoverflow post, I supplied 2 URLs, the base app URL and the <base app="" url=""></base>/auth/twitter/callback both with https. I also noticed that the callback locking and allow application to be used with <i>Sign in with Twitter</i> checkboxes were unchecked and checked those.<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://3.bp.blogspot.com/-goWxfuWvGVU/Wygaibs_kxI/AAAAAAAA3Kg/IO6HWlKEntYd5fUMnazFsS1npDyuJ4u4ACLcBGAs/s1600/twitter_oauth.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="83" data-original-width="809" height="65" src="https://3.bp.blogspot.com/-goWxfuWvGVU/Wygaibs_kxI/AAAAAAAA3Kg/IO6HWlKEntYd5fUMnazFsS1npDyuJ4u4ACLcBGAs/s640/twitter_oauth.png" width="640" /></a></div><br /><img src="http://feeds.feedburner.com/~r/PuttingTheFunIntoFunkworks/~4/Cv3DhupwfXc" height="1" width="1" alt=""/>2018-06-18T20:49:43Zurn:uuid:e2977fe4-efb9-5161-31a4-1a7447392d79PSA: TIL how to stop Salesforce from truncating your debug logsA tip for anyone who might need it. Those who have used the<span style="font-family: Courier New, Courier, monospace;"> System.debug()</span> call will have noticed that it truncates the output. A workaround is to convert the data to JSON with a <span style="font-family: Courier New, Courier, monospace;">JSON.serialize()</span> call.<br /><br />So instead of<br /><br /><span style="font-family: Courier New, Courier, monospace;">System.debug(haccIdList);</span><br /><br />Do this<br /><br /><span style="font-family: Courier New, Courier, monospace;">System.debug(JSON.serialize(haccIdList));</span><br /><div><br /></div><div>Answer found <a href="https://salesforce.stackexchange.com/questions/116502/console-log-truncates-my-debug" target="_blank">here</a>.</div><div><br /></div><div>Additional Search words: salesforce trucates logs, full salesforce output, System.debug</div><img src="http://feeds.feedburner.com/~r/PuttingTheFunIntoFunkworks/~4/3Dfy_PPGpkw" height="1" width="1" alt=""/>2018-01-31T00:05:10Zurn:uuid:4924156d-1671-df5d-c4ed-b89366f5e466RejectConf, Saving Ruby, JDD<div id="content"><div class="outline-2" id="outline-container-orgheadline1"><h2 id="orgheadline1">The 1st ever RejectConf</h2><h2 id="orgheadline1"><div id="content" style="-webkit-text-stroke-width: 0px; color: black; font-family: Times; font-size: medium; font-style: normal; font-variant-caps: normal; font-variant-ligatures: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-decoration-color: initial; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px;"><div class="outline-2" id="outline-container-orgheadline1"><div class="outline-text-2" id="text-orgheadline1">So at RubyConf 2006 in Denver Colorado, <a href="https://twitter.com/the_zenspider">Ryan Davis</a> organized the first <a href="http://juixe.com/techknow/index.php/2006/10/29/rubyconf-first-annual-rejectconf/">RejectConf</a> - an opportunity to present for people whose conference proposals were rejected. I think there a few more RejectConfs were organized at various Ruby related conferences. RejectConfs got renamed as Lightning Talks, becaming an official conference slot at many Ruby related conferences. In my experience, Lightning Talks an opportunity to see talks that got rejected by the conference commitee. Very often there's really good talks. Capped at a short time (generally 5 minutes), it allows one to sample many different topics. Definitely worthwhile attending one when at a conference</div></div></div></h2><h2 id="orgheadline2">Reevaluating the ROI on writing proposals</h2><h2><div style="-webkit-text-stroke-width: 0px; color: black; font-family: Times; font-size: medium; font-style: normal; font-variant-caps: normal; font-variant-ligatures: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-decoration-color: initial; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px;"><div class="outline-2" id="outline-container-orgheadline2"><div class="outline-text-2" id="text-orgheadline2">I write a number of conference proposals a year. I spend a fair amount of time on writing proposals. At least an hour, frequently a good amount more - because I do want them to be accepted. I propose a number of different topics with a lot of frequency and have been doing so since 2006, but I don't really have good success ratio. (See my email screenshot below). While versions of some of the these talks do get presented at <a href="https://www.meetup.com/ocruby/">OCRuby,</a> the local Ruby Users Group that I run, I find myself reevaluating the ROI in writing proposals. Apparently I'm not good at writing them.<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://2.bp.blogspot.com/-JQ83nAgeMIg/WdfhKR4wkyI/AAAAAAAAxks/NEca9XIaCRsOJoVOT9YQKZzrDb6b3O-bQCLcBGAs/s1600/Pasted_Image_10_6_17__12_26_PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="946" data-original-width="1197" height="504" src="https://2.bp.blogspot.com/-JQ83nAgeMIg/WdfhKR4wkyI/AAAAAAAAxks/NEca9XIaCRsOJoVOT9YQKZzrDb6b3O-bQCLcBGAs/s640/Pasted_Image_10_6_17__12_26_PM.png" width="640" /></a></div><span id="goog_948099706"></span><span id="goog_948099707"></span><br /></div></div></div></h2><h2 id="orgheadline3">The Overlying Theme - Saving Ruby and JDD</h2><h2><div style="-webkit-text-stroke-width: 0px; color: black; font-family: Times; font-size: medium; font-style: normal; font-variant-caps: normal; font-variant-ligatures: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-decoration-color: initial; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px;"><div class="outline-2" id="outline-container-orgheadline3"><div class="outline-text-2" id="text-orgheadline3">A few years ago, I realized the underlying theme of nearly all presentations I make - conference, meetup, is along the lines of "Ongoing Joy Driven Development (JDD) for someone who loves Ruby." I love to code, I love Ruby and I want to keep on doing it for a long time. To support this theme I've explored a lot of "less than mainstream" Ruby and/Ruby-esque technology to aid me in continuinge to be a "Happily employed Rubyist" indefinitely. Being a Happy Programmer is the Ruby Way. As someone who is a passionate about being a happy programmer I want to give back - spread the joy - thus ongoing efforts to do so. Part of that story is the notion - "Save Ruby" - Mainstream Ruby isn't hotbed of growth and innovation it once was. In many areas hot and up and coming areas of technology Ruby isn't applicable or even considered - that's bad for people who want to keep doing Ruby professionally. I want to help address that.<br />As a person of limited resources, I have to evaluate the best use my time, and I'm thinking perhaps I should blog more instead of submitting proposals. Hopefully blogging, maybe some screencasts help help me spread the word compared to all the hours that go into proposals that never get seen by many people. So I thought I'd just include my 3 proposals for RubyConf this year in their entirety, with hopes of spreading the message. Please tell if any of topics are of interest and I'll write more on them.</div></div></div></h2><h2 id="orgheadline16">Proposals</h2><h3 id="orgheadline4">Title - COMPS - The All Ruby Isomorphic Application Architecture</h3><h3 id="orgheadline5">Abstract</h3><h2><div style="-webkit-text-stroke-width: 0px; color: black; font-family: Times; font-size: medium; font-style: normal; font-variant-caps: normal; font-variant-ligatures: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-decoration-color: initial; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px;"><div class="outline-2" id="outline-container-orgheadline16"><div class="outline-text-2" id="text-orgheadline16"></div><div class="outline-3" id="outline-container-orgheadline4"></div><div class="outline-3" id="outline-container-orgheadline5"><div class="outline-text-3" id="text-orgheadline5">Building big apps is a big problem. There's complexity on the frontend, complexity on backend, and one has to link the 2 together.<br />As popluar as React is, which has several comprehensive solutions for the frontend, there still no integrated front/backend framework (think Meteor) for React for JS.<br />COMPS - Components, Operations, Models, Policies, Stores is not only the 1st integrated front/backend architecture for React, but is also an all Ruby solution.<br />Build your big apps with the best that React and Rails have to offer in an integrated all Ruby architecture. For the Rubyist Joy, Productivity, Familiarity in 1 package<br />front backend, on front end many options flux, redux, mobx<br />A solution, ALl ruby<br />Hinging both front and back</div></div></div></div></h2><h3 id="orgheadline6">Details</h3><h2><div style="-webkit-text-stroke-width: 0px; color: black; font-family: Times; font-size: medium; font-style: normal; font-variant-caps: normal; font-variant-ligatures: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-decoration-color: initial; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px;"><div class="outline-2"><div class="outline-3" id="outline-container-orgheadline6"><div class="outline-text-3" id="text-orgheadline6">When webapps were primarily server rendered pages will small amounts of AJAX, Rails was a sublime solution. It still is. Rails is omokase, a carefully selected package that was designed to fit together, with a beautiful, programmer happiness inducing Ruby DSL. One thing that continues to bring, joy, productivity, and maintainability to Rails application is that "there's a place for everything."<br />Fast forward to today, where SPAs (Single Page Applications) are common place and Javascript rules the heap with a vibrantly expanding, but ever chaotic ecosystem that promises things such as Isomorphic code (running on both ends), Rails and thus Ruby is less relevant. One can integrate React.js with Ruby on Rails, but it's still largely duct taped together, there's no built integration. On top of that there 2 languates, 2 paradigms, 2 tool sets - a costly integration.<br />COMPS is the architecture that is offered by ruby-hyperloop, a framework that integrates React.js on the frontend with Ruby on Rails on the backend via a single Ruby DSL. Like Rails, there's a place for everything. Components deliver interactive user experiences, Operations encapsulate business logic, Models magically synchronize data between clients and servers, Policies govern authorization and Stores hold local state.<br />Also like Rails, it is unified in a single Ruby DSL. Ruby is used to write front end logic, backend logic, logic that is shared by both front and backend and even markup via the React.rb DSL that can be rendered on both server and client side. As a Ruby framework, the toolset is that of Ruby, so there's a single, stable toolset which Rubyists already know.<br />For Ruby web developers, it is a route to continued relevance and employability in a world where server rendered Ruby webapps are having less and less relevance.</div></div></div></div></h2><h3 id="orgheadline7">Pitch</h3><h2><div style="-webkit-text-stroke-width: 0px; color: black; font-family: Times; font-size: medium; font-style: normal; font-variant-caps: normal; font-variant-ligatures: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-decoration-color: initial; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px;"><div class="outline-2"><div class="outline-3" id="outline-container-orgheadline7"><div class="outline-text-3" id="text-orgheadline7">In a technology landscape where SPAs, speed, scaleability, concurrency, etc. are the hot topics, Ruby and Rails are less and less relevant. COMPS provides an all Ruby path for Ruby based web developers to stay relevant, productive, employable while continuing to enjoy the Joy of Ruby.<br />I am a core committer and contributor to aforementioned and related technologies and a would be evangelist for keeping Rubyist happy and future employable.</div></div></div></div></h2><h3 id="orgheadline8">Title - Is Ruby dying? What's a Rubyist to do?</h3><h3 id="orgheadline9">Abstract</h3><h2><div style="-webkit-text-stroke-width: 0px; color: black; font-family: Times; font-size: medium; font-style: normal; font-variant-caps: normal; font-variant-ligatures: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-decoration-color: initial; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px;"><div class="outline-2"><div class="outline-3" id="outline-container-orgheadline8"></div><div class="outline-3" id="outline-container-orgheadline9"><div class="outline-text-3" id="text-orgheadline9">The language we love is fading, reduced #s of: conferences, projects, commits, job positions. It's dropped out of the IEEE Spectrum top 10, and is being dropped by numerous code schools,<br />While reports of Ruby's demise are greatly exaggerated, where there's smoke, there's fire. What's Ruby's relevance in these hot areas in tech:<br /><ul class="org-ul"><li>Front end web frameworks</li><li>Isomorphic code</li><li>Mobile</li><li>Scaling, speed, size, paralleism/concurrency</li></ul>The typical answer is that Ruby is not. It's little known that Ruby or Ruby-esque solutions exist in these categories. There is a future off the beaten path!</div></div></div></div></h2><h3 id="orgheadline10">Details</h3><h2><div style="-webkit-text-stroke-width: 0px; color: black; font-family: Times; font-size: medium; font-style: normal; font-variant-caps: normal; font-variant-ligatures: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-decoration-color: initial; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px;"><div class="outline-2"><div class="outline-3" id="outline-container-orgheadline10"><div class="outline-text-3" id="text-orgheadline10">This talk will go over not typically known Ruby and/or Ruby-esque solutions to the tech areas in which the world at large feels Ruby has little to no relevance. The desired outcome is to educate Rubyists on paths that can lead to their future happiness and employability.</div></div></div></div></h2><h3 id="orgheadline11">Pitch</h3><h2><div style="-webkit-text-stroke-width: 0px; color: black; font-family: Times; font-size: medium; font-style: normal; font-variant-caps: normal; font-variant-ligatures: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-decoration-color: initial; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px;"><div class="outline-2"><div class="outline-3" id="outline-container-orgheadline11"><div class="outline-text-3" id="text-orgheadline11">I love Ruby the language, it's library and community. It's given me years of joyful employment and I've been trying to give back at every opportunity. While I've been trying to explore, create, spread, and evangelize the "Future of Ruby" for a while now, seeing this year that Ruby has dropped out of the IEEE Spectrum top 10 and that code schools are dropping Ruby has convince me that it is still worth trying to show the Ruby community that there is a future for Ruby and Ruby-esque solutions. Hopefully the message can be heard, else we all switch to being Javascript programmers.</div></div></div></div></h2><h3 id="orgheadline12">Title - The Web is better in Ruby</h3><h3 id="orgheadline13">Abstract</h3><h2><div style="-webkit-text-stroke-width: 0px; color: black; font-family: Times; font-size: medium; font-style: normal; font-variant-caps: normal; font-variant-ligatures: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-decoration-color: initial; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px;"><div class="outline-2"><div class="outline-3" id="outline-container-orgheadline12"></div><div class="outline-3" id="outline-container-orgheadline13"><div class="outline-text-3" id="text-orgheadline13">Look at where web tech has evolved towards.<br />HTML: HTML is a bad programming language, thus: erb, haml, JSX. Rails introduced partials coz there's no extract method for HTML.<br />CSS: SASS, LESS were invented to make CSS a real programming language: inheritance, reuse, variables, etc.<br />Javascript: Many made their own JS classes, then ES6 made classes official.<br />All 3 technologies are evolving to be OO programming languages. What if they could all be modeled by a single language. One can do all 3 as JS, but it's not a great OO language. Did you know you can already do them all as Ruby, & it's good?</div></div></div></div></h2><h3 id="orgheadline14">Details</h3><h2><div style="-webkit-text-stroke-width: 0px; color: black; font-family: Times; font-size: medium; font-style: normal; font-variant-caps: normal; font-variant-ligatures: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-decoration-color: initial; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px;"><div class="outline-2"><div class="outline-3" id="outline-container-orgheadline14"><div class="outline-text-3" id="text-orgheadline14">Web technology is a jumble of different languages that somehow manages to work together. It is unlikey that Tim Berners-Lee would have imagined how web technology would've evolved from what he initially invented.<br />As web programming approaches and tools have evolved they are reaching more towards becoming Object Oriented programming languages.<br />Markup: The web world has re-learned what the Java world learned decades ago, that XML is a bad as programming language (i.e. Ant). Libraries like erb, haml, slim etc, try to add programming to HTML, and a such are popular. Libraries like markaby, paggio, builder bring the full power of Ruby to markup, leveraging on Ruby's DSL abilities to make Ruby look something like haml or slim.<br />Rails added partials to solve cut and paste of HTML. If HTML were a real programming language, one could refactor.<br />Css: SASS and LESS were invented to make Css a real programming language. SASS brings variables, nesting, partials, imports, mixins, inheritance, operators. As such it's used very widely. It should be no surprise that SASS's creator was a Rubyist.<br />Javascript: Many libraries implemented classes, then ES6 made those classes official. Unfortunately the Javascript world is divided classes and Object Oriend, some love ES6 classes, others think it's the worst addition. As such, Javascript isn't a particularly good Object Oriented language.<br />Since the 3 main web technologies are evolving towards being Object Oriented programming languages, it would be nice to have a single language to express them. This trend has been happening in the Javascript world, where esp. the React.js folk are trying to express everything in Javascript.<br />However, Ruby is both a better Object Oriented language, fulling embracing the Object Oriented paradigm, and a has stronger DSL capabilities than Javascript. JSX was likely invented in part because Javascript cannot provide a DSL that looks like markup, Ruby can.<br />A Ruby based approach to modeling all 3 could be both more expressive and more beautiful. Similarly, Ruby being a very Object Oriented programming language, perhaps second only to Smalltalk, lends to Object Oriented solutions, not commonly done in Javascript simply because Javascript is not as Object Oriented a language.<br />This talk will explore how that would look and would be better and then show how the capability already exists and is being used.</div></div></div></div></h2><h3 id="orgheadline15">Pitch</h3><h2><div style="font-size: medium; font-weight: normal;"><div class="outline-2"><div class="outline-3" id="outline-container-orgheadline15"><div class="outline-text-3" id="text-orgheadline15">The web is better in Ruby, which should make Rubyists happier and more productive. Combined with some other technologies, an all Ruby approach could allow Ruby based web developers to stay relevant and employable as server rendered webapps become less relevant.</div></div></div></div><div class="status" id="postamble" style="font-size: medium; font-weight: normal;"><div class="author" style="font-size: 14.4px; margin: 0.2em;"><br /></div></div></h2></div></div><img src="http://feeds.feedburner.com/~r/PuttingTheFunIntoFunkworks/~4/4zURezjYpJs" height="1" width="1" alt=""/>2017-10-06T20:20:33Zurn:uuid:b4b81a6e-c1f6-371a-49a6-66ac13234b00BJJ diary 9/12/17<div class="outline-2" id="outline-container-orgheadline11"><h2 id="orgheadline11">Class</h2><div class="outline-text-2" id="text-orgheadline11"></div><div class="outline-3" id="outline-container-orgheadline3"><h3 id="orgheadline3">Clock choke</h3><div class="outline-text-3" id="text-orgheadline3"></div><div class="outline-4" id="outline-container-orgheadline1"><h4 id="orgheadline1">Setup</h4><div class="outline-text-4" id="text-orgheadline1"><ul class="org-ul"><li>On left of turtle</li><li>Left hand over, seat belt grip</li></ul></div></div><div class="outline-4" id="outline-container-orgheadline2"><h4 id="orgheadline2">Action</h4><div class="outline-text-4" id="text-orgheadline2"><ul class="org-ul"><li>Right hand guides opponent's right lapel to sink left hand in deep</li><li>Right arm chicken wing opponent's OR grab opponent's left lapel w/right hand</li><li>cinch tight, walk clockwise</li><li>I found the other lapel tighter</li><li>Can't really tell if choke is in until guy taps</li></ul></div></div></div><div class="outline-3" id="outline-container-orgheadline6"><h3 id="orgheadline6">Turn into guard against clock choke</h3><div class="outline-text-3" id="text-orgheadline6"></div><div class="outline-4" id="outline-container-orgheadline4"><h4 id="orgheadline4">Setup</h4><div class="outline-text-4" id="text-orgheadline4"><ul class="org-ul"><li>turtled with opponent on top, with clock choke grep</li></ul></div></div><div class="outline-4" id="outline-container-orgheadline5"><h4 id="orgheadline5">Action</h4><div class="outline-text-4" id="text-orgheadline5"><ul class="org-ul"><li>turn into him to guard, or half guard</li><li>If he stays connected he gets rolled</li></ul></div></div></div><div class="outline-3" id="outline-container-orgheadline10"><h3 id="orgheadline10">Specific Training</h3><div class="outline-text-3" id="text-orgheadline10"></div><div class="outline-4" id="outline-container-orgheadline7"><h4 id="orgheadline7">Setup</h4><div class="outline-text-4" id="text-orgheadline7"><ul class="org-ul"><li>One guy in turtle, one guy on back</li></ul></div></div><div class="outline-4" id="outline-container-orgheadline8"><h4 id="orgheadline8">Action</h4><div class="outline-text-4" id="text-orgheadline8"><ul class="org-ul"><li>Guy on bottom tries to get to guard</li><li>Guy on top takes back or submits</li></ul></div></div><div class="outline-4" id="outline-container-orgheadline9"><h4 id="orgheadline9">Insights</h4><div class="outline-text-4" id="text-orgheadline9"><ul class="org-ul"><li>Turning into guard works well</li></ul></div></div></div></div><div class="outline-2" id="outline-container-orgheadline12"><h2 id="orgheadline12">Sparring</h2><div class="outline-text-2" id="text-orgheadline12"><ul class="org-ul"><li>Bobby's knee shield hard to pass, unable to get around fast enough, or get pulled back in to 1/2 guard before finishing pass</li><li>Got a feel of what electric chair feels like, able to pull leg out before too late</li></ul></div></div><img src="http://feeds.feedburner.com/~r/PuttingTheFunIntoFunkworks/~4/XIEuTNlxOxg" height="1" width="1" alt=""/>2017-09-14T22:08:30Zurn:uuid:7b221f1f-e063-4962-2429-db2d1f2bc760BJJ Diary 9/11/17<br /><div class="status" id="postamble" style="-webkit-text-stroke-width: 0px; color: black; font-family: Times; font-size: medium; font-style: normal; font-variant-caps: normal; font-variant-ligatures: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-decoration-color: initial; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px;"></div><br /><div id="content" style="-webkit-text-stroke-width: 0px; color: black; font-family: Times; font-size: medium; font-style: normal; font-variant-caps: normal; font-variant-ligatures: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-decoration-color: initial; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px;">Instead of just taking notes for myself, I decided to put these notes in a blog in case they are of any use to anyone else.<br /><br />YIL (yesterday I learned)<br /><div class="outline-2" id="outline-container-orgheadline10"><h2 id="orgheadline10">Class</h2><div class="outline-text-2" id="text-orgheadline10"></div><div class="outline-3" id="outline-container-orgheadline3"><h3 id="orgheadline3">Gravity Throw</h3><div class="outline-text-3" id="text-orgheadline3"></div><div class="outline-4" id="outline-container-orgheadline1"><h4 id="orgheadline1">Setup</h4><div class="outline-text-4" id="text-orgheadline1"><ul class="org-ul"><li>Left hand grips elbow</li><li>Right hand grabs same side collar</li></ul></div></div><div class="outline-4" id="outline-container-orgheadline2"><h4 id="orgheadline2">Action</h4><div class="outline-text-4" id="text-orgheadline2"><ul class="org-ul"><li>Circle left</li><li>Drop, left foot can block is right foot</li><li>Right leg butterfly hook throws</li><li>Come to mount or kesa gatame</li></ul></div></div></div><div class="outline-3" id="outline-container-orgheadline6"><h3 id="orgheadline6">Turtle Escape 1</h3><div class="outline-text-3" id="text-orgheadline6"></div><div class="outline-4" id="outline-container-orgheadline4"><h4 id="orgheadline4">Setup</h4><div class="outline-text-4" id="text-orgheadline4"><ul class="org-ul"><li>Opponent on top, North/South style, with seat belt grip</li><li>Stays in position</li></ul></div></div><div class="outline-4" id="outline-container-orgheadline5"><h4 id="orgheadline5">Action</h4><div class="outline-text-4" id="text-orgheadline5"><ul class="org-ul"><li>Sit to guard</li><li>Head up, guard choke<ul class="org-ul"><li>Head down gives guillotine</li></ul></li><li>If can, can put fist in throat to pry head out if he doen't let go of heads</li></ul></div></div></div><div class="outline-3" id="outline-container-orgheadline9"><h3 id="orgheadline9">Turtle Escape 2</h3><div class="outline-text-3" id="text-orgheadline9"></div><div class="outline-4" id="outline-container-orgheadline7"><h4 id="orgheadline7">Setup</h4><div class="outline-text-4" id="text-orgheadline7"><ul class="org-ul"><li>like above but circling to your right side</li></ul></div></div><div class="outline-4" id="outline-container-orgheadline8"><h4 id="orgheadline8">Action</h4><div class="outline-text-4" id="text-orgheadline8"><ul class="org-ul"><li>Fall to left shoulder, shoulder needs to be low to have space</li><li>Get knee or foot in</li><li>Turn to guard</li></ul></div></div></div></div><div class="outline-2" id="outline-container-orgheadline14"><h2 id="orgheadline14">Sparring</h2><div class="outline-text-2" id="text-orgheadline14"></div><div class="outline-3" id="outline-container-orgheadline11"><h3 id="orgheadline11">Don't let heavier guard passer secure belt</h3><div class="outline-text-3" id="text-orgheadline11"><ul class="org-ul"><li>Gives him grips</li><li>Makes it hard to to use legs as frames</li><li>Happened when I pulled a closer guard on Matt</li></ul></div></div><div class="outline-3" id="outline-container-orgheadline12"><h3 id="orgheadline12">Technical standup is an option to stopping guard pass</h3><div class="outline-text-3" id="text-orgheadline12"><ul class="org-ul"><li>In response to above, I had a hard time keeping him at correct distance until I stood up</li></ul></div></div><div class="outline-3" id="outline-container-orgheadline13"><h3 id="orgheadline13">Protect face when going for armbar</h3><div class="outline-text-3" id="text-orgheadline13"><ul class="org-ul"><li>Got backfisted in the eye by Alex when hitting the armbar</li><li>If going for opponents right arm, left hand in front of face ad right grabs his arm, if hand comes loose, left hand catches</li></ul></div></div></div></div><img src="http://feeds.feedburner.com/~r/PuttingTheFunIntoFunkworks/~4/3MJC3FUeVcs" height="1" width="1" alt=""/>2017-09-12T20:59:13Zurn:uuid:7258b014-78f1-919b-b888-12783738e922PSA: Logitech Remotes and Cox Contour 2 boxesIt took me a while to find a solution, so I thought I'd make this hopefully a little more easier to find so others in the same boat can find it. I did find the solution from this <a href="http://forums.cox.com/forum_home/tv_forum/f/4/t/15989.aspx" target="_blank">forum article</a>. I recently upgraded from the old Cox Contour boxes to the new improved, and pretty spiffy w/voice control <a href="https://www.cox.com/residential/tv/contour.html?campcode=flowlanding_contour_learn1_3_19052016085608#home" target="_blank">Cox Contour </a>2. The new system is good, though the experience was terrible and I eventually hope find a way to give Cox the boot for their terrible practices. I have among other universal remotes, the Logitech Harmony 650 remote and I wanted to set it up to be able to control the cable box/DVR. The model number on the box is a Cisco 9865HDC which the Logitech supported, so I plugged that in, but it didn't work. So then I tried to learn the remote keys, which the Logitech can also do, but nothing happened. From that I learned that the <a href="https://www.cox.com/residential/support/contour-2-xr5-remote.html" target="_blank">Cox remote works on RF</a> - which is more efficient on the battery. I, as did some others thought that we were SOL as the Logitech is an IR universal remote, but it turns out that the Contour box can in fact be controlled by IR, but just not the generic (?) Cisco 9865HDC code. Apparently the code that works is the Comcast Xfinity XiD-P (vs the Cox XiD-P which doesn't work). Apparently Cox are using the same cable box technology.<br /><br />So now I'm happily using my <a href="https://www.amazon.com/gp/product/B004OVECU0/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=B004OVECU0&linkCode=as2&tag=fkchang-20&linkId=41212e6fe16d19b8bd19b523b0d13c45" target="_blank">Logitech Harmony 650</a><img alt="" border="0" height="1" src="//ir-na.amazon-adsystem.com/e/ir?t=fkchang-20&l=am2&o=1&a=B004OVECU0" style="border: none !important; margin: 0px !important;" width="1" /> to control my Contour 2 box. I also used my Logitech to IR program my <a href="https://www.amazon.com/gp/product/B072MF92S5/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=B072MF92S5&linkCode=as2&tag=fkchang-20&linkId=9cf7fc364872f75705df9cb4b8992ff8" target="_blank">Broadlink RM Pro</a><img alt="" border="0" height="1" src="//ir-na.amazon-adsystem.com/e/ir?t=fkchang-20&l=am2&o=1&a=B072MF92S5" style="border: none !important; margin: 0px !important;" width="1" /> (which also doesn't recognize the cable box as Cisco) so I can control it from my <a href="https://www.amazon.com/gp/product/B01DFKC2SO/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=B01DFKC2SO&linkCode=as2&tag=fkchang-20&linkId=c2092bfacec500f3e089c49b6aa3d4b7" target="_blank">Echo Dot</a><img alt="" border="0" height="1" src="//ir-na.amazon-adsystem.com/e/ir?t=fkchang-20&l=am2&o=1&a=B01DFKC2SO" style="border: none !important; margin: 0px !important;" width="1" /> along with my TV.<br /><br />Hope this helps somebody!<br /><br />Search keywords (just repeated to hopefully give higher search index): Logitech, Harmony, 650, Cox Contour 2, Cisco 9856HDC, IR, RF, Universal remote<img src="http://feeds.feedburner.com/~r/PuttingTheFunIntoFunkworks/~4/dmkqpay-73U" height="1" width="1" alt=""/>2017-08-04T01:03:58Zurn:uuid:300ef2dd-3167-892e-fa7e-692602284acfPSA: fixing nokogiri compile errors after upgrading to SierraHopefully bumping this up in the search to make it easier for others to find<br /><br />Long story short, I had to recompile nokogiri after upgrading to Sierra and I got errors like this<br /><br /><code>warning: cast from 'unsigned char *' to 'unsigned short *' increases required alignment from 1 to 2 [-Wcast-align]</code><br /><br /><code></code>It took a while to find the right answer for my situation, to include some false starts, ultimate the advice here solved it for me.<br /><br /><a href="https://stackoverflow.com/questions/39671424/gemloaderror-in-rails-application-due-to-nokogiri-gem">https://stackoverflow.com/questions/39671424/gemloaderror-in-rails-application-due-to-nokogiri-gem</a><br /><br />In short: <span style="background-color: white; color: #242729; font-family: Arial, "Helvetica Neue", Helvetica, sans-serif; font-size: 15px;">I needed to reinstall nokogiri and link it to libxml2 in the appropriate MacOSX SDK directory.</span><br /><span style="background-color: white; color: #242729; font-family: Arial, "Helvetica Neue", Helvetica, sans-serif; font-size: 15px;"><br /></span><pre class="lang-rb prettyprint prettyprinted" style="background-color: #eff0f1; border: 0px; color: #393318; font-family: Consolas, Menlo, Monaco, "Lucida Console", "Liberation Mono", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Courier New", monospace, sans-serif; font-size: 13px; font-stretch: inherit; font-variant-numeric: inherit; line-height: inherit; margin-bottom: 1em; max-height: 600px; overflow: auto; padding: 5px; vertical-align: baseline; width: auto; word-wrap: normal;"><code style="border: 0px; font-family: Consolas, Menlo, Monaco, "Lucida Console", "Liberation Mono", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Courier New", monospace, sans-serif; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline; white-space: inherit;"><span class="pln" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">gem install nokogiri </span><span class="pun" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">--</span><span class="pln" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;"> </span><span class="pun" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">--</span><span class="pln" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">with</span><span class="pun" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">-</span><span class="pln" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">xml2</span><span class="pun" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">-</span><span class="pln" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">include</span><span class="pun" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">=</span><span class="str" style="border: 0px; color: #7d2727; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">/Applications/</span><span class="typ" style="border: 0px; color: #2b91af; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">Xcode</span><span class="pun" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">.</span><span class="pln" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">app</span><span class="pun" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">/</span><span class="typ" style="border: 0px; color: #2b91af; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">Contents</span><span class="pun" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">/</span><span class="typ" style="border: 0px; color: #2b91af; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">Developer</span><span class="pun" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">/</span><span class="typ" style="border: 0px; color: #2b91af; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">Platforms</span><span class="pun" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">/</span><span class="typ" style="border: 0px; color: #2b91af; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">MacOSX</span><span class="pun" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">.</span><span class="pln" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">platform</span><span class="pun" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">/</span><span class="typ" style="border: 0px; color: #2b91af; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">Developer</span><span class="pun" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">/</span><span class="typ" style="border: 0px; color: #2b91af; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">SDKs</span><span class="pun" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">/</span><span class="typ" style="border: 0px; color: #2b91af; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">MacOSX10</span><span class="pun" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">.</span><span class="lit" style="border: 0px; color: #7d2727; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">12.sdk</span><span class="pun" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">/</span><span class="pln" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">usr</span><span class="pun" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">/</span><span class="pln" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">include</span><span class="pun" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">/</span><span class="pln" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">libxml2</span><span class="pun" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">/</span><span class="pln" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;"> </span><span class="pun" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">--</span><span class="pln" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">use</span><span class="pun" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">-</span><span class="pln" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">system</span><span class="pun" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">-</span><span class="pln" style="border: 0px; color: #303336; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">libraries</span></code></pre><img src="http://feeds.feedburner.com/~r/PuttingTheFunIntoFunkworks/~4/eHHmkj1br7E" height="1" width="1" alt=""/>2017-06-27T17:25:56Zurn:uuid:4d0f04de-0fde-401a-df2c-42d2b96c24e3PSA: ftp default modes, linux and OSXIf you've encountered the error 500 Illegal PORT command on something that worked on OSX (i.e. development environment), but failed on Linux (staging/production environments), I may have a fix for you. Linux FTP defaults to <a href="http://www.serv-u.com/kb/1138/Active-and-Passive-FTP-Transfers-Defined" target="_blank">active mode</a>, so you need to switch to passive mode for it to work as it did in development. It would seem that FTP defaults to passive mode on OSX (might be a BSD thing). If for example you were using Ruby's Net::FTP.<br /><br /><br /><div class="org-src-container" style="-webkit-text-stroke-width: 0px; color: black; font-family: Times; font-size: medium; font-style: normal; font-variant-caps: normal; font-variant-ligatures: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-decoration-color: initial; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px;"></div><br /><div class="org-src-container" style="-webkit-text-stroke-width: 0px; color: black; font-family: Times; font-size: medium; font-style: normal; font-variant-caps: normal; font-variant-ligatures: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-decoration-color: initial; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px;"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; font-family: monospace; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;"><span style="color: #7cb8bb;">Net</span>::<span style="color: #7cb8bb;">FTP</span>.open(<span style="color: #7cb8bb;">ENV</span>[<span style="color: #cc9393;">'FTP_HOSTNAME'</span>]) <span style="color: #f0dfaf; font-weight: bold;">do</span> |ftp|<br /> ftp.login(<span style="color: #7cb8bb;">ENV</span>[<span style="color: #cc9393;">'FTP_USERNAME'</span>], <span style="color: #7cb8bb;">ENV</span>[<span style="color: #cc9393;">'FTP_PASSWORD'</span>])<br /> ftp.chdir(<span style="color: #cc9393;">'incoming'</span>)<br /> ftp.list(<span style="color: #cc9393;">'*.csv'</span>)<br /><span style="color: #f0dfaf; font-weight: bold;">end</span><br /></pre></div><br />Would fail on 500 Illegal PORT command, but it's e<br /><br /><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;"><span style="color: #7cb8bb;">Net</span>::<span style="color: #7cb8bb;">FTP</span>.open(<span style="color: #7cb8bb;">ENV</span>[<span style="color: #cc9393;">'FTP_HOSTNAME'</span>]) <span style="color: #f0dfaf; font-weight: bold;">do</span> |ftp|<br /> ftp.login(<span style="color: #7cb8bb;">ENV</span>[<span style="color: #cc9393;">'FTP_USERNAME'</span>], <span style="color: #7cb8bb;">ENV</span>[<span style="color: #cc9393;">'FTP_PASSWORD'</span>])<br /> ftp.chdir(<span style="color: #cc9393;">'incoming'</span>)<br /> ftp.passive = <span style="color: #dfaf8f;">true</span><br /> ftp.list(<span style="color: #cc9393;">'*.csv'</span>)<br /><span style="color: #f0dfaf; font-weight: bold;">end</span></pre>Make the corresponding switch to passive as appropriate to your system.<br /><br />Have a good day!<img src="http://feeds.feedburner.com/~r/PuttingTheFunIntoFunkworks/~4/4WIiXZD_3Zc" height="1" width="1" alt=""/>2017-06-01T22:38:16Zurn:uuid:e08293ee-e42f-1179-1b41-e52b844c9113Opening up the node ecosystem for Ruby - http example<div id="content"><div class="outline-2" id="outline-container-orgheadline1"><h2 id="orgheadline1">Non browser use of npm modules from Ruby</h2><h2 id="orgheadline1"><div id="content" style="-webkit-text-stroke-width: 0px; color: black; font-family: Times; font-size: medium; font-style: normal; font-variant-caps: normal; font-variant-ligatures: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-decoration-color: initial; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px;"><div class="outline-2" id="outline-container-orgheadline1"><div class="outline-text-2" id="text-orgheadline1">In my <a href="http://funkworks.blogspot.com/2017/05/require-nodejs-opening-entire-server.html">last article,</a> I teased of the capability of <code>require('nodejs')</code> to open up the entire node.js ecosystem to the Rubyist. While using npm modules from Opal in the browser via Webpack has been written about, no one has written about using npm modules from <a href="http://opalrb.org/">Opal</a> on node.js.</div></div></div></h2><h2 id="orgheadline2">Node module as a Javascript object</h2><h2><div style="-webkit-text-stroke-width: 0px; color: black; font-family: Times; font-size: medium; font-style: normal; font-variant-caps: normal; font-variant-ligatures: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-decoration-color: initial; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px;"><div class="outline-2" id="outline-container-orgheadline2"><div class="outline-text-2" id="text-orgheadline2">When using <code>require()</code> in node, you will get back either a Javascript object or a function. Since handling a Javascript object from Opal is the easier situation, we'll start with an example of such</div></div></div></h2><h2 id="orgheadline3">Hello World with the http module</h2><h2><div style="-webkit-text-stroke-width: 0px; color: black; font-family: Times; font-size: medium; font-style: normal; font-variant-caps: normal; font-variant-ligatures: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-decoration-color: initial; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px;"><div class="outline-2" id="outline-container-orgheadline3"><div class="outline-text-2" id="text-orgheadline3">For the example, we will use the built in node module - <code>http</code>. The code:<br /><div class="org-src-container"><pre class="src src-javascript" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; font-family: monospace; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;">http = require(<span style="color: green;">'http'</span>)<br /><span style="color: blue;">var</span> <span style="color: #ba36a5;">port</span> = process.env.port || 1337;<br />http.createServer(<span style="color: blue;">function</span>(<span style="color: #ba36a5;">req</span>, <span style="color: #ba36a5;">res</span>) {<br /> res.writeHead(200, { <span style="color: green;">'Content-Type'</span>: <span style="color: green;">'text/plain'</span> });<br /> res.end(<span style="color: green;">'Hello World\n'</span>);<br />}).listen(port);<br /></pre></div>This example simply requires the http module and then uses <code>http.createServer()</code> to create an http server that returns "Hello World" on port 1337.</div></div></div></h2><h2 id="orgheadline4">Compiling Ruby to Javascript</h2><h2><div style="-webkit-text-stroke-width: 0px; color: black; font-family: Times; font-size: medium; font-style: normal; font-variant-caps: normal; font-variant-ligatures: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-decoration-color: initial; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px;"><div class="outline-2" id="outline-container-orgheadline4"><div class="outline-text-2" id="text-orgheadline4">Before we get started, we'll need to transpile the Ruby code into Javascript. While one can use opal to compile and run a ruby file on node (the default runner) i.e. like this:<br /><div class="org-src-container"><pre class="src src-bash" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; font-family: monospace; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;">opal hello_world.rb<br /></pre></div>with the current Opal verision (v0.11.0.rc1), it is better to compile to Javascript 1st and then run with node for these reasons:<br /><ul class="org-ul"><li>You get a Javascript file that you can inspect - helpful for debugging. <code>opal hello_world.rb</code> doesn't persist the compiled Javascript</li><li>The NODE_PATH is set and not modifiable, so you won't be able to npm install packages. I may submit a pull request to fix this in the future.</li><li>Because node is event driven, you need to to pass a -E if you DON'T want node to exit after the Javascript is evaluated. This would be the case for applications that need to stay and respond to input</li></ul>To automate the process to keep the developer happy, I suggest a Rakefile. As a project setup to explore this capability, I planned to make a number of experiments, each in their own files in the <code>src</code> directory. My Rakefile dynamically creates a "compile and run" Rake task for every ruby file in the <code>src</code> directory.<br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; font-family: monospace; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;"><span style="color: #006fe0; font-weight: bold;">require</span> <span style="color: green;">'opal'</span><br /><br /><span style="color: #6434a3;">Dir</span>[<span style="color: green;">'src/*.rb'</span>].each { |rb_file|<br /> desc <span style="color: green;">"Build and run </span><span style="color: #ba36a5;">#{rb_file}</span><span style="color: green;">"</span><br /> basename = <span style="color: #6434a3;">File</span>.basename(rb_file, <span style="color: green;">'.rb'</span>) <br /> task basename <span style="color: blue;">do</span><br /> <span style="color: #006fe0; font-weight: bold;">puts</span> <span style="color: green;">"Building </span><span style="color: #ba36a5;">#{basename}</span><span style="color: green;">"</span><br /> <span style="color: #006fe0; font-weight: bold;">system</span> <span style="color: green;">"opal -rconsole -Ec </span><span style="color: #ba36a5;">#{rb_file}</span><span style="color: green;"> > </span><span style="color: #ba36a5;">#{basename}</span><span style="color: green;">.js"</span><br /> <span style="color: #006fe0; font-weight: bold;">puts</span> <span style="color: green;">"\trunning"</span><br /> <span style="color: #006fe0; font-weight: bold;">system</span> <span style="color: green;">'say running'</span><br /> <span style="color: #006fe0; font-weight: bold;">system</span> <span style="color: green;">"node </span><span style="color: #ba36a5;">#{basename}</span><span style="color: green;">.js"</span><br /> <span style="color: blue;">end</span><br />}<br /></pre></div>I put some debugging info to know what state the process was, to include an audio clue that the web server has started "running."</div></div></div></h2><h2 id="orgheadline5">Baby steps</h2><h2><div style="-webkit-text-stroke-width: 0px; color: black; font-family: Times; font-size: medium; font-style: normal; font-variant-caps: normal; font-variant-ligatures: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-decoration-color: initial; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px;"><div class="outline-2" id="outline-container-orgheadline5"><div class="outline-text-2" id="text-orgheadline5">To get started, let's require the module and examine the object. Since I require console in the opal compile line in the Rakefile, I have access to the console via <code>$console</code>. In Ruby (and with Opal as well), one typically uses the <code>p()</code> method which calls the inspect() method of the object. Being that we will deal with a plain Javascript object which doesn't have a <code>$inspect()</code> method like all Opal objects, we can't call that and want must use <code>$console.log()</code> instead.<br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; font-family: monospace; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;"><span style="color: #006fe0; font-weight: bold;">require</span> <span style="color: green;">'nodejs'</span><br /><br />http = node_require(<span style="color: green;">'http'</span>)<br /><br /><span style="color: #ba36a5;">$console</span>.log http<br /></pre></div>Running that file will print out the contents of the http object, which looks like this on my system.<br /><div class="org-src-container"><pre class="src src-javascript" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; font-family: monospace; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;">$ rake http_ex<br />Building http_ex<br /> running<br />{ IncomingMessage:<br /> { [Function: IncomingMessage]<br /> super_:<br /> { [Function: Readable]<br /> ReadableState: [Function: ReadableState],<br /> super_: [Object],<br /> _fromList: [Function: fromList] } },<br /> OutgoingMessage:<br /> { [Function: OutgoingMessage]<br /> super_:<br /> { [Function: Stream]<br /> super_: [Object],<br /> Readable: [Object],<br /> Writable: [Object],<br /> Duplex: [Object],<br /> Transform: [Object],<br /> PassThrough: [Object],<br /> Stream: [Circular] } },<br /> METHODS:<br /> [ <span style="color: green;">'ACL'</span>,<br /> <span style="color: green;">'BIND'</span>,<br /> <span style="color: green;">'CHECKOUT'</span>,<br /> <span style="color: green;">'CONNECT'</span>,<br /> <span style="color: green;">'COPY'</span>,<br /> <span style="color: green;">'DELETE'</span>,<br /> <span style="color: green;">'GET'</span>,<br /> <span style="color: green;">'HEAD'</span>,<br /> <span style="color: green;">'LINK'</span>,<br /> <span style="color: green;">'LOCK'</span>,<br /> <span style="color: green;">'M-SEARCH'</span>,<br /> <span style="color: green;">'MERGE'</span>,<br /> <span style="color: green;">'MKACTIVITY'</span>,<br /> <span style="color: green;">'MKCALENDAR'</span>,<br /> <span style="color: green;">'MKCOL'</span>,<br /> <span style="color: green;">'MOVE'</span>,<br /> <span style="color: green;">'NOTIFY'</span>,<br /> <span style="color: green;">'OPTIONS'</span>,<br /> <span style="color: green;">'PATCH'</span>,<br /> <span style="color: green;">'POST'</span>,<br /> <span style="color: green;">'PROPFIND'</span>,<br /> <span style="color: green;">'PROPPATCH'</span>,<br /> <span style="color: green;">'PURGE'</span>,<br /> <span style="color: green;">'PUT'</span>,<br /> <span style="color: green;">'REBIND'</span>,<br /> <span style="color: green;">'REPORT'</span>,<br /> <span style="color: green;">'SEARCH'</span>,<br /> <span style="color: green;">'SUBSCRIBE'</span>,<br /> <span style="color: green;">'TRACE'</span>,<br /> <span style="color: green;">'UNBIND'</span>,<br /> <span style="color: green;">'UNLINK'</span>,<br /> <span style="color: green;">'UNLOCK'</span>,<br /> <span style="color: green;">'UNSUBSCRIBE'</span> ],<br /> Agent:<br /> { [Function: Agent]<br /> super_:<br /> { [Function: EventEmitter]<br /> EventEmitter: [Circular],<br /> usingDomains: <span style="color: #d0372d;">false</span>,<br /> defaultMaxListeners: [Getter/Setter],<br /> init: [Function],<br /> listenerCount: [Function] },<br /> defaultMaxSockets: <span style="color: #d0372d;">Infinity</span> },<br /> globalAgent:<br /> Agent {<br /> domain: <span style="color: #d0372d;">null</span>,<br /> _events: { free: [Function] },<br /> _eventsCount: 1,<br /> _maxListeners: <span style="color: #d0372d;">undefined</span>,<br /> defaultPort: 80,<br /> protocol: <span style="color: green;">'http:'</span>,<br /> options: { path: <span style="color: #d0372d;">null</span> },<br /> requests: {},<br /> sockets: {},<br /> freeSockets: {},<br /> keepAliveMsecs: 1000,<br /> keepAlive: <span style="color: #d0372d;">false</span>,<br /> maxSockets: <span style="color: #d0372d;">Infinity</span>,<br /> maxFreeSockets: 256 },<br /> ServerResponse: { [Function: ServerResponse] super_: { [Function: OutgoingMessage] super_: [Object] } },<br /> STATUS_CODES:<br /> { <span style="color: green;">'100'</span>: <span style="color: green;">'Continue'</span>,<br /> <span style="color: green;">'101'</span>: <span style="color: green;">'Switching Protocols'</span>,<br /> <span style="color: green;">'102'</span>: <span style="color: green;">'Processing'</span>,<br /> <span style="color: green;">'200'</span>: <span style="color: green;">'OK'</span>,<br /> <span style="color: green;">'201'</span>: <span style="color: green;">'Created'</span>,<br /> <span style="color: green;">'202'</span>: <span style="color: green;">'Accepted'</span>,<br /> <span style="color: green;">'203'</span>: <span style="color: green;">'Non-Authoritative Information'</span>,<br /> <span style="color: green;">'204'</span>: <span style="color: green;">'No Content'</span>,<br /> <span style="color: green;">'205'</span>: <span style="color: green;">'Reset Content'</span>,<br /> <span style="color: green;">'206'</span>: <span style="color: green;">'Partial Content'</span>,<br /> <span style="color: green;">'207'</span>: <span style="color: green;">'Multi-Status'</span>,<br /> <span style="color: green;">'208'</span>: <span style="color: green;">'Already Reported'</span>,<br /> <span style="color: green;">'226'</span>: <span style="color: green;">'IM Used'</span>,<br /> <span style="color: green;">'300'</span>: <span style="color: green;">'Multiple Choices'</span>,<br /> <span style="color: green;">'301'</span>: <span style="color: green;">'Moved Permanently'</span>,<br /> <span style="color: green;">'302'</span>: <span style="color: green;">'Found'</span>,<br /> <span style="color: green;">'303'</span>: <span style="color: green;">'See Other'</span>,<br /> <span style="color: green;">'304'</span>: <span style="color: green;">'Not Modified'</span>,<br /> <span style="color: green;">'305'</span>: <span style="color: green;">'Use Proxy'</span>,<br /> <span style="color: green;">'307'</span>: <span style="color: green;">'Temporary Redirect'</span>,<br /> <span style="color: green;">'308'</span>: <span style="color: green;">'Permanent Redirect'</span>,<br /> <span style="color: green;">'400'</span>: <span style="color: green;">'Bad Request'</span>,<br /> <span style="color: green;">'401'</span>: <span style="color: green;">'Unauthorized'</span>,<br /> <span style="color: green;">'402'</span>: <span style="color: green;">'Payment Required'</span>,<br /> <span style="color: green;">'403'</span>: <span style="color: green;">'Forbidden'</span>,<br /> <span style="color: green;">'404'</span>: <span style="color: green;">'Not Found'</span>,<br /> <span style="color: green;">'405'</span>: <span style="color: green;">'Method Not Allowed'</span>,<br /> <span style="color: green;">'406'</span>: <span style="color: green;">'Not Acceptable'</span>,<br /> <span style="color: green;">'407'</span>: <span style="color: green;">'Proxy Authentication Required'</span>,<br /> <span style="color: green;">'408'</span>: <span style="color: green;">'Request Timeout'</span>,<br /> <span style="color: green;">'409'</span>: <span style="color: green;">'Conflict'</span>,<br /> <span style="color: green;">'410'</span>: <span style="color: green;">'Gone'</span>,<br /> <span style="color: green;">'411'</span>: <span style="color: green;">'Length Required'</span>,<br /> <span style="color: green;">'412'</span>: <span style="color: green;">'Precondition Failed'</span>,<br /> <span style="color: green;">'413'</span>: <span style="color: green;">'Payload Too Large'</span>,<br /> <span style="color: green;">'414'</span>: <span style="color: green;">'URI Too Long'</span>,<br /> <span style="color: green;">'415'</span>: <span style="color: green;">'Unsupported Media Type'</span>,<br /> <span style="color: green;">'416'</span>: <span style="color: green;">'Range Not Satisfiable'</span>,<br /> <span style="color: green;">'417'</span>: <span style="color: green;">'Expectation Failed'</span>,<br /> <span style="color: green;">'418'</span>: <span style="color: green;">'I\'m a teapot'</span>,<br /> <span style="color: green;">'421'</span>: <span style="color: green;">'Misdirected Request'</span>,<br /> <span style="color: green;">'422'</span>: <span style="color: green;">'Unprocessable Entity'</span>,<br /> <span style="color: green;">'423'</span>: <span style="color: green;">'Locked'</span>,<br /> <span style="color: green;">'424'</span>: <span style="color: green;">'Failed Dependency'</span>,<br /> <span style="color: green;">'425'</span>: <span style="color: green;">'Unordered Collection'</span>,<br /> <span style="color: green;">'426'</span>: <span style="color: green;">'Upgrade Required'</span>,<br /> <span style="color: green;">'428'</span>: <span style="color: green;">'Precondition Required'</span>,<br /> <span style="color: green;">'429'</span>: <span style="color: green;">'Too Many Requests'</span>,<br /> <span style="color: green;">'431'</span>: <span style="color: green;">'Request Header Fields Too Large'</span>,<br /> <span style="color: green;">'451'</span>: <span style="color: green;">'Unavailable For Legal Reasons'</span>,<br /> <span style="color: green;">'500'</span>: <span style="color: green;">'Internal Server Error'</span>,<br /> <span style="color: green;">'501'</span>: <span style="color: green;">'Not Implemented'</span>,<br /> <span style="color: green;">'502'</span>: <span style="color: green;">'Bad Gateway'</span>,<br /> <span style="color: green;">'503'</span>: <span style="color: green;">'Service Unavailable'</span>,<br /> <span style="color: green;">'504'</span>: <span style="color: green;">'Gateway Timeout'</span>,<br /> <span style="color: green;">'505'</span>: <span style="color: green;">'HTTP Version Not Supported'</span>,<br /> <span style="color: green;">'506'</span>: <span style="color: green;">'Variant Also Negotiates'</span>,<br /> <span style="color: green;">'507'</span>: <span style="color: green;">'Insufficient Storage'</span>,<br /> <span style="color: green;">'508'</span>: <span style="color: green;">'Loop Detected'</span>,<br /> <span style="color: green;">'509'</span>: <span style="color: green;">'Bandwidth Limit Exceeded'</span>,<br /> <span style="color: green;">'510'</span>: <span style="color: green;">'Not Extended'</span>,<br /> <span style="color: green;">'511'</span>: <span style="color: green;">'Network Authentication Required'</span> },<br /> _connectionListener: [Function: connectionListener],<br /> Server: { [Function: Server] super_: { [Function: Server] super_: [Object] } },<br /> createServer: [Function: createServer],<br /> ClientRequest: { [Function: ClientRequest] super_: { [Function: OutgoingMessage] super_: [Object] } },<br /> request: [Function: request],<br /> get: [Function: get] }<br /></pre></div>So now that we've confirmed that we have a way to require node modules into Opal, we just need to manipulate the Javascript objects. The basic techniques of accessing Javascript from Opal that we will be using have been written about <a href="http://funkworks.blogspot.com/2015/06/accessing-javascript-from-opal.html">here</a>.</div></div></div></h2><h2 id="orgheadline6">Simplest thing that could possibly work</h2><h2><div style="-webkit-text-stroke-width: 0px; color: black; font-family: Times; font-size: medium; font-style: normal; font-variant-caps: normal; font-variant-ligatures: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-decoration-color: initial; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px;"><div class="outline-2" id="outline-container-orgheadline6"><div class="outline-text-2" id="text-orgheadline6">To duplicate the original in the simplest way possible, we could call the original javascript via x-strings (the Opal mechanism for invoking arbitrary Javascript), like such.<br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; font-family: monospace; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;"><span style="color: #006fe0; font-weight: bold;">re2017-05-27T10:26:28Zurn:uuid:c481a838-a261-2d19-61f5-b49b1ce4fcberequire 'nodejs' - opening the entire server side node.js to Rubyist - in RubyI preface this with the<a href="http://funkworks.blogspot.com/2017/05/taking-andrzejs-blogging-challenge-of.html" target="_blank"> Roderick Disclaimer (scroll to the end)</a><br /><br />I'm going to spill the current "best kept secret in Opal." The two words<br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;"><span style="color: #dcdccc; font-weight: bold;">require</span> <span style="color: #cc9393;">'nodejs'</span><br /></pre></div>has the ability open the entire server side Node.js ecosystem to Rubyists, in Ruby. For Rubyists, think about that.<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://cdn.meme.am/instances/76828158.jpg#.WQz-bedX5n0.twitter" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://cdn.meme.am/instances/76828158.jpg#.WQz-bedX5n0.twitter" width="320" /></a></div><br /><br />It's a little known fact <a href="http://ruby-hyperloop.io/tutorials/showcase/">webpack</a> with Opal has opened up the npm ecosystem to Rubyists in Ruby on the browser. This fact is known and leveraged heavily by <a href="http://ruby-hyperloop.io/">ruby-hyperloop.io</a> allowing one to transparently use React.js npm modules as Ruby object.<br /><br /><code>require 'nodejs'</code> on the other hand is not documented. You'll only find out about if you know where in the <a href="https://github.com/opal/opal">opal source code</a> to look, or if you ask the right person the right question.<br /><br />I'm going to document this with a series articles showing the wrapping of a npm module to be used via Ruby DSL. I'll show techniques and approaches to enable it. Enjoy the journey.<br /><br />Rubyists can now enjoy the benefits of the juggernaut Javascript phenomenon outside the browser, happily via Ruby. Npm, prepare to be assimilated!<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://i.imgflip.com/1oielp.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://i.imgflip.com/1oielp.jpg" width="320" /></a></div><br /><img src="http://feeds.feedburner.com/~r/PuttingTheFunIntoFunkworks/~4/rUGvMvzGDEQ" height="1" width="1" alt=""/>2017-05-05T22:41:36Zurn:uuid:9783c95d-4856-accf-eabd-f5a46e0cb650Taking Andrzej's blogging challenge of sorts - and you should too<div class="separator" style="clear: both; text-align: center;"><a href="https://cdn-images-1.medium.com/max/800/1*cYK866CP_fzsJ-APGlc9cg.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://cdn-images-1.medium.com/max/800/1*cYK866CP_fzsJ-APGlc9cg.png" width="320" /></a></div><div><br /></div>The best tech person I follow on Snapchat is <a href="https://twitter.com/andrzejkrzywda" target="_blank">Andrzej Krzywda</a>. His efforts to make <a href="https://medium.com/planet-arkency/snapchat-for-programmers-84f5aee42e1c" target="_blank">Snapchat for programmers</a> really changed my opinion on what Snapchat could be - that is it's own post. Long story short, Snapchat is not longer just entertainment for me. Today he snapped about blogging and programmer, and in participating in you Snap question, we discussed a number of things privately.<br /><div><br /></div><div>I fit the profile of someone who blogs, but not as frequent as I want to. On Snapchat Andrzej talked a brought up a topic that struck a strong chord with me. There's substantial knowledge out there that never goes anywhere because some one doesn't write/blog about it. In our private discussion he asked me:</div><div><span style="font-family: "courier new" , "courier" , monospace;"><br /></span></div><div><blockquote><span style="font-family: "courier new" , "courier" , monospace;">how many blogposts did you write in 2017? could it be that you want to cover a topic really well and this limits you?</span></blockquote></div><div><br /></div><div>His question is really on the spot. I definitely want to write an article well, and usually don't have the time time get it done at that level I would like. I might start, but I don't finish. Consequently, I have a huge backlog of topics I want or wanted to write about that grows all the time. Aside from this perfectionist type issue, I believe the other part of the problem is that things I want to write about are deep. Andrezj asked me what I meant by deep, and I gave him a couple of examples topics and he was very interested in those topics. This took me back to the point I noticed in his Snap, that this knowledge, which at least is of interest to him, is going no where unless it gets persisted outside my head.</div><div><br /></div><div>An a programmer who works with and contributes to open source, this is especially important to me. So much has been shared to me via code, blog posts, videos, etc., it's only fitting that I give back. I have the desire to do so, and hopefully have experiences worth sharing, so time to do something about it.<br /><br /><h2>Take the challenge!</h2></div><div><br /></div><div>Another other topic from Andrzej's Snap storm, was about getting the content out there. Blogging doesn't have to a the novel level effort. He presented an anecdote about someone he worked with who would put out timely blogs before and after lunch, showing what is possible. This was revisited when Andrzej gave me this advice</div><div><br /></div><div><blockquote><span style="font-family: "courier new" , "courier" , monospace;">open your blog editor and this there and publish :) timebox to 10 mninutes, just your toughts. won't be perfect but 15 minutes from now more people will be able to read it :)</span></blockquote></div><div><br /></div><div>It struck me that I should do just that. Time boxing helps w/so many types of paralysis. 10 minutes, I could afford 10 minutes. So I'll do it<br /><br /><br /><h2>Take the challenge!</h2><br /> So this is not the topic he was asking about, but the first post by me in accepting that challenge. If your in the same boat as me, blogs, but much less than you would like to, you should too.<br /><br /><h2>Take the challenge!</h2><br /><h4>The Roderick Disclaimer</h4>I should add a "I want to get this info out there, so please excuse any mistakes made in haste" disclaimer going forward. This disclaimer needs a name. I shall call it Roderick, because I have no better name and the time box is up</div><img src="http://feeds.feedburner.com/~r/PuttingTheFunIntoFunkworks/~4/YxuhX3DHy08" height="1" width="1" alt=""/>2017-05-05T21:43:13Zurn:uuid:d4baf7e7-caa0-1817-bc80-175c00392bfa<div class="outline-2" id="outline-container-orgheadline1"><h2 id="orgheadline1">A Positive note - I'm on Team Hanna</h2><div class="outline-text-2" id="text-orgheadline1">This is not my usual faire, but I think in world that seems to be more and more divided and negative, a positive note is warranted. So let reaffirm my view of the future is not completely bleak. Also, I want to much deserved kudos, cuz that's just the kind of thing more people should do.<br /><br />My kids' piano teacher, also my oldest sons' former classmate <a href="https://www.hannaeyre.com/">Hanna Eyre</a> got onto "Team Adam" on <a href="http://www.nbc.com/the-voice">The Voice</a> last week.</div></div><div class="outline-2" id="outline-container-orgheadline2"><h2 id="orgheadline2">The Blind Audition</h2><div class="outline-text-2" id="text-orgheadline2">Here's her blind audition video:<br /><iframe allowfullscreen="" frameborder="0" height="335" src="https://www.youtube.com/embed/hmHQxaW6Eaw" width="440"></iframe><br /><br />Hanna did well - 3 out of 4 judges turned around. The judges were also impressed that Hanna is only 15 years old.<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://2.bp.blogspot.com/-F210EFGMWns/WNAoxxMiM7I/AAAAAAAAuoI/jJdUIb7EDGwMj2SqMl74atueGC1ux4MMgCLcB/s1600/15_years_old.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="250" src="https://2.bp.blogspot.com/-F210EFGMWns/WNAoxxMiM7I/AAAAAAAAuoI/jJdUIb7EDGwMj2SqMl74atueGC1ux4MMgCLcB/s320/15_years_old.png" width="320" /></a></div><br /><br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://4.bp.blogspot.com/-YjnVs5bwDDw/WNAozPpeQDI/AAAAAAAAuoo/-1KJMKHh4_sphM-bzf4juhaFIS636vk7ACLcB/s1600/wow_15.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="250" src="https://4.bp.blogspot.com/-YjnVs5bwDDw/WNAozPpeQDI/AAAAAAAAuoo/-1KJMKHh4_sphM-bzf4juhaFIS636vk7ACLcB/s320/wow_15.png" width="320" /></a></div><br /><br /> <br />So before she gets famous, let me take the opportunity to tell you a few "Things about Hanna you didn't know"</div></div><div class="outline-2" id="outline-container-orgheadline3"><h2 id="orgheadline3">Talent</h2><div class="outline-text-2" id="text-orgheadline3"><div class="figure" style="padding: 1em;"><div style="text-align: center;"><br /></div></div><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-BWMzEejlOrY/WNAoyQRZqwI/AAAAAAAAuoQ/cSX6-UBub003bFpvoGbOMZx0bocMDsaLQCLcB/s1600/miraculously_talented.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="203" src="https://1.bp.blogspot.com/-BWMzEejlOrY/WNAoyQRZqwI/AAAAAAAAuoQ/cSX6-UBub003bFpvoGbOMZx0bocMDsaLQCLcB/s320/miraculously_talented.png" width="320" /></a></div>Ok, so musical talent is something you probably DID know her. Adam Levine (I realize the pic has Blake, but I can't control when the subtitles show) called her "miraculously talented" just from listening to one song. Besides singing well, Hanna also plays and teaches piano. She is a student at the <a href="http://www.ocsarts.net/">Orange County School of the Arts (OCSA)</a> - an audition based arts school where she eagerly soaking as much as the music program as she can. I remember her telling me about looking forward to composition classes and learning other instruments. So she is ever expanding here palette. Her sheer excitement over her opportunities is uplifting. She has been writing songs since she was 11, one of which won her 1st place in the Music category and 1st place overall at Mind the Gap, the 2013 TEDx Bommer Canyon competition.<br /><div class="figure" style="padding: 1em;"><div class="separator" style="clear: both; text-align: center;"><a href="https://3.bp.blogspot.com/-xo3Z_sbC_UA/WNAoxz6BNQI/AAAAAAAAuoE/rsehTmnfsSchaTLMec7aklgGcTm6dYiLgCLcB/s1600/good_voice.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="173" src="https://3.bp.blogspot.com/-xo3Z_sbC_UA/WNAoxz6BNQI/AAAAAAAAuoE/rsehTmnfsSchaTLMec7aklgGcTm6dYiLgCLcB/s320/good_voice.png" width="320" /></a></div><div style="text-align: center;"><br /></div></div>Anyways, she's not just a good singer, more on this. Oh, and a reminder - only 15, she's just getting started<br /><div class="figure" style="padding: 1em;"><div style="text-align: center;"><br /></div></div></div></div><div class="outline-2" id="outline-container-orgheadline4"><div class="separator" style="clear: both; text-align: center;"><a href="https://2.bp.blogspot.com/-7kVObHcul-o/WNAoyZcmrXI/AAAAAAAAuoM/nb7sNlAcM9oAAwsNK2lAEIrFZs8Km3ICgCLcB/s1600/gwen_15.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="194" src="https://2.bp.blogspot.com/-7kVObHcul-o/WNAoyZcmrXI/AAAAAAAAuoM/nb7sNlAcM9oAAwsNK2lAEIrFZs8Km3ICgCLcB/s320/gwen_15.png" width="320" /></a></div><h2 id="orgheadline4">Smart</h2><div class="outline-text-2" id="text-orgheadline4">Aside musically talented, she's smart cookie. She was in the all the "smart kids classes" along w/my son, so if she didn't take the musical route via OCSA, I have no doubt she'd be tearing through honors and AP classes in high school, outside of her various musical pursuits.<br />Talented and smart? What else could you ask for?</div></div><div class="outline-2" id="outline-container-orgheadline5"><h2 id="orgheadline5">Sweet</h2><div class="outline-text-2" id="text-orgheadline5"><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-Z9wRq3VTcb4/WNAoyyoWPAI/AAAAAAAAuoc/I7JoKSU0GakOePM6luASB1cpykBQk-hQgCLcB/s1600/sweet_hanna.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="212" src="https://1.bp.blogspot.com/-Z9wRq3VTcb4/WNAoyyoWPAI/AAAAAAAAuoc/I7JoKSU0GakOePM6luASB1cpykBQk-hQgCLcB/s320/sweet_hanna.png" width="320" /></a></div><div class="figure" style="padding: 1em;"><div style="text-align: center;"><br /></div></div>She and my oldest son went to school together up until high school. Before I learned how musically talented she is, I remember a sweet, petite girl was who helped my son carry back large percussion pieces (remember the petite part?) back from the bus after an away performance. I was impressed at how helpful she was at a time most kids couldn't wait to go home after a long day out. She is genuinely a sweet soul.<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://3.bp.blogspot.com/-1LHp78BMpwU/WNAoywxb6YI/AAAAAAAAuog/vqVrHpsP9SQvUCV7sVcH_ksV2d6Kh3RzgCLcB/s1600/sparkly_talented_and_young.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="253" src="https://3.bp.blogspot.com/-1LHp78BMpwU/WNAoywxb6YI/AAAAAAAAuog/vqVrHpsP9SQvUCV7sVcH_ksV2d6Kh3RzgCLcB/s320/sparkly_talented_and_young.png" width="320" /></a></div><div class="figure" style="padding: 1em;"><div style="text-align: center;"><br /></div></div>Gwen Stefani mentioned "sparkly" - I think that's a good word.</div></div><div class="outline-2" id="outline-container-orgheadline6"><h2 id="orgheadline6">That Smile</h2><div class="outline-text-2" id="text-orgheadline6"><div class="figure" style="padding: 1em;"><div style="text-align: center;"><br /></div></div><div class="separator" style="clear: both; text-align: center;"><a href="https://2.bp.blogspot.com/-AKIyd2CTptk/WNAoykfJDEI/AAAAAAAAuok/MoFDWUbYyNMeBQJp4cDvrd-EomdYEm9hACLcB/s1600/smile.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="203" src="https://2.bp.blogspot.com/-AKIyd2CTptk/WNAoykfJDEI/AAAAAAAAuok/MoFDWUbYyNMeBQJp4cDvrd-EomdYEm9hACLcB/s320/smile.png" width="320" /></a></div>Blake Shelton mentioned when she smiles "the room lights up" when she smiles, and later references her smile again "stand there and do that and I'll buy your record." Hanna smiles a lot. It is an honest, joyous smile that is pretty infectious.<br /><div class="figure" style="padding: 1em;"><div class="separator" style="clear: both; text-align: center;"><a href="https://4.bp.blogspot.com/-Pe14r7kH-KE/WNAoyjJdt-I/AAAAAAAAuoY/gAzO24aqi_gC3LFWQXb7Wup8KWtTi9SaQCLcB/s1600/room_lights_up.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="263" src="https://4.bp.blogspot.com/-Pe14r7kH-KE/WNAoyjJdt-I/AAAAAAAAuoY/gAzO24aqi_gC3LFWQXb7Wup8KWtTi9SaQCLcB/s320/room_lights_up.png" width="320" /></a></div><div style="text-align: center;"><br /></div></div></div></div><div class="outline-2" id="outline-container-orgheadline7"><h2 id="orgheadline7">Music lessons and my kids</h2><div class="outline-text-2" id="text-orgheadline7">As mentioned before, Hanna teaches my kids piano. While I may some coincidental semblance the "Asian Tiger parent", I'm interested in music lessons NOT to get them into Juilliard, NOR as typical Asian "piano and/or violin" Tiger level training, because that's something some of us do. I love music, and I want them to have the opportunity to enjoy it. I have a friend who's mother taught piano, but he didn't want to learn it. He regretted setting the foundation younger in life as he tried to learn in his adulthood. I want to put that foundation in early, while's it's easier and they have the time build that skill. But most importantly, I want them to enjoy it.</div></div><div class="outline-2" id="outline-container-orgheadline8"><h2 id="orgheadline8">Talented teacher</h2><div class="outline-text-2" id="text-orgheadline8">While she clearly has talent as performer she is also a gifted teacher. My kids have had another teacher, but Hanna is a better fit for my musical goals. Firstly, Hanna loves music, it should be obvious, but I'll restate it. That comes through in her teaching, along with her normal "sparkly" enthusiasm. She loves music, and she wants you love music.<br /><br />Secondly, she's good at teaching kids (my kids range from 6-15 a pretty wide range). To start, she has seemingly unlimited patience, at least with my kids. I'm told that might not be true for her siblings, but hey, she's human. I have recollection of less patient music teachers. Arguably anyone with longer attention spans should also benefit from her patience.She's also a kid herself, so there's a relate-ability there that maybe a 50 year old virtuoso might not have with kids. Her approach is non traditional in that she quickly makes the jump to songs the kids want to play after establishing a certain level of basics. This differs from a typical classical music based curriculum where the songs are often not what the children want to play<br /><br />What she did with my kids was to find a song they liked and then teach them to play it. My kids being very into computer games picked themes to various games that Hanna never heard before. Hanna would find them on YouTube with her iPhone and after a few listens turnaround and teach them. It's impressive to see how quickly she's able to learn the song and then make a suitable level piano arrangement on the fly. Getting quickly to a song they like rewarding and motivating way to teach. Some of her students take the more traditional classical music approach, as I noted from classical pieces in their recitals, so she can also teach that route, if desired. I know from talking with her, her approach to teaching mirrors her own progression from classical music to the contemporary music that she loved.<br /><br />I think an approach that explicitly follows the direction of "music you like" needs to be more prevalent. Like any skill, when music lessons become a chore, it's harder for most people to progress. A related anecdone, my wife, who loves music and singing, took piano lessons for several years, but pretty much forgot it all. Reason: she never learned songs she liked so she didn't stick with it. When I asked her why she didn't to go to a store like the (long defunct) <a href="https://en.wikipedia.org/wiki/Sam_Goody">Sam Goody</a> and just buy the sheet music to songs she liked, her reply was she didn't know that was even a possibility. I'm fortunate enough that I knew at a young age, that one could just learn to play songs they liked regardles of what they were being taught, but apparently it is not common knowledge.</div></div><div class="outline-2" id="outline-container-orgheadline9"><h2 id="orgheadline9">Deserving</h2><div class="outline-text-2" id="text-orgheadline9">So I think I've been a #TeamHanna member before there was such a thing. I've known for a long time that she would do well in whatever she pursued. she pursued. For those who don't know Hanna, if the video gives you the impression that she's awesome, know that it's true. Hard to think of someone more deserving of the success she's currently experiences. I wish her the best on the Voice and everything else she pursues. Hanna, if you read this, I hope this makes your day, because you deserve it. Keep living the dream and realize that this is the tip of the iceberg and it will all just keep going up. Don't listen to any haters that might come your way. May you inspire many other young people. Stay sparkly!<br /><div class="figure" style="padding: 1em;"><div class="separator" style="clear: both; text-align: center;"><a href="https://3.bp.blogspot.com/-nS39P4vGmcc/WNAoyeBiA6I/AAAAAAAAuoU/zzTpjxl_go4q6fcmp4CdNtNGVolD4tQ5gCLcB/s1600/only_the_tip.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="209" src="https://3.bp.blogspot.com/-nS39P4vGmcc/WNAoyeBiA6I/AAAAAAAAuoU/zzTpjxl_go4q6fcmp4CdNtNGVolD4tQ5gCLcB/s320/only_the_tip.png" width="320" /></a></div><div style="text-align: center;"><br /></div></div></div></div><div class="outline-2" id="outline-container-orgheadline10"><h2 id="orgheadline10">Random Aside</h2><div class="outline-text-2" id="text-orgheadline10">So I'm plenty backlogged on desired blog posts, but I just wanted to get a positive word out in a timely manner.</div></div><img src="http://feeds.feedburner.com/~r/PuttingTheFunIntoFunkworks/~4/xQ3bwHXs_UI" height="1" width="1" alt=""/>2017-03-20T19:18:22Zurn:uuid:cd3427dd-e0ea-3ffc-8c77-161742088ca8A clever use of social media by @24fox<style type="text/css">p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Calibri} </style> <br /><div class="p1">So I’m a 24 (the show) fan. I see this in my tweet stream:</div><div class="p1"><br /></div><div class="p1"><style type="text/css">p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Calibri} </style> <br /><blockquote class="twitter-tweet" data-lang="en"><div dir="ltr" lang="en">Join the CTU — Tweet <a href="https://twitter.com/24fox">@24fox</a> + ⏰ + <a href="https://twitter.com/hashtag/24Legacy?src=hash">#24Legacy</a> to receive your badge! <a href="https://t.co/I2ADUR4p4b">pic.twitter.com/I2ADUR4p4b</a></div>— 24: Legacy (@24fox) <a href="https://twitter.com/24fox/status/827240296408707072">February 2, 2017</a></blockquote><br /></div><div class="p1">So of course, I’m in. I tweet (trying to be slightly clever w/my wording) and 2 min later I get my badge.</div><div class="p1"><br /></div><div class="p1"><br /></div><blockquote class="twitter-tweet" data-lang="en"><div dir="ltr" lang="en"><a href="https://twitter.com/fkchang2000">@fkchang2000</a> The CTU could use someone like you. Catch the series premiere of <a href="https://twitter.com/hashtag/24Legacy?src=hash">#24Legacy</a>, February 5 on <a href="https://twitter.com/FOXTV">@FOXTV</a>. ⏰ <a href="https://t.co/CeDAJJx9ut">pic.twitter.com/CeDAJJx9ut</a></div>— 24: Legacy (@24fox) <a href="https://twitter.com/24fox/status/827266876187086848">February 2, 2017</a></blockquote><script async="" charset="utf-8" src="//platform.twitter.com/widgets.js"></script><script async="" charset="utf-8" src="//platform.twitter.com/widgets.js"></script> <style type="text/css">p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Calibri} li.li1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Calibri} ul.ul1 {list-style-type: disc} ul.ul2 {list-style-type: circle} </style> <br /><div class="p1">So I think this is brilliant.</div><ul class="ul1"><li class="li1">I’m already a fan, and thus I follow @24fox.</li><li class="li1">Though I already know the new 24 reboot is airing right after Superbowl, I won’t be forgetting, make no doubt.</li><li class="li1">I get to be immersed a bit more in the 24 “fantasy”. I can get a CTU badge simply by tweeting</li><ul class="ul2"><li class="li1">So of course, I do it. ROI guaranteed.</li></ul><li class="li1">24 legacy gets advertised to my (paltry) twitter follower base, non 24 fans get some advertising that I happily buy into.</li><li class="li1">I love my badge, it’s taped on the whiteboard to my left.</li><li class="li1">Win win, I’m happy, they get more exposure</li><li class="li1">If they choose to look, they can see which one of the fanbase will respond and follow directions - include 3 specific words in their reply. This might be useful data.</li></ul><span style="font-family: "calibri"; font-size: 14px;">Technology wise , it looks pretty simple. It's almost certainly done with a twitter bot that looks for the specific 3 words tweeted to @24fox. When it gets such a tweet, it slaps the tweeter’s avatar and user name onto a CTU badge template, and then tweets the image with a random selection of canned response.</span><span style="font-family: "calibri"; font-size: 14px;">Very nice, hats of the 24 Legacy social media team</span><br /><script async="" charset="utf-8" src="//platform.twitter.com/widgets.js"></script><img src="http://feeds.feedburner.com/~r/PuttingTheFunIntoFunkworks/~4/hHtjVuRGt0Y" height="1" width="1" alt=""/>2017-02-02T22:07:47Zurn:uuid:3febf293-6256-b7a0-e9eb-843eb72f4585WEWLJCIO - Working Effectively with Legacy Javascript Code in Opal<div id="content"><div class="outline-2" id="outline-container-orgheadline1"><h2 id="orgheadline1"><span style="font-family: "arial" , "helvetica" , sans-serif;">Background</span></h2><div class="outline-text-2" id="text-orgheadline1"><span style="font-family: "arial" , "helvetica" , sans-serif;">I've been working on <a href="https://github.com/fkchang/opal-hot-reloader" target="_blank">opal-hot-reloader</a>, a hot reloader for <a href="http://opalrb.org/" target="_blank">Opal</a> applications including built in <a href="http://reactrb.org/" target="_blank">react.rb</a> support. I merged a pull request (thanks Brock!) that added hot css reloading. The code worked, but it was implemented primarily in Javascript and did not have tests. To make the code easier to read, maintain and extend, I wanted to both add tests and convert it to Ruby/Opal. In particular, I plan to extend the code to handle css served by the <a href="http://rubyonrails.org/" target="_blank">Ruby on Rails</a> pipeline so I want to shape the code to be more amenable to extension.</span><br /><span style="font-family: "arial" , "helvetica" , sans-serif;">Now if this situation sounds a bit like <a href="http://c2.com/cgi/wiki?WorkingEffectivelyWithLegacyCode" target="_blank">Working Effectively With Legacy Code</a> by Michael Feathers, it should, and thus the title of this post. This particular situation does present a slightly different set of conditions than many refactorings, i.e.:</span><br /><ul class="org-ul"><li><span style="font-family: "arial" , "helvetica" , sans-serif;">The desire to convert languages - Javascript to Ruby (or transpiled Ruby via Opal). The interface between those Javascript and Opal objects in the browser affects how things need to proceed.</span></li><li><span style="font-family: "arial" , "helvetica" , sans-serif;">The predefined browser API and test doubles</span></li></ul><span style="font-family: "arial" , "helvetica" , sans-serif;">Hopefully, this article can bring some different insights for Opal developers.</span></div></div><div class="outline-2" id="outline-container-orgheadline2"><h2 id="orgheadline2"><span style="font-family: "arial" , "helvetica" , sans-serif;">Where to start?</span></h2><div class="outline-text-2" id="text-orgheadline2"><span style="font-family: "arial" , "helvetica" , sans-serif;">Let's look at the code. An additional <code>if clause</code> was added to <code>OpalHotReloader#reload()</code> that performs the css hot reloading on the client side. The functionality is implemented in Javascript via an Opal x-string.</span><br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;"><span style="color: #56b4e9; font-weight: bold;">def</span> <span style="color: #d55e00;">reload</span>(e)<br /> <span style="color: #5f7f5f;"># </span><span style="color: #009e73; font-style: italic;">original code</span><br /> reload_request = <span style="color: #0072b2; font-weight: bold;">JSON</span>.parse(<span style="color: #848ea9;">`e.data`</span>)<br /> <span style="color: #56b4e9; font-weight: bold;">if</span> reload_request[<span style="color: #d55e00; font-weight: bold;">:type</span>] == <span style="color: #848ea9;">"ruby"</span><br /> <span style="color: #0072b2; font-weight: bold;">puts</span> <span style="color: #848ea9;">"Reloading ruby </span><span style="color: #e69f00; font-weight: bold;">#{reload_request[:filename]}</span><span style="color: #848ea9;">"</span><br /> <span style="color: #0072b2; font-weight: bold;">eval</span> reload_request[<span style="color: #d55e00; font-weight: bold;">:source_code</span>]<br /> <span style="color: #56b4e9; font-weight: bold;">if</span> <span style="color: #e69f00; font-weight: bold;">@reload_post_callback</span><br /> <span style="color: #e69f00; font-weight: bold;">@reload_post_callback</span>.call<br /> <span style="color: #56b4e9; font-weight: bold;">else</span><br /> <span style="color: #0072b2; font-weight: bold;">puts</span> <span style="color: #848ea9;">"not reloading code"</span><br /> <span style="color: #56b4e9; font-weight: bold;">end</span><br /> <span style="color: #56b4e9; font-weight: bold;">end</span><br /> <span style="color: #5f7f5f;"># </span><span style="color: #009e73; font-style: italic;">the new css hot reloading code</span><br /> <span style="color: #56b4e9; font-weight: bold;">if</span> reload_request[<span style="color: #d55e00; font-weight: bold;">:type</span>] == <span style="color: #848ea9;">"css"</span><br /> url = reload_request[<span style="color: #d55e00; font-weight: bold;">:url</span>]<br /> <span style="color: #0072b2; font-weight: bold;">puts</span> <span style="color: #848ea9;">"Reloading CSS: </span><span style="color: #e69f00; font-weight: bold;">#{url}</span><span style="color: #848ea9;">"</span><br /> <span style="color: #5f7f5f;"># </span><span style="color: #009e73; font-style: italic;">Work outsources Javascript via x-string</span><br /> <span style="color: #848ea9;">%x{</span><br /><span style="color: #848ea9;"> var toAppend = "t_hot_reload=" + (new Date()).getTime();</span><br /><span style="color: #848ea9;"> var links = document.getElementsByTagName("link");</span><br /><span style="color: #848ea9;"> for (var i = 0; i < links.length; i++) {</span><br /><span style="color: #848ea9;"> var link = links[i];</span><br /><span style="color: #848ea9;"> if (link.rel === "stylesheet" && link.href.indexOf(</span><span style="color: #e69f00; font-weight: bold;">#{url}</span><span style="color: #848ea9;">) >= 0) {</span><br /><span style="color: #848ea9;"> if (link.href.indexOf("?") === -1) {</span><br /><span style="color: #848ea9;"> link.href += "?" + toAppend;</span><br /><span style="color: #848ea9;"> } else {</span><br /><span style="color: #848ea9;"> if (link.href.indexOf("t_hot_reload") === -1) {</span><br /><span style="color: #848ea9;"> link.href += "&" + toAppend;</span><br /><span style="color: #848ea9;"> } else {</span><br /><span style="color: #848ea9;"> link.href = link.href.replace(/t_hot_reload=\d{13}/, toAppend)</span><br /><span style="color: #848ea9;"> }</span><br /><span style="color: #848ea9;"> }</span><br /><span style="color: #848ea9;"> }</span><br /><span style="color: #848ea9;"> }</span><br /><span style="color: #848ea9;"> }</span><br /><br /> endx<br /><span style="color: #56b4e9; font-weight: bold;">end</span><br /></pre></div></div></div><div class="outline-2" id="outline-container-orgheadline3"><h2 id="orgheadline3"><span style="font-family: "arial" , "helvetica" , sans-serif;">First steps</span></h2><div class="outline-text-2" id="text-orgheadline3"><span style="font-family: "arial" , "helvetica" , sans-serif;">The first thing I noticed was the formerly smallish <code>reload()</code> method had tripled in size and gained a new responsibilty - css hot reloading. I'll want to refactor that code out the body of the <code>reload()</code> method as a method of a new class,<code>OpalHotReloader::CssReloader</code> dedicated to just css hot reloading (<a href="https://en.wikipedia.org/wiki/Single_responsibility_principle" target="_blank">SRP</a>). We'll instantiate this class instance as<code>@css_reloader</code> in the initializer for <code>OpalHotReloader</code> and then delegate css hot reloading to that instance in<code>OpalHotReloader::CssReloader::reload()</code>.</span><br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;"><span style="color: #56b4e9; font-weight: bold;">def</span> <span style="color: #d55e00;">reload</span>(e)<br /> reload_request = <span style="color: #0072b2; font-weight: bold;">JSON</span>.parse(<span style="color: #848ea9;">`e.data`</span>)<br /> <span style="color: #56b4e9; font-weight: bold;">if</span> reload_request[<span style="color: #d55e00; font-weight: bold;">:type</span>] == <span style="color: #848ea9;">"ruby"</span><br /> <span style="color: #0072b2; font-weight: bold;">puts</span> <span style="color: #848ea9;">"Reloading ruby </span><span style="color: #e69f00; font-weight: bold;">#{reload_request[:filename]}</span><span style="color: #848ea9;">"</span><br /> <span style="color: #0072b2; font-weight: bold;">eval</span> reload_request[<span style="color: #d55e00; font-weight: bold;">:source_code</span>]<br /> <span style="color: #56b4e9; font-weight: bold;">if</span> <span style="color: #e69f00; font-weight: bold;">@reload_post_callback</span><br /> <span style="color: #e69f00; font-weight: bold;">@reload_post_callback</span>.call<br /> <span style="color: #56b4e9; font-weight: bold;">else</span><br /> <span style="color: #0072b2; font-weight: bold;">puts</span> <span style="color: #848ea9;">"not reloading code"</span><br /> <span style="color: #56b4e9; font-weight: bold;">end</span><br /> <span style="color: #56b4e9; font-weight: bold;">end</span><br /> <span style="color: #56b4e9; font-weight: bold;">if</span> reload_request[<span style="color: #d55e00; font-weight: bold;">:type</span>] == <span style="color: #848ea9;">"css"</span><br /> <span style="color: #e69f00; font-weight: bold;">@css_reloader</span>.reload(reload_request) <span style="color: #5f7f5f;"># </span><span style="color: #009e73; font-style: italic;">extracted method called here</span><br /> <span style="color: #56b4e9; font-weight: bold;">end</span><br /><span style="color: #56b4e9; font-weight: bold;">end</span><br /></pre></div></div></div><div class="outline-2" id="outline-container-orgheadline4"><h2 id="orgheadline4"><span style="font-family: "arial" , "helvetica" , sans-serif;">CssReloader</span></h2><div class="outline-text-2" id="text-orgheadline4"><span style="font-family: "arial" , "helvetica" , sans-serif;">For our combination of <a href="http://refactoring.com/catalog/extractMethod.html" target="_blank">Extract Method</a> and <a href="http://refactoring.com/catalog/extractClass.html" target="_blank">Extract Class</a>, we simply relocated the code to a <code>reload()</code> method of the new class. We don't have tests yet, so we test it manually. The code still works! Here is the code for the new class.</span><br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;"><span style="color: #56b4e9; font-weight: bold;">class</span> <span style="color: #0072b2; font-weight: bold;">OpalHotReloader</span><br /> <span style="color: #56b4e9; font-weight: bold;">class</span> <span style="color: #0072b2; font-weight: bold;">CssReloader</span><br /><br /> <span style="color: #56b4e9; font-weight: bold;">def</span> <span style="color: #d55e00;">reload</span>(reload_request)<br /> url = reload_request[<span style="color: #d55e00; font-weight: bold;">:url</span>]<br /> <span style="color: #848ea9;">%x{</span><br /><span style="color: #848ea9;"> var toAppend = "t_hot_reload=" + (new Date()).getTime();</span><br /><span style="color: #848ea9;"> var links = document.getElementsByTagName("link");</span><br /><span style="color: #848ea9;"> for (var i = 0; i < links.length; i++) {</span><br /><span style="color: #848ea9;"> var link = links[i];</span><br /><span style="color: #848ea9;"> if (link.rel === "stylesheet" && link.href.indexOf(</span><span style="color: #e69f00; font-weight: bold;">#{url}</span><span style="color: #848ea9;">) >= 0) {</span><br /><span style="color: #848ea9;"> if (link.href.indexOf("?") === -1) {</span><br /><span style="color: #848ea9;"> link.href += "?" + toAppend;</span><br /><span style="color: #848ea9;"> } else {</span><br /><span style="color: #848ea9;"> if (link.href.indexOf("t_hot_reload") === -1) {</span><br /><span style="color: #848ea9;"> link.href += "&" + toAppend;</span><br /><span style="color: #848ea9;"> } else {</span><br /><span style="color: #848ea9;"> link.href = link.href.replace(/t_hot_reload=\d{13}/, toAppend)</span><br /><span style="color: #848ea9;"> }</span><br /><span style="color: #848ea9;"> }</span><br /><span style="color: #848ea9;"> }</span><br /><span style="color: #848ea9;"> }</span><br /><span style="color: #848ea9;"> }</span><br /> <span style="color: #56b4e9; font-weight: bold;">end</span><br /><br /> <span style="color: #56b4e9; font-weight: bold;">end</span><br /><span style="color: #56b4e9; font-weight: bold;">end</span><br /></pre></div></div></div><div class="outline-2" id="outline-container-orgheadline5"><h2 id="orgheadline5"><span style="font-family: "arial" , "helvetica" , sans-serif;">Writing tests</span></h2><div class="outline-text-2" id="text-orgheadline5"><span style="font-family: "arial" , "helvetica" , sans-serif;">Now that we've verified it still works as expected. We will write some tests. We'll use <a href="https://github.com/opal/opal-rspec" target="_blank">opal-rspec</a>, which is a popular choice in the opal community and my preferred test framework for Ruby. From here on, I will be referring to the tests as specs since I'm using RSpec.</span><br /><span style="font-family: "arial" , "helvetica" , sans-serif;">Because of how we hot reload css via direct maninpulation of stylesheet links in the <code>document</code>, I was faced with the problem of how to set up a test. I could create stylesheet links in the actual DOM of spec runner (fairly easy to do), but I don't like the idea of calling out to methods the on the global <code>document</code> object. I want to be able to inject a <i>test document</i> for the spec, and inject the real <code>document</code> for the actual application. Fortunately, we can dispense with standard dependency injection (constructor, setter, interface) techniques altogether and just pass the "document" in as a parameter to the method.</span><br /><span style="font-family: "arial" , "helvetica" , sans-serif;">Here's where it gets little tricky. To do hot css reloading we have to manipulate the browser DOM via the global <code>document</code>object. This browser interface is not under my control, so we have to abide by this interface. Because of the interface between Opal objects and Javascript objects (some details described <a href="http://funkworks.blogspot.com/2015/06/accessing-javascript-from-opal.html" target="_blank">here</a>) we'll want to create suitable test doubles in pure Javascript. While opal-rspec gives you the full power of rspec's mock library, these test doubles are Opal objects which won't act the way the Javascript DOM objects I'm looking to mock/stub out will. We'll need to create our own method of creating these test doubles.</span><br /><span style="font-family: "arial" , "helvetica" , sans-serif;">To create the doubles, I've created two methods: </span><br /><ul class="org-ul"><li><span style="font-family: "arial" , "helvetica" , sans-serif;"><code>create_link()</code> to create the link DOM object that will get altered to facillitate the css hot reloading and</span></li><li><span style="font-family: "arial" , "helvetica" , sans-serif;"><code>fake_links_document()</code> a convenience method which returns both a test double for global <code>document</code> object, which responds to the <code>getElementsByTagName('link')</code> call and a test double for the link itself, that I will inspect to see whether it has been correctly altered.</span></li></ul><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;"><span style="color: #56b4e9; font-weight: bold;">def</span> <span style="color: #d55e00;">create_link</span>( href)<br /> <span style="color: #848ea9;">%x|</span><br /><span style="color: #848ea9;"> var ss = document.createElement("link");</span><br /><span style="color: #848ea9;"> ss.type = "text/css";</span><br /><span style="color: #848ea9;"> ss.rel = "stylesheet";</span><br /><span style="color: #848ea9;"> ss.href = </span><span style="color: #e69f00; font-weight: bold;">#{href}</span><span style="color: #848ea9;">;</span><br /><span style="color: #848ea9;"> return ss;</span><br /><span style="color: #848ea9;">|</span><br /><span style="color: #56b4e9; font-weight: bold;">end</span><br /><br /><span style="color: #56b4e9; font-weight: bold;">def</span> <span style="color: #d55e00;">fake_links_document</span>(href)<br /> link = create_link(href)<br /> doc = <span style="color: #848ea9;">`{ getElementsByTagName: function(name) { links = [ </span><span style="color: #e69f00; font-weight: bold;">#{link}</span><span style="color: #848ea9;">]; return links;}}`</span><br /> { <span style="color: #d55e00; font-weight: bold;">link</span>: link, <span style="color: #d55e00; font-weight: bold;">document</span>: doc}<br /><span style="color: #56b4e9; font-weight: bold;">end</span><br /></pre></div><span style="font-family: "arial" , "helvetica" , sans-serif;">To suport this new "parameter injection" we need to change the existing interface. We change the signature of the<code>OpalHotReloard::CssReloader#reload()</code> to take in the document</span><br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;"><span style="color: #5f7f5f;"># </span><span style="color: #009e73; font-style: italic;">from</span><br /><span style="color: #56b4e9; font-weight: bold;">def</span> <span style="color: #d55e00;">reload</span>(reload_request) <br /><span style="color: #5f7f5f;"># </span><span style="color: #009e73; font-style: italic;">to</span><br /><span style="color: #56b4e9; font-weight: bold;">def</span> <span style="color: #d55e00;">reload</span>(reload_request, document)<br /></pre></div><span style="font-family: "arial" , "helvetica" , sans-serif;">Then we call it with the new signature.</span><br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;"><span style="color: #5f7f5f;"># </span><span style="color: #009e73; font-style: italic;">in OpalHotReloader#reload()</span><br /><span style="color: #5f7f5f;"># </span><span style="color: #009e73; font-style: italic;">instead of calling it this way</span><br /><span style="color: #e69f00; font-weight: bold;">@css_reloader</span>.reload(reload_request)<br /><span style="color: #5f7f5f;"># </span><span style="color: #009e73; font-style: italic;">we pass in the real browser document</span><br /><span style="color: #e69f00; font-weight: bold;">@css_reloader</span>.reload(reload_request, <span style="color: #848ea9;">`document`</span>)<br /></pre></div><span style="font-family: "arial" , "helvetica" , sans-serif;">The class at this point looks like this</span><br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;"><span style="color: #56b4e9; font-weight: bold;">class</span> <span style="color: #0072b2; font-weight: bold;">OpalHotReloader</span><br /> <span style="color: #56b4e9; font-weight: bold;">class</span> <span style="color: #0072b2; font-weight: bold;">CssReloader</span><br /><br /> <span style="color: #56b4e9; font-weight: bold;">def</span> <span style="color: #d55e00;">reload</span>(reload_request, document) <span style="color: #5f7f5f;"># </span><span style="color: #009e73; font-style: italic;">pass in the "document"</span><br /> url = reload_request[<span style="color: #d55e00; font-weight: bold;">:url</span>]<br /> <span style="color: #848ea9;">%x{</span><br /><span style="color: #848ea9;"> var toAppend = "t_hot_reload=" + (new Date()).getTime();</span><br /><span style="color: #848ea9;"> // invoke it here</span><br /><span style="color: #848ea9;"> var links = </span><span style="color: #e69f00; font-weight: bold;">#{document}</span><span style="color: #848ea9;">.getElementsByTagName("link");</span><br /><span style="color: #848ea9;"> for (var i = 0; i < links.length; i++) {</span><br /><span style="color: #848ea9;"> var link = links[i];</span><br /><span style="color: #848ea9;"> if (link.rel === 2016-06-03T00:59:37Zurn:uuid:1cc60578-b5a6-8127-bab6-442c6ca4b45cGetting started with react.rb and Ruby on Rails<br /><div class="status" id="postamble" style="-webkit-text-stroke-width: 0px; color: black; font-family: -webkit-standard; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px;"></div><br /><div id="content" style="-webkit-text-stroke-width: 0px; color: black; font-family: -webkit-standard; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px;"><div class="outline-2" id="outline-container-orgheadline1"><div class="outline-text-2" id="text-orgheadline1"><div target=""_blank""><a href="https://facebook.github.io/react/" target="_blank">React.js</a> was the hottest Javascript framework of 2015. The <b>reactjs</b> team brought this goodness to Ruby on Rails via the <a href="https://github.com/reactjs/react-rails" target="_blank">react-rails</a> gem. From its project page, React-rails can:</div><ul class="org-ul"><li>Provide various react builds to your asset bundle</li><li>Transform .jsx in the asset pipeline</li><li>Render components into views and mount them via view helper & react_ujs</li><li>Render components server-side with prerender: true</li><li>Generate components with a Rails generator</li><li>Be extended with custom renderers, transformers and view helpers</li></ul><div target=""_blank"">While <code>react-rails</code> provides easy integration with Rails, a Rails developer cannot leverage the full benefits of React.js, particularly isomorphic/<a href="https://medium.com/@mjackson/universal-javascript-4761051b7ae9#.rxrgqe5wb" target="_blank">universal</a> domain logic code and views since different languages are used on server and client sides. <a href="https://github.com/zetachang/react.rb#changing-the-top-level-component-name-and-search-path" target="_blank">React.rb/reactive-ruby</a> (<b>react.rb</b> from here on) addresses this by allowing one to write React components in Ruby, courtesy of <a href="http://opalrb.org/" target="_blank">Opal</a>. Now the Rails programmer can also enjoy universal domain logic and views written in Ruby. I should note that using Opal with Rails without react.rb also enables univeral domain logic and views (via opal-haml and opal-erb).</div>Since I plan to write in depth about react.rb in subsequent posts, the focus of this article will be limited to just getting <code>react.rb</code> up and running on Rails from scratch.</div></div><div class="outline-2" id="outline-container-orgheadline2"><h2 id="orgheadline2">Generate a rails project that uses Opal</h2><div class="outline-text-2" id="text-orgheadline2"><div target=""_blank"">The easiest way to create a Rails project that uses <a href="http://opalrb.org/" target="_blank">Opal</a> is to use the <code>--javascript=opal</code> option. Manual instructions on how add Opal support to an existing Rails project are given on the <a href="https://github.com/opal/opal-rails" target="_blank">opal-rails</a> site. Create a new Rails project with the following command:</div><div class="org-src-container"><pre class="src src-bash" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; font-family: monospace; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;">% rails new getting-started-react-rails --javascript=opal<br /></pre></div></div></div><div class="outline-2" id="outline-container-orgheadline3"><h2 id="orgheadline3">Add react.rb gems to Gemfile</h2><div class="outline-text-2" id="text-orgheadline3">To use <code>react.rb</code>, you need to add 3 gems to your Gemfile: reactive-ruby<sup><a class="footref" href="#fn.1" id="fnr.1">1</a></sup>, react-rails and therubyracer</div><div class="outline-3" id="outline-container-orgheadline4"><h3 id="orgheadline4">Update</h3><div class="outline-text-3" id="text-orgheadline4">Since this article was written there has been Rails generator code that has been written as a <a href="https://rubygems.org/gems/reactive_rails_generator" target="_blank">standalone gem</a> that is pending integration with react.rb gem. Some of conventions described in this article, which currently match that of existing documentation and sample Rails project in react.rb will likely be changing as part of that.<br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; font-family: monospace; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;">gem <span style="color: green;">'reactive-ruby'</span>, <span style="color: green;">'0.7.29'</span> <span style="color: #8d8d84;"># </span><span style="color: #8d8d84; font-style: italic;">nail down compatible version w/ pre 0.14 react-rails</span><br />gem <span style="color: green;">'react-rails'</span>, <span style="color: green;">'1.3.2'</span> <span style="color: #8d8d84;"># </span><span style="color: #8d8d84; font-style: italic;">react.rb not compatible ith 1.4.* yet so use this one</span><br />gem <span style="color: green;">'opal-rails'</span> <span style="color: #8d8d84;"># </span><span style="color: #8d8d84; font-style: italic;">already added w/the --javascript=opal option</span><br />gem <span style="color: green;">'therubyracer'</span>, <span style="color: #d0372d;">platforms</span>: <span style="color: #d0372d;">:ruby</span> <span style="color: #8d8d84;"># </span><span style="color: #8d8d84; font-style: italic;">Required for server side prerendering</span><br /></pre></div>Run <code>bundle install</code> after these have been added to your Gemfile.</div></div></div><div class="outline-2" id="outline-container-orgheadline5"><h2 id="orgheadline5">Convert application.js to application.js.rb</h2><div class="outline-text-2" id="text-orgheadline5">When using opal-rails, it is recommented<sup><a class="footref" href="#fn.2" id="fnr.2">2</a></sup> to convert the application.js file to application.js.rb. Make yours look like this:<br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; font-family: monospace; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;"><span style="color: #8d8d84;"># </span><span style="color: #8d8d84; font-style: italic;">app/assets/javascripts/application.js.rb</span><br /><span style="color: #006fe0; font-weight: bold;">require</span> <span style="color: green;">'opal'</span><br /><span style="color: #006fe0; font-weight: bold;">require</span> <span style="color: green;">'opal_ujs'</span><br /><span style="color: #006fe0; font-weight: bold;">require</span> <span style="color: green;">'turbolinks'</span><br /><span style="color: #006fe0; font-weight: bold;">require</span> <span style="color: green;">'react'</span><br /><span style="color: #006fe0; font-weight: bold;">require</span> <span style="color: green;">'react_ujs'</span><br /><span style="color: #006fe0; font-weight: bold;">require</span> <span style="color: green;">'components'</span> <span style="color: #8d8d84;"># </span><span style="color: #8d8d84; font-style: italic;">to include isomorphic react components on the client</span><br />require_tree <span style="color: green;">'.'</span><br /></pre></div></div></div><div class="outline-2" id="outline-container-orgheadline6"><h2 id="orgheadline6">Setup for isomorphic<sup><a class="footref" href="#fn.3" id="fnr.3">3</a></sup> React components</h2><div class="outline-text-2" id="text-orgheadline6"><div target=""_blank"">A big perk of react.js is isomorphic code (same code on server and client side), which leads to A united UI layer. As mentioned before <a href="https://github.com/reactjs/react-rails" target="_blank">react-rails</a> provides server rendered react.js components, as well as other perks as detailed in this <a href="http://bensmithett.com/server-rendered-react-components-in-rails/" target="_blank">this article</a>. This quote from the aforementioned article gives one a sense of how big a perk this is.</div><blockquote>The Holy Grail. The united UI layer. Serve up real HTML on first page load, then kick off a client side JS app. All without duplicating a single line of UI code.</blockquote>Those who have struggled with duplicated views on front and back ends, in different languages should appreciate that sentiment. To support isomorphic react.rb components you need to setup a structure for these <b>shared</b>components. The current convention is to make a <code>app/views/components</code> directory containing the components and a <code>components.rb</code> manifest file that will require all the <code>react.rb</code> components, like so:<br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; font-family: monospace; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;"><span style="color: #8d8d84;"># </span><span style="color: #8d8d84; font-style: italic;">app/views/components.rb</span><br /><span style="color: #006fe0; font-weight: bold;">require</span> <span style="color: green;">'opal'</span><br /><span style="color: #006fe0; font-weight: bold;">require</span> <span style="color: green;">'reactive-ruby'</span><br />require_tree <span style="color: green;">'./components'</span><br /></pre></div>You may have noticed that, that the <code>application.js.rb</code> we created <code>require=s this =components.rb</code> file to compile these universal <code>react.rb</code> components.</div></div><div class="outline-2" id="outline-container-orgheadline7"><h2 id="orgheadline7">Make a controller to demonstrate react components</h2><div class="outline-text-2" id="text-orgheadline7">We will be demonstrating several types of components as examples. Let's make a dedicated controller to demo these components with dedicated actions for each case.<br /><div class="org-src-container"><pre class="src src-bash" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; font-family: monospace; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;">% rails g controller home isomorphic iso_convention search_path client_only<br /></pre></div></div></div><div class="outline-2" id="outline-container-orgheadline8"><h2 id="orgheadline8">Create your first React Component</h2><div class="outline-text-2" id="text-orgheadline8">So now that we're setup for isomorphic components, lets make our first react.rb component. We'll start with a simple "Hello World" component. This component takes a single, required param message of type <code>String</code>. Note, param in <code>react.rb</code> corresonds to prop in react.js; <code>react.rb</code> calls props "params" to provide a more Rails familiar API. The component renders this message param in an <b>h1</b> element, and renders a button that, when clicked, calls <code>alert()</code> with the same message.<br />Put the following into this file <b>app/views/components/hello.rb</b>:<br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; font-family: monospace; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;"><span style="color: blue;">class</span> <span style="color: #6434a3;">Hello</span><br /> <span style="color: #006fe0; font-weight: bold;">include</span> <span style="color: #6434a3;">React</span>::<span style="color: #6434a3;">Component</span><br /> required_param <span style="color: #d0372d;">:what</span>, <span style="color: #d0372d;">type</span>: <span style="color: #6434a3;">String</span><br /><br /> <span style="color: blue;">def</span> <span style="color: #006699;">message</span><br /> <span style="color: green;">"Hello </span><span style="color: #ba36a5;">#{what}</span><span style="color: green;">"</span><br /> <span style="color: blue;">end</span><br /><br /> <span style="color: blue;">def</span> <span style="color: #006699;">render</span><br /> div {<br /> h1 { message }<br /> button {<span style="color: green;">"Press me"</span>}.on(<span style="color: #d0372d;">:click</span>) {alert message}<br /> }<br /> <span style="color: blue;">end</span><br /><span style="color: blue;">end</span><br /></pre></div>You can render the <code>Hello</code> component directly without needing a template file in your controller with <code>render_component()</code>. <code>render_component()</code> takes an optional (more on this later) class name of the component and any parameters you wish to pass the component. Implement the <code>isomorphic</code> action in the <code>HomeController</code> like so<br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; font-family: monospace; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;"><span style="color: blue;">class</span> <span style="color: #6434a3;">HomeController</span> < <span style="color: #6434a3;">ApplicationController</span><br /> <span style="color: blue;">def</span> <span style="color: #006699;">isomorphic</span><br /> render_component <span style="color: green;">'Hello'</span>, <span style="color: #d0372d;">message</span>: <span style="color: green;">'World'</span><br /> <span style="color: blue;">end</span><br /><span style="color: blue;">end</span><br /></pre></div>Start the server, then visit <a href="http://localhost:3000/home/isomorphic" target="_blank"></a><a href="http://localhost:3000/home/isomorphic" target="_blank">http://localhost:3000/home/isomorphic</a> to view the component. By default, react.rb prerenders the component on the server (the reverse of react-rails' <code>react_component()</code>, but you can force Rails to NOT prerender by appending ?no_prerender=1 to the url, like so<br /><div class="org-src-container"><pre class="src src-bash" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; font-family: monospace; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;">http://localhost:3000/home/isomorphic?<span style="color: #ba36a5;">no_prerender</span>=1<br /></pre></div>Let's take a quick look at the HTML returned by the server in both cases (formatted to be more human-readable)<br />For <a href="http://localhost:3000/home/isomorphic" target="_blank"></a><a href="http://localhost:3000/home/isomorphic" target="_blank">http://localhost:3000/home/isomorphic</a> we see the <b>h1</b> and button rendered from the server:<br /><div class="org-src-container"><pre class="src src-html" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; font-family: monospace; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;"><<span style="color: #006699;">div</span> <span style="color: #ba36a5;">data-react-class</span>=<span style="color: green;">"React.TopLevelRailsComponent"</span><br /> <span style="color: #ba36a5;">data-react-props</span>=<span style="color: green;">"{"render_params":{"message":"World"},"component_name":"Hello","controller":"Home"}"</span>><br /> <<span style="color: #006699;">div</span> <span style="color: #ba36a5;">data-reactid</span>=<span style="color: green;">".3hx9dqn6rk"</span><br /> <span style="color: #ba36a5;">data-react-checksum</span>=<span style="color: green;">"487927662"</span>><br /> <<span style="color: #006699;">h1</span> <span style="color: #ba36a5;">data-reactid</span>=<span style="color: green;">".3hx9dqn6rk.0"</span>><span style="color: black; font-weight: bold; text-decoration: underline;">Hello World</span></<span style="color: #006699;">h1</span>><br /> <<span style="color: #006699;">button</span> <span style="color: #ba36a5;">data-reactid</span>=<span style="color: green;">".3hx9dqn6rk.1"</span>>Press me</<span style="color: #006699;">button</span>><br /> </<span style="color: #006699;">div</span>><br /></<span style="color: #006699;">div</span>><br /></pre></div>For <a href="http://localhost:3000/home/isomorphic?no_prerender=1" target="_blank"></a><a href="http://localhost:3000/home/isomorphic?no_prerender=1" target="_blank">http://localhost:3000/home/isomorphic?no_prerender=1</a> there is no prerendering and the rendering is done by the client<br /><div class="org-src-container"><pre class="src src-html" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; font-family: monospace; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;"><<span style="color: #006699;">div</span> <span style="color: #ba36a5;">data-react-class</span>=<span style="color: green;">"React.TopLevelRailsComponent"</span><br /> <span style="color: #ba36a5;">data-react-props</span>=<span style="color: green;">"{"render_params":{"message":"World"},"component_name":"Hello","controller":"Home"}"</span>><br /></<span style="color: #006699;">div</span>><br /></pre></div></div></div><div class="outline-2" id="outline-container-orgheadline9"><h2 id="orgheadline9">Rails conventions, isomorphic (i.e. universal) components and the "default" component</h2><div class="outline-text-2" id="text-orgheadline9">In the Rails tradition of convention over configuration, you can structure/namne your components to match your controllers to support a "default" component, i.e. a component you do NOT need to specify, for a controller action. To make a default component for the <code>HomeController#iso_convention</code> action, create the following file:<br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; font-family: monospace; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;"><span style="color: #8d8d84;"># </span><span style="color: #8d8d84; font-style: italic;">app/views/components/home/iso_convention.rb</span><br /> <span style="color: blue;">module</span> <span style="color: #6434a3;">Components</span><br /> <span style="color: blue;">module</span> <span style="color: #6434a3;">Home</span><br /> <span style="color: blue;">class</span> <span style="color: #6434a3;">IsoConvention</span><br /> <span style="color: #006fe0; font-weight: bold;">include</span> <span style="color: #6434a3;">React</span>::<span style="color: #6434a3;">Component</span><br /><br /> <span style="color: blue;">def</span> <span style="color: #006699;">render</span><br /> h1 { <span style="color: green;">"the message is: </span><span style="color: #ba36a5;">#{params[:message]}</span><span style="color: green;">"</span> }<br /> <span style="color: blue;">end</span><br /> <span style="color: blue;">end</span><br /> <span style="color: blue;">end</span><br /> <span style="color: blue;">end</span><br /></pre></div>We now call <code>render_component()</code> in the action, passing only the desired params in the action. <code>render_component()</code> will instantiate the <b>default</b> component.<br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; font-family: monospace; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;"><span style="color: blue;">class</span> <span style="color: #6434a3;">HomeController</span> < <span style="color: #6434a3;">ApplicationController</span><br /> <span style="color: blue;">def</span> <span style="color: #006699;">iso_convention</span><br /> render_component <span style="color: #d0372d;">message</span>: <span style="color: green;">'World'</span><br /> <span style="color: blue;">end</span><br /><span style="color: blue;">end</span><br /></pre></div>Browsing <a href="http://localhost:3000/home/iso_convention" target="_blank"></a><a href="http://localhost:3000/home/iso_convention" target="_blank">http://localhost:3000/home/iso_convention</a> will render the <code>Components::Home::IsoConvention</code> component</div></div><div class="outline-2" id="outline-container-orgheadline10"><h2 id="orgheadline10">The component search path</h2><div class="outline-text-2" id="text-orgheadline10">For consistency, you should stick with the Rails directory and filename conventions. There is some flexibility in where you can place components. The search path for isomorphic components in react.rb is described here: <a href="https://github.com/zetachang/react.rb#changing-the-top-level-component-name-and-search-path" target="_blank">here</a> which writes:<br /><blockquote>Changing the top level component name and search path<br />You can control the top level component name and search path.<br />You can specify the component name explicitly in the render_component method. render_component "Blatz will search the for a component class named Blatz regardless of the controller method.<br />Searching for components normally work2016-01-25T19:54:32Zurn:uuid:78034457-82f2-5f44-7391-154d17ab2d26Opal-irb upgraded to 0.8.0 opal and released as rubygems<a href="https://github.com/fkchang/opal-irb" target=""_blank"">Opal-irb</a>, which previously had to be installed as a gem on github has been released as bonafide rubygems.org gem. This means that instead of doing this:<br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;">gem <span style="color: #4e9a06;">'opal-irb'</span>, <span style="color: #f5666d;">github</span>: <span style="color: #4e9a06;">'fkchang/opal-irb'</span><br /></pre></div>You can get it from rubygems<br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;">gem <span style="color: #4e9a06;">'opal-irb'</span>, <span style="color: #4e9a06;">'0.7.0'</span> <span style="color: #5f7f5f;"># </span><span style="color: #204a87;">for Opal 0.7.*</span><br />gem <span style="color: #4e9a06;">'opal-irb'</span>, <span style="color: #4e9a06;">'0.8.*'</span> <span style="color: #5f7f5f;"># </span><span style="color: #204a87;">for Opal 0.8.*</span><br />gem <span style="color: #4e9a06;">'opal-irb'</span> <span style="color: #5f7f5f;"># </span><span style="color: #204a87;">for latest, 0.8.0 at time of writing</span><br /></pre></div>The 3 example apps have also been upgraded to opal 0.8.0 as part of this move. I've taken to using the same major/minor versions of opal to signify compatibility w/the corresponding versions of opal.<img src="http://feeds.feedburner.com/~r/PuttingTheFunIntoFunkworks/~4/3CgguCaAXA4" height="1" width="1" alt=""/>2015-09-04T16:34:41Zurn:uuid:2770fdf2-6201-4bef-b5f7-a016a255267aAn overview of opal-irb's featuresI have discovered that many of the features in <a href="https://github.com/fkchang/opal-irb/blob/master/README.md" target=""_blank"">opal-irb</a> are unknown to people I would've expected to have known about those features. This post is an attempt to rectify that.<br />My approach has 3 sections:<br /><ul class="org-ul"><li>What opal-irb has in common with irb</li><li>How opal-irb differs from irb</li><li>Features unique to opal-irbIn Common with irb</li></ul><div class="outline-3" id="outline-container-sec-0-1"><h3 id="sec-0-1">Terminal Emulation (of sorts)</h3><div class="outline-text-3" id="text-0-1">Some people might take this for granted, but irb runs in a terminal, and thus supports terminal formating among other things. Opal-irb presently uses a fork of <code>jqconsole</code>, which supports some level of ANSI codes display. For example, to change the default text to red (like test runners often do) in opal-irb, you can run the following:<br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;"><span style="color: #dcdccc; font-weight: bold;">puts</span> <span style="color: #cc9393;">"\033[31mRed Text"</span><br /></pre></div>You can try this <a href="http://git.io/vmQVY" target=""_blank"">from here</a> if the embedded iframe below doesn't look right<br /><iframe allowfullscreen="" frameborder="0" height="500" src="http://git.io/vmQVY" width="100%"></iframe></div></div><div class="outline-3" id="outline-container-sec-0-2"><h3 id="sec-0-2">History up down history</h3><div class="outline-text-3" id="text-0-2">Like irb, opal-irb stores a history of previous statements. You can navigate this history with the up/down arrows or use the "GNU readline commands".</div><div class="outline-4" id="outline-container-sec-0-2-1"><h4 id="sec-0-2-1">GNU readline commands (emacs subset)</h4><div class="outline-text-4" id="text-0-2-1">A subset of the gnu readline manipulation is supported. I'll likely add more support over time. The currently supported bindings are:<br /><pre class="example" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: auto; padding: 8pt;">Up/Down Arrow and ctrl-p/ctrl-n: Navigate through history<br />ctrl-a: Beginning of line<br />ctrl-e: End of line<br />ctrl-b: Back 1 character<br />ctrl-f: Forward 1 character<br />ctrl-d: Delete 1 character<br />ctrl-k: Kill to the end of the line<br />alt-b: Back 1 word<br />alt-f: Forward 1 word<br />alt-d: Delete 1 word<br /></pre></div></div></div><div class="outline-3" id="outline-container-sec-0-3"><h3 id="sec-0-3">Autocomplete</h3><div class="outline-text-3" id="text-0-3">A work-in-progress (I am presently adding auto complete to the multi-line editor); auto complete is supported. It follows the same tab completion model that irb does. It looks like this:<br /><pre class="example" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: auto; padding: 8pt;">opal> S<tab><br />STDERR STDIN STDOUT ScriptError Set<br />SignalException StandardError StopIteration String StringIO<br />StringScanner Struct Symbol SyntaxError SystemCallError<br />SystemExit<br /></tab></pre><pre class="example" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: auto; padding: 8pt;">opal> ST<tab><br />STDERR STDIN STDOUT<br /></tab></pre><pre class="example" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: auto; padding: 8pt;">opal> STDI<tab><br /></tab></pre><pre class="example" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: auto; padding: 8pt;">opal> STDIN<br /></pre></div></div><div class="outline-3" id="outline-container-sec-0-4"><h3 id="sec-0-4">Multi Line Input</h3><div class="outline-text-3" id="text-0-4">Like <b>irb</b>, you can enter multi-line input. You can type multiple lines until your entry is complete. It indicates an incomplete line with leading periods:<br /><pre class="example" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: auto; padding: 8pt;">Welcome to Opal 0.7.1<br />type help for assistance<br />opal> class Foo<br />... def bar<br />... :bar<br />... end<br />... end<br /> => "bar"<br />opal><br /></pre></div></div><div class="outline-2" id="outline-container-sec-1"><h2 id="sec-1">Different than irb</h2><div class="outline-text-2" id="text-1"></div><div class="outline-3" id="outline-container-sec-1-1"><h3 id="sec-1-1">Last value returned</h3><div class="outline-text-3" id="text-1-1">In irb the last value returned is stored in _ variable:<br /><pre class="example" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: auto; padding: 8pt;">2.2.1 :001 > 2 * 3<br /> => 6<br />2.2.1 :002 > puts _<br />6<br /> => nil<br />2.2.1 :003 ><br /></pre>In opal-irb, the last value is stored as $_. This departure from irb's behavior is due to issues I had wrt binding of variables and visibility from the repl. I may revisit this again to make it match irb's behavior since I did not intend to change the behavior.<br /><pre class="example" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: auto; padding: 8pt;">opal> 2 * 3<br /> => 6<br />opal> puts $_<br />6<br /> => nil<br />opal><br /></pre></div></div><div class="outline-3" id="outline-container-sec-1-2"><h3 id="sec-1-2">Help</h3><div class="outline-text-3" id="text-1-2">Help in irb allows you to lookup documentation for methods via ri and rdoc docs.<br /><pre class="example" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: auto; padding: 8pt;">2.0.0-p247 :001 > help<br /><br />Enter the method name you want to look up.<br />You can use tab to autocomplete.<br />Enter a blank line to exit.<br /><br />>><br /></pre>Opal-irb's help shows how to operate opal-irb. The present output is below. I intend to implement help for method lookup in a different fashion.<br /><pre class="example" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: auto; padding: 8pt;">opal> help<br />help: This text<br />$_ last value returned is stored in this global<br />history: Shows history<br />irb_link_for history_num: Create a link for the code in the history<br />ctrl-c: Abort prompt<br />ctrl-m: Pop up multi-line editor<br />ctrl-Enter: Submit code in multi-line editor<br />ctrl-l: Creates a link with the code you have on the current line/lines<br /><br />EDITOR FUNCTIONALITY<br />Up/Down Arrow and ctrl-p/ctrl-n: Navigate through history<br />ctrl-a: Beginning of line<br />ctrl-e: End of line<br />ctrl-b: Back 1 character<br />ctrl-f: Forward 1 character<br />ctrl-d: Delete 1 character<br />ctrl-k: Kill to the end of the line<br />alt-b: Back 1 word<br />alt-f: Forward 1 word<br />alt-d: Delete 1 word<br /> => nil<br /></pre></div></div></div><div class="outline-2" id="outline-container-sec-2"><h2 id="sec-2">Beyond irb</h2><div class="outline-text-2" id="text-2">There are number of things that opal-irb does that irb does not:</div><div class="outline-3" id="outline-container-sec-2-1"><h3 id="sec-2-1">"Live gist", create a link w/code</h3><div class="outline-text-3" id="text-2-1">To be able to share the code at the prompt in <b>opal-irb</b>, hit <code>ctl-l</code> (<code>l</code> for <code>link</code>), and the link will be shown above the line in question. To get a link for any other part of the history you can simply navigate the history and then hit ctl-l on the desired code.<br />I like to call this feature "Live gist." Like a gist, it's shareable bit of code. Unlike a gist, it's "live code" – clicking link puts you in an environment in which you can play with the code.<br />This code link can be shared in real app that has opal-irb embedded. I have already used this in a production codebase to duplicate a bug condition for a coworker to debug.<br />The various embedded opal-irb's on this page are all done via "live gist."</div></div><div class="outline-3" id="outline-container-sec-2-2"><h3 id="sec-2-2">Enhanced History</h3><div class="outline-text-3" id="text-2-2"></div><div class="outline-4" id="outline-container-sec-2-2-1"><h4 id="sec-2-2-1">History Command (like shells have, bash, etc.)</h4><div class="outline-text-4" id="text-2-2-1">You can type <code>history</code> at the prompt to get a listing of your history. You will get a listing of the code you've typed in including line numbers.<br /><pre class="example" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: auto; padding: 8pt;">opal> history<br />1: class Foo<br /> def bar<br /> :bar<br /> end<br />end<br />2: f = Foo.new<br />3: f.bar<br />4: history<br /> => nil<br />opal><br /></pre></div></div><div class="outline-4" id="outline-container-sec-2-2-2"><h4 id="sec-2-2-2">Link for History</h4><div class="outline-text-4" id="text-2-2-2">As an alternate to navigating through history and typing <code>ctrl-l</code>, you can generate a "live gist" with the <code>irb_link_for</code> command, using the history number shown by the history command. This behavior differs from <code>jsbin</code>, <code>jsfiddle</code>, etc. in that you do a bunch of experimentation and then quickly generate several "live gists" - a behavior that I think matches repl-based experimentation.<br /><pre class="example" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: auto; padding: 8pt;">irb_link_for <history_num><br /></history_num></pre></div></div></div><div class="outline-3" id="outline-container-sec-2-3"><h3 id="sec-2-3">Multiline edit</h3><div class="outline-text-3" id="text-2-3">While <b>opal-irb</b> supports multi-line input in the same fashion as <b>irb</b>, I have to confess that I've often made typos with multi-line input. This required me to start over since there is no way edit a previous line. If you've done this, you know my pain. You have to hit <code>ctrl-c</code> to interrupt it and start again.<br />To address this, I added multi-line editor support. To invoke it, hit <code>ctl-m</code> (m for the multi-line editor) and a window will pop up with whatever code you had on the prompt. The editor has syntax highlighting, some level smart indenting and a WIP autocomplete functionality.<br />The editor can be used with all code in the history. Simply navigate back to the desired code in your history and hit <code>ctl-m</code>.<br />To run the code either hit the run it button, or the <code>ctrl-Enter</code> short cut. To close the window, either hit the close icon, or hit escape.</div></div><div class="outline-3" id="outline-container-sec-2-4"><h3 id="sec-2-4">Requiring code at run time</h3><div class="outline-text-3" id="text-2-4">One of the purposes for <b>opal-irb</b> is to be able to explore things at run-time. To help facilitate this, there are 2 commands.</div><div class="outline-4" id="outline-container-sec-2-4-1"><h4 id="sec-2-4-1">require_remote</h4><div class="outline-text-4" id="text-2-4-1">Part of <b>opal-parser</b>, <code>require_remote</code> allows you require a remote ruby file.<br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;">require_remote <url_to_ruby_file><span style="color: #cc9393;">"</span><br /></url_to_ruby_file></pre></div>As an example, I'll require the raw form of this gist, which prints out "require_remote is cool" 10 times.<br /><div class="gist" id="gist24880677" style="color: #333333; direction: ltr; font-size: 16px;"><div class="gist-file" style="border-bottom-left-radius: 3px; border-bottom-right-radius: 3px; border-color: rgb(221, 221, 221) rgb(221, 221, 221) rgb(204, 204, 204); border-style: solid; border-top-left-radius: 3px; border-top-right-radius: 3px; border-width: 1px; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; margin-bottom: 1em;"><div class="gist-data" style="background-color: white; border-bottom-color: rgb(221, 221, 221); border-bottom-style: solid; border-bottom-width: 1px; overflow: auto; word-wrap: normal;"><div class="js-gist-file-update-container js-task-list-container file-box"><div class="file" id="file-require_remote_is_cool-rb"><div class="blob-wrapper data type-ruby" style="border-bottom-left-radius: 3px; border-bottom-right-radius: 3px; overflow-x: auto; overflow-y: hidden;"><table class="highlight tab-size js-file-line-container" data-tab-size="8" style="border-collapse: collapse; border: 0px; color: #333333; font-size: 12px; line-height: 1.4; margin: 0px; padding: 0px;"><tbody><tr><td class="blob-num js-line-number" data-line-number="1" id="file-require_remote_is_cool-rb-L1" style="-webkit-user-select: none; background-color: transparent; border-color: rgb(238, 238, 238); border-style: solid; border-width: 0px 1px 0px 0px; color: rgba(0, 0, 0, 0.298039); cursor: pointer; line-height: 18px; min-width: inherit; padding: 1px 10px !important; text-align: right; vertical-align: top; white-space: nowrap; width: 7px;"></td><td class="blob-code blob-code-inner js-file-line" id="file-require_remote_is_cool-rb-LC1" style="background-color: transparent; border: 0px; overflow: visible; padding: 1px 10px !important; position: relative; vertical-align: top; white-space: pre; word-wrap: normal;">(<span class="pl-c1" style="color: #0086b3;">0</span>..<span class="pl-c1" style="color: #0086b3;">10</span>).each {|<span class="pl-smi">i</span>| puts <span class="pl-s" style="color: #183691;"><span class="pl-pds">"</span>require_remote is cool<span class="pl-pds">"</span></span> }</td></tr></tbody></table></div></div></div></div><div class="gist-meta" style="background-color: #f7f7f7; color: #767676; font-family: Helvetica, arial, nimbussansl, liberationsans, freesans, clean, sans-serif, 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 12px; overflow: hidden; padding: 10px;"><a href="https://gist.github.com/fkchang/c52173c276976a9ccede/raw/e1f92fdcc1592401cb0811dd1d180521ba0c65d3/require_remote_is_cool.rb" style="border: 0px; color: #666666; float: right; font-weight: bold; text-decoration: none;">view raw</a><a href="https://gist.github.com/fkchang/c52173c276976a9ccede#file-require_remote_is_cool-rb" style="border: 0px; color: #666666; font-weight: bold; text-decoration: none;">require_remote_is_cool.rb</a> hosted with ❤ by <a href="https://github.com/" style="border: 0px; color: #666666; font-weight: bold; text-decoration: none;">GitHub</a></div></div></div>You can try this <a href="http://git.io/vOmoR" target=""_blank"">from here</a> if the embedded iframe below doesn't look right<br /><iframe allowfullscreen="" frameborder="0" height="500" src="http://git.io/vOmoR" width="100%"></iframe></div></div><div class="outline-4" id="outline-container-sec-2-4-2"><h4 id="sec-2-4-2">require_js (asynchronous require)</h4><div class="outline-text-4" id="text-2-4-2">require_js allows you to require javascript with a URL. It is asynchronous, which when typed in by hand, is usually fine, the file will get required before your code that uses it gets done. If not hand typed, say via live-gist, you'll need to put some sort of delay. I've made a raphael based example.<br /><ul class="org-ul"><li>does a <code>require_js</code> of the raphael.js lib</li><li>Adds a reanimate button (via Opal-browser's DOM DSL)</li><li>delays via a <code>Timeout</code> and creates an animation and bind reanimation code to the reanimate button</li></ul>You can try this <a href="http://git.io/vOm1c" target=""_blank"">directly here</a> if the embedded iframe below doesn't look right<br /><iframe allowfullscreen="" frameborder="0" height="500" src="http://git.io/vOm1c" width="100%"></iframe></div></div><div class="outline-4" id="outline-container-sec-2-4-3"><h4 id="sec-2-4-3">require_js_sync (synchronous require)</h4><div class="outline-text-4" id="text-2-4-3">Synchronous calls are atypical with javascript api's, chrome says this is deprecated, so it might be going away in chrome any time. That being said, if you are going to script a "live gist" or similar, this may be more handing than putting in a delay<br />For an example, I do the same raphael example, but without the delay<br />You can try this <a href="http://git.io/vOmDg" target=""_blank"">directly here</a> if the embedded iframe below doesn't look right<br /><iframe allowfullscreen="" frameborder="0" height="500" src="http://git.io/vOmDg" width="100%"></iframe></div></div></div><div class="outline-3" id="outline-container-sec-2-5"><h3 id="sec-2-5">Say, say, say, what you want…</h3><div class="outline-text-3" id="text-2-5">On <b>osx</b>, I make lots of use of the built-in <code>say</code> command. For example, I have a function that copies over the production database and loads it into a designated database. The data is not small and this can take a while, so the function lets me know what step it's doing. This allows me to do other work while it's happening. So even if my terminal window is obscured, I'll know when the job is done the moment it happens.<br />This might be a novelty in the browser, but I decided to implement that for opal-irb. I'll likely make it a full-fledged gem in the future.<br />You can try this <a href="http://git.io/vYveZ" target=""_blank"">directly here</a> if the embedded iframe below doesn't look right. In either case, type in <code>say_something</code> to try it out. This is only supported by default in reasonably modern webkit browsers. Firefox supports speech but it has to be turned on. You can check for browser capability <a href="http://caniuse.com/#feat=speech-synthesis">here</a><br /><iframe allowfullscreen="" frameborder="0" height="500" src="http://git.io/vYveZ" width="100%"></iframe></div></div></div><div class="outline-2" id="outline-container-sec-3"><h2 id="sec-3">Examples</h2><div class="outline-text-2" id="text-3">I have several example opal-irb pages referenced from the README on <a href="https://github.com/fkchang/opal-irb">https://github.com/fkchang/opal-irb</a>. They are:</div><div class="outline-3" id="outline-container-sec-3-1"><h3 id="sec-3-1">Homebrew console example</h3><div class="outline-text-3" id="text-3-1"><a href="http://fkchang.github.io/opal-irb/index-homebrew.html" target=""_blank"">http://fkchang.github.io/opal-irb/index-homebrew.html</a><br /><iframe allowfullscreen="" frameborder="0" height="500" src="http://fkchang.github.io/opal-irb/index-homebrew.html" width="100%"></iframe>This was my 1st attempt, port of a <a href="https://github.com/larryng/coffeescript-repl" target=""_blank"">coffescript repl</a> with hand written terminal code. It's not as full featured as the following <b>jq-console example</b>:</div></div><div class="outline-3" id="outline-container-sec-3-2"><h3 id="sec-3-2">jq-console Example</h3><div class="outline-text-3" id="text-3-2"><a href="http://fkchang.github.io/opal-irb/index-jq.html" target=""_blank"">http://fkchang.github.io/opal-irb/index-jq.html</a><br /><iframe allowfullscreen="" frameborder="0" height="500" src="http://fkchang.github.io/opal-irb/index-jq.html" width="100%"></iframe>This uses (a fork of) <code>jq-console</code>, for improved console support. Most of my development has been on a <code>jq-console</code> based <b>opal-irb</b>, though I might revisit a <code>jq-console</code> port because I'd like to remove the <b>jquery</b> dependency in <b>opal-irb</b></div></div><div class="outline-3" id="outline-container-sec-3-3"><h3 id="sec-3-3">Embedded console example</h3><div class="outline-text-3" id="text-3-3"><a href="http://fkchang.github.io/opal-irb/index-embeddable.html" target=""_blank"">http://fkchang.github.io/opal-irb/index-embeddable.html</a><br /><iframe allowfullscreen="" frameborder="0" height="700" src="http://fkchang.github.io/opal-irb/index-embeddable.html" width="100%"></iframe>This example is closer to how I see <b>opal-irb</b> being used in real apps - where <b>opal-irb</b> is embedded with in th2015-08-11T23:13:47Zurn:uuid:c9799b82-fc32-4334-6a07-797458c44dc1Sharing executable test code with opal-playground<br /><div class="status" id="postamble" style="-webkit-text-stroke-width: 0px; color: black; font-family: Times; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px;"></div><br /><div id="content" style="-webkit-text-stroke-width: 0px; color: black; font-family: Times; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px;"><div class="outline-2" id="outline-container-sec-1"><h2 id="sec-1">Situation</h2><div class="outline-text-2" id="text-1">I was reviewing a pull request from a non-colocated team mate. In discussing the pull request, I realized one of the assertions on his spec (we use <a href="http://rspec.info/" target=""_blank"">rspec</a>) didn't work exactly the way he expected. To aid in the discussion, I sent him some code via chat. Shortly thereafter, I realized I could do better than just pasting in some code.<br />"Do better?" you ask. What do I mean? In general, what's the problem with sending a teammate a spec via chat, email, whatever?<br />When the teammate receives the code, he may want to run it in order to understand it. In this case, being a spec file, he will need to paste the code into a running test system to explore how it works.<br />That can be a bit of a hassle, especially when using Rails where specs often do not run that fast. It's not that running them is impossible, but it may be sufficiently inconvenient that one wouldn't just immediately do it.<br />Here's the epiphany:<br />What if I could send someone specs that:<br /><ul class="org-ul"><li>ran nearly instantenously</li><li>didn't require any setup</li></ul>The remainder of this article shows you how.</div></div><div class="outline-2" id="outline-container-sec-2"><h2 id="sec-2">Solution</h2><div class="outline-text-2" id="text-2">Aside from the <code>jsbin</code>-like workspace, <a href="http://fkchang.github.io/opal-playground" target=""_blank"">opal-playground</a> has a <a href="http://fkchang.github.io/opal-playground/rspec/">rspec page</a> configured to run spec tests by writing or pasting them in and executing. Now instead of sending my co-workers snippets of code, I test the spec in the Opal playground and test it. When I'm satisfied that it's doing what I want, I send the link directly to him. Here's what I originally pasted to him:<br />(I've embedded the actual rspec page in an iframe <a href="http://git.io/vmalc" target=""_blank"">direct link</a> if below iframe doesn't look right in your browser). You can vertically scroll the text pane to see the remaining <b>rspec</b> content.<br /><iframe allowfullscreen="" frameborder="0" height="500" src="http://git.io/vmalc" width="100%"></iframe>Note that both tests pass. You can change the code and press the blue <b>Run Code</b> button (it will be under the "hamburger menu" in the upper right corner if the browser width is narrow enough) to run it again. Notice that the 2nd test's "it" statement says it should fail, but it passes.</div></div><div class="outline-2" id="outline-container-sec-3"><h2 id="sec-3">How Technology Helped Solve the Original Problem</h2><div class="outline-text-2" id="text-3">The code in these rspec pages are not the code in the PR, but rather simplified code that exercises expectations in the same or similar fashion to the code in the original PR. The problem in the original pull request contained a rspec stating that a method (in this sample's case <code>#name</code>) must be invoked.<br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; font-family: monospace; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;">it <span style="color: #cc9393;">"should initialize with the given name"</span> <span style="color: #f0dfaf; font-weight: bold;">do</span><br /> allow_any_instance_of(<span style="color: #7cb8bb;">User</span>).to receive(<span style="color: #bfebbf;">:name</span>)<br /> u = <span style="color: #7cb8bb;">User</span>.new<br /><span style="color: #f0dfaf; font-weight: bold;">end</span><br /></pre></div>As you can see above, the <code>#name</code> method is never invoked and yet the specs still passes. So I changed the "it" comment to point this out and sent it to the teammate:<br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; font-family: monospace; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;">it <span style="color: #cc9393;">"should fail"</span> <span style="color: #f0dfaf; font-weight: bold;">do</span><br /> allow_any_instance_of( <span style="color: #7cb8bb;">User</span>).to receive(<span style="color: #bfebbf;">:name</span>)<br /> u = <span style="color: #7cb8bb;">User</span>.new<br /><span style="color: #f0dfaf; font-weight: bold;">end</span><br /></pre></div>Before my teammate had the time to digest the implications, he countered with a suggestion code suggestion.<br /><blockquote>try allow_any_instance_of(Kass).to receive(:stuff).with(params)</blockquote>To which I replied<br /><blockquote>Why should adding <code>with</code> make it pass?</blockquote>Following my lead, he countered with his own spec to demonstrate<br />(<a href="http://git.io/vma45" target=""_blank"">direct link</a> if below iframe doesn't look right)<br /><iframe allowfullscreen="" frameborder="0" height="500" src="http://git.io/vma45" width="100%"></iframe>What his spec showed was that is if he added a parameter expecation, and the method expected to be called was called, but not with that parameter, the spec will fail<br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; font-family: monospace; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;">it <span style="color: #cc9393;">"should fail"</span> <span style="color: #f0dfaf; font-weight: bold;">do</span><br /> allow_any_instance_of(<span style="color: #7cb8bb;">User</span>).to receive(<span style="color: #bfebbf;">:name</span>).with(<span style="color: #cc9393;">'hello'</span>)<br /> u = <span style="color: #7cb8bb;">User</span>.new<br /> u.name<br /><span style="color: #f0dfaf; font-weight: bold;">end</span><br /></pre></div>Soon thereafter, he reasoned about he realized I was right.<br /><blockquote>Oh… but I see that you're saying what if it's not called at all?</blockquote>To confirm his understanding, I sent a spec which uses his expectation, should fail if his understanding was correct, but passed:<br />(<a href="http://git.io/vqMDa" target=""_blank"">direct link</a> if below iframe doesn't look right)<br /><iframe allowfullscreen="" frameborder="0" height="550" src="http://git.io/vqMDa" width="100%"></iframe>The spec in question that proved my point, which was the mistake in his PR.<br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; font-family: monospace; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;">it <span style="color: #cc9393;">"will pass if not called at all"</span> <span style="color: #f0dfaf; font-weight: bold;">do</span><br /> allow_any_instance_of(<span style="color: #7cb8bb;">User</span>).to receive(<span style="color: #bfebbf;">:name</span>).with(<span style="color: #cc9393;">'hello'</span>)<br /> u = <span style="color: #7cb8bb;">User</span>.new<br /><span style="color: #f0dfaf; font-weight: bold;">end</span><br /></pre></div>With that knowledge gained, my teammate was able to construct his spec in a way that tested what it was supposed to.</div></div><div class="outline-2" id="outline-container-sec-4"><h2 id="sec-4">Summary</h2><div class="outline-text-2" id="text-4">In doing code discussion, some tool support can be handy. Sometimes even the exact code is not enough - while it's not ambiguous to the interpreter or compiler, it's not always clear to the programmer. A key goal of Agile is to improve communication. In this case, discussing <b>rspec</b> specs using the rspec "worksheet" page helped a bunch.<br />It was:<br /><ul class="org-ul"><li>Quick: write some, easily run without any setup</li><li>Collaborative: you can send them back and forth</li><li>Facillitated communication: my teammate quickly responded in kind</li><li>Retainable as a url: he can always go back and review the example via the url vs. digging through a large spec file to find it. While collocation is best for communication, "executable rspec gists" can help to mitigate the "distance" barrier when the teammate is remote.</li><li>While facilitated by opal, the code itself doesn't have to be client-side opal; the code discussed above was, indeed, backend code.</li></ul>Other uses:<br /><ul class="org-ul"><li>Send exercises while mentoring</li><li>Perform a code interview without needing setup.</li></ul></div></div><div class="outline-2" id="outline-container-sec-5"><h2 id="sec-5">Teaser</h2><div class="outline-text-2" id="text-5">I plan on making some more log posts on how opal based tools can help with collaboration</div></div></div><img src="http://feeds.feedburner.com/~r/PuttingTheFunIntoFunkworks/~4/dIOPF-K0_-E" height="1" width="1" alt=""/>2015-07-19T04:48:34Zurn:uuid:34f939ee-f10f-fa25-9aca-6c900044ee19Ruby-fying JavaScript - Wrapping opal-jqueryIn my last post about <a href="http://funkworks.blogspot.com/2015/06/accessing-javascript-from-opal.html" target=""_blank"">accessing JavaScript from Opal</a>, I asserted that it is preferable to wrap JavaScript libraries in <b>Opal</b>; the reasons why are best shown by example.<br />Let's look at <a href="https://github.com/opal/opal-jquery" target=""_blank"">Opal-jquery</a>, one of the "official Opal libraries", as a source of good concrete examples. <b>Opal-jquery</b> wraps <b>jQuery</b> with an idiomatic <b>Ruby</b> frontend. There are some differences from the original JavaScript API, but my opinion it that these changes are for the better.<br /><div class="outline-2" id="outline-container-sec-1"><h2 id="sec-1">Basic jQuery</h2><div class="outline-text-2" id="text-1">I call the <code>jQuery/$</code> function "jQuery's universal entry point." Nearly all uses of <b>jQuery</b> start from this function. One of the primary uses of <code>$</code> is as a selector for elements. It is very typical to chain one or more actions directly from the selector, as shown here:<br /><div class="org-src-container"><pre class="src src-javascript" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;">$(<span style="color: #4e9a06;">'a'</span>).action() <span style="color: #5f7f5f;">// </span><span style="color: #204a87;">select by node type</span><br />$(<span style="color: #4e9a06;">'#element-id'</span>).action().action2() <span style="color: #5f7f5f;">// </span><span style="color: #204a87;">select by id</span><br />$(<span style="color: #4e9a06;">'.cssSelector'</span>).action().action2().action3() <span style="color: #5f7f5f;">// </span><span style="color: #204a87;">select by class</span><br /></pre></div>Interestingly, this same "universal entry point" function is also how one sets up "Document ready" actions. All of the various invocations below <i>can</i> be used to setup "Document ready" actions.<br /><div class="org-src-container"><pre class="src src-javascript" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;">$(document).ready(<span style="color: brown; font-weight: bold;">function</span>() {alert(<span style="color: #4e9a06;">'document is ready'</span>)});<br />$(<span style="color: #4e9a06;">'p'</span>).ready(<span style="color: brown; font-weight: bold;">function</span>(){alert(<span style="color: #4e9a06;">'fires whether or not there is p element'</span>)});<br />$(<span style="color: #4e9a06;">'never-matching_selector'</span>).ready(<span style="color: brown; font-weight: bold;">function</span>(){alert(<span style="color: #4e9a06;">'still fires'</span>)});<br />$().ready(<span style="color: brown; font-weight: bold;">function</span>() { alert(<span style="color: #4e9a06;">'this works, but the doc says "not recommended"'</span>)});<br />$(<span style="color: brown; font-weight: bold;">function</span>(){ alert(<span style="color: #4e9a06;">'function called directly'</span>);});<br /></pre></div></div></div><div class="outline-2" id="outline-container-sec-2"><h2 id="sec-2">Ruby-fying Javascript - Object oriented basic jQuery</h2><div class="outline-text-2" id="text-2">Rather than having a function that is the universal entry point, <code>opal-jquery</code> assigns selector entry points to appropriate constants (classes/objects). At the top level, we call these selectors from the constants themselves.</div><div class="outline-3" id="outline-container-sec-2-1"><h3 id="sec-2-1">Selectors</h3><div class="outline-text-3" id="text-2-1"><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;"><span style="color: #2f8b58; font-weight: bold;">Element</span>.find(<span style="color: #4e9a06;">'#mydiv'</span>) <span style="color: #5f7f5f;"># </span><span style="color: #204a87;">top level find</span><br /><span style="color: #2f8b58; font-weight: bold;">Document</span>.find(<span style="color: #4e9a06;">'#mydiv'</span>) <span style="color: #5f7f5f;"># </span><span style="color: #204a87;">Document is a special instance of Element</span><br /><span style="color: #2f8b58; font-weight: bold;">Element</span>.id(<span style="color: #4e9a06;">'mydiv'</span>) <span style="color: #5f7f5f;"># </span><span style="color: #204a87;">find by id</span><br /></pre></div>I like that <code>find()</code> is a consistent selector. It can find from the top, and relative to an <code>Element</code> instance.<br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;">mydiv = <span style="color: #2f8b58; font-weight: bold;">Element</span>.find <span style="color: #4e9a06;">'#mydiv'</span><br />h1_children_of_mydiv = mdiv.find <span style="color: #4e9a06;">'h1'</span><br /></pre></div><code>Opal-jquery</code> also implements a Ruby convention where the <code>Element.[]</code> method is used as a selector. This comes in handy for those who miss the terseness of <b>$</b>.<br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;">mydiv = <span style="color: #2f8b58; font-weight: bold;">Element</span>[<span style="color: #4e9a06;">'#mydiv'</span>]<br />ols = <span style="color: #2f8b58; font-weight: bold;">Element</span>[<span style="color: #4e9a06;">'ol'</span>]<br />help_sections = <span style="color: #2f8b58; font-weight: bold;">Element</span>[<span style="color: #4e9a06;">'.help'</span>]<br /></pre></div>Opal-jquery moves jQuery functionality from a "universal entry point" function, to appropriate constants, classes and modules. Here's a brief summary of these constants:<br /><ul class="org-ul"><li><code>Element</code> - bridged class to the <b>jQuery</b> object.</li><li><code>Document</code> - object as constance, instance of <code>Element</code>, extended with selected document specific methods.</li><li><code>Event</code> - class encapsulating events.</li><li><code>HTTP</code> - class wrapping <b>jQuery</b> ajax functionality.</li><li><code>LocalStorage</code> - not part of <b>jQuery</b>, local storage wrapper for convenience.</li><li><code>Kernel</code> - extended with <code>alert()</code> so it's accessible everywhere, like it is in JavaScript.</li></ul></div></div><div class="outline-3" id="outline-container-sec-2-2"><h3 id="sec-2-2">Document Ready</h3><div class="outline-text-3" id="text-2-2">In <b>Opal-jQuery</b> "Document ready" actions are setup with <code>Document.ready?()</code> method:<br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;"><span style="color: #2f8b58; font-weight: bold;">Document</span>.ready? <span style="color: brown; font-weight: bold;">do</span><br /> alert <span style="color: #4e9a06;">"Welcome to opal-jquery!"</span><br /><span style="color: brown; font-weight: bold;">end</span><br /></pre></div><code>Document.ready?()</code> employs 2 Ruby idioms:<br /><ol class="org-ol"><li>The method name ends in a '?', which reads well to my eyes.</li><li>It takes a block, which also reads well. <b>Opal-jquery</b> departs from <b>jQuery</b>'s single universal function for all of the ways to setup "Document ready" actions.</li></ol>A number of the "Document ready" Javascript examples I showed above work, but don't make logical sense - i.e. calling <code>ready()</code> off of the results of a selector where the element is not <code>document</code>. In <b>Opal-jQuery</b> you can only set "Document ready" actions with <code>Document.ready</code> . I feel this expresses the intent clearly and makes for less of a violation of the Principle of Least Surprise, but not even having the option to construct semantically odd lines. The following below will <b>not</b> work because there is no <code>Element#ready?()</code><br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;"><span style="color: #2f8b58; font-weight: bold;">Element</span>.find(<span style="color: #4e9a06;">'a'</span>).ready? { action } <span style="color: #5f7f5f;"># </span><span style="color: #204a87;">equivalent to $('a').ready(function(){action});</span><br /></pre></div></div></div></div><div class="outline-2" id="outline-container-sec-3"><h2 id="sec-3">Blocks</h2><div class="outline-text-2" id="text-3">As is typical with Ruby, in <b>Opal-jquery</b> we make use of blocks where anonymous functions are typically used in <b>jQuery</b>, or JavaScript in general. The examples read well.<br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;">element = <span style="color: #2f8b58; font-weight: bold;">Element</span>.find <span style="color: #4e9a06;">'#my_element'</span><br />element.on(<span style="color: #f5666d;">:click</span>) { alert(<span style="color: #4e9a06;">"I've been clicked"</span>) } <span style="color: #5f7f5f;"># </span><span style="color: #204a87;">no event</span><br />element.on(<span style="color: #f5666d;">:click</span>) { |event| alert(<span style="color: #4e9a06;">"</span><span style="color: #0084c8; font-weight: bold;">#{evt.current_target}</span><span style="color: #4e9a06;"> been clicked"</span>) } <span style="color: #5f7f5f;"># </span><span style="color: #204a87;">event</span><br /></pre></div>Note that you can pass lambdas, methods, etc. in place of a block, similar to how one would use a named function or function stored in a variable in JavaScript.<br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;">element = <span style="color: #2f8b58; font-weight: bold;">Element</span>.find <span style="color: #4e9a06;">'#my_element'</span><br />my_lambda = <span style="color: #a020f0; font-weight: bold;">lambda</span> { alert <span style="color: #4e9a06;">"separate lambda"</span> }<br />element.on(<span style="color: #f5666d;">:click</span>) &my_lambda<br /><br /><span style="color: brown; font-weight: bold;">def</span> <span style="color: #00578e; font-weight: bold;">some_method</span><br /> alert <span style="color: #f5666d;">:some_method</span><br /><span style="color: brown; font-weight: bold;">end</span><br />element.on(<span style="color: #f5666d;">:click</span>) &some_method<br /><br />instance_method = obj.method(<span style="color: #f5666d;">:an_instance_method</span>)<br />element.on(<span style="color: #f5666d;">:click</span>) &instance_method<br /></pre></div></div></div><div class="outline-2" id="outline-container-sec-4"><h2 id="sec-4">Consistency with Native</h2><div class="outline-text-2" id="text-4">In <a href="http://funkworks.blogspot.com/2015/06/accessing-javascript-from-opal.html" target=""_blank"">accessing JavaScript from Opal</a>, I showed how attributes of <code>Native</code> are accessed by the <code>[]</code> and <code>[]=</code> methods. <code>Element</code> instances adhere to this convention. - Consistency is good and helps support the <b>Principle of Least Surprise</b>.<br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;">native = Native(<span style="color: #4e9a06;">`returnsJsObject`</span>)<br />value = native[<span style="color: #f5666d;">:attr_name</span>]<br />native[<span style="color: #f5666d;">:attr_name</span>] = new_value<br /><br />elt = <span style="color: #2f8b58; font-weight: bold;">Element</span>.find(<span style="color: #4e9a06;">'#anId'</span>)<br />value = elt[<span style="color: #f5666d;">:attr_name</span>]<br />elt[<span style="color: #f5666d;">:attr_name</span>] = new_value<br /></pre></div></div></div><div class="outline-2" id="outline-container-sec-5"><h2 id="sec-5">Conclusion</h2><div class="outline-text-2" id="text-5">For the Rubyist reader, there are advantages in wrapping <b>jQuery</b> in <b>Opal-JQuery</b>: the code reads well, expresses intent clearly, and uses conventions and idioms that a Rubyist should already be familiar with. Hopefully this brief overview gives you ideas on how one could wrap other JavaScript libraries w/idiomatic Ruby for similar benefits. In future articles we will look at more examples of Ruby-fication of jQuery's API, as well as show you some techniques used to wrap jQuery so that you might be able to apply them to wrapping other JavaScript libraries.<br />Thanks to <a href="http://blog.scottnelsonsmith.com/">Scott Smith</a> for proofreading this and a number of the previous Opal blog posts.</div></div><img src="http://feeds.feedburner.com/~r/PuttingTheFunIntoFunkworks/~4/9ydkn8C7I7M" height="1" width="1" alt=""/>2015-06-25T03:07:18Zurn:uuid:83fc9b1b-8ea4-eb35-3e40-5d18e6103d27Accessing Javascript from Opal<div class="status" id="postamble" style="-webkit-text-stroke-width: 0px; color: black; font-family: Times; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px;"></div><br /><div id="content" style="-webkit-text-stroke-width: 0px; color: black; font-family: Times; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px;">So if you've decided to take the plunge and try Opal, the 1st question you might have is, "Do I lose access to all the JavaScript libraries out there?" The answer is no. Ideally you'll want Ruby wrappers that "ruby-fy" the JavaScript in question. It'll make you happier but that's not always available. A great example of a Ruby-fied wrapper is <a _blank="" href="https://github.com/opal/opal-jquery" target="">opal-jquery</a>; this shows how the jQuery interface can be made more Ruby like.<br /><div class="outline-2" id="outline-container-sec-1"><h2 id="sec-1">Calling JavaScript Directly</h2><div class="outline-text-2" id="text-1">To invoke JavaScript and store the return value in Opal, use x-strings, either via the backtick operator or the %x syntax. Some documentation here: <a _blank="" href="http://opalrb.org/docs/compiled_ruby/#javascript-from-ruby" target="">http://opalrb.org/docs/compiled_ruby/#javascript-from-ruby</a><br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; font-family: monospace; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;"><span style="color: #5f7f5f;"># </span><span style="color: #204a87;">backticks, good for 1 line</span><br />location = <span style="color: #4e9a06;">`window.location`</span><br /><br /><span style="color: #5f7f5f;"># </span><span style="color: #204a87;">%x for multi line, I often use | as delimeter, esp. for JavaScript</span><br /><span style="color: #4e9a06;">%x|</span><br /><span style="color: #4e9a06;"> var f = new Foo();</span><br /><span style="color: #4e9a06;"> f.bar</span><br /><span style="color: #4e9a06;">|</span><br /></pre></div></div></div><div class="outline-2" id="outline-container-sec-2"><h2 id="sec-2">Interpolation</h2><div class="outline-text-2" id="text-2"><b>Opal</b> uses <b>x-strings</b> to support <i>interpolation</i> just as <b>Ruby</b> does:<br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; font-family: monospace; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;"><span style="color: #5f7f5f;"># </span><span style="color: #204a87;">it should be noted that prompt is already exposed in opal-browser</span><br /><span style="color: #4e9a06;">`prompt('what have you done since ' + </span><span style="color: #0084c8; font-weight: bold;">#{RUBY_RELEASE_DATE}</span><span style="color: #4e9a06;">)`</span><br /></pre></div></div></div><div class="outline-2" id="outline-container-sec-3"><h2 id="sec-3">Simple return values</h2><div class="outline-text-2" id="text-3">So you can now invoke any JavaScript you want, but what about the return values? X-strings return the native types, some of which you can use and manipulate directly in Opal.<br />In Opal, 3 "Ruby" types get compiled to the native Javascript types but are still treated as Ruby objects by Opal. These types are<br /><ul class="org-ul"><li>Strings (with the exception that these are treated as immutable)</li><li>Numeric</li><li>Array</li></ul>See <a _blank="" href="http://opalrb.org/docs/compiled_ruby/" target="">http://opalrb.org/docs/compiled_ruby/</a> for more details on what Opal compiles to.<br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; font-family: monospace; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;"><span style="color: #5f7f5f;"># </span><span style="color: #204a87;">numbers</span><br />n = <span style="color: #4e9a06;">`2 + 2`</span><br />triple product = n * 3 <span style="color: #5f7f5f;"># </span><span style="color: #204a87;">=> 12</span><br /><br /><span style="color: #5f7f5f;"># </span><span style="color: #204a87;">strings</span><br />str = <span style="color: #4e9a06;">'abcdef'</span><br />str[3..-1] <span style="color: #5f7f5f;"># </span><span style="color: #204a87;">=> 'def'</span><br /><br /><span style="color: #5f7f5f;"># </span><span style="color: #204a87;">array</span><br />array = <span style="color: #4e9a06;">`[1, 2, 3]`</span><br />array.size <span style="color: #5f7f5f;"># </span><span style="color: #204a87;">=> 3</span><br /></pre></div>In addition, <code>true</code>, <code>false</code>, and <code>null</code> map to <code>true</code>, <code>false</code>, and <code>nil</code> respectively. JavaScript that evaluates these values can be used directly in Opal.</div></div><div class="outline-2" id="outline-container-sec-4"><h2 id="sec-4">Manipulating JavaScript objects</h2><div class="outline-text-2" id="text-4">Manipulating native JavaScript objects is a little more cumbersome. Because of how Opal is compiled <sup><a class="footref" href="file:///Users/fkchang/Documents/blog/git/accessing_js.html#fn.1" id="fnr.1" name="fnr.1">1</a></sup>, you cannot access methods and properties directly. (This, however, changes in a future version of opal <sup><a class="footref" href="file:///Users/fkchang/Documents/blog/git/accessing_js.html#fn.2" id="fnr.2" name="fnr.2">2</a></sup>). You <i>can</i> do further manipulation via x-strings if needed.<br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; font-family: monospace; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;">w = <span style="color: #4e9a06;">`window`</span><br />location = <span style="color: #4e9a06;">`</span><span style="color: #0084c8; font-weight: bold;">#{w}</span><span style="color: #4e9a06;">.location`</span><br />href = <span style="color: #4e9a06;">`</span><span style="color: #0084c8; font-weight: bold;">#{location}</span><span style="color: #4e9a06;">.href`</span><br /></pre></div></div></div><div class="outline-2" id="outline-container-sec-5"><h2 id="sec-5">Native - Making life easier</h2><div class="outline-text-2" id="text-5">That's pretty cumbersome. Pretty un-Ruby-like if you ask me. In the Ruby way of making the developer happy, you can use <code>Kernel#Native()</code> to wrap a native object for more convenient access in Opal.<br />Ruby implements the <a _blank="" href="http://en.wikipedia.org/wiki/Uniform_access_principle" target=""><b>Uniform Access Principle</b></a>. As such, you can't access the equivalent of properties on Ruby objects; rather all access is via methods even if they look like properties (due to parentheses being optional in Ruby). In JavaScript, the difference between properties and functions/methods is significant. So how would you do the different accesses in Opal if everything is a method?<br />In a nutshell, what <code>Native()</code> does is create a Ruby/Opal object with a <code>@native</code> instance variable wrapping the native object. It uses <code>method_missing()</code> to call native methods on <code>@native</code>, and <code>[]</code> and <code>[]=</code> for reading/setting properties<br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; font-family: monospace; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;">obj = Native(<span style="color: #4e9a06;">`returnSomeObj()`</span>) <span style="color: #5f7f5f;"># </span><span style="color: #204a87;">need x-strings to get a JS object into opal</span><br />obj[<span style="color: #f5666d;">:property</span>] <span style="color: #5f7f5f;"># </span><span style="color: #204a87;">property reader</span><br />obj[<span style="color: #f5666d;">:property</span>] = <span style="color: #4e9a06;">'new value'</span> <span style="color: #5f7f5f;"># </span><span style="color: #204a87;">property writer</span><br />obj.method <span style="color: #5f7f5f;"># </span><span style="color: #204a87;">method call without parameters</span><br />obj.method2(1, 2) <span style="color: #5f7f5f;"># </span><span style="color: #204a87;">method call with parameters</span><br /></pre></div>To get a feel of accessing a property vs a method/function on a native object, play around w/this <a _blank="" href="http://fkchang.github.io/opal-irb/index-embeddable.html#code:w%20=%20Native(`window`)%0Aw.prompt%20%20%20#%20calls%20method,%20pops%20up%20empty%20prompt%0Aw[:prompt]%20#%20give%20you%20back%20the%20function%20that%20is%20in%20the%20property" target="">opal-irb example</a> a bit<br />Let's revisit of href example using <code>Native()</code>:<br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; font-family: monospace; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;">w = Native(<span style="color: #4e9a06;">`window`</span>)<br />location = w.location<br />href = location.href<br /></pre></div>Much nicer, don't you agree?<br />It's worth noting that <code>Native()</code> works where methods will resolve themselves as properties if that's what they are.<br /><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; font-family: monospace; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;">w = Native(<span style="color: #4e9a06;">`window`</span>) <span style="color: #5f7f5f;"># </span><span style="color: #204a87;">=> #<native: object="" window=""></native:></span><br />w[<span style="color: #f5666d;">:location</span>] <span style="color: #5f7f5f;"># </span><span style="color: #204a87;">property => #<native:http: fkchang.github.io="" index-embeddable.html="" opal-irb=""></native:http:></span><br />w.location <span style="color: #5f7f5f;"># </span><span style="color: #204a87;">=> #<native:http: fkchang.github.io="" index-embeddable.html="" opal-irb=""></native:http:></span><br /></pre></div></div></div><div class="outline-2" id="outline-container-sec-6"><h2 id="sec-6">Conclusion</h2><div class="outline-text-2" id="text-6">Writing wrapper classes is useful in making the interfaces to your Javascript code more Ruby-like. Even without wrapper classes, you will find x-strings and <code>Native()</code> options useful. These Opal features can bring a lot of joy and productivity to the client-side Ruby developer. I'll cover wrapping Javascript in Opal classes in a future issue.<br />Enjoy!</div></div><div id="footnotes"><h2 class="footnotes">Footnotes: </h2><div id="text-footnotes"><div class="footdef" style="margin-bottom: 1em;"><sup><a class="footnum" href="file:///Users/fkchang/Documents/blog/git/accessing_js.html#fnr.1" id="fn.1" name="fn.1">1</a></sup><br /><div class="footpara" style="display: inline;">As indicated by <a _blank="" href="http://opalrb.org/docs/compiled_ruby/#ruby-from-javascript" target="">http://opalrb.org/docs/compiled_ruby/#ruby-from-javascript</a>, compiled Opal method names get a <code>$</code> prepended to them to avoid conflict with JavaScript methods. As such running a method on a native object would compile to that method name prepended with a "$", which would not exist on the object.</div><div class="footpara" style="display: block;">This <a _blank="" href="http://opalrb.org/try/?code:w%20=%20`window`%0Aw.location" target="">example</a> ought to make it a bit clearer </div></div><div class="footdef" style="margin-bottom: 1em;"><sup><a class="footnum" href="file:///Users/fkchang/Documents/blog/git/accessing_js.html#fnr.2" id="fn.2" name="fn.2">2</a></sup><br /><div class="footpara" style="display: inline;">This recently merged <a _blank="" href="https://github.com/opal/opal/pull/879" target="">PR</a> from <a href="https://github.com/jeremyevans">Jeremy Evans</a> into master supports a much handier way to access JavaScript functions and properties than using x-stings. With a native object, you can use object.JS to get at the JavaScript. This functionality will most likely NOT go into 0.8, but some future version, possibly 0.9. Examples:</div><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; font-family: monospace; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;">o = <span style="color: #4e9a06;">`returnAnObject()`</span><br /><span style="color: #5f7f5f;"># </span><span style="color: #204a87;">access properties like this</span><br />o.<span style="color: #2f8b58; font-weight: bold;">JS</span>[<span style="color: #f5666d;">:property</span>]<br /><span style="color: #5f7f5f;"># </span><span style="color: #204a87;">access functions like this</span><br />o.<span style="color: #2f8b58; font-weight: bold;">JS</span>.javascriptMethod(1, 2)<br /></pre></div><div class="footpara" style="display: block;">Let's look at the href example I've been using with this new syntax</div><div class="org-src-container"><pre class="src src-ruby" style="border: 1px solid rgb(204, 204, 204); box-shadow: rgb(238, 238, 238) 3px 3px 3px; font-family: monospace; margin: 1.2em; overflow: visible; padding: 1.2em 8pt 8pt; position: relative;">w = <span style="color: #4e9a06;">`window`</span><br />location = w.<span style="color: #2f8b58; font-weight: bold;">JS</span>[<span style="color: #f5666d;">:location</span>]<br />href = location.<span style="color: #2f8b58; font-weight: bold;">JS</span>[<span style="color: #f5666d;">:href</span>]<br /></pre></div></div></div></div></div><img src="http://feeds.feedburner.com/~r/PuttingTheFunIntoFunkworks/~4/P2m9nesJAt8" height="1" width="1" alt=""/>2015-06-10T01:35:12Zurn:uuid:6490dddd-d7a7-1de7-ef43-428b306c72e4Ruby-fying Javascript - Avoiding jQuery Spaghetti slidesHi All:<br /><br />I did a talk at <a href="http://www.meetup.com/ocruby/" target="_blank">OCRuby</a> last night. It went pretty well. Here are the It was based on my <a href="http://funkworks.blogspot.com/2015/04/ruby-fying-javascript-avoiding-jquery.html" target="_blank">blog post</a> on the subject, so hopefully the 2 will reinforce each other I do think the missing ingredient could be been the in person explanations. If there's interested, I could probably try to record it. Let me know.<br /><br />Resources:<br /><br /><ul><li>OCRuby <a href="http://www.slideshare.net/fkchang/rubyfying-javascript-avoiding-j-query-spaghetti" target="_blank">slides</a></li><li> The original <a href="http://funkworks.blogspot.com/2015/04/ruby-fying-javascript-avoiding-jquery.html" target="_blank">blog post</a></li><li><a href="http://funkworks.blogspot.com/2014/01/opal-new-hope-for-ruby-programmers.html" target="_blank">Opal talk at RubyConf 2013 and slides</a></li></ul><br /><br /><br /><br /><br /><br /><img src="http://feeds.feedburner.com/~r/PuttingTheFunIntoFunkworks/~4/s2P-BsR0go0" height="1" width="1" alt=""/>2015-05-29T17:08:34Zurn:uuid:bf96e04d-b87c-96d6-d669-d2441a143abdConfused about which Ruby Event Sourcing library to use? This might help.<p>Interest in Event Sourcing is picking up in Ruby-land, and there's a lot to wrap your head around. In the interest of helping Ruby programmers pick a path forward, I've published a <a href="/g/Event-Sourcing-Libraries-in-Ruby-A-Guide">guide on Ruby Event Sourcing libraries</a> that I hope you'll find useful.</p>
2015-05-10T18:51:16Zurn:uuid:5ee42d32-c447-51f2-1d4b-cf955a3718dcPower BI tip: Parameters table for Power Query queries to connect to different databasesI've been using Microsoft's Power BI to help a wonderful client of mine build self-service BI for their analysts and away from a system where they generate csv data dumps out of a Mainframe and then load them manually into Excel spreadsheets.
<p>
Most analysts love to use Excel, so Power BI is a no-brainer for getting them their data but <a href="https://support.office.com/en-us/article/Introduction-to-Microsoft-Power-Query-for-Excel-6E92E2F4-2079-4E1F-BAD5-89F6269CD605">Power Query</a> M, though incredibly powerful, doesn't have a way to switch which DB to use. As a developer who develops against my local machine, tests in a test environment and only then promotes my code to production and gives it to the users, is unacceptable.
<p>
Here's a workaround I came up for specifying which Server and DB to use through parameters. It's based on <a href="http://blog.oraylis.de/2013/05/using-dynamic-parameter-values-in-power-query-queries/">Using dynamic parameter values in Power Query Queries</a>. That approach doesn't explain how to use it to connect to different Servers or DBs, which it turns out is trivial, but it also expects the order of parameters not to change, something that strikes me as much too fragile.
<p>
First, create a new Sheet and in it, create a table in Excel. You'll need to columns, <code>Name of Parameter</code> and <code>Value</code>. For dynamically specifying the server and database to use, add two rows, with the <code>Name of Parameter</code> of <code>DB Server</code> and <code>DB</code>. Under the <code>Value</code> enter the server and database you want to use.
<br/>
<a href="http://1.bp.blogspot.com/-P8U5nQToLVk/VQX_ouki2cI/AAAAAAAAGhU/et8q4LpdoKc/s1600/Parameters%2Btable.PNG" imageanchor="1" ><img border="0" src="http://1.bp.blogspot.com/-P8U5nQToLVk/VQX_ouki2cI/AAAAAAAAGhU/et8q4LpdoKc/s400/Parameters%2Btable.PNG" /></a>
<br/>
Don't forget to (Home ribbon) "Format As Table" then (Table Tools / Design ribbon) set the table name to what you want to call it, Parameters, in this example.
<p>Now, we'll use these parameters in our Power Query queries to the DB. To edit your query: (Power Query ribbon) Show Pane, Right click on the query. Then, in the Query Editor that pops up, (in the Home ribbon), click "Advanced Editor". This will show you the source code of the in <a href="https://support.office.com/en-us/article/Learn-about-Power-Query-formulas-6BC50988-022B-4799-A709-F8AAFDEE2B2F">Power Query Formula language</a> (usually called "M" by Power BI users). Here's an example query:
<pre><code>
let
Source = Sql.Database("devserver", "maindb", [Query="SELECT * FROM [Reporting].[Users]"])
in
Source
</code>
</pre>
<p>We're going to change the query to load the parameters and use them instead of the hardcoded "dbserver" and "maindb" in Sql.Database function call.
<pre><code>
let
Parameters = Excel.CurrentWorkbook(){[Name="Parameters"]}[Content],
DBServer = Table.SelectRows(Parameters, each [Name of Parameter] = "DB Server"){0}[Value],
DB = Table.SelectRows(Parameters, each [Name of Parameter] = "DB"){0}[Value],
Source = Sql.Database(DBServer, DB, [Query="SELECT * FROM [Reporting].[Users]"])
in
Source
</code>
</pre>
<p>You'll then change every query that uses this DB to similarly get those parameters first and then use them in the Sql.Database call. The code duplication is unfortunate. I tried a couple of other approaches that take advantage of Power Query's (creating a power query for each of the parameters and creating a function that passes the parameter name) but Power Query's security model doesn't (at least of March 2015) allow another Power Query query or function when retrieving data from an external source.
<p>We're done. To try it out and use a different Server or DB, modify the Parameters table as necessary then refresh the individual query (right click on the query and refresh) or all your queries (Data ribbon then Refresh All).
<p>If you've never connected to the new server via power query, you'll get a popup asking for them as usual when first connecting to a new database. That popup only works properly when you refresh a single query, so you'll want to do that if it looks like your Refresh All is hanging.
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/muness?a=poYKjp0jMyo:7xxyTQJ6hoI:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/muness?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=poYKjp0jMyo:7xxyTQJ6hoI:63t7Ie-LG7Y"><img src="http://feeds.feedburner.com/~ff/muness?d=63t7Ie-LG7Y" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=poYKjp0jMyo:7xxyTQJ6hoI:YwkR-u9nhCs"><img src="http://feeds.feedburner.com/~ff/muness?d=YwkR-u9nhCs" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=poYKjp0jMyo:7xxyTQJ6hoI:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/muness?i=poYKjp0jMyo:7xxyTQJ6hoI:F7zBnMyn0Lo" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=poYKjp0jMyo:7xxyTQJ6hoI:dnMXMwOfBR0"><img src="http://feeds.feedburner.com/~ff/muness?d=dnMXMwOfBR0" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=poYKjp0jMyo:7xxyTQJ6hoI:4cEx4HpKnUU"><img src="http://feeds.feedburner.com/~ff/muness?i=poYKjp0jMyo:7xxyTQJ6hoI:4cEx4HpKnUU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=poYKjp0jMyo:7xxyTQJ6hoI:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/muness?i=poYKjp0jMyo:7xxyTQJ6hoI:V_sGLiPBpWU" border="0"></img></a>
</div><img src="//feeds.feedburner.com/~r/muness/~4/poYKjp0jMyo" height="1" width="1" alt=""/>2015-03-15T23:22:29Zurn:uuid:fc77ce56-4a52-6e44-7b68-b4f9ad59c085Event Sourcing with Rails: A Case Study<p>In December I gave a talk at <a href="http://www.meetup.com/NYC-rb/">NYC.rb</a> on Event Sourcing with Rails. <a href="https://www.hakkalabs.co/">Hakka Labs</a> was there to take video, which is now up:</p>
<iframe width="640" height="360" src="https://www.youtube.com/embed/_pbO0yAxfH0" frameborder="0" allowfullscreen></iframe>
<p><a href="https://github.com/fhwang/event_sourced_record">Event Sourced Record</a>, the gem I mentioned in that talk, is now up. Check it and let me know what you think.</p>
2015-01-30T02:23:21Zurn:uuid:2b5735af-1ffe-3764-15f9-f0b4d7577203Not a software delivery maturity model<h3>Not a what?</h3>
<p/>
Over years of building software, I've found that when there's a performance problem, I usually jump to conclusions, put in a crazy fix and observe that it makes no real difference. I then remember what I've learned a dozen times, step back, instrument the systems involved, collect and analyze performance data. Lo and behold, the solution put in place usually has a significant impact.
<p/>
<blockquote>Optimization matters only when it matters. When it matters, it matters a lot, but <a href="http://www.flounder.com/optimization.htm">until you know that it matters, don't waste a lot of time doing it</a>.</blockquote>
<p/>
In my experience, the same tendency to guess at problems and apply fixes randomly applies to improving software teams. Here's a process I use to avoid such unilateral process changes and instead talk openly about issues and create consensus before acting.
<h3>But we follow [insert methodology here]. Why would we change anything?</h3>
At OOPSLA 2005, <a href="http://www.vanderburg.org/">Glenn Vanderburg</a> presented a paper, <a href="http://www.vanderburg.org/Writing/xpannealed.pdf">A Simple Model of Agile Software Processes – or – Extreme Programming Annealed (PDF)</a> that I've thought about every few months since.
<p/>
My takeaway: XP practices are a coherent set of practices that support the success factor whose health is necessary for software. Embracing the individual practices isn't the point. What matters is ensuring that those success factor are healthy. If you're aware and mature enough to swap a practice out while maintaining the health of that area, go for it; by doing so, over time you can create a process that better suits your needs than the generic one.
<h3>Let's get to it...</h3>
For a software delivery organization, here's the list of those success factor that I start with:
<ul>
<li>Asthetic design: Does it hurt my eyes to use this app?</li>
<li>Company direction: What are our goals? What do we do? What are our values? Does everyone understand them?</li>
<li>Design and architecture: Is your system robust? Is the emergent architecture you have today what you'd use if you were designing the system today? Can you diagnose issues across systems?</li>
<li>Dev: Are the individual systems easy to change? Do they fail too often?</li>
<li>Inter-team collaboration: Do teams work well together?</li>
<li>Intra-team collaboration: Do members of a team feel like they have common goals and priorities?</li>
<li>People: Are we supporting people? Are we giving people information they need? How do well do we make decisions?</li>
<li>Portfolio management: Are we focusing our teams on the right projects?</li>
<li>Product vision: Are we building a system that makes for <a href="http://vimeo.com/theblnbusinessofsoftware/review/54469442/9e94db785d">badass users</a>?</li>
<li>Reliability: Can we run this? Can we recover quickly enough when we fail?</li>
<li>Scalability: Do we understand the scaling characterstics of this system and are they appropriate for what we're doing?</li>
<li>Security: Do we understand the security risks and are they appropriate for this system?</li>
<li>Usability: Are we building a system people enjoy using?</li>
</ul>
<h3>Now what?</h3>
As a team, review the list of success factors and come up with ones that work for you. Then use interviews and <a href="http://en.wikipedia.org/wiki/Blink_(book)">expert intuition</a> to assess which of the success factors matter most right now and how the team thinks you're doing. Here's an example:<br/>
<img border="0" src="http://1.bp.blogspot.com/-nM0GuKa65sQ/U83OgANMAmI/AAAAAAAAF60/JiekzujillE/s600/Capture.PNG" /><br/><img border="0" src="http://2.bp.blogspot.com/-zgE7MWeUrsc/U83NU5ledwI/AAAAAAAAF6g/c6X3luLs-UI/s600/Capture-2.PNG" />
<p/>
Note that I sorted the list by ratio of competence relative to perceived importance. That gives you a suggested order of what you should tackle first. For the first 3 factors, come up with a list of practices that people on the team has used, seen used or have heard or read about that may help you improve it. With the people who will be impacted by any changes, review current practices and potential practices you could introduce. Finally, pick one that you can try quickly at a small scale. Now <a href="http://leanchange.org/2014/06/why-changes-should-be-called-experiments/">experiment</a>.
<p/>
For example, here are some practices I'd consider implementing when looking for ways to improve code quality:
<ul>
<li>CI</li>
<li>Code reviews</li>
<li>Coding standard</li>
<li>Pair programming</li>
<li>Pull requests</li>
<li>QA by developers who didn’t develop the code</li>
<li>Refactoring as an explicit, expected step during development</li>
<li>Static code analysis</li>
<li>TDD</li>
</ul>
<h3>What about me?</h3>
If you're assessing a software delivery organization, formally or not, come up with your own <em>not a software delivery maturity model</em> success factors list and practice list. It should be a list that you can relate to your own past experiences as a team.
<p/>
Most importantly, remember that improvement isn't something you do in isolation but is rather a team activity. With the whole team working together, you'll gather better information, have a more nuanced analysis, a better chance of consensus and ownership of the improvement process.
<h3>You just don't get us: we're a special snowflake!</h3>
I concede that you are unique. You should think of other people's practices as tools in a global toolkit. You get to pick which ones are relevant to you. But don't create all of your practices from scratch or assume that other people's experiences are irrelevant to you.
<p/>
<blockquote>Immature poets imitate; mature poets steal; bad poets deface what they take, and good poets make it into something better, or at least something different. <em>The good poet welds his theft into a whole of feeling which is unique, utterly different from that from which it was torn</em>; the bad poet throws it into something which has no cohesion. A good poet will usually borrow from authors remote in time, or alien in language, or diverse in interest. -- <a href="http://www.bartleby.com/200/sw11.html">T.S. Elliot</a></blockquote>
<h3>Doesn't this model imply I may not need some practices already in place?</h3>
Absolutely.<p/>
But you may not want to stop them: You don't want to kill a practice that people value or enjoy. If you see a conflict between an existing practice and a potential one, be honest; explain to the team where you see the conflict and work with them to resolve it.
<h3>Should I <em>only</em> implement changes that improve the weakest factor?</h3>
No! Limiting the number of improvement projects your team is implementing indicates that you have a bottleneck in your improvement process, probably a manager. If so, you're <a href="http://www.leanblog.org/2014/07/picking-on-the-pick-chart/">going about it the wrong way</a>.<p/>
Improvement processes can be going on at once improving various factors.
<h3>What if the improvement process takes a long time to implement?</h3>
Then implement a different practice. Or find a way to chop it up into smaller chunks. You need <a href="http://progressprinciple.com/books/single/the_progress_principle">lots of small wins</a>, at least at first.
<p/>Or do it anyway. The key is to find a way to measure results along the way so you can adapt or abort it if it isn't working.
<h3>Why do you say this <em>not</em> a software delivery maturity model?</h3>
It isn't <em>a</em> model but rather <em>your</em> model: It only works in context; customize it before using.
<h3>Now what?</h3>
If you think this might help, use it! Over the next week, draft a version of the table for your team. Share it with your team members and incorporate their feedback. Pick one of the red areas in the table, and introduce one practice that could help improve that area. Set a timeframe for the experiment and iterate.
<h3>Shameless plug</h3>
I help software delivery organizations <a href="http://217castle.com">get more</a> out of their teams. <a href="http://www.217castle.com/contact/">Drop me</a> a line if you're thinking about those kinds of issues. I'd love to help you figure out how to use this process and otherwise work with your agile software delivery organization for improved results.
<p/>
<hr>
<p/>Thanks to <a href="https://twitter.com/bendycode">Stephen Anderson</a> for discussing the process and <a href="http://jasonrudolph.com/">Jason Rudolph</a> for feedback on making the post more relevant and actionable.<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/muness?a=oRp8gV7VpZs:lBukrINh3UE:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/muness?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=oRp8gV7VpZs:lBukrINh3UE:63t7Ie-LG7Y"><img src="http://feeds.feedburner.com/~ff/muness?d=63t7Ie-LG7Y" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=oRp8gV7VpZs:lBukrINh3UE:YwkR-u9nhCs"><img src="http://feeds.feedburner.com/~ff/muness?d=YwkR-u9nhCs" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=oRp8gV7VpZs:lBukrINh3UE:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/muness?i=oRp8gV7VpZs:lBukrINh3UE:F7zBnMyn0Lo" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=oRp8gV7VpZs:lBukrINh3UE:dnMXMwOfBR0"><img src="http://feeds.feedburner.com/~ff/muness?d=dnMXMwOfBR0" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=oRp8gV7VpZs:lBukrINh3UE:4cEx4HpKnUU"><img src="http://feeds.feedburner.com/~ff/muness?i=oRp8gV7VpZs:lBukrINh3UE:4cEx4HpKnUU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=oRp8gV7VpZs:lBukrINh3UE:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/muness?i=oRp8gV7VpZs:lBukrINh3UE:V_sGLiPBpWU" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/muness/~4/oRp8gV7VpZs" height="1" width="1"/>2014-08-22T21:11:51Zurn:uuid:7096d2b2-e628-11c7-3c59-4ea60b20a26cIt's your data, not TripIt's, take 2<h2>Minimal viable products are sometimes too minimal</h2>A couple of weeks ago, I posted <a href="http://muness.blogspot.com/2014/06/its-your-data-not-tripits-example-of.html">a blog post</a> on how I extracted my trip information from TripIt to calculate the number of days I traveled last year. Being the second iteration of the process (the first one was: open the page in TripIt, copy and paste to a text file, calculate manually), it worked, but I knew I could make it less crappy: introducing OpenFlights.org wasn't great: every time I fly, I would have to go back there and reimport the new records. The process included manual work to identify trip start and end dates and to distinguish between business and personal trips. Also, I didn't like that only trips with flights were included as some of my trips were<br />
<h2>Let's automate some things</h2>Tonight, I looked a into TripIt's API and once I figured out <i><a href="http://blog.andydenmark.com/2009/03/how-to-build-oauth-consumer.html">their</a> </i>OAuth scheme, I had a <a href="http://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop">REPL</a> session working, getting data from TripIt's servers. After a spike, processing a page of the data, I decided not to change the data. I then wrote a script that grabbed all the pages of my trips data, combined all <i>Trip</i> nodes and saved them all to an XML file for <a href="http://www.microsoft.com/en-us/download/details.aspx?id=39379">import</a> and <a href="http://office.microsoft.com/en-us/excel/">analysis</a>.<br />
<h2>Some code...</h2>My script for exporting TripIt data is over in a <a href="https://github.com/muness/tripit-export-trips-to-xml">github repo</a>. Enjoy! Aside from the authentication, there are only a <a href="https://gist.github.com/muness/f771e4eee45b6b801e06#file-gistfile1-rb">few of lines code</a>:<br />
<code><br />
<pre>xml_out.Trips do # build a Trips node
for page in (1..pages) # defeat pagination...
past_trips = t.list.trip({'past' => 'true', 'include_objects' => 'false', 'traveler' => 'true', "page_size" => "30", "page_num" => page})
node = Nokogiri::XML(past_trips.to_xml.to_s)
xml_out << node.xpath("//Trip").to_xml # extract each Trip node and add it to the Trips node built above
end
end
</code></pre><h2>I can haz xml... now what?</h2>Caveat: the data format from TripIt's API isn't pleasant to work with. Use your ETL skills to make it useful. I used Power Query because I want to get better at using it. With Power Query, you define your data source and apply filters, schema manipulations and value manipulations. I did several manipulations to make the data useful: I expanded some XML nodes, moved some columns, removed others and renamed some. I also manipulated the data by defining data types, doing some string manipulations so that partial URIs like /trip/show/id/xxxx would become full trip it URLs I can follow and changing values that match "Raleigh, NC" to "Durham, NC". <br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="http://3.bp.blogspot.com/-zUGAcluUO9Q/U65eze0KCfI/AAAAAAAAFWw/afdoGuBUXSQ/s1600/Power+Query+steps.PNG" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="http://3.bp.blogspot.com/-zUGAcluUO9Q/U65eze0KCfI/AAAAAAAAFWw/afdoGuBUXSQ/s1600/Power+Query+steps.PNG" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Power Query steps</td></tr>
</tbody></table>On loading this data to <a href="http://office.microsoft.com/en-us/excel-help/create-a-data-model-in-excel-HA102923361.aspx">the data model</a>, I added a calculated field (trip duration) and a couple of clicks later, the report I wanted was ready. To update the report, I rerun the script, launch Excel and press Refresh All. <br />
<div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-nymPA3rBZdA/U65nIKVP4wI/AAAAAAAAFXA/2Okqy6jDYZI/s1600/imageedit_7_9857635915.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-nymPA3rBZdA/U65nIKVP4wI/AAAAAAAAFXA/2Okqy6jDYZI/s1600/imageedit_7_9857635915.jpg" height="320" width="252" /></a></div><br />
What are you going to do with your TripIt data?<br />
<br />
[Self promotion ahead!]<br />
<br />
I'm a developer who uses whatever tools I have to quickly produce results. These days, I regularly write scripts to load, manipulate, correlate and analyze data to make financial or operational recommendations based on real data. <a href="http://www.217castle.com/">consulting site</a> for a sample of things I've done recently then give me a <a href="http://www.217castle.com/contact/">call</a> if you have data of your own to wrestle or understand.<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/muness?a=iAPoHxP4SIE:hvl6vRtCsAA:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/muness?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=iAPoHxP4SIE:hvl6vRtCsAA:63t7Ie-LG7Y"><img src="http://feeds.feedburner.com/~ff/muness?d=63t7Ie-LG7Y" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=iAPoHxP4SIE:hvl6vRtCsAA:YwkR-u9nhCs"><img src="http://feeds.feedburner.com/~ff/muness?d=YwkR-u9nhCs" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=iAPoHxP4SIE:hvl6vRtCsAA:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/muness?i=iAPoHxP4SIE:hvl6vRtCsAA:F7zBnMyn0Lo" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=iAPoHxP4SIE:hvl6vRtCsAA:dnMXMwOfBR0"><img src="http://feeds.feedburner.com/~ff/muness?d=dnMXMwOfBR0" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=iAPoHxP4SIE:hvl6vRtCsAA:4cEx4HpKnUU"><img src="http://feeds.feedburner.com/~ff/muness?i=iAPoHxP4SIE:hvl6vRtCsAA:4cEx4HpKnUU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=iAPoHxP4SIE:hvl6vRtCsAA:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/muness?i=iAPoHxP4SIE:hvl6vRtCsAA:V_sGLiPBpWU" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/muness/~4/iAPoHxP4SIE" height="1" width="1"/>2014-07-01T15:30:00Zurn:uuid:2b91b6f8-7460-60b7-785d-2fbf6bbcd9d8Fully loaded what?Over my years as a hiring manager I've found that I really like working with contractors. Some of the reasons are what you'd expect: it's nice to work together on a temporary basis on things we both find explicitly and mutually beneficial, the contractors I work with are usually experts on the things I hire them for and can hit the ground running.<br />
<br />
One operational benefit that I didn't value until recently was the clarity of their hourly rate: when you work with a contractor, you specifically agree on an hourly rate with them. At least in the US, that kind of cost clarity is non-existent for employees.<br />
<br />
Especially in the context of professional services, where you're selling someone's time, knowing someone's hourly cost can is quite relevant in setting the price of their services. In that vein, I've seen people use a rule of thumb to approximate hourly cost: divide their annual salary by 2000. For example, for someone making $120,000, the rule of thumb would say the equivalent hourly cost is $60.<br />
<br />
This rule of thumb is utterly wrong. The first page of results for a Google search on calculating the "fully loaded cost" of an employee has a telling example. <a href="http://research.unc.edu/offices/sponsored-research/policies-procedures/section-300/policy-7/procedure-5/">UNC Research provides a formula</a> to help those submitting budget proposals calculate fully loaded hourly salaries. And the example they use is telling: for a full time EPA employee with a $150,000 in salary, their formula comes up with a very different $157.36/hr rate. Remember, the naive rule of thumb I see used all the time would result in an hourly rate of only $75/hr.<br />
<br />
Where does this difference come from? As a sometimes hiring manager, what rate should I use when I am thinking through the difference in cost from hiring an employee or a contractor? I really wasn't sure. And many fellow hiring managers weren't sure either. So I came up with <a href="https://docs.google.com/spreadsheets/d/1xncCxeKsmEfgUDZpyTHE8le9SUn0m0RvoOWqBdLEF1Q/edit#gid=826308895">a model</a> to help me think through the costs.<br />
<br />
Before I go any further, I'd like to remind you of the <a href="http://en.wikiquote.org/wiki/George_E._P._Box">quote</a>: "Essentially, all models are wrong, but some are useful." <b>This model is wrong</b>. Every business will incur different costs for benefits. You'll have different time off policies. It's a model that I've used to estimate employees' fully loaded cost. If it seems generous, that is because I've been lucky to have been working in tech where the benefits are incredible. But I find it useful, and think you will too.<br />
<br />
The model is based on a few assumptions:<br />
<br />
<ul>
<li>US based.</li>
<li>2014 <a href="http://www.ssa.gov/policy/docs/quickfacts/prog_highlights/RatesLimits2014.html">FICA rates</a>.</li>
<li>Paid time off is based on <a href="http://www.bls.gov/news.release/ebs.t05.htm">BLS 1996 averages</a> and updated based on my personal experience.</li>
<li>401(k), state unemployment insurance costs and health care insurance based on a <a href="http://money.cnn.com/2013/02/28/smallbusiness/salary-benefits/">2013 CNN Money article</a> and updated based on my personal experience.</li>
<li>No <a href="http://thenextweb.com/insider/2012/04/09/12-startups-that-offer-their-employees-the-coolest-perks/">superawesome</a> <a href="http://www.businessinsider.com/everyone-wants-to-work-at-tech-companies-2013-1">mega</a> <a href="http://bendyworks.com/growth-day-happenings-friday-the-13th/">wowza</a> <a href="http://mashable.com/2011/10/17/google-facebook-twitter-linkedin-perks-infographic/">perks</a> such as lunch, massages, car washes. Instead I've only included the "normal" ones I've come to expect including a conference budget and a new laptops every couple of years.</li>
<li>No other indirect costs like rent.</li>
<li>I factored in a utilization rate (85%) to reflect the fact that even for a billable person, not every hour of the day will go towards billing. Things that I have seen taking up time of delivery personnel include travel, context switch time, internal meetings, reporting hours and expenses and sales support. I expect that even in a product company you'll have time lost to these sorts of things, though the utilization rate may be higher.</li>
</ul>
<div>
With the model a $150,000 full time salaried employee will cost approximately $181,000 a year. With an expected 1530 utilized hours, that works out to $118/hr.</div>
<div>
<br /></div>
<div>
If you're curious about your own fully loaded costs, make a copy of the model and tweak away.<br />
<br />
[Self promotion ahead!]<br />
<br />
I was a developer and architect. I wrote Java, JavaScript, Perl, Ruby, bash and even VB, C# and Python once in a while. Then I did agile coaching, people management, project and/or product management and as an executive helped grow a software services firm.<br />
<br />
Now I use the experience and my passion for lean and data driven decisions to help software delivery teams (companies or departments) run better. If you'd like help understanding your own costs, utilization rates <a href="http://www.217castle.com/services/">or otherwise</a>, <a href="http://www.217castle.com/contact/">drop me a line</a>.</div>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/muness?a=5YeAB6bYKYc:vUG1v-FUrjs:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/muness?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=5YeAB6bYKYc:vUG1v-FUrjs:63t7Ie-LG7Y"><img src="http://feeds.feedburner.com/~ff/muness?d=63t7Ie-LG7Y" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=5YeAB6bYKYc:vUG1v-FUrjs:YwkR-u9nhCs"><img src="http://feeds.feedburner.com/~ff/muness?d=YwkR-u9nhCs" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=5YeAB6bYKYc:vUG1v-FUrjs:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/muness?i=5YeAB6bYKYc:vUG1v-FUrjs:F7zBnMyn0Lo" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=5YeAB6bYKYc:vUG1v-FUrjs:dnMXMwOfBR0"><img src="http://feeds.feedburner.com/~ff/muness?d=dnMXMwOfBR0" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=5YeAB6bYKYc:vUG1v-FUrjs:4cEx4HpKnUU"><img src="http://feeds.feedburner.com/~ff/muness?i=5YeAB6bYKYc:vUG1v-FUrjs:4cEx4HpKnUU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=5YeAB6bYKYc:vUG1v-FUrjs:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/muness?i=5YeAB6bYKYc:vUG1v-FUrjs:V_sGLiPBpWU" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/muness/~4/5YeAB6bYKYc" height="1" width="1"/>2014-06-18T03:29:07Zurn:uuid:5eda2646-b9d0-41f3-692e-5a8147a5fbb7It's your data, not TripIt's... an example of dragging information out of your buried dataMy tax accountant recently asked me for a rundown of days I had traveled for work in 2013. I use TripIt pro to track my travel, this should be trivial. Hooray for SaaS!<br />
<br />
Alas, I log in, and look around... nah, no reports here. The closest thing is the <a href="https://www.tripit.com/trip/listPast">past trips list</a>. Alas I can't filter by a date range or destination or purpose. (Aside: the "Traveler" filter is implemented client side and takes down the 10 listed to 0-10 entries. A minimal viable version of the feature. And now we can ignore it. Bah humbug, I say!) No matter, I can do this myself in a spreadsheet, I'll just export my trips out to a csv...<br />
<br />
Thwarted again! TripIt doesn't provide an export from what I can tell. A <a href="http://bit.ly/1q9FrQn">google search leads me in the right direction</a>. The first link is to a discussion about moving data to other systems for analysis of one's own flight data. It refers to <a href="http://openflights.org/">OpenFlights.org</a> being able to <a href="http://openflights.org/blog/2012/03/16/import-flights-from-tripit/">import TripIt</a> flight information. A couple of clicks later and yet another sign up to yet another site and a dozen more click I had my TripIt data imported to Openflight.org. And another click later I had it exported to a <a href="https://docs.google.com/spreadsheets/d/16f1fgjvuyW3vvCHQHe6PCfoTSHT0U6ToZ_-Z0H28W4E/edit#gid=0">csv</a>. Finally, my - flight - data is mine!<br />
<br />
As I am sure you've guessed, I am not really done shaving yaks. Along the way the data got stripped down to flight information, not trip information. So I have to <a href="https://docs.google.com/spreadsheets/d/16f1fgjvuyW3vvCHQHe6PCfoTSHT0U6ToZ_-Z0H28W4E/edit#gid=1888096476">recreate that</a>.<br />
<br />
Finally I have the data my accountant needs. But I don't want to send him csv, but rather just the info he wants: which days did I not work in Ohio, <a href="https://docs.google.com/spreadsheets/d/16f1fgjvuyW3vvCHQHe6PCfoTSHT0U6ToZ_-Z0H28W4E/edit#gid=2024031244">a pivot table later, and I have what he wants</a>. There's actually a bug in this calculation, but I'll leave grumbling about the issues I have with Google Spreadsheets vis a vis Excel for another post.<br />
<br />
Unlike my previous post, I didn't write any Python or Ruby to solve my problem though I <a href="https://github.com/ianalexander/tripit-python">was</a> <a href="https://github.com/flextrip/tripit">tempted</a> (and still am: when I do this next time I'll probably use the ruby library to generate the csv in hopes that I don't have to fill in trip purpose, fill in return automatically and get non-flight trip data in). Using spreadsheets in lieu of real programming, I've been able to tackle a surprising number of problems over the last couple of years. Little ones like the one presented, but also more interesting problems like analyzing sales trends for a client and ball-parking the completion date for a 6+ month data center migration so we could schedule with vendors, business teams and engineering teams. Or tracking down why Akamai CDN usage had gone up over a couple of quarters and prioritizing which technical fixes to put in place first. Or calculating billable utilization rates monthly by team over a couple of years.<br />
<br />
If you want to hear more about one of those cases <a href="http://twitter.com/muness">let me know</a>. For now, I encourage you to put your data in spreadsheets and play around: they're good for a lot more than laying out your data into a grid.<br />
<br />
[Self promotion ahead!]<br />
<br />
I used to be a developer and architect. I wrote Java, JavaScript, Perl, Ruby, bash and even VB, C# and Python once in a while. Then I did agile coaching, people management, project and/or product management and as an executive helped grow a pretty awesome software services firm.<br />
<br />
Now I use the experience and my passion for lean and data driven decisions to help software delivery teams (companies or departments) run better. See my <a href="http://www.217castle.com/">consulting site</a> for more, and <a href="http://www.217castle.com/contact/">drop me a line</a> if you'd like to chat about a problem you want to tackle.<br />
<br />
<br />
<br />
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/muness?a=HFHp_5R_SKc:tL58njWYSkE:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/muness?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=HFHp_5R_SKc:tL58njWYSkE:63t7Ie-LG7Y"><img src="http://feeds.feedburner.com/~ff/muness?d=63t7Ie-LG7Y" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=HFHp_5R_SKc:tL58njWYSkE:YwkR-u9nhCs"><img src="http://feeds.feedburner.com/~ff/muness?d=YwkR-u9nhCs" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=HFHp_5R_SKc:tL58njWYSkE:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/muness?i=HFHp_5R_SKc:tL58njWYSkE:F7zBnMyn0Lo" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=HFHp_5R_SKc:tL58njWYSkE:dnMXMwOfBR0"><img src="http://feeds.feedburner.com/~ff/muness?d=dnMXMwOfBR0" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=HFHp_5R_SKc:tL58njWYSkE:4cEx4HpKnUU"><img src="http://feeds.feedburner.com/~ff/muness?i=HFHp_5R_SKc:tL58njWYSkE:4cEx4HpKnUU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=HFHp_5R_SKc:tL58njWYSkE:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/muness?i=HFHp_5R_SKc:tL58njWYSkE:V_sGLiPBpWU" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/muness/~4/HFHp_5R_SKc" height="1" width="1"/>2014-06-13T03:43:46Zurn:uuid:d1923902-a888-bd67-f4d4-a8eeffd33a83The long-rumored Apple console, free-to-play games, and the future of the living room<p>I've never worked in the games business, and I have no major reason to have opinions about it. But I've been thinking about this one for a really long time and I don't see it being fully explained anywhere. So here's my thesis about what's going to happen with console gaming.</p>
<blockquote><p>One day, Apple will expand Apple TV into an iOS device that can support third-party apps, perhaps renaming it in the process. The biggest reason they have to take this step is the rise of free-to-play (F2P) games. F2P games have made major inroads in mobile and PC gaming, but nobody has taken them into the living room. Apple will be the company to do this, beating Sony, Microsoft, Nintendo, and Valve to the punch. It will give the company a major footprint in the living room, strengthen its position as a distributor of streaming entertainment, and grow Apple TV into a massive new product line.</p></blockquote>
<h2>Free-to-play gaming is a tidal wave</h2>
<p>Give this a try: Find good free games for your iPhone or iPad. Find them on the App Store, or even better, through gaming blogs like <a href="http://kotaku.com/">Kotaku</a>. Download every single one, play it for as long you enjoy it. Don't spend any money. When you get bored, find another one. Repeat until you run out of good free games.</p>
<p>You can't actually do this, right? There's simply too much out there. There are so many good and even great games for free that to do this you would sacrifice your job, your family, and your social life.</p>
<p>Free-to-play games are, like any freemium good, one part marketing and one part for-sale product. The games business has always been incredibly competitive, but in the past you still had to charge for games because there was no other way to get paid. But once companies like Apple and Valve made it easy to embed payment opportunities inside the game after the download, that created an immense downward pressure on purchase price. That pressure has now brought the price all the way down to zero.</p>
<p>There are holdouts, to be sure. And they have good reasons, arguing that F2P dynamics ruin games. I think that's a respectable position, but I also think it's not a position that most studios will be able to afford.</p>
<p>Imagine a world where almost every restaurant let you try the first five bites of any dish for free. How many people would try the restaurant that made you pay up front? Wouldn't almost everybody walk up and down the street at dinner time, eating five bites at a time until they were full?</p>
<h2>We have met the enemy, and he is currently downloading Flappy Bird</h2>
<p>Now is probably the time to say that I don't think restauranteurs should offer five free bites: Their lives are hard enough as it is. And I don't at all think that F2P is all great for everybody all the time.</p>
<p>But it's interesting to compare mobile F2P and PC F2P, which are starkly different in terms of game quality. In mobile, F2P brings out the worst in app economics, with crippleware, shameless ripoffs, misleading names, ratings scammers, and everything else. But on the PC side, F2P is a done deal, and if you check out <a href="http://store.steampowered.com/genre/Free%20to%20Play/">Steam's list of F2P games</a> you'll find successful and critically respectable entries in a wide range of genres. If you want a team-based shooter, go for Team Fortress 2 or Loadout. If you want a grindy RPG, try Path of Exile. You can be a superhero in Marvel Heroes, or a mechanized war machine in Hawken.</p>
<p>And then there's <a href="leagueoflegends.com">League of Legends</a>, the biggest e-sports game today. You probably couldn't find a player base more concerned with game balance, and yet LoL is F2P too. If it's harmed the game, the fans don't seem to care: For last year's world championships, <a href="http://www.staplescenter.com/events/detail/league-of-legends-world-championships">Riot Games booked the Staples Center</a> and then sold every last ticket.</p>
<p>So I guess, generally, I consider F2P a classic disruption that will be very noisy for a while but generally better for consumers. And I think when F2P hits the living room, it may look like the current mobile F2P at first, but as it gets bigger it will get a bit more mature, looking like the more sensible PC F2P scene.</p>
<p>(If you're curious: I play mostly console games with a few PC games on the side, with a leaning towards intense shooters and games that are grim in a novel way. Recent faves include the Dark Souls series, Battlefield 3, Don't Starve, <a href="http://www.specopstheline.com/index.html">Spec Ops: The Line</a>, and Hotline Miami. I think <a href="http://www.titanfall.com/">Titanfall</a> lives up to the hype. I'm trying to not suck at League of Legends.)</p>
<h2>Who loses?</h2>
<p>Once you call something a "classic disruption", you need to enumerate who's going to end up on what side when all the dust is settled. <a href="http://fhwang.net/2011/12/12/What-we-mean-when-we-say-disruption">As I've written before</a>, "disruptive" is often a synonym for "somebody's going to lose their job". Vote Democrat, kids.</p>
<h3>AAA Game Studios</h3>
<p>There are a handful of studios still launching $60 titles for consoles and PC. But as much as I'm enjoying Titanfall, I think that perch is going to get really precarious over the next few years. Some of those studios will figure out how to make the games they want with a blend of lower purchase prices and more in-game purchasing. Maybe a few will be able to justify their high purchase price with a high reputation for quality. (I'd probably pay $100 for Half-Life 3 at this point.) And some won't make the transition at all.</p>
<h3>Microsoft and Sony</h3>
<p>The Xbox and Playstation product lines are fairly expensive machines whose main selling point is the ability to power AAA titles. If all you want to play is AAA games, those consoles offer strong value, but as F2P games improve that value only gets weaker.</p>
<p>So even if they see F2P coming at them, it's hard to know how Microsoft and Sony would even maneuver. They're joined at the hip to the AAA studios, and the right console exclusive can drive a lot of hardware sales: Personally I bought my Xbox One bundled with Titanfall. Serious moves to make these consoles more F2P-friendly would likely erode the strength of the AAA studios, leading to a lot of stressful conference calls.</p>
<p>And, yes, Microsoft and Sony keep making noises about their consoles working as general entertainment hubs, but that's a secondary point at best when Roku and Chromecast cost less than $50. Nobody buys an Xbox One to watch Netflix. That'd be like buying a car because you need a cup holder.</p>
<h3>Nintendo</h3>
<p>Business schools will be picking over the bones of this one for decades to come: A game company with a string of hardware hits and a stable of beloved characters, failing because it couldn't see that F2P would catastrophically erode its hold among its casual, price-sensitive customers. F2P needs social dynamics and in-game payments, but Nintendo has not seriously invested in either. And while they can honestly say they provide a safer gaming environment for kids than Apple or anyone else, that will be scant consolation when they're looking like the next Sega, making one Mario game a year to release on other people's hardware.</p>
<h2>Next stop: The living room</h2>
<p>As for Apple, I believe they're headed to the living room to take on console makers Sony and Microsoft. It might go something like this:</p>
<p>One day—maybe tomorrow, maybe in a year—Apple will release an iOS console that integrates with your TV and supports iOS apps. This will make the <a href="https://developer.apple.com/library/ios/documentation/ServicesDiscovery/Conceptual/GameControllerPG/Introduction/Introduction.html">iOS Controller API</a> ten times more important than before, and iOS game developers everywhere will rush to update their existing titles for controller use. As soon as they're done with that enhancement they'll start work on new games, the kind that work better on a big flatscreen TV.</p>
<p>The reaction of the hardcore gaming community will be far more negative than positive. They'll call Apple console users "casuals" and worse. But it won't matter, because Apple isn't selling to them. Slashdot commenters complained that the iPod was expensive on a per-megabyte basis, and that turned out to be irrelevant too.</p>
<p>Instead, Apple will market to its existing customers, with a tagline something like: "$199. 10,000 free games." And then they'll have a foothold.</p>
<p>The first generation of this device will support fairly low-powered games, only a bit more graphically intensive than an iPad game, but as time goes on Apple will push up into bigger hardware, and some of their iOS developer base will follow. Before long Sony and Microsoft will lose some of their top-notch studios to this platform. And soon after that hardcore gamers will have fewer and fewer reasons to buy a box from Sony or Microsoft.</p>
<h2>What about Valve?</h2>
<p><a href="http://www.polygon.com/2013/1/30/3934112/gabe-newell-steam-boxs-biggest-threat-isnt-consoles-its-apple">Gabe Newell sees this coming</a>. He needs to be a little paranoid, because Valve is also pushing into the living room with their <a href="http://store.steampowered.com/livingroom/SteamMachines/">Steam Machine</a> initiative, in which third-party manufacturers sell PCs designed as under-the-TV game consoles with Steam pre-installed.</p>
<p>Valve and Apple resemble each other more than a little: They're both demanding, high-performing technology organizations founded by leaders with a perfectionist streak and a knack for playing a few steps ahead of their competitors. They might meet in the living room, and if that competition heats up it will be fascinating to watch.</p>
<p>However, I'm not sure it'll come to that. Valve might not connect with the mass-market audience that Apple is shooting for. Steam Machine might appeal to PC gamers who are willing to pay more for well-designed hardware, but the reliance on third-party vendors will fragment the brand and intimidate casual customers.</p>
<p>And even Valve themselves have been serving PC gamers for so long that they may not be capable of, or interested in, connecting with a broader audience. I'm a happy Steam user, but I also think it's a perfect example of the company's gamer-centric mindset. Everything about its design—from the dark color scheme to the lousy social features to the tiny fonts—just screams out that it was designed for 18-year-olds who are sitting too close to their monitors.</p>
<h2>Predictions</h2>
<p>This post would hardly qualify as tech-pundit windbaggery if I didn't make specific claims, would it? How else are you going to tease me when it turns out I'm wrong?</p>
<p>So, here are some things I'm predicting about the games business in five years:</p>
<h3>In five years, Apple gaming in the living room will be mainstream</h3>
<p>The new Apple console will be a mainstream success, far bigger than Apple TV is now. Over time the iOS gaming library will come to resemble that of the libraries for Xbox and Playstation, with major entries in genres such as FPS, racing, fighting games, etc. Most but not all of these games will be free-to-play.</p>
<p>The only major limitations of the Apple console library will be the same as of Xbox and Playstation: Nobody will be playing an RTS like Starcraft or a MOBA like League of Legends. Because nobody, not even Apple, can make mainstream consumers use a keyboard while sitting on a couch.</p>
<h3>In five years, Valve's Steam Machines will be a respectable but minor niche in the console category</h3>
<p>These will be quality products that allow Steam to extends its reach without embarrassing Gabe Newell, but they won't break out of the niche PC gaming audience.</p>
<h3>In five years, either Sony or Microsoft will retire its console line</h3>
<p>My guess is that there will be enough space for one console dependent on AAA titles. Sony or Microsoft will stare at each other for a long time, and then one side will blink.</p>
<p>I'm really not sure which side this will be. The needs of adjacent business units would favor Sony staying in the game: It's a full-fledged entertainment company. Meanwhile, a Microsoft run by Satya Nadella will likely be focusing around its enterprise and cloud offerings, making Xbox look like a red-headed stepchild inside of its own company.</p>
<p>But from the point of view of the developer ecosystem, Microsoft has a much easier time than Sony encouraging developers to make Xbox games, since Windows game programming is so similar. So while I think one of them will bow out, it's very hard to guess which one it'll be.</p>
<h3>In five years, Nintendo will be done in the hardware business</h3>
<p>Sorry, guys. It was a good run while it lasted.</p>
<h2>What does disruption feel like?</h2>
<p>I'm generally quite skeptical of tech business books, but I find myself recommending Clayton Christensen's <a href="http://www.amazon.com/The-Innovators-Dilemma-Revolutionary-Business/dp/0062060244">The Innovator's Dilemma</a> all the time. It's almost 20 years old, but it feels like it was written yesterday. And it defines disruptive innovation so precisely and so persuasively that sometimes I feel like you shouldn't be able to use the word "disruptive" without having read it.</p>
<p>One of the things Christensen talks about is "value networks": How new technologies don't just succeed or fail on their technical merits alone, but on the incentives of the various participants positioned around those technologies. You may have permission to work on some new kind of hard drive, but you're never going to get a salesperson to sell them if they have the same commission structure as the older models with a well-defined market. You will fail if you try selling new backhoes to the construction contractors who need the power of an older steam shovel, but maybe you can get the attention of groundskeepers and start from there.</p>
<p>I think one of the tricky parts about spotting a disruptive innovation is that while the old value network is established and easy to describe, the new value network doesn't exist yet. It has yet to be built out of unmet desires and interests that are diffused throughout the world, and watching for its formation is like watching sugar water crystallize into rock candy.</p>
<p>But I believe that this network will take shape, because there are a diffuse set of interests whose desires will be met more precisely by it. Consumers who want to try before they buy. Developers who love making games so much that they're willing to give away a lot of their work. Writers and web programmers who want to help people choose between good and bad in a world flooded with new games. Publishers and investors who understand that the power-law economics of social games means there's money to be made by having the deep pockets to fund twenty games only to see one succeed.</p>
<p>As for whether it'll be good for games, I think generally yes. I'd like to think for every shitty, deceptive piece of crippleware, we'll see two new entries that are sharp and imaginative. Of course, I work in tech, and maybe I'm predisposed to that optimism.</p>
<p>But I also don't think my optimism doesn't matter that much. We're headed there either way.</p>
2014-03-30T19:39:02Zurn:uuid:7f70f076-15af-8b4d-b6e3-4c7407b39b59JsonInference<p>More than once, I've been asked to make sense of a document store underneath an out-of-control codebase. For my last project, I wrote <a href="https://github.com/fhwang/json-inference">JsonInference</a> to help me see the entire data store all at once, looking for common patterns.</p>
<p>Given a bunch of JSON documents that are assumed to be similar, JsonInference reports on statistical patterns about commonality. For example, feed a report object a bunch of JSON hashes:</p>
<pre><code>report = JsonInference.new_report
huge_json['docs'].each do |doc|
report << doc
end
puts report.to_s
</code></pre>
<p>And you receive output that looks like this.</p>
<pre><code>JsonInference report: 21 documents
:root > ._id: 21/21 (100.00%)
String: 100.00%, 0.00% empty
:root > ._rev: 21/21 (100.00%)
String: 100.00%, 0.00% empty
:root > .author_id: 14/21 (66.67%)
Fixnum: 100.00%
:root > .sections: 21/21 (100.00%)
Array: 100.00%, 0.00% empty
:root > .sections:nth-child(): 50 children
Hash: 100.00%
:root > .sections:nth-child() > .title: 50/50 (100.00%)
String: 100.00%, 0.00% empty
:root > .sections:nth-child() > .subhead: 50/50 (100.00%)
String: 100.00%, 2.00% empty
:root > .sections:nth-child() > .body: 50/50 (100.00%)
String: 100.00%, 0.00% empty
:root > .sections:nth-child() > .permalink: 46/50 (92.00%)
String: 100.00%, 15.22% empty
</code></pre>
<p>I keep meaning to write more about document stores and challenges they represent to teams in modeling data. I don't necessarily think they're <em>worse</em> than relational stores, but they do seem to offer lots of unfamiliar pitfalls.</p>
2014-03-21T21:03:41Zurn:uuid:c7a3feea-fc36-19ee-3271-b385def4bcfbJsonDeepCompare<p>For a recent consulting project, I found myself comparing a lot of large JSON documents in tests, which can be frustrating since differences don't show up well when comparing the hashes normally. Hence <a href="https://github.com/fhwang/json-deep-compare">JsonDeepCompare</a>, a Ruby gem for comparing large JSON documents and showing the most specific points of difference if they are unequal.</p>
<p>Let's say you've got a test case:</p>
<pre><code>class MyTest
include JsonDeepCompare::Assertions
def test_comparison
left_value = {
'total_rows' => 2,
'rows' => [
{
'id' => 'foo',
'doc' => {
'_id' => 'foo', 'title' => 'Foo', 'sub_document' => { 'one' => 'two' }
}
}
]
}
right_value = {
'total_rows' => 2,
'rows' => [
{
'id' => 'foo',
'doc' => {
'_id' => 'foo', 'title' => 'Foo', 'sub_document' => { 'one' => '1' }
}
}
]
}
assert_json_equal(left_value, right_value)
end
end
</code></pre>
<p>Running it will output this error:</p>
<pre><code>RuntimeError: ":root > .rows :nth-child(1) > .doc > .sub_document > .one" expected to be "two" but was "1"
</code></pre>
<p>The selector syntax uses a limited subset of <a href="http://jsonselect.org/">JSONSelect</a> to describe where to find the differences.</p>
2014-03-21T20:56:59Zurn:uuid:d5fcec6b-6fab-62e6-33c5-b5a08b1d18b4Why Whyday<p style="padding-left:3em;"><em>This next Thursday is Whyday. Celebrate with some outragous coding.</em></p>
<h2>What are you doing for Whyday?</h2>
<p>This next Thursday is designated as <a href="http://whyday.org/">Whyday</a>, a
day set aside to commemorate all the many wacky contributions of Why
the Lucky Stiff to the Ruby community. How do you celebrate WhyDay?
The WhyDay web page suggests:</p>
<ul>
<li>See how far you can push some weird corner of Ruby (or some other language).</li>
<li>Choose a tight constraint (for example, 4 kilobytes of source code) and see what you can do with it.</li>
<li>Try that wild idea you’ve been sitting on because it’s too crazy.</li>
<li>You can work to maintain some of the software Why left us (although Why is more about creating beautiful new things than polishing old things).</li>
<li>On the other hand, Why is passionate about teaching programming to children. So improvements to Hackety Hack would be welcome.</li>
<li>Or take direct action along those lines, and teach Ruby to a child.</li>
</ul>
<p>As for me, I have several ideas:</p>
<ol>
<li>Play with the <span class="caps">HTML5</span> canvas, maybe writing a Ruby <span class="caps">DSL</span> for easily generating diagrams in Ruby.</li>
<li>Play with some Grit (a Ruby/Git library) and see if I can categorize git commits into a swimlane structure.</li>
<li>Combine the two ideas into program that generates a graphical swimlane representation (using an <span class="caps">HTML5</span> canvas) of a git project history (similar to the hand drawn swimlanes in <a href="http://nvie.com/git-model">Vincent Driessen’s Article</a>).</li>
</ol>
<p>Those are just my ideas. And I reserve the right to change my mind at a moments notice.</p>
<p>So, what are you doing for whyday?</p>2013-07-09T15:42:16Zurn:uuid:eeb84dd8-efbf-4950-61dc-70bb8ce4dd03Now Using Pivotal Tracker<p style="padding-left:3em;"><em>I’m switching from self-hosting Redmine to using Pivotal
Tracker for issue tracking on my Open Source projects.</em></p>
<h2>Switching to Pivotal Tracker</h2>
<p>After running Redmine locally for a while, I’ve decided to switch to a
hosted issue tracking service. I’ve moved all the open tickets on the
onestepback.org Redmine app to my account on Pivotal Tracker.</p>
<h2>Current Project Links</h2>
<p>Here are the links to my current projects:</p>
<ul>
<li><a href="http://www.pivotaltracker.com/projects/28469">Rake</a></li>
<li><a href="http://www.pivotaltracker.com/projects/28401">FlexMock</a></li>
<li><a href="http://www.pivotaltracker.com/projects/29210">Builder</a></li>
<li><a href="http://www.pivotaltracker.com/projects/42967">TExp</a></li>
<li><a href="http://www.pivotaltracker.com/projects/36147">Given</a></li>
</ul>
<p>All the projects are marked public so you should be able to view
the projects (and subscribe to an <span class="caps">RSS</span> feed) without actually signing
up for anything.</p>
<h2>Did I miss anything?</h2>
<p>All the open tickets should be migrated to Pivotal Tracker. If you
notice anything missing, let me know. Thanks.</p>2013-07-09T15:42:16Zurn:uuid:b8428f32-78b1-059e-c0cd-2a36f780165aWhat Are Metaclasses?<p style="padding-left:3em;"><em>I seemed to have accidently started a twitter storm debate
on metaclasses in Ruby. Somethings are just are to say in 140
characters. So here’s my take on the issue.</em></p>
<h2>Singleton Class / Eigenclass / Metaclass … What?</h2>
<p>Ruby has this concept of per-class methods. In other words, you
can define a method on an object, rather than on its class. Such
per-object methods are callable on that object only, and not any other
object in the same class.</p>
<p>Implementation wise, these per-object methods are defined in an
almost invisible class-like object called the “Singleton Class”. The
singleton class injects itself into the object’s method lookup list
immediately prior to the object’s class.</p>
<p>Some people object to the name “singleton class”, complaining that
it is easily confused with the singleton pattern from the Gang of Four
book. Other suggested names are “Eigenclass” and “Metaclass”.</p>
<p>I objected to the use of the name “metaclass” for the singleton
class on the grounds that metaclass has a well understood meaning in
non-Ruby circles, and that the singleton class is not a metaclass.</p>
<h2>So, What’s a Metaclass?</h2>
<p>According to <a href="http://en.wikipedia.org/wiki/Metaclass">wikipedia</a>:</p>
<blockquote>
<p><em>In object-oriented programming, a metaclass is a class whose
instances are classes. Just as an ordinary class defines the behavior
of certain objects, a metaclass defines the behavior of certain
classes and their instances.</em></p>
</blockquote>
<p>I get two things out of this: (1) instances of metaclasses are
classes, and (2) the metaclass defines the behavior of a class.</p>
<h2>So Singleton classes aren’t Metaclasses?</h2>
<p>No, not according to the definition of metaclass.</p>
<p>In general, instances of singleton classes are regular objects, not
classes. Singleton classes define the behavior of regular objects,
not class objects.</p>
<h2>But Everybody Calls them Metaclasses!</h2>
<p>I can’t change what everybody calls them. But calling a dog a
horse doesn’t make it a horse. I’m just pointing out that the common
usage of the term metaclass is contrary to the definition of metaclass
used outside the Ruby community.</p>
<h2>Does Ruby have Metaclasses?</h2>
<p>Yes … I mean no … well maybe.</p>
<p>Is there something that creates classes in Ruby? Yes, the class
Class is used to create new classes. (e.g. <tt>Class.new</tt>). All
classes are instances of Class.</p>
<p>Is there something that defines the behavior of classes? Yes, any
class can have its own behavior by defining singleton methods. The
singleton methods go into the singleton class of the class.</p>
<p>Ruby doesn’t have a single “thing” that is a full metaclass. The
role of a metaclass is split between Class (to create new classes) and
singleton classes of classes (to define class specific behavior).</p>
<h2>So, Singleton Classes Are Metaclasses?</h2>
<p>You weren’t listening. Not all singleton classes are metaclasses.
Only singleton classes of classes are metaclasses. And then only
weak, partial metaclasses.</p>
<h2>Does it Matter What We Call Them?</h2>
<p>In the long run, probably not. Most people seem happy to
(incorrectly) call them metaclasses, and this post is unlikely to
change that behavior. Shoot, it seems the Rails team has already
<a href="http://api.rubyonrails.org/classes/Object.html#M000287">immortalized</a>
the term.</p>
<p>However, if reading this post has helped you understand what real
metaclasses are, then it was worthwhile.</p>2013-07-09T15:42:16Zurn:uuid:cd5af53a-2046-8ef5-25dd-387707c3a107Source Control Made Easy<p style="padding-left:3em;"><em>“Have I mentioned today how much git rocks?”</em> — One of my office mates</p>
<p>I hear that spontaneous outpouring of appreciation for git about
once a day. Usually it is someone in the office who just finished a
task that would have been difficult with any of the source control
systems we had used previously. Git has really impacted our day to
day development process, and that’s the sign of a powerful tool.</p>
<p>It wasn’t always like this. I remember when EdgeCase made a rather
abrupt switchover from subversion to git. I had only been dabbling
with git at the time, but the guys in charge of our code repositories
said “Here, this is good stuff. We’re switching … Now.”</p>
<p>Let me tell you, it was a little rough for a few days. Eventually
we figured it out. Although we love the tool now, the learning curve
was a bit steep to climb.</p>
<p>About two months ago we were talking in the office about git and
how to encourage people to adopt it. We talked abou the need for a
gentle introduction to git that quickly gets the user over the
learning curve quickly. That gave me the idea for the “Souce Control
Made Easy” talk that teaches the concepts behind git by starting from
scratch developing the ideas behind git one by one.</p>
<h2>A Pragmatic Screen Cast</h2>
<p>Mike Clark of the Pragmatic Studio contacted me about turning the talk
into a screencast that could reach a wider audience than the normal
conference-going crowd. I’m happy to say that <a href="http://www.pragprog.com/screencasts/v-jwsceasy/source-control-made-easy">Source Control Made
Easy</a>
is now available from the Prags.</p>
<p>If you are thinking about adopting git, or have already started using
git but are still at the awkward stage, then this screen cast is
design for you. The Pragmatic Studio page has a link to a preview of
the screen cast. I hope you check it out.</p>
<p><a href="http://www.pragprog.com/screencasts/v-jwsceasy/source-control-made-easy"><img src='http://onestepback.org/images/v-jwsceasy.jpg'></a></p>2013-07-09T15:42:16Zurn:uuid:3b736394-06f1-dd08-3950-23ce46237686Using p4merge with Git<p style="padding-left:3em;"><em>Git doesn’t come with a merge tool, but will gladly use
third party tools…</em></p>
<h2>The Git Debate</h2>
<p>The reoccurring debate on switching from svn to git is going on
again on the Ruby Core mailing list. Amoung the many objections to
git is that it doesn’t come with a nice merge tool. Perforce was held
up as an example of a tool that does merging right. Although I’m not
a big fan of perforce in general, the merge tool in perforce was one
of its two redeeming aspects.</p>
<h2>Now You Can Have Your Cake and Eat it Too!</h2>
<p>Although it is correct that git doesn’t come with a nice merge tool, it
is quite happy to use any merge tool that you have on hand. And since
Perforce’s merge tool is available free (from
<a href="http://www.perforce.com/perforce/products/merge.html">here</a>), you can
use p4merge with git.</p>
<p>Just add the following to your .gitconfig file in your home directory:</p>
<pre>
[merge]
summary = true
tool = "p4merge"
[mergetool "p4merge"]
cmd = /PATH/TO/p4merge \
"$PWD/$BASE" \
"$PWD/$LOCAL" \
"$PWD/$REMOTE" \
"$PWD/$MERGED"
keepBackup = false
trustExitCode = false
</pre>
<p>Now, whenever git complains that a conflict must be resolved, just
type:</p>
<pre>
git mergetool
</pre>
<p>When you are done resolving the merge conflicts, save the result from
p4merge and then quit the utility. If git has additional files that
need conflict resolution, it will restart p4merge with the next file.</p>
<p>Enjoy.</p>
<h2>Interested in (not) Hearing about Git?</h2>
<p>I’m doing a talk that’s not about git at the <a href="http://oink-pug.org">Ohio, Indiana, Northern
Kentucky <span class="caps">PHP</span> Users Group</a> (yes indeed, that
acronym is <span class="caps">OINK</span>-PUG) on September 17th. Although the talk is
explicitly <em>not</em> about git, you will come away from the talk with a
much deeper understanding on what goes on behind the curtains with
using git.</p>
<p>If there are other local groups interested in <em>not</em> hearing about git,
feel free to contact me.</p>
<h2>Update (6/Sep/09)</h2>
<p>Several people have mentioned that it is not obvious where to get the
p4merge tool from the perforce page. Go to the Perforce
<a href="http://www.perforce.com/perforce/downloads">downloads</a> page and click
on the proper platform choice under “The Perforce Visual Client”
section. When you download “P4V: The Visual Client”, you will get
both the <span class="caps">P4V GUI</span> application and the p4merge application.</p>
<h2>Oops, Another Update (7/Sep/09)</h2>
<p>I forgot that the shell script that runs p4merge is something you have
to create yourself. Here’s mine:</p>
<pre>
#!/bin/sh
/Applications/p4merge.app/Contents/Resources/launchp4merge $*
</pre>
<p>There are more detais <a href="http://www.andymcintosh.com/?p=33">Andy McIntosh’s site</a></p>2013-07-09T15:42:16Zurn:uuid:2a32d7ff-948e-0bd8-f51a-0535505df216The Adhearsion Demo From Mountain West Ruby Conf<p style="padding-left:3em;"><em>Try Jay’s demo at home.</em></p>
<h2>Jay Phillip’s Adhearsion Demo at the Moutain West Ruby Conf</h2>
<p>Jay Phillip’s talk at <span class="caps">MWRC</span> attempted to get the audience involved in
actually running an Adhearsion demo on their own laptops.
Unfortunately, the demo at <span class="caps">MWRC</span> was plagued with firewall and network
problems, but eventually I was able to get it working. Here are the
steps needed.</p>
<p>Go ahead, try this at home. It’s a lot of fun.</p>
<h3>Step 1—Sign up for an Adhearsion account.</h3>
<p>You can do that here: <a href="http://adhearsion.com/signup">http://adhearsion.com/signup</a></p>
<p>You will need a skype account to complete the sign-up. After signing
up, you should get an email with a link that you need to click before
your account is activated. Go ahead and activate the account now.</p>
<h3>Step 2—Install the Adhearsion Gem</h3>
<p>Run:</p>
<pre>
gem install adhearsion
</pre>
<p>I’m running the 0.8.2 version of the gem.</p>
<h3>Step 3—Create an Adhearsion project</h3>
<p>Run:</p>
<pre>
ahn create project_name
</pre>
<h3>Step 4—Enable the Sandbox</h3>
<p>Run:</p>
<pre>
cd project_name
ahn enable component sandbox
</pre>
<h3>Step 5—Edit Your Credentials</h3>
<p>Edit the file: <tt>components/sandbox/sandbox.yml</tt> and update the
username and password you used when you created the Adhearsion account
in step 1.</p>
<h3>Step 6—Create a Dial Plan for the Sandbox</h3>
<p>Edit the <tt>dialplan.rb</tt> file to contain the following:</p>
<pre class="ruby-code">
adhearsion {
simon_game
}
sandbox {
play "hello-world"
}
</pre>
<p>The adhearsion section should alread be in the file. You will be
adding the sandbox section.</p>
<h3>Step 7—Star the Adhearsion Server</h3>
<p>Run:</p>
<pre>
cd ..
ahn start project_name
</pre>
You should see:
<pre>
INFO ahn: Adhearsion initialized!
</pre>
<p>Errors at this stage might mean that your adhearsion account isn’t
setup properly, you don’t have the right user name and password (in
step 5), or that you have firewall issues preventing you from
connecting to the Adhearsion server.</p>
<h3>Step 8—Call The Sandbox</h3>
<p>Using Skype, call the Skype user named <b>sandbox.adhearsion.com</b>.
You should hear a hello world message.</p>
<h3>Step 9—Change the Dial Plan</h3>
<p>Just for fun, change the <tt>dialplan.rb</tt> file to contain:</p>
<pre class="ruby-code">
adhearsion {
simon_game
}
sandbox {
play "hello-world"
play "tt-monkeys"
}
</pre>
<p>(Add the tt-monkeys line to the sandbox dial plan).</p>
<p>Now call the sandbox again (skyping user sandbox.adhearsion.com)
to hear the change in the dial plan. Monkeys <span class="caps">FTW</span>.</p>
<h3>More Examples</h3>
<p>Here’s a example of what can be done in a dial plan. I was just
goofing around with my dial plan.</p>
<pre>
adhearsion {
simon_game
}
sandbox {
play "vm-enter-num-to-call"
digits = input 1, :timeout => 10.seconds
case digits
when '1'
play "hello-world"
when '2'
play "tt-monkeys"
when '3'
play "what-are-you-wearing"
when '4'
play 'conf-unmuted'
when '5'
play 'tt-weasels'
when '6'
play "pbx-invalidpark"
when '7'
play "1000", "dollars"
when '8'
play "followme/sorry"
when '9'
simon_game
when '0'
play Time.now
else
play "demo-nomatch"
end
sleep 1
play "demo-thanks"
}
</pre>
<p>See <a href="http://adhearsion.com/examples">http://adhearsion.com/examples</a>
for more dialplan examples.</p>
<h2>That’s It</h2>
<p>Think about what you are doing. You are calling the Adhearsion server
and controlling how that remote server responds by the adhearsion
program running on your own local box. That is wild.</p>
<p>The adhearsion sandbox makes it easy to play around with telephony
programming without any investment in the associated hardware.</p>
<p>I hope this demo encourages you to give Adhearsion a try.</p>2013-07-09T15:42:16Zurn:uuid:1c399dea-4ca9-7d9a-88b9-d099f7e71eb7You Are Invited<p style="padding-left:3em;"><em>All Rails Conf 2009 speakers are invited to a special event.</em></p>
<h2>Who?</h2>
<p>Anyone speaking at RailsConf 2009</p>
<h2>When?</h2>
<p>Sunday, May 3, 4:00PM – 6:00PM<br />
(The day before RailsConf 2009 officially begins)</p>
<h2>Where?</h2>
<p>Las Vegas Hilton in Pavilion 1</p>
<h2>What?</h2>
<p><b><em>Presentations for Presenters.</em></b></p>
<h2>Why?</h2>
<p>You’ve come all the way to Las Vegas to tell the world about your
latest Ruby/Rails project or idea. You want to make sure that you
really get your message across. So, how do you do that?</p>
<p>The <b><em>Presentations for Presenters</em></b> session will give you
practical tips for improving your RailsConf presentation. We will
cover all aspects of planning, preparing, creating and delivering your
talk, so that your unique message will get across to your audience.</p>
<p>Plus we will have a lot of fun. Hope to see you there.</p>
<h2>What do I need to do?</h2>
<p>Start planning now to attend. Since this session is actually the day
<em>before</em> RailsConf officially begins, make sure that your
travel plans gets you there in time.</p>2013-07-09T15:42:16Zurn:uuid:0837f27f-e894-f17f-74a2-49fd05636a7fPresenting for Presenters<p style="padding-left:3em;"><em>If you are speaking at RubyConf this year, we have a special
opportunity for you.</em></p>
<h2>Are You Speaking at RubyConf 2008?</h2>
<p>If so, congratuations! And have we got a deal for you …</p>
<p>Wednesday evening, Nov 5, at 6:00 pm, (that’s the night before the
conference) we are inviting all speakers to a special training
session. I’m going to be sharing some ideas for putting together and
delivering a good presentation.</p>
<p>After my talk, Patrick Ewing and Adam Keys are geared up to do some
Powerpoint Karaoke with everyone there. I’m not even sure what
Powerpoint Karaoke is, but it sounds like fun.</p>
<p>I hope to see everyone there.</p>
<h2>Update (4/Nov/08)</h2>
<p>I’ve talked to Adam today. He says that Patrick isn’t going to
able to make RubyConf this year, but we will be ready to roll with
Powerpoint Karaoke anyways.</p>
<h2>Update (5/Nov/08)</h2>
<p>It looks like the speakers training will be in the Olympic Room
tonight. The Olympic Room is on the same floor as the registration
desk. Go to the left past the elevators and turn right down that hall
(or ask someone who looks like they know what they are doing).</p>2013-07-09T15:42:16Zurn:uuid:d25f4fb0-84ea-a5f3-c862-a6f85e285895Articles are Back!<p style="padding-left:3em;"><em>I’ve received a lot of requests for my old articles …</em></p>
<h2>The Article Section has been Restored</h2>
<p>When I changed to my new hosting machine, I moved all my blog posts
but didn’t move any of the articles. Of course I <em>intended</em> to move
them eventually but never got around to it.</p>
<p>A lot of people have been asking for this article or that
presentation, or pointing out that a number of old bookmarked links
are no longer any good. So due to popular demand the <b>Articles and
Presentations</b> section of onestepback.org is now restored.</p>
<p>Enjoy</p>2013-07-09T15:42:16Zurn:uuid:25262252-fea6-64c4-bded-9940056007ccQR Code Fun<p style="padding-left:3em;"><em>Aaron Patterson put a QR code into his RubyConf Brazil Presentation.</em></p>
<h2>Aaron Patterson Shows the Way</h2>
<p>At <a href="http://rubyconf.com.br/">RubyConf Brazil</a> last week, Aaron
Patterson (<a href="http://twitter.com/tenderlove">@tenderlove</a>) posted his
contact information as a QR code as a slide in his presentation. How
cool is that? I managed to snap a picture of it while it was on the
screen and later downloaded a QR scan program for my iPhone to decode
it.</p>
<p>QR codes are 2D bar codes that can easily be printed. You can
encode URLs, phone numbers, <span class="caps">SMS</span> messages, or even just plain text in a
QR code. Check out <a href="http://en.wikipedia.org/wiki/QR_Code">Wikipedia</a>
for more information.</p>
<p>To get started, grab a QR scanner for you phone. The <a href="http://www.wireless.att.com/businesscenter/solutions/mobile-marketing/mobile-barcode-download.jsp">AT&T
scanner</a>
works pretty well and is available on a wide selection of phones.</p>
<h2>QR Code Examples</h2>
<p>To get you started, here’s a link to my twitter feed. If you are
using the AT&T scanner, just start the program and aim the camera at
the graphic below. After a moment, the scanner will automatically
detect the QR code and offer to send you to the <span class="caps">URL</span>.</p>
<p><img src="http://onestepback.org/images/rublog/qrcodes/JimTweeterFeed.png" title="Twitter Feed" alt="Twitter Feed" /></p>
<p>Next up, my contact information:</p>
<p><img src="http://onestepback.org/images/rublog/qrcodes/JimVCard.png" title="Contact Info" alt="Contact Info" /></p>
<p>And finally, a little snippet of Ruby code. Does anybody recognized it?</p>
<p><img src="http://onestepback.org/images/rublog/qrcodes/RubyCode.png" title="Ruby Code" alt="Ruby Code" /></p>
<h2>Generating QR Codes</h2>
<p>I used <a href="http://keremerkan.net/qr-code-and-2d-code-generator/">this generator
site</a> to generate
the QR codes seen on this page.</p>
<p>Give it a try and have some fun!</p>
<p>(QR Code is registered trademark of <a href="http://www.denso-wave.com/en/adcd/"><span class="caps">DENSO WAVE INCORPORTATED</span></a>)</p>2013-07-09T15:42:15Zurn:uuid:3b3cb80c-c025-30a4-7451-dee589b57f84No network after copying an Ubuntu VMWare imageAfter you copy an Ubuntu (or any Debian based) image, you'll probably lose your network connectivity. After a little bit of <a href="http://communities.vmware.com/message/521604#521604">digging</a>, it turns out that udev persists the MAC address of the network device in <code>/etc/udev/rules.d/*net.rules</code> . The fix: <pre><code>sudo rm /etc/udev/rules.d/*net.rules</code>
<code>sudo shutdown -r now #to reboot</code>
</pre></p><div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/muness?a=PU94EaZQROE:59TGJR0lV8o:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/muness?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=PU94EaZQROE:59TGJR0lV8o:63t7Ie-LG7Y"><img src="http://feeds.feedburner.com/~ff/muness?d=63t7Ie-LG7Y" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=PU94EaZQROE:59TGJR0lV8o:YwkR-u9nhCs"><img src="http://feeds.feedburner.com/~ff/muness?d=YwkR-u9nhCs" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=PU94EaZQROE:59TGJR0lV8o:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/muness?i=PU94EaZQROE:59TGJR0lV8o:F7zBnMyn0Lo" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=PU94EaZQROE:59TGJR0lV8o:dnMXMwOfBR0"><img src="http://feeds.feedburner.com/~ff/muness?d=dnMXMwOfBR0" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=PU94EaZQROE:59TGJR0lV8o:4cEx4HpKnUU"><img src="http://feeds.feedburner.com/~ff/muness?i=PU94EaZQROE:59TGJR0lV8o:4cEx4HpKnUU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=PU94EaZQROE:59TGJR0lV8o:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/muness?i=PU94EaZQROE:59TGJR0lV8o:V_sGLiPBpWU" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/muness/~4/PU94EaZQROE" height="1" width="1"/>After you copy an Ubuntu (or any Debian based) image, you'll probably lose your network connectivity. After a little bit of <a href="http://communities.vmware.com/message/521604#521604">digging</a>, it turns out that udev persists the MAC address of the network device in <code>/etc/udev/rules.d/*net.rules</code> . The fix: <pre><code>sudo rm /etc/udev/rules.d/*net.rules</code>
<code>sudo shutdown -r now #to reboot</code>
</pre></p><div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/muness?a=PU94EaZQROE:59TGJR0lV8o:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/muness?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=PU94EaZQROE:59TGJR0lV8o:63t7Ie-LG7Y"><img src="http://feeds.feedburner.com/~ff/muness?d=63t7Ie-LG7Y" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=PU94EaZQROE:59TGJR0lV8o:YwkR-u9nhCs"><img src="http://feeds.feedburner.com/~ff/muness?d=YwkR-u9nhCs" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=PU94EaZQROE:59TGJR0lV8o:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/muness?i=PU94EaZQROE:59TGJR0lV8o:F7zBnMyn0Lo" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=PU94EaZQROE:59TGJR0lV8o:dnMXMwOfBR0"><img src="http://feeds.feedburner.com/~ff/muness?d=dnMXMwOfBR0" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=PU94EaZQROE:59TGJR0lV8o:4cEx4HpKnUU"><img src="http://feeds.feedburner.com/~ff/muness?i=PU94EaZQROE:59TGJR0lV8o:4cEx4HpKnUU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=PU94EaZQROE:59TGJR0lV8o:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/muness?i=PU94EaZQROE:59TGJR0lV8o:V_sGLiPBpWU" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/muness/~4/PU94EaZQROE" height="1" width="1"/>2013-06-26T02:40:23Zurn:uuid:19d5855f-f205-7957-7a48-339a8b52e8fdLife hack: GCal IM reminders<br />
I am in a lot of meetings. I am also on IM much if not all the time. Despite all the hoopla around Google Hangouts, Google Calendar doesn't support hangout/google talk reminders.<br />
<br />
The chrome desktop notifications aren't quite what I need either: they don't give me a link straight into the hangout I am supposed to be in. Instead, when I click the link in them, it opens up Google calendar in the default week view, I have to scan to right now, find the meeting, click it and then click on the "Join hangout" link therein.<br />
<br />
A couple of weeks ago I got fed up with this and decided to hack something together to make life easier. I added hangout link support to gcalcli (<a href="https://github.com/muness/gcalcli">see my fork</a>), wrote <a href="https://gist.github.com/muness/5778339#file-gcalcli_parse_tsv">a script that parses the TSV output</a> finding the links I care about in events coming up in the next 10 minutes, and then wired it up with <a href="http://sendxmpp.hostname.sk/">sendxmpp </a>and cron <a href="https://gist.github.com/muness/5778339#file-cal_im_reminder">to send me an IM</a> with the meeting title, start time and either a hangout link or a deep link to the event in google calendar.<br />
<br />
(While I was at it, I also put together a mini life hack: a <a href="https://gist.github.com/muness/5778339#file-soon">script</a> that shows me all the events I have coming up in the next 4 hours.)<br />
<br />
If you too want to get your calendar reminders via IM:<br />
<ol>
<li>Install <a href="https://github.com/muness/gcalcli">my fork of gcalcli</a> (master branch)</li>
<li>Authorize gcalcli to your account by launching it -- it'll walk you through the oauth2 stuff including launching your browser</li>
<li>Install sendxmpp</li>
<li>Make your local install of sendxmpp work with the SSL requirements from google by editing and <a href="https://gist.github.com/muness/5778339#file-gistfile1-pl">forcing RC4-MD5</a>.</li>
<li>Download/tweak to suit your needs: <a href="https://gist.github.com/muness/5778339#file-gcalcli_parse_tsv">gcalcli_parse_tsv</a>, <a href="https://gist.github.com/muness/5778339#file-cal_im_reminder">cal_im_reminder</a> and install <a href="https://gist.github.com/muness/5778339#file-cal_im_reminder-L4">a cron job</a></li>
</ol>
<div>
If you enjoy this life hack or have a better way to quickly jump into that hangout, chime in with a comment!</div>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/muness?a=hcWTYly4Ge0:W3Dop1MBcro:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/muness?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=hcWTYly4Ge0:W3Dop1MBcro:63t7Ie-LG7Y"><img src="http://feeds.feedburner.com/~ff/muness?d=63t7Ie-LG7Y" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=hcWTYly4Ge0:W3Dop1MBcro:YwkR-u9nhCs"><img src="http://feeds.feedburner.com/~ff/muness?d=YwkR-u9nhCs" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=hcWTYly4Ge0:W3Dop1MBcro:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/muness?i=hcWTYly4Ge0:W3Dop1MBcro:F7zBnMyn0Lo" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=hcWTYly4Ge0:W3Dop1MBcro:dnMXMwOfBR0"><img src="http://feeds.feedburner.com/~ff/muness?d=dnMXMwOfBR0" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=hcWTYly4Ge0:W3Dop1MBcro:4cEx4HpKnUU"><img src="http://feeds.feedburner.com/~ff/muness?i=hcWTYly4Ge0:W3Dop1MBcro:4cEx4HpKnUU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=hcWTYly4Ge0:W3Dop1MBcro:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/muness?i=hcWTYly4Ge0:W3Dop1MBcro:V_sGLiPBpWU" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/muness/~4/hcWTYly4Ge0" height="1" width="1"/><br />
I am in a lot of meetings. I am also on IM much if not all the time. Despite all the hoopla around Google Hangouts, Google Calendar doesn't support hangout/google talk reminders.<br />
<br />
The chrome desktop notifications aren't quite what I need either: they don't give me a link straight into the hangout I am supposed to be in. Instead, when I click the link in them, it opens up Google calendar in the default week view, I have to scan to right now, find the meeting, click it and then click on the "Join hangout" link therein.<br />
<br />
A couple of weeks ago I got fed up with this and decided to hack something together to make life easier. I added hangout link support to gcalcli (<a href="https://github.com/muness/gcalcli">see my fork</a>), wrote <a href="https://gist.github.com/muness/5778339#file-gcalcli_parse_tsv">a script that parses the TSV output</a> finding the links I care about in events coming up in the next 10 minutes, and then wired it up with <a href="http://sendxmpp.hostname.sk/">sendxmpp </a>and cron <a href="https://gist.github.com/muness/5778339#file-cal_im_reminder">to send me an IM</a> with the meeting title, start time and either a hangout link or a deep link to the event in google calendar.<br />
<br />
(While I was at it, I also put together a mini life hack: a <a href="https://gist.github.com/muness/5778339#file-soon">script</a> that shows me all the events I have coming up in the next 4 hours.)<br />
<br />
If you too want to get your calendar reminders via IM:<br />
<ol>
<li>Install <a href="https://github.com/muness/gcalcli">my fork of gcalcli</a> (master branch)</li>
<li>Authorize gcalcli to your account by launching it -- it'll walk you through the oauth2 stuff including launching your browser</li>
<li>Install sendxmpp</li>
<li>Make your local install of sendxmpp work with the SSL requirements from google by editing and <a href="https://gist.github.com/muness/5778339#file-gistfile1-pl">forcing RC4-MD5</a>.</li>
<li>Download/tweak to suit your needs: <a href="https://gist.github.com/muness/5778339#file-gcalcli_parse_tsv">gcalcli_parse_tsv</a>, <a href="https://gist.github.com/muness/5778339#file-cal_im_reminder">cal_im_reminder</a> and install <a href="https://gist.github.com/muness/5778339#file-cal_im_reminder-L4">a cron job</a></li>
</ol>
<div>
If you enjoy this life hack or have a better way to quickly jump into that hangout, chime in with a comment!</div>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/muness?a=hcWTYly4Ge0:W3Dop1MBcro:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/muness?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=hcWTYly4Ge0:W3Dop1MBcro:63t7Ie-LG7Y"><img src="http://feeds.feedburner.com/~ff/muness?d=63t7Ie-LG7Y" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=hcWTYly4Ge0:W3Dop1MBcro:YwkR-u9nhCs"><img src="http://feeds.feedburner.com/~ff/muness?d=YwkR-u9nhCs" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=hcWTYly4Ge0:W3Dop1MBcro:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/muness?i=hcWTYly4Ge0:W3Dop1MBcro:F7zBnMyn0Lo" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=hcWTYly4Ge0:W3Dop1MBcro:dnMXMwOfBR0"><img src="http://feeds.feedburner.com/~ff/muness?d=dnMXMwOfBR0" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=hcWTYly4Ge0:W3Dop1MBcro:4cEx4HpKnUU"><img src="http://feeds.feedburner.com/~ff/muness?i=hcWTYly4Ge0:W3Dop1MBcro:4cEx4HpKnUU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/muness?a=hcWTYly4Ge0:W3Dop1MBcro:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/muness?i=hcWTYly4Ge0:W3Dop1MBcro:V_sGLiPBpWU" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/muness/~4/hcWTYly4Ge0" height="1" width="1"/>2013-06-14T13:54:08Zurn:uuid:e4ac69bd-99b7-7109-3d84-d4516a196abfWhat database should I choose if I'm a total Rails n00b?<p>TL;DR:</p>
<blockquote><p>Stick with SQL databases for now, and use whatever's easiest in whatever environment you're using. That's probably SQLite on your laptop, PostgreSQL if you're deploying to Heroku, and PostgreSQL or MySQL if you're deploying somewhere besides Heroku. And as soon as you deploy anything that has data that you care about, and will have multiple people using it, make sure to not be on SQLite.</p></blockquote>
<p>More explanation below:</p>
<h2>How much research should I be doing on my own?</h2>
<p>Very little. If you're a complete newcomer to programming, you're going to have your hands full as it is. Your choice of database is not the first problem you have to solve.</p>
<h2>SQL or NoSQL?</h2>
<p>You may have heard of these new-fangled databases loosely called "NoSQL". They're cool, but they're a lot newer than SQL databases, which will cause you troubles when you're getting started. This isn't so much an issue of the technical merits of one style vs. another, but if you choose a NoSQL store that you're going to have an uphill climb with setup, installation, library support, documentation, etc.</p>
<p>SQL, on the other hand, is super-popular and <a href="http://en.wikipedia.org/wiki/SQL#History">might actually be older than you</a>. That means that for every single SQL question you have, you're only a Google search away from an answer. Stick with SQL for now.</p>
<h2>Which SQL database should I use?</h2>
<p>There are three meaningful open-source SQL databases: SQLite, MySQL, and PostgreSQL. An over-simplified comparison might go like this:</p>
<ul>
<li>SQLite, as the name implies, is meant to be a very light implementation of SQL functionality. You can use it, say, if you're making a Mac application and need a good way to store structured data, but you don't want to go to the trouble of setting up a SQL server. This also makes it great for developing on your own machine, but since it's multi-connection functionality is limited, you're not going to be able to run an app on production with it.</li>
<li>MySQL is a full-scale SQL server, and many large websites use it in production.</li>
<li>PostgreSQL is also a full-scale SQL server, used by other large websites in production. This is actually my personal favorite, but that distinction doesn't matter when you're just getting started.</li>
</ul>
<p>Any of these will get you what you need: A place to store your Rails data and a way to start learning SQL bit-by-bit. You should optimize for ease of installation or deployment. So that means:</p>
<ul>
<li>SQLite when you're working on your laptop</li>
<li>PostgreSQL if you're deploying to Heroku, since Heroku favors PostgreSQL over MySQL</li>
<li>PostgreSQL or MySQL if you're deploying somewhere else. Check their docs and use whatever they tell you to use.</li>
</ul>
<p>And it's going to be fine, at first, to have the same app using different SQL engines in different environments. At some point you may end up using some really specific, optimized SQL that SQLite can't run, but that's not a newbie problem.</p>
<p>TL;DR:</p>
<blockquote><p>Stick with SQL databases for now, and use whatever's easiest in whatever environment you're using. That's probably SQLite on your laptop, PostgreSQL if you're deploying to Heroku, and PostgreSQL or MySQL if you're deploying somewhere besides Heroku. And as soon as you deploy anything that has data that you care about, and will have multiple people using it, make sure to not be on SQLite.</p></blockquote>
<p>More explanation below:</p>
<h2>How much research should I be doing on my own?</h2>
<p>Very little. If you're a complete newcomer to programming, you're going to have your hands full as it is. Your choice of database is not the first problem you have to solve.</p>
<h2>SQL or NoSQL?</h2>
<p>You may have heard of these new-fangled databases loosely called "NoSQL". They're cool, but they're a lot newer than SQL databases, which will cause you troubles when you're getting started. This isn't so much an issue of the technical merits of one style vs. another, but if you choose a NoSQL store that you're going to have an uphill climb with setup, installation, library support, documentation, etc.</p>
<p>SQL, on the other hand, is super-popular and <a href="http://en.wikipedia.org/wiki/SQL#History">might actually be older than you</a>. That means that for every single SQL question you have, you're only a Google search away from an answer. Stick with SQL for now.</p>
<h2>Which SQL database should I use?</h2>
<p>There are three meaningful open-source SQL databases: SQLite, MySQL, and PostgreSQL. An over-simplified comparison might go like this:</p>
<ul>
<li>SQLite, as the name implies, is meant to be a very light implementation of SQL functionality. You can use it, say, if you're making a Mac application and need a good way to store structured data, but you don't want to go to the trouble of setting up a SQL server. This also makes it great for developing on your own machine, but since it's multi-connection functionality is limited, you're not going to be able to run an app on production with it.</li>
<li>MySQL is a full-scale SQL server, and many large websites use it in production.</li>
<li>PostgreSQL is also a full-scale SQL server, used by other large websites in production. This is actually my personal favorite, but that distinction doesn't matter when you're just getting started.</li>
</ul>
<p>Any of these will get you what you need: A place to store your Rails data and a way to start learning SQL bit-by-bit. You should optimize for ease of installation or deployment. So that means:</p>
<ul>
<li>SQLite when you're working on your laptop</li>
<li>PostgreSQL if you're deploying to Heroku, since Heroku favors PostgreSQL over MySQL</li>
<li>PostgreSQL or MySQL if you're deploying somewhere else. Check their docs and use whatever they tell you to use.</li>
</ul>
<p>And it's going to be fine, at first, to have the same app using different SQL engines in different environments. At some point you may end up using some really specific, optimized SQL that SQLite can't run, but that's not a newbie problem.</p>
2013-06-03T14:39:21Zurn:uuid:77f73896-93af-f52e-f4e9-1aaa49f9a760Vim<p>So I switched to <a href="http://www.vim.org/">Vim</a>, and now I love it.</p>
<p>For years, I was actually using <a href="http://www.jedit.org/">jEdit</a>, of all things, even in the face of continued mockery by other programmers. My reasoning was well-practiced: TextMate didn't support split-pane, all the multi-key control sequences in Emacs had helped give me RSI, and Vim was just too hard to learn. jEdit isn't very good at anything, but it's okay at lots of things, and for years it was fine.</p>
<p>But eventually, I took on a consulting gig where I was forced to learn Vim. And, as so many have promised, once I got over the immensely difficult beginning of the learning curve, I was hooked.</p>
<h2>Beneath the text, a structure</h2>
<p>I'm now one of those smug Vim partisans, and one of the cryptic things we like to say is that Insert mode is the weakest mode in Vim. So what the hell does that mean? It means that if you accept the (lunatic, counterintuitive) idea that you can't just insert the letter <code>*</code> by typing <code>*</code>, what you get is that every character on the keyboard becomes a way to manipulate text without having to prefix it with Control, Cmd, Option, etc. (In fact, the letter <code>*</code> searches for the next instance of the word that is currently under the cursor.)</p>
<p>When you edit source code, or any form of highly structured text, this matters, because over the course of your programming career you're far more likely to spend time navigating and editing existing text than inserting brand new text. So the promise of Vim is that if you optimize navigating and editing over inserting, your work will go faster. After months of practice, it does feel like I edit text far more quickly--that hitting Cmd/Ctrl/etc less often compensates for the up-front investment of learning these highly optimized keystroke sequences.</p>
<p>But it's definitely a strange mindset. In most text editors, you think of the document as a casual smear of characters on a screen, to be manipulated in a number of ways that are all okay but never extremely focused. But Vim assumes you're editing highly structured text for computers, and in some ways it pays more attention to the structure than the characters. So after a while it feels like you're operating an ancient, intricate machine that manipulates that structure, and that the text you see on the screen is just a side-effect of those manipulations.</p>
<h2>Investments</h2>
<p>Is it worth the time? To answer that question you have to get almost existential about your career: How many more decades of coding do you have in front of you? If you're planning on an IPO next year and then you're going to devote the rest of your life to your true passion of playing EVE Online, then maybe keep using your current text editor. But for most of us, a few weeks or more of hindered productivity might be worth the eventual gains.</p>
<p>As is often the case, it's not about raw, straight-line speed--it's about fewer chances to get distracted from the task at hand. Nobody ever codes at breakneck speed for 60 minutes straight. But when you're in the middle of a really thorny problem, maybe you'd be better off with a tool that's that much faster at finding/replacing/capitalizing/incrementing/etc, which might give you fewer chances to get distracted from the problem you're actually trying to solve.</p>
<p>As somebody who's used Vim for a little less than a year, that's what it feels like to me. Most of the time. I have to admit that once in a while the Vim part of my brain shuts down and I stare at the monitor for a few seconds. Those moments are happening less and less, though.</p>
<h2>Beginner steps</h2>
<p>When it came to learning Vim, here's what worked for me:</p>
<h3><cite>Practical Vim</cite></h3>
<p>Drew Neil's <a href="http://pragprog.com/book/dnvim/practical-vim"><cite>Practical Vim</cite></a> is, uh, the best book about a text editor I've ever read. It does a great job of explaining the concepts embedded inside of Vim. I skim through this every few months to try to remember even more tips, and can imagine myself doing that for years.</p>
<h3>MacVim</h3>
<p>As <a href="http://yehudakatz.com/2010/07/29/everyone-who-tried-to-convince-me-to-use-vim-was-wrong/">Yehuda recommends</a>, I started in <a href="http://code.google.com/p/macvim/">MacVim</a> and not just raw Vim. At first you should let yourself use whatever crutches you need--mouse, arrow keys, Cmd-S--to help you feel like you can still do your work. I agree with Yehuda that tool dogmatism is going to be counterproductive if it makes the beginning of the learning process so painful that you give up.</p>
<p>And I still use the arrow keys, and don't really buy the argument that it's ever important to unlearn those.</p>
<h3>Janus</h3>
<p>As with Emacs, a lot of the power of Vim is in knowing which plugins & customizations to choose. Yehuda and Carl's <a href="https://github.com/carlhuda/janus">Janus</a> project is a pretty great place to start for this. I'd install it, and skim the README so you can at least know what sorts of features you're adding, even though you won't use them all for some time.</p>
<h3><cite>vim:essentials</cite></h3>
<p><cite>Practical Vim</cite> is great for reading when you're on the subway or whatever, but you'll need something more boiled-down for day-to-day use. For a while I had this super-short <a href="http://www.stanford.edu/~jacobm/vim.html"><cite>vim:essentials</cite> page</a> open all the time.</p>
<h2>Extra credit: One intermediate step</h2>
<p>After I got minimally familiar with Normal mode, I started hating the way that I would enter Insert mode, switch to my browser to look up something, and switch back to Vim forgetting which mode I was in. I entered the letter <code>i</code> into my code a lot.</p>
<p>I suspect many Vim users just get used to holding that state in their head, or never leaving the Vim window without hitting Esc first, but I decided to simply install a timeout which pulls out of Insert mode after 4 seconds of inactivity:</p>
<pre><code>au CursorHoldI * stopinsert
</code></pre>
<p>As explained in <cite>Practical Vim</cite>, the best way to think of Insert mode is as a precise, transactional operation: Enter Insert mode, edit text, exit Insert mode. The timeout helped me get into that mindset quickly, and live in Normal mode, which is the place to see most of the gains from Vim.</p>
<p>This is an intermediate step, and you shouldn't try it right away. If you're a beginner you're probably not going to benefit from being in Normal mode all the time--if anything the frustration would be likely to make you give up on it. But once Vim starts feeling less disorienting, and you're ready to really learn what <cite>Practical Vim</cite> has been telling you, I'd give this a try.</p>
<p>Hope that's helpful. And I hope that after a month or two of this, you become as smug and self-assured about your text editor as I am today.</p>
<p>So I switched to <a href="http://www.vim.org/">Vim</a>, and now I love it.</p>
<p>For years, I was actually using <a href="http://www.jedit.org/">jEdit</a>, of all things, even in the face of continued mockery by other programmers. My reasoning was well-practiced: TextMate didn't support split-pane, all the multi-key control sequences in Emacs had helped give me RSI, and Vim was just too hard to learn. jEdit isn't very good at anything, but it's okay at lots of things, and for years it was fine.</p>
<p>But eventually, I took on a consulting gig where I was forced to learn Vim. And, as so many have promised, once I got over the immensely difficult beginning of the learning curve, I was hooked.</p>
<h2>Beneath the text, a structure</h2>
<p>I'm now one of those smug Vim partisans, and one of the cryptic things we like to say is that Insert mode is the weakest mode in Vim. So what the hell does that mean? It means that if you accept the (lunatic, counterintuitive) idea that you can't just insert the letter <code>*</code> by typing <code>*</code>, what you get is that every character on the keyboard becomes a way to manipulate text without having to prefix it with Control, Cmd, Option, etc. (In fact, the letter <code>*</code> searches for the next instance of the word that is currently under the cursor.)</p>
<p>When you edit source code, or any form of highly structured text, this matters, because over the course of your programming career you're far more likely to spend time navigating and editing existing text than inserting brand new text. So the promise of Vim is that if you optimize navigating and editing over inserting, your work will go faster. After months of practice, it does feel like I edit text far more quickly--that hitting Cmd/Ctrl/etc less often compensates for the up-front investment of learning these highly optimized keystroke sequences.</p>
<p>But it's definitely a strange mindset. In most text editors, you think of the document as a casual smear of characters on a screen, to be manipulated in a number of ways that are all okay but never extremely focused. But Vim assumes you're editing highly structured text for computers, and in some ways it pays more attention to the structure than the characters. So after a while it feels like you're operating an ancient, intricate machine that manipulates that structure, and that the text you see on the screen is just a side-effect of those manipulations.</p>
<h2>Investments</h2>
<p>Is it worth the time? To answer that question you have to get almost existential about your career: How many more decades of coding do you have in front of you? If you're planning on an IPO next year and then you're going to devote the rest of your life to your true passion of playing EVE Online, then maybe keep using your current text editor. But for most of us, a few weeks or more of hindered productivity might be worth the eventual gains.</p>
<p>As is often the case, it's not about raw, straight-line speed--it's about fewer chances to get distracted from the task at hand. Nobody ever codes at breakneck speed for 60 minutes straight. But when you're in the middle of a really thorny problem, maybe you'd be better off with a tool that's that much faster at finding/replacing/capitalizing/incrementing/etc, which might give you fewer chances to get distracted from the problem you're actually trying to solve.</p>
<p>As somebody who's used Vim for a little less than a year, that's what it feels like to me. Most of the time. I have to admit that once in a while the Vim part of my brain shuts down and I stare at the monitor for a few seconds. Those moments are happening less and less, though.</p>
<h2>Beginner steps</h2>
<p>When it came to learning Vim, here's what worked for me:</p>
<h3><cite>Practical Vim</cite></h3>
<p>Drew Neil's <a href="http://pragprog.com/book/dnvim/practical-vim"><cite>Practical Vim</cite></a> is, uh, the best book about a text editor I've ever read. It does a great job of explaining the concepts embedded inside of Vim. I skim through this every few months to try to remember even more tips, and can imagine myself doing that for years.</p>
<h3>MacVim</h3>
<p>As <a href="http://yehudakatz.com/2010/07/29/everyone-who-tried-to-convince-me-to-use-vim-was-wrong/">Yehuda recommends</a>, I started in <a href="http://code.google.com/p/macvim/">MacVim</a> and not just raw Vim. At first you should let yourself use whatever crutches you need--mouse, arrow keys, Cmd-S--to help you feel like you can still do your work. I agree with Yehuda that tool dogmatism is going to be counterproductive if it makes the beginning of the learning process so painful that you give up.</p>
<p>And I still use the arrow keys, and don't really buy the argument that it's ever important to unlearn those.</p>
<h3>Janus</h3>
<p>As with Emacs, a lot of the power of Vim is in knowing which plugins & customizations to choose. Yehuda and Carl's <a href="https://github.com/carlhuda/janus">Janus</a> project is a pretty great place to start for this. I'd install it, and skim the README so you can at least know what sorts of features you're adding, even though you won't use them all for some time.</p>
<h3><cite>vim:essentials</cite></h3>
<p><cite>Practical Vim</cite> is great for reading when you're on the subway or whatever, but you'll need something more boiled-down for day-to-day use. For a while I had this super-short <a href="http://www.stanford.edu/~jacobm/vim.html"><cite>vim:essentials</cite> page</a> open all the time.</p>
<h2>Extra credit: One intermediate step</h2>
<p>After I got minimally familiar with Normal mode, I started hating the way that I would enter Insert mode, switch to my browser to look up something, and switch back to Vim forgetting which mode I was in. I entered the letter <code>i</code> into my code a lot.</p>
<p>I suspect many Vim users just get used to holding that state in their head, or never leaving the Vim window without hitting Esc first, but I decided to simply install a timeout which pulls out of Insert mode after 4 seconds of inactivity:</p>
<pre><code>au CursorHoldI * stopinsert
</code></pre>
<p>As explained in <cite>Practical Vim</cite>, the best way to think of Insert mode is as a precise, transactional operation: Enter Insert mode, edit text, exit Insert mode. The timeout helped me get into that mindset quickly, and live in Normal mode, which is the place to see most of the gains from Vim.</p>
<p>This is an intermediate step, and you shouldn't try it right away. If you're a beginner you're probably not going to benefit from being in Normal mode all the time--if anything the frustration would be likely to make you give up on it. But once Vim starts feeling less disorienting, and you're ready to really learn what <cite>Practical Vim</cite> has been telling you, I'd give this a try.</p>
<p>Hope that's helpful. And I hope that after a month or two of this, you become as smug and self-assured about your text editor as I am today.</p>
2012-11-25T16:48:12Zurn:uuid:e3803a9d-5fd8-485c-6188-c2ce5e6ce688The law of constant testing hassle<p>Over time, technological progress makes it easier to write automated tests for familiar forms of technology.</p>
<p>Meanwhile, economic progress forces you to spend more time working with unfamiliar forms of technology.</p>
<p>Thus, the amount of hassle that automated testing causes you is constant.</p><p>Over time, technological progress makes it easier to write automated tests for familiar forms of technology.</p>
<p>Meanwhile, economic progress forces you to spend more time working with unfamiliar forms of technology.</p>
<p>Thus, the amount of hassle that automated testing causes you is constant.</p>2012-10-09T23:28:59Zurn:uuid:751904b8-3da0-6f61-13bf-96faca220abd