tag:blogger.com,1999:blog-68886587499956171332024-03-13T19:09:39.159-07:00Wee will rock you - Software Development TalesWeehttp://www.blogger.com/profile/04042457149790214773noreply@blogger.comBlogger15125tag:blogger.com,1999:blog-6888658749995617133.post-17013502176895730622014-04-04T14:11:00.005-07:002014-04-04T14:11:55.392-07:00Locate deleted files in git<h2>
Problem</h2>
You want to undelete the file you removed from git long ago but don't remember the path or the actual file name.<br />
<br />
<h2>
Solution</h2>
Use git log to search for deleted file. Then use another git log command to find all checkins related to this file.<br />
<br />
<h2>
Example</h2>
<span style="font-family: Courier New, Courier, monospace;">git log --diff-filter=D --summary | grep delete >/tmp/out.txt</span><br />
<br />
Search for the full paths of the files you want to recover from /tmp/out.txt<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">git log --all -- path/to/file</span><br />
<br />
It will show you all commits that touched the file.Weehttp://www.blogger.com/profile/04042457149790214773noreply@blogger.com0tag:blogger.com,1999:blog-6888658749995617133.post-48751798535193164342014-02-21T08:30:00.004-08:002014-02-21T08:38:02.004-08:00Preventing UIWebView to detect & style phone numbers in a web page<b>Problem: iOS7's UIWebView incorrectly detects and turned numbers into links</b><br />
<br />
Our app shows webpage contents in UIWebViews and after we switched to XCode5/iOS7SDK, we started seeing some numbers in webpages turning into clickable links that pointed to invalid telephone numbers. <br />
<br />
After looking inside the HTML body, I found that iOS7 UIWebView injected an <a href="https://www.blogger.com/blogger.g?blogID=6888658749995617133"> tag around the number inside the </a><br />
<div>
<a href="https://www.blogger.com/blogger.g?blogID=6888658749995617133">tag.</a></div>
<a href="https://www.blogger.com/blogger.g?blogID=6888658749995617133">
</a>
<br />
Mobile Safari<br />
<div class="coordinate" style="left: 0.5px; position: absolute; top: 130px;"><br />
0</div><br />
<div class="coordinate" style="left: 25px; position: absolute; top: 130px;"><br />
5</div>
<br />
<div>
...</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="http://3.bp.blogspot.com/-YiItPhTcFpI/Uwd9WxplmjI/AAAAAAAALWA/7grNJbwAtl8/s1600/Screenshot+2014-02-21+10.22.23.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-YiItPhTcFpI/Uwd9WxplmjI/AAAAAAAALWA/7grNJbwAtl8/s1600/Screenshot+2014-02-21+10.22.23.png" /></a></div>
<br />
<div>
<br /></div>
<div>
<br /></div>
<div>
<div>
UIWebView</div>
<div>
<div class="coordinate" style="left: 25.5px; position: absolute; top: 130px;"><br />
<a href="tel:0510152025" x-apple-data-detectors-result="14" x-apple-data-detectors-type="telephone" x-apple-data-detectors="true">5</a></div></div>
</div>
<div>
...</div>
<div>
<a href="http://2.bp.blogspot.com/-XWHCGhEv26w/Uwd9YoQxWSI/AAAAAAAALWI/hbI_2rMxbwE/s1600/Screenshot+2014-02-21+10.22.29.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-XWHCGhEv26w/Uwd9YoQxWSI/AAAAAAAALWI/hbI_2rMxbwE/s1600/Screenshot+2014-02-21+10.22.29.png" /></a></div>
<br />
<br />
It looks like iOS7 UIWebView tries to be smart about data format detection and saw that there were a series of numbers that it thought could be a telephone number. However, in this case it's not. <br />
<br />
<b>Solution: Disable the data format detection</b><br />
<br />
There are 2 ways to fix this.<br />
<br />
<br />
<ol>
<li>If you own the web page and can modify the HTML, add this meta tag to disable telephone number detection.<br /><meta name = "format-detection" content = "telephone=no"><br /></li>
<li>If you don't have access to the web page, you can programmatically disable the detection from UIWebView instance.<br /><br />self.webView.dataDetectorTypes = UIDataDetectorTypeNone;</li>
</ol>
<div>
<br /></div>
<br />
The UIDataDetectorTypeNone will not detect any telephone number, link, address, calendar event. If you want UIWebView to try detect some of these, you may have to use bit-or UIDataDetectorTypes enum values defined in UIKit's UIDataDetectors.h.<br />
<br />Weehttp://www.blogger.com/profile/04042457149790214773noreply@blogger.com1tag:blogger.com,1999:blog-6888658749995617133.post-84294717571926309992014-02-13T14:46:00.001-08:002014-02-21T12:43:31.634-08:00[iOS] Fix for GoogleAnalytics 3.0.3a referencing ASIdentifierManager's advertisingIdentifer<b>Updated</b>: Google updated the GA library to <a href="https://developers.google.com/analytics/devguides/collection/ios/resources">3.0.3c</a> and it no longer refers to ASIdentifierManager in AdSupport.framework. This allows apps to continue using -ObjC without -force_load.<br />
<br />
Since early Feb 2014, Apple started rejecting app submissions that linked to AdSupport.framework but did not display advertising (see Program License Agreement at the end of this blog) to prevent potential abuse of this unique identifier. See <a href="http://techcrunch.com/2014/02/03/apples-latest-crackdown-apps-pulling-the-advertising-identifier-but-not-showing-ads-are-being-rejected-from-app-store/">news</a> from TechCrunch.<br />
<br />
The problem with this is that many of analytics tools use this identifier to track app activities so any apps that included Google Analytics (up to 3.0.3), TestFlight (up to 2.1.x), Flurry and others will be rejected even though the apps themselves do not use this framework.<br />
<br />
Most of the analytics vendors quickly updated their libraries to remove references to the ASIdentifierManager's advertisingIdentifier. So upgrade to the latest version would solve the problem. <br />
However, as of now, GoogleAnalytics' latest version (<a href="https://developers.google.com/analytics/devguides/collection/ios/v3/">3.0.3a</a>) requires a linker flag that would break many apps. <br />
<br />
<blockquote class="tr_bq">
Important: If you are currently using the Google Analytics Services SDK but are not using Google Tag Manager, we recommend using the -force_load library_name linker flag instead of the -ObjC flag. The -ObjC flag causes all libraries not to be dead-stripped; by using -force_load library_name to selectively turn off dead-stripping only for particular object files, Google Tag Manager and other unnecessary libraries will be dead-stripped, which may significantly reduce the size of your binary.</blockquote>
If you don't use GTM in GA, this instruction will allow you to remove AdSupport.framework from the Link Binary With Libraries section. Removing -ObjC and replacing with the -force_load on GA static library will no longer require AdSupport.framework. This is problematic for apps that use other static libraries and it's a default flag if you use CocoaPods. Removing the -ObjC will prevent the linker to load all symbols from static libraries. If other libraries have category extensions, they will not be linked into the binary and the app will crash from runtime exceptions.<br />
<br />
<h2>
Short term fix</h2>
Until Google provides us with a better fix that works with -ObjC, your only option is to add -force_load to all static libraries that have category extensions. In my app, there are 3, RegexKitLite, CorePlot and GA.<br />
<br />
If you use CocoaPods, make sure that your Podfile contains the latest GA version.<br />
<br />
<blockquote class="tr_bq">
pod 'GoogleAnalytics-iOS-SDK', '3.0.3a'</blockquote>
<div>
Then run 'pod install'.<br />
<br />
If you do not use CocoaPods, it may be a good time to use one. :) Or you can manually download the latest Google Analytics iOS SDK from the <a href="https://developers.google.com/analytics/devguides/collection/ios/resources">website</a>.</div>
<div>
<br /></div>
<div>
After this, launch Xcode and go to your project target in TARGETS section. You will do two things.</div>
<div>
<br /></div>
<div>
1. Ensure that your rejected app has references to advertisingIdentifier by unpacking the ipa into a Payload folder.</div>
<div>
1.1 Use a grep: "grep -r advertisingIdentifier ." Notice the dot at the end of the command. It will show a match in the </div>
<div>
1.2 Use nm tool: "nm your_app.app/your_app >output.txt" Replace your_app with your actual ipa file name. Then search for advertisingIdentifier in output.txt.</div>
<div>
<br /></div>
<div>
2. Remove AdSupport.framework from Link Binary With Libraries in the Build Phases tab.</div>
<div>
<br /></div>
<div>
3. Go to Build Settings tab and search for Other Linker Flags. Double click the target value and remove -ObjC and replace it with:</div>
<div>
<br /></div>
<div>
-Wl,-force_load,$(BUILT_PRODUCTS_DIR)/libPods-GoogleAnalytics-iOS-SDK.a</div>
<div>
<br /></div>
<div>
Note: If you do not use CocoaPods, you will have to find the GA library .a file in the BUILT_PRODUCTS_DIR which is normally in your ~/Library/Developer/Xcode/DerivedData/your_project_name/Build/Products.</div>
<div>
<br /></div>
<div>
4. The link step should now pass but the app may crash if other 3rd party static libraries use category extensions. Test thoroughly and if the app crashes, note the 3rd party class/method that causes the crash, find which library contains the class/method. Then add -force_load for those libraries. In my app, they are CorePlot and RegexKitLite so I added two more linker flag.</div>
<div>
<br /></div>
<div>
-Wl,-force_load,$(BUILT_PRODUCTS_DIR)/libPods-RegexKitLite.a</div>
<div>
-Wl,-force_load,$(BUILT_PRODUCTS_DIR)/libPods-CorePlot.a</div>
<div>
<br /></div>
5. Rebuild, re-test. Once you are sure that the app runs fine. You can build the final ipa file and search for any references to advertisingIdentifer by unpacking the ipa file and repeat step 0.<br />
<br />
grep -r advertisingIdentifier .<br />
<br />
If you don't see any match, your binary is clean and ready for a resubmission. Good luck!<br />
<br />
<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">PLA 3.3.12</span><br />
<span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;">We found your app uses the iOS Advertising Identifier but does not include ad functionality. This does not comply with the terms of the iOS Developer Program License Agreement, as required by the App Store Review Guidelines.</span><br />
<span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;">Specifically, section 3.3.12 of the iOS Developer Program License Agreement states:</span><br />
<span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;">"You and Your Applications (and any third party with whom you have contracted to serve advertising) may use the Advertising Identifier, and any information obtained through the use of the Advertising Identifier, only for the purpose of serving advertising. If a user resets the Advertising Identifier, then You agree not to combine, correlate, link or otherwise associate, either directly or indirectly, the prior Advertising Identifier and any derived information with the reset Advertising Identifier."</span><br />
<span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;">Please check your code - including any third-party libraries - to remove any instances of:</span><br />
<span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;">class: ASIdentifierManager</span><br />
<span style="font-family: Courier New, Courier, monospace;">selector: advertisingIdentifier</span><br />
<span style="font-family: Courier New, Courier, monospace;">framework: AdSupport.framework</span><br />
<span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;">If you are planning to incorporate ads in a future version, please remove the Advertising Identifier from your app until you have included ad functionality.</span><br />
<br />
<br />Weehttp://www.blogger.com/profile/04042457149790214773noreply@blogger.com0tag:blogger.com,1999:blog-6888658749995617133.post-25646830248589582852014-02-13T12:57:00.002-08:002014-02-13T12:57:37.136-08:00[iOS] How to determine whether a static library (.a) has 64-bit sliceIf you build a 64-bit version of your iOS app, be sure that any 3rd party static libraries the app link must have 64-bit slice. Otherwise your app will still require iOS 32-bit runtime support.<br />
<br />
How to determine whether a static library (.a) has 64-bit slice? It's actually easy. Just use lipo -info command but the trick is to run it under the xcrun as follows:<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">$ xcrun -sdk iphoneos lipo -info Pods/GoogleAnalytics-iOS-SDK/libGoogleAnalyticsServices.a</span><br />
<span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;">Architectures in the fat file: Pods/GoogleAnalytics-iOS-SDK/libGoogleAnalyticsServices.a are: armv7 armv7s i386 x86_64 arm64</span><br />
<br />
If the result contains arm64, the static library was built with a 64-bit version (A7 chip in iPhone 5S+ and iPad Air+). In addition, i386 and x86_64 are slices for simulator while arm7 and arm7s are for up to iPhone 4S's chips and iPhone5's A6 chip.Weehttp://www.blogger.com/profile/04042457149790214773noreply@blogger.com0tag:blogger.com,1999:blog-6888658749995617133.post-73674170524198889392014-01-13T16:07:00.001-08:002014-01-13T16:19:17.917-08:00Migrate code to support 64-bit in iOS7As Apple requires all app submissions to use XCode5 with iOS7 SDK after February 1st, 2014, it becomes important to make sure that your codebase is compatible with iOS7 SDK, especially the 64-bit support since the primitive types change from 32 to 64 bits.<br />
<br />
This means that NSInteger/NSUInteger are <b>long</b> instead of int. CGFloat is <b>double</b>, not float.<br />
<br />
There are many possible compiler warnings and here are the resolutions.<br />
<h4>
Case 1: Implicit conversion loses integer precision: 'long' to 'int'</h4>
Example:<br />
<div>
<br /></div>
<div>
NSInteger row = ...;</div>
<br />
<div class="p1">
<span class="s1">int</span> rowWithLineCount = (rows - <span class="s2">1</span>) / <span class="s2">5</span> ; <span style="color: #333333; font-size: 14px; line-height: 20px; white-space: pre-wrap;">// compiler warning</span></div>
<div>
<br /></div>
Solution:<br />
<div class="p1">
<span class="s1">NSInteger</span> rowWithLineCount = (rows - <span class="s2">1</span>) / <span class="s2">5</span> ;</div>
<div>
<br /></div>
<div>
Note: Use NSUInteger if the right hand expression is evaluated to unsigned integer.</div>
<div>
<h4>
Case 2: Implicit conversion loses integer precision: 'NSInteger' (aka 'long') to SomeEnum'</h4>
Example:<br />
<br />
typedef enum {<br />
...<br />
} SomeEnum;<br />
<br />
NSInteger someValue;<br />
SomeEnum somefield = someValue; <span style="color: #333333; font-size: 14px; line-height: 20px; white-space: pre-wrap;">// compiler warning</span>
<br />
<div class="p1">
<br /></div>
<div class="p1">
Solution: Use NS_ENUM</div>
<div class="p1">
<br /></div>
typedef NS_ENUM(NSInteger, SomeEnum) {<br />
...<br />
};<br />
<div class="p1">
<br /></div>
<!------></div>
<h4>
Case 3: Implicit conversion loses integer precision: 'SomeEnum' (aka 'enum SomeEnum') to int'</h4>
Example:<br />
<br />
typedef NS_OPTIONS(NSUInteger, SomeEnum) {<br />
...<br />
}<br />
SomeEnum somefield;<br />
<br />
<div class="p1">
<span class="s1">NSNumber</span> *number = [<span class="s1">NSNumber</span> <span class="s1">numberWithInt</span>:<span class="s2">self</span>.<span class="s3">someField</span>]; <span style="color: #333333; font-size: 14px; line-height: 20px; white-space: pre-wrap;">// compiler warning</span><br />
<div class="p1">
<br /></div>
<div class="p1">
Solution:</div>
<div class="p1">
<br /></div>
<div class="p1">
<span class="s1">NSNumber</span> *number = [<span class="s1">NSNumber</span> <span class="s1">numberWithLong</span>:<span class="s2">self</span>.<span class="s3">someField</span>];</div>
<div>
<br /></div>
<h4>
Case 4: Values of type 'NSInteger' should not be used as format arguments; add an explicit cast to 'long' instead</h4>
Example:<br />
<br />
NSInteger statusCode = ...;<br />
<br />
<div class="p1">
<span class="s1">NSLog</span><span class="s2">(</span>@"Failed response code:%d"<span class="s2">, </span><span class="s1">statusCode</span><span class="s2">); </span><span style="color: #333333; font-size: 14px; line-height: 20px; white-space: pre-wrap;">// compiler warning</span></div>
<br />
Solution:<br />
<div class="p1">
<span class="s1"><br /></span></div>
<div class="p1">
<span class="s1">NSLog</span><span class="s2">(</span>@"Failed response code:%ld"<span class="s2">, (long)</span><span class="s1">statusCode</span><span class="s2">);</span></div>
<div class="p1">
<span class="s2"><br /></span></div>
<div class="p1">
<span class="s2">Note: This applies to %lu string pattern and (unsigned long) casting for NSUInteger as well.</span></div>
<div class="p1">
<br /></div>
<h4>
Case 5: Replace abs((NSInteger)value) with labs()</h4>
<div class="p1">
<br /></div>
<div class="p1">
<span class="s2"><br /></span></div>
<div>
<span class="s2"><br /></span></div>
<!------></div>
Weehttp://www.blogger.com/profile/04042457149790214773noreply@blogger.com0tag:blogger.com,1999:blog-6888658749995617133.post-21771853436867313952013-08-21T14:00:00.002-07:002013-08-21T14:43:44.863-07:00Submitting iOS App in Application Loader with Firewall RestrictionsIf you submit an iOS ipa file via Apple's Application Loader and see the activity gets stuck at the TCP/UDP connectivity testing step. It may be because your current network is blocked by firewall. An easy solution but may be hard to find is to change the Delivery mechanism option in the Application Loader's Preferences.
<br />
<br />
Uncheck 'Signiant' and 'Aspera' will force the loader to use DAV SSL port 443. It worked for me.
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://3.bp.blogspot.com/-u_ACYoMIs8E/UhUpgJ6gjbI/AAAAAAAAB1M/VQKCYj21nBg/s1600/Screen+Shot+2013-08-21+at+3.35.30+PM.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-u_ACYoMIs8E/UhUpgJ6gjbI/AAAAAAAAB1M/VQKCYj21nBg/s320/Screen+Shot+2013-08-21+at+3.35.30+PM.png" /></a></div>
Weehttp://www.blogger.com/profile/04042457149790214773noreply@blogger.com11tag:blogger.com,1999:blog-6888658749995617133.post-21695130142125134682013-05-06T15:19:00.000-07:002013-05-06T15:19:02.299-07:00Use HTTP Options to query whether a RESTful resource can be createdMy colleagues and I ran into a problem about how to check whether a RESTful resource can be created before sending the HTTP POST to create the resource. The use case is that there is a business requirement setting a limit on how many resources can be stored for a user.<br />
<br />
For example, a bank may limit the number of accounts a customer can open. It would be a bad user experience if we allow a user to type information about a new account and hit submit to see an error that the maximum number of accounts has been reached.<br />
<br />
<blockquote class="tr_bq">
Request: HTTP POST /customers/xxx/accounts<br />Response: HTTP 400 Bad Request with 'Maximum number of accounts has been reached' error message. </blockquote>
<br />
We decided to make a request to check whether a new account resource can be crated before we ask users to enter information. We explored a couple of options. One is to define a new endpoint to query whether a new resource can be created.<br />
<br />
<blockquote class="tr_bq">
Request: HTTP GET /customers/xxx/accounts/can_create<br />Response: 200 OK or 400 Bad Request
</blockquote>
<br />
However, this seems to introduce another verb to the resource. One of my colleagues referred me to <a href="http://zacstewart.com/2012/04/14/http-options-method.html">HTTP Options</a> and it works perfectly for this use case.<br />
<br />
HTTP Options checks whether what HTTP verbs we can operate over the resource URI. It is specified in the 'Allow' response header. So if we see that we can POST to the resource, it means that we can create the resource.<br />
<br />
<blockquote class="tr_bq">
Request: HTTP OPTIONS /customers/xxx/accounts<br />Response: 200 OK<br /> Allow: HEAD,GET,POST,PUT,DELETE,OPTIONS</blockquote>
Using HTTP Options allows us to query possible actions we can do with resources without requiring a new end point or payload. I wish it was mentioned more often in other RESTful resources.Weehttp://www.blogger.com/profile/04042457149790214773noreply@blogger.com0tag:blogger.com,1999:blog-6888658749995617133.post-11181339249815855192013-03-28T12:37:00.003-07:002013-03-28T12:37:50.393-07:00Use curl to check website gzip encoding supportIf you want to check whether a website has gzip encoding support, you can use curl with Accept-Encoding: gzip.deflate http header. Use size_download to check the size like this.
<blockquote>$ curl http://www.yahoo.com --silent -H "Accept-Encoding: gzip,deflate" --write-out "size_download=%{size_download}\n" --output /dev/null
size_download=50613</blockquote>
And compare without the Accept-Encoding:
<blockquote>$ curl http://www.yahoo.com --silent --write-out "size_download=%{size_download}\n" --output /dev/null
size_download=245298</blockquote>
If you see the same result with and without Accept-Encoding, the web server does not support gzip.Weehttp://www.blogger.com/profile/04042457149790214773noreply@blogger.com0tag:blogger.com,1999:blog-6888658749995617133.post-47329430556101190652009-08-12T17:32:00.001-07:002009-08-12T18:55:32.713-07:00Rails Dump Format Error MysteryOne day your rails app stopped working and the only clue you saw from console log was the 'Dump format error' exception. It started when users logged in and visit a page and received the dreaded HTTP 500 internal error. After it happened, they could no longer browse to any page of your rails app, what should you do?<br /><br />The first I did was trying to find out what caused this problem, once this exception was thrown, I couldn't reload the page or visit path of the app as it kept showing the 500 error. I knew it must have something to do with session. So I closed the web browser and reopened it, it worked but as long as I visited the problematic page, the same problem happened again.<br /><br />What does the dump format error mean? After a quick google, I found out that it was thrown when there was a problem with marshalling/unmarshalling objects. Who did that? Why did rails send objects over the wire? Then I looked at the controller's action and found out this line:<br /><br />flash[:object_with_error] = user<br /><br />So someone put an object to the flash which was in turn kept in a cookie session data store. Since the cookie is limited to 4K bytes, storing I suspect that the cookie was full and the data couldn't be fully marshalled. That caused the dump format error.<br /><br />The flash hash usually contains a message to be displayed in a view. Why did someone put a binary object (in this case, it's an activerecord model) in it? I found out later that it was because the action took a form, verify the data, and redirect to a different action. If there were validation errors, it stored the activerecord with errors in the flash so that the redirecting action would be able to fill out the form with errors.<br /><br />However, we should avoid storing binary objects in flash (and in cookies) since the object graph can easily exceed the cookie size limit. Instead, if we really need to pass temporary data between sessions, pass only string values. In this case, pass only the form data and error messages.<br /><br />Problem solved, lesson learned. Case closed.Weehttp://www.blogger.com/profile/04042457149790214773noreply@blogger.com3tag:blogger.com,1999:blog-6888658749995617133.post-73675247533416725262009-07-25T14:50:00.000-07:002009-07-25T15:43:52.271-07:00Sort your flickr albums in Ruby with Flickr_fuI have tons of photos in <a href="http://www.flickr.com/">flickr</a> organized by photo sets. Each photo set name has a date prefix like '20090725 Chicago downtown'. I use the prefix to sort album folders in my local drive. However when I upload them to flickr, if I don't upload them in the same order, I will have to use Flickr's <a href="http://www.flickr.com/photos/organize/">Organize</a> to manually drag and drop ones to the right place. It is tedious and there is no quick and easy way to automate it.<br /><br />I know that flickr provides APIs that allow developers to manage photos programatially in pratically any modern language. Since I am a fan of Ruby, I chose a ruby libray, <a href="http://github.com/commonthread/flickr_fu/tree/master">flickr_fu</a> from <a href="http://blog.commonthread.com/">commontread</a>.<br /><br />So I take an hour to set up and write a ruby class to re-order my 1000+ photosets. The class name is FlickrPhotoSetSorter. Here is the step that I did and you can follow, assuming you have ruby 1.8+ installed.<br /><br />1. Install flickr_fu<br /><pre>sudo gem install flickr-fu<br /><br /></pre>You may have to install a required gem; xml-magic separately.<br /><br />2. Create flickr.yml to store my flickr API key and secret key. Replace "your key" and "your secret" with the yours. You may obtain them from <a href="http://www.flickr.com/services/api/keys/">Flickr API keys</a>. The token_cache.yml is a file flickr_fu uses to store your session token (flickr calls it 'frob').<br /><pre>## YAML Template.<br />--- !map:Hash<br />key: "your key"<br />secret: "your secret"<br />token_cache: "token_cache.yml"</pre>3. Authorize your program to read/write your albums. I defined a method, authorize, in my FlickrPhotoSetSorter class.<br /><pre><br />require 'flickr_fu'<br /><br />class FlickrPhotoSetSorter<br /> def initialize<br /> @flickr = Flickr.new('flickr.yml')<br /> end<br /><br /> def authorize<br /> puts "visit the following url, then click <enter> once you have authorized:"<br /> # request write permissions<br /> puts @flickr.auth.url(:write)<br /> gets<br /> flickr.auth.cache_token<br /> end<br />end<br /><br />FlickrPhotoSetSorter.new.authorize<br /></pre>The method generates and displays a URL to flickr.com that you will have to visit in order to ensure that you have rights to your account. Here is an example of the result. I replaced all ids with a dummy 12345.<br /><pre><br />visit the following url, then click <enter> once you have authorize<br />http://flickr.com/services/auth/?frob=12345&auth_token=12345&api_key=12345&perms=write&api_sig=12345<br /></pre>4. Once I authorized the application, I can start loading a list of my photosets using flickr_fu's Photosets class.<br /><br /><pre> def photosets_list<br /> Flickr::Photosets.new(@flickr).get_list<br /> end</pre>5. Since I have a large collection of my photosets, I don't want to load them from flickr.com every time I run the script. So I chose to store the result into a file that I can load later. I created a file, list.txt and store each photoset with id, number of photos, title and description separated by a pipe symbol '|'<br /><pre> def store_photoset_list(file_name = 'list.txt')<br /> File.open(file_name, "w") do |file|<br /> photosets_list.map do |photoset|<br /> file.puts("#{photoset.id}|#{photoset.num_photos}|#{photoset.title}|#{photoset.description}")<br /> end<br /> end<br /> end<br /><br />FlickrPhotoSetSorter.new.store_photoset_list</pre>6. To load it back, firstly I created a method to read a line and returns a hash that contain the information from a photoset.<br /><pre> def photoset_from(line)<br /> id, num_photos, title, description = line.split('|')<br /> return nil unless id.to_i > 0<br /> { :id => id,<br /> :num_photos => num_photos,<br /> :title => title,<br /> :description => description }<br /> end</pre><br /><br />Then, I defined a method to load the file and build an photoset array.<br /><br /><pre> def load_photoset_list(file_name = 'list.txt')<br /> return @photosets_list if @photosets_list<br /> @photosets_list = []<br /> lines = IO.readlines(file_name)<br /> lines.each do |line|<br /> @photosets_list << photoset_from(line)<br /> end<br /> @photosets_list.compact<br /> end</pre>7. Since flickr_fu does not have a method to sort or update the order of photosets, I have to do it by myself. It is super easy since I already have everything I need. Flickr has an API to order photo sets, <a href="http://www.flickr.com/services/api/flickr.photosets.orderSets.html">flickr.photosets.orderSets</a> and it requires a list of photoset ids separated by commas.<br /><br />So I sort the photoset by title descending and collect ids and join them with ','. At the end, I use flickr_fu's send_request and pass the API name with the list of photoset ids using HTTP post.<br /><br /><pre> def order_photosets_by_title<br /> ordered_list = load_photoset_list.sort { |a,b| (b[:title] || "") <=> (a[:title] || "") }<br /> ordered_ids = ordered_list.map { |set| set[:id] }.join(',')<br /> @flickr.send_request('flickr.photosets.orderSets', { :photoset_ids => ordered_ids }, :post)<br /> end</pre>8. To run the script, just use:<br /><br /><pre>FlickrPhotoSetSorter.new.order_photosets_by_title</pre><br /><br />That's it. Now my photoset list are sorted by title descending order. I can upload older photosets, rerun the script and have them sort again and never worry that my photosets will be out of order again.<br /><br />Here is the entire source code of the FlickrPhotoSetSorter class.<br /><pre>require 'flickr_fu'<br /><br />class FlickrPhotoSetSorter<br /> def initialize<br /> @flickr = Flickr.new('flickr.yml')<br /> end<br /><br /> def authorize<br /> puts "visit the following url, then click <enter> once you have authorized:"<br /> # request write permissions<br /> puts @flickr.auth.url(:write)<br /> gets<br /> flickr.auth.cache_token<br /> end<br /><br /> def photosets_list<br /> Flickr::Photosets.new(@flickr).get_list<br /> end<br /><br /> def store_photoset_list(file_name = 'list.txt')<br /> File.open(file_name, "w") do |file|<br /> photosets_list.map do |photoset|<br /> file.puts("#{photoset.id}|#{photoset.num_photos}|#{photoset.title}|#{photoset.description}")<br /> end<br /> end<br /> end<br /><br /> def load_photoset_list(file_name = 'list.txt')<br /> return @photosets_list if @photosets_list<br /> @photosets_list = []<br /> lines = IO.readlines(file_name)<br /> lines.each do |line|<br /> @photosets_list << photoset_from(line)<br /> end<br /> @photosets_list.compact<br /> end<br /><br /> def photoset_from(line)<br /> id, num_photos, title, description = line.split('|')<br /> return nil unless id.to_i > 0<br /> { :id => id,<br /> :num_photos => num_photos,<br /> :title => title,<br /> :description => description }<br /> end<br /><br /> def order_photosets_by_title<br /> ordered_list = load_photoset_list.sort { |a,b| (b[:title] || "") <=> (a[:title] || "") }<br /> ordered_ids = ordered_list.map { |set| set[:id] }.join(',')<br /> @flickr.send_request('flickr.photosets.orderSets', { :photoset_ids => ordered_ids }, :post)<br /> end<br /><br />end</pre><br /><br />Feel free to use the code for you needs. If you want to reorder with different criteria, update the sort block in the order_photosets_by_title method. Rename the method as appropriate.<br /><br />Enjoy.Weehttp://www.blogger.com/profile/04042457149790214773noreply@blogger.com2tag:blogger.com,1999:blog-6888658749995617133.post-3239526953553049712009-03-14T21:35:00.000-07:002009-03-14T22:38:29.367-07:00Small quiz solutions in 12 languagesLast week I posted a short programming quiz in my Thai development community at narisa.com and asked members to come up with solutions in many languages as possible. We finally got 12 solutions implemented in C, C#, Clojure, Erlang, Groovy, Haskell, Java, PHP, Python, Ruby, Scala, Visual Basic with LINQ. Some languages have more than one implementations.<br /><br />The quiz is about parsing and sorting an input text file. There are records; one per line. Each record has many fields delimited by a pipe symbol '|'. The first field is a record number. We want to sort all the records by this number and display the sorted result one record per line. Each line will display the record number followed by sorted fields. (The original problem didn't have number sort and first column exclusion constraints)<br /><br />For example, an input.txt file:<br /><blockquote>13|hello|world|bangkok<br />4|1monkey|ant|dog|cow|cat<br />2|pink|yellow|red|magenta<br />1|earth|sun|jupiter|pluto<br /></blockquote><br /><br />The expected result:<br /><blockquote>1 earth jupiter pluto sun<br />2 magenta pink red yellow<br />4 1monkey ant cat cow dog <br />13 bangkok hello world<br /></blockquote><br /><br />Notice that the record number 13 is after record number 4. So we need to compare the number by value, not by string literal. Each record has to exclude the field number and sort the rest alphabetically.<br /><br />Here are solutions. Your truly provided 2 solutions in Ruby and Scala. You will see that Thai development community is quite vibrant from a variety of language choices. Look like Groovy is among the most popular one. You will see that scripting languages are good for this kind of problem in terms of compactness and declarative style. Read the original post in Thai <a href="http://www.narisa.com/forums/index.php?showtopic=27192&st=0">here</a>.<br /><br /><span style="font-weight:bold;">C (gcc)</span> by iWat<br /><code>#include <stdio.h><br />#include <stdlib.h><br />#include <string.h><br /><br />struct record<br />{<br />int number;<br /><br />int num_fields;<br />char **fields;<br /><br />char *buffer;<br />};<br /><br />static int record_number_comparator(struct record *first, struct record *second)<br />{<br />return first->number - second->number;<br />}<br /><br />static void bubble_sort(void **ar_p_struct, int count, int (*comparator)(void *, void *))<br />{<br />int i;<br />int j;<br /><br />for (i = 0; i < count - 1; ++i)<br />{<br />for (j = 0; j < count - 1 - i; ++j)<br />{<br />if (comparator(ar_p_struct[j+1], ar_p_struct[j]) < 0) {<br />void *p_tmp;<br /><br />p_tmp = ar_p_struct[j];<br />ar_p_struct[j] = ar_p_struct[j + 1];<br />ar_p_struct[j + 1] = p_tmp;<br />}<br />}<br />}<br />}<br /><br />static void print_record(struct record *my_record)<br />{<br />int i;<br /><br />for (i = 0; i < my_record->num_fields; ++i)<br />{<br />printf("%s ", my_record->fields[i]);<br />}<br /><br />printf("\n");<br />}<br /><br />static void parse_records(FILE *fp, struct record ***p_ar_p_records, int *p_num_records)<br />{<br />char buffer[256];<br /><br />*p_num_records = 0;<br />*p_ar_p_records = NULL;<br /><br />while (fgets(buffer, 256, fp) != NULL)<br />{<br />struct record *current_record = malloc(sizeof(struct record));<br /><br />current_record->num_fields = 0;<br />current_record->fields = malloc(10 * sizeof(char *));<br />current_record->buffer = strdup(buffer);<br /><br />char *start = current_record->buffer;<br />char *pointer = start;<br /><br />while(1)<br />{<br />if (*pointer == '|' || *pointer == '\0')<br />{<br />current_record->fields[current_record->num_fields] = start;<br /><br />start = pointer + 1;<br /><br />if (current_record->num_fields == 0)<br />current_record->number = atoi(current_record->fields[0]);<br /><br />++current_record->num_fields;<br /><br />if (*pointer == '\0')<br />{<br />if (*(pointer - 1) == '\n')<br />*(pointer - 1) = '\0';<br />break;<br />}<br /><br />*pointer = '\0';<br />}<br /><br />++pointer;<br />}<br /><br />++(*p_num_records);<br />*p_ar_p_records = (struct record **) realloc(*p_ar_p_records, sizeof(struct record *) * (*p_num_records));<br />(*p_ar_p_records)[(*p_num_records) - 1] = current_record;<br />}<br />}<br /><br />int main(int argc, char **argv)<br />{<br />int i;<br />int num_records;<br />struct record **ar_p_records;<br /><br />FILE *fp;<br /><br />if (argc != 2)<br />{<br />printf("Usage: %s <input file>\n", argv[0]);<br />return 1;<br />}<br /><br />fp = fopen(argv[1], "r");<br /><br />if (fp == NULL)<br />{<br />printf("error reading file: %s\n", argv[1]);<br />return 2;<br />}<br /><br />parse_records(fp, &ar_p_records, &num_records);<br /><br />printf("===== parse result\n");<br /><br />for (i = 0; i < num_records; ++i)<br />{<br />print_record(ar_p_records[i]);<br />}<br /><br />bubble_sort((void **) ar_p_records, num_records, (int(*)(void *, void *)) record_number_comparator);<br /><br />printf("\n===== sort result\n");<br /><br />for (i = 0; i < num_records; ++i)<br />{<br />bubble_sort(<br />(void **) (ar_p_records[i]->fields + 1),<br />ar_p_records[i]->num_fields - 1,<br />(int(*)(void *, void *)) strcmp);<br />print_record(ar_p_records[i]);<br />}<br /><br /><br />fclose(fp);<br />}</code><br /><br /><span style="font-weight:bold;">C# with LINQ</span> by bleak<br /><code>using System;<br />using System.Linq;<br /><br />class FunCode<br />{<br /> static void Main(string[] args)<br /> {<br /> string[] records = System.IO.File.ReadAllLines("input.txt");<br /> var rs = from r in records<br /> let fields = r.Split('|')<br /> orderby int.Parse(fields[0])<br /> select string.Concat(fields[0], " ", string.Join(" ", fields.Skip(1).OrderBy(x => x).ToArray()));<br /> foreach (var r in rs)<br /> Console.WriteLine(r);<br /> }<br />}</code><br /><br /><span style="font-weight:bold;">Clojure</span> by pphetra<br /><code>(defn outer-sort [xs]<br /> (sort-by #(first %) xs))<br /><br />(defn inner-sort [xs]<br /> (let [fst (first xs)<br /> rst (sort (rest xs))]<br /> (cons fst rst)))<br /><br />(defn solve [xs]<br /> (outer-sort (map inner-sort xs)))<br /><br />(defn read-file [file-name]<br /> (let [raw-lines (seq (.split (slurp file-name) "\n"))<br /> lines (map #(seq (.split % "\\|")) raw-lines)]<br /> lines))<br /><br />(defn pretty-print [xs]<br /> (doseq [line xs]<br /> (do (doseq [word line]<br /> (print word ""))<br /> (print "\n"))))<br /><br />;; ตัวอย่างการใช้<br />(pretty-print (solve (read-file "input.txt")))</code><br /><br /><span style="font-weight:bold;">Coldfusion</span> by iporsut<br /><code><cfset recordset = QueryNew("id, list" , "Integer, VarChar")><br /><cfset count = 1 /><br /><cfloop file="#Expandpath('/')#input.txt" index="line"><br /><cfscript><br /> line = listChangeDelims(line,",","|");<br /> id = listfirst(line);<br /> list = listsort(listrest(line) , "text", "asc");<br /> list = listChangeDelims(list," ",",");<br /><br /> QueryAddRow(recordset,1);<br /> QuerySetCell(recordset,"id",id,count);<br /> QuerySetCell(recordset,"list",list,count);<br /><br /> count++;<br /></cfscript><br /></cfloop><br /><br /><cfquery dbtype="query" name="qSortedRecord"><br /> select id,list<br /> from recordset<br /> order by id asc<br /></cfquery><br /><br /><cfoutput query="qSortedRecord"><br /> #qSortedRecord.id# #qSortedRecord.list# <br /><br /></cfoutput></code><br /><br /><span style="font-weight:bold;">Erlang</span> by pphetra<br /><code>-module(s1).<br />-export([solve/1]).<br /><br />-import(lists, [sort/2,sort/1]).<br />-import(string, [tokens/2, to_integer/1]).<br /><br />% เขาว่า read แบบ binary จะ performance ดีกว่า<br />readfile(FileName) -><br /> {ok, Binary} = file:read_file(FileName),<br /> tokens(erlang:binary_to_list(Binary), "\n").<br /><br />% แต่ละ line ที่ได้มาให้ split tokens ด้วย <br />parse(FileName) -><br /> [ tokens(Line, "|") || Line <- readfile(FileName)].<br /><br />% sort โดยใช้ Head Element และแปลงเป็น integer ก่อน sort <br />outer_sort(List) -><br /> sort(fun ([H1|_], [H2|_]) -> to_integer(H1) =< to_integer(H2) end, List).<br /> <br />solve(FileName) -><br /> outer_sort([ [H | sort(T)] || [H|T] <- parse(FileName)]).<br /><br />1> c(s1). <br />{ok,s1}<br />2> s1:solve('input.txt').<br /></code><br /><br /><span style="font-weight:bold;">Groovy</span> #1 by cblue<br /><code>def result=[:]<br />new File('input.txt').eachLine {<br /> def line = it.tokenize('|')<br /> result[line[0] as int] = line[1..-1].sort()<br />}<br />result.sort{ e1, e2 -> e1.key - e2.key }.each { k, v -><br /> println "$k ${v.join(' ')}"<br />}</code><br /><br /><span style="font-weight:bold;">Groovy</span> #2 by cblue<br /><code>def result = new File('input.txt').readLines().collect {<br /> def line = it.tokenize('|')<br /> [line.head() as int] + line.tail().sort() <br />}<br />result.sort { o1, o2 -> o1[0] <=> o2[0] }.each {<br /> println it.join(' ')<br />}</code><br /><br /><span style="font-weight:bold;">Groovy</span> #3 by xcaleber<br /><code><br />new File("input.txt").readLines()*.tokenize("|").sort{one, another -> one[0] <=> another[0]}.each{ println "${it[0]} ${it[1..-1].sort().join(' ')}" }</code><br /><br /><span style="font-weight:bold;">Haskell</span> #1 by iporsut<br /><code>import List<br /><br />compareFirst::[[Char]]->[[Char]]->Ordering<br />compareFirst a b | (read (head a)::Int) >= (read (head b)::Int) = GT<br /> | otherwise = LT<br /><br />transformPipeToSpace::[Char]->[Char]<br />transformPipeToSpace a = map transform a<br /> where<br /> transform b | b == '|' = ' '<br /> | otherwise = b<br />inputToWords::[Char]->[[[Char]]]<br />inputToWords input = map words (map transformPipeToSpace (lines input))<br /><br /><br />wordsToOutput::[[[Char]]]->[Char]<br />wordsToOutput word = foldl (++) "" (map unwordsNewline word)<br /> where<br /> unwordsNewline w = (unwords w)++['\n']<br /><br />sortRecords::[Char]->[[[Char]]]<br />sortRecords input = map sortWords (sortBy compareFirst (inputToWords input))<br /> where<br /> sortWords (x:xs) = x:(sort xs)<br />main = do<br /> input <- readFile "input.txt"<br /> putStrLn (wordsToOutput (sortRecords input))</code><br /><br /><span style="font-weight:bold;">Haskell</span> #2 by pphetra<br /><code>import List<br /><br />split delim s<br /> | [] <- rest = [token]<br /> | otherwise = token : split (delim) (tail rest)<br /> where (token,rest) = span (/= delim) s<br /><br />compareFirst a b | (read (head a)::Int) >= (read (head b)::Int) = GT<br /> | otherwise = LT<br /> <br />splitAndSort s = head xs : sort (tail xs)<br /> where xs = split '|' s<br /><br />main = do<br /> input <- readFile "s1.txt"<br /> putStrLn $ (unlines . map unwords . sortBy compareFirst . map splitAndSort . lines) input</code><br /><br /><span style="font-weight:bold;">Java</span> #1 by comx<br /><code>package javaapplication1;<br /><br />import java.io.*;<br />import java.util.*;<br /><br />public class Main {<br /> public static void main(String[] args) throws Exception {<br /> Reader r = new FileReader("c:/input.txt");<br /> BufferedReader br = new BufferedReader(r);<br /> SortedSet<LineItem> lineItemHolder = new TreeSet<LineItem>();<br /> while (true) {<br /> String line = br.readLine();<br /> if (line == null) {<br /> break;<br /> }<br /> lineItemHolder.add(new LineItem(line));<br /> }<br /> print(lineItemHolder);<br /> }<br /><br /> public static void print(Collection items) {<br /> for (Object object : items) {<br /> System.out.println(object);<br /> }<br /> }<br />}</code><br /><br /><span style="font-weight:bold;">Java</span> #2 by panther<br /><code>import java.io.*;<br />import java.util.*;<br /><br />class FooSort<br />{<br /> public static void main(String[] args)throws Exception<br /> { <br /> BufferedReader br = new BufferedReader( new FileReader("c:/input.txt") );<br /> Map<Integer, List<String>> tm = new TreeMap<Integer, List<String>>();<br /><br /> while( true ){<br /> <br /> String line = br.readLine();<br /> <br /> if( line == null ){<br /> break;<br /> }<br /> <br /> String[] record = line.split("[|]");<br /> List<String> list = Arrays.asList( record );<br /> <br /> Collections.sort( list );<br /> <br /> tm.put( Integer.parseInt( list.get( 0 ) ) , list ); <br /> }<br /> <br /> Iterator<Map.Entry<Integer, List<String>>> it =<br /> ((Set<Map.Entry<Integer, List<String>>>)tm.entrySet()).iterator();<br /> <br /> while( it.hasNext() ){<br /> renderlist( it.next().getValue() );<br /> }<br /> }<br /> <br /> public static void renderlist( List<String> list ){<br /> <br /> Iterator<String> it = list.iterator();<br /> <br /> while( it.hasNext() ){<br /> System.out.print( it.next() + " " );<br /> }<br /> <br /> System.out.println();<br /> }<br />}</code><br /><br /><span style="font-weight:bold;">PHP</span> by Rux<br /><code><?php<br /> $lines = file('input.txt');<br /><br /> foreach($lines as $line){<br /> $key = substr($line, 0, strpos($line, '|'));<br /> $ds[$key] = explode('|', $line);<br /> }<br /><br /> ksort($ds);<br /><br /> foreach($ds as $rw){<br /> sort($rw);<br /> echo implode("&nbsp;", $rw);<br /> echo "<br />";<br /> } <br />?></code><br /><br /><span style="font-weight:bold;">Ruby</span> by your truely<br /><code>lines = IO.readlines('input.txt').map do |line|<br /> items = line.chomp.split('|')<br /> [items.shift] + items.sort<br />end<br />lines.sort { |one, another| one[0].to_i <=> another[0].to_i }.each do |line|<br /> puts line.join(' ')<br />end</code><br /><br /><span style="font-weight:bold;">Ruby with UNIX commands</span> by pphetra<br /><code>ruby -F'\|' -nlae 'puts $F[1..-1].sort().unshift($F[0]).join(" ")' input.txt | sort -nk 1</code><br /><br /><span style="font-weight:bold;">Scala</span> by your truly<br /><code>import scala.io.Source<br />import scala.util.Sorting<br /><br />def show_sort(fileName: String) = {<br /> def sort_record(line: String) = {<br /> val fields = List.fromString(line.replace("\n", ""), '|')<br /> fields.head :: fields.tail.sort((a,b) => (a compareTo b) < 0)<br /> }<br /> val tuples = Source.fromFile(fileName).getLines.map(sort_record).collect<br /> val sort_tuples = Sorting.stableSort(tuples, (a:List[String], b:List[String]) => a.head.toInt < b.head.toInt)<br /> sort_tuples.map(tuple => println(tuple.mkString("", " ", "")))<br />}<br />show_sort("input.txt")</code><br /><br /><span style="font-weight:bold;">VisualBasic.NET with LINQ</span> by tuckclub<br /><code>Module Module1<br /><br /> Sub Main()<br /> Dim records = System.IO.File.ReadAllLines("input.txt")<br /> Dim rs = From r In records _<br /> Let ia = r.Split("|") _<br /> Let ca = (From c In ia.Skip(1) Order By c) _<br /> Order By ia(0) _<br /> Select ia.Take(1).Union(ca).ToArray<br /> For Each r In rs<br /> Console.WriteLine(String.Join(" ", r))<br /> Next<br /> Console.ReadKey()<br /> End Sub<br /><br />End Module</code>Weehttp://www.blogger.com/profile/04042457149790214773noreply@blogger.com0tag:blogger.com,1999:blog-6888658749995617133.post-33169812387656466362009-02-20T16:44:00.000-08:002009-02-20T16:57:10.382-08:00irb with autocompletion supportInteractive ruby shell (irb) is a handy tool for exploratory programming in ruby. One of lesser known features in irb is tab autocompletion support. Say, you want to know instance methods of a particular object, you can type the object followed by a dot and then hit a tab. irb will display all instance methods on that object.<br /><br />Below is an example of methods for a number 1. After you type 1., hit a tab key. (Oh yeah, 1 is an object in ruby)<br /><br /><code>$ irb -r irb/completion<br />irb(main):001:0> 1.<br /> 1.eql? 1.instance_variable_get 1.prec_f 1.taint<br />1.__id__ 1.equal? 1.instance_variable_set 1.prec_i 1.tainted?<br />1.__send__ 1.even? 1.instance_variables 1.pred 1.tap<br />1.abs 1.extend 1.integer? 1.private_methods 1.times<br />1.between? 1.fdiv 1.is_a? 1.protected_methods 1.to_a<br />1.ceil 1.floor 1.kind_of? 1.public_methods 1.to_enum<br />1.chr 1.freeze 1.method 1.quo 1.to_f<br />1.class 1.frozen? 1.methods 1.remainder 1.to_i<br />1.clone 1.hash 1.modulo 1.respond_to? 1.to_int<br />1.coerce 1.id 1.next 1.round 1.to_s<br />1.display 1.id2name 1.nil? 1.send 1.to_sym<br />1.div 1.inspect 1.nonzero? 1.singleton_method_added 1.truncate<br />1.divmod 1.instance_eval 1.object_id 1.singleton_methods 1.type<br />1.downto 1.instance_exec 1.odd? 1.size 1.untaint<br />1.dup 1.instance_of? 1.ord 1.step 1.upto<br />1.enum_for 1.instance_variable_defined? 1.prec 1.succ 1.zero?</code><br /><br />You may scope down results by typing more, say, show all instance methods that start with 'i'.<br /><br /><code>irb(main):001:0> 1.i<br />1.id 1.instance_eval 1.instance_variable_defined? 1.instance_variables <br />1.id2name 1.instance_exec 1.instance_variable_get 1.integer? <br />1.inspect 1.instance_of? 1.instance_variable_set 1.is_a? </code><br /><br />To enable tab completion in irb, you need to start irb with -r irb/completion option. Or if you are already in irb, do 'require irb/completion'.<br /><br />As a bonus, require irb/completion works in rails script/console too.Weehttp://www.blogger.com/profile/04042457149790214773noreply@blogger.com0tag:blogger.com,1999:blog-6888658749995617133.post-77678333940610908052009-01-24T05:40:00.000-08:002009-01-24T06:03:35.123-08:00How to make Ruby and JRuby live peacefullySimilar to each Ruby versions, Ruby and JRuby maintain their own gems separately. While we can use jruby and jirb to specifically tell we want to JRuby version, JRuby does not have jrake or jgem. How do we specify we want to execute rake/gem in Ruby and JRuby?<br /><br />A solution is to prefix the program with ruby -S and jruby -S like<br /><br /><blockquote>ruby -S rake<br />jruby -S rake</blockquote><br /><br />or <br /><br /><blockquote>ruby -S gem list<br />jruby -S gem list</blockquote><br /><br />This way, we explicitly tell which interpreter we want to use and it will automatically find the right program in the right path. Problem solved.<br /><br />If you want to install multiple Ruby vesions in the same machine, use MultiRuby as described in Dr Nic's <a href="http://drnicwilliams.com/2008/12/11/future-proofing-your-ruby-code/">Future proofing your Ruby code. Ruby 1.9.1 is coming.</a>Weehttp://www.blogger.com/profile/04042457149790214773noreply@blogger.com0tag:blogger.com,1999:blog-6888658749995617133.post-42966919805971116772008-11-30T06:05:00.000-08:002008-11-30T08:43:39.721-08:00Finding which rspec tests produce unexpected outputsYou know running hundred of tests should only show '.', 'F', or 'E' and nothing more. You feel disturbed when some passing tests print debugging statements or rspec matcher complains that one of erb/rhtml views do not have a closing html tag and print a bunch of html outputs. <br /><br />You want to find which tests among hundreds produce these but you have no clue where to find them. You don't want to search for 'puts', 'p' or 'inspect' from the entire codebase. Nor would you want to find which erb/rhtml files have missing closing tags. What should you do?<br /><br />My colleague, Toby, suggested me to look at rspec source code, specifically at the formatters so that we can find a spot that runs examples and shows the '.' output. So I did and found the progress_bar_formatter.rb in lib/spec/runner/formatter.<br /><br /><code> def example_passed(example)<br /> @output.print green('.')<br /> @output.flush<br /> end</code><br /><br />We can put a print statement to identify which example (rspec test) is passing. Either @output.print example.description or @output.print example.inspect should be fine. Once we know the example names, we should be able to find where they are. Now we can fix the problem and make all test outputs produce those little green dots.Weehttp://www.blogger.com/profile/04042457149790214773noreply@blogger.com0tag:blogger.com,1999:blog-6888658749995617133.post-2916066097097336862008-11-23T16:05:00.000-08:002008-11-23T16:26:00.036-08:00Ruby: Treat object and collection uniformlyProblem: You write a method that takes a collection and iterates over it. However, you also want to accept a single object parameter as well. <br /><br />Solutions: Convert a single object into an array so that we can manipulate the parameter uniformly. As always, there are many ways to do this in Ruby. However, I prefer the last one.<br /><br />1. Make sure it is an array by using Object#to_a. However, to_a is deprecated and not recommended.<br /><br /><code>def f(a)<br /> a = a.to_a<br /> a.each { |item| ... }<br /> ...<br />end<br /></code><br /><br />2. Check the parameter type and wrap it into an array if it is not an array already.<br /><br /><code>def f(a)<br /> a = [a] if a.is_a? Array<br /> ...<br />end<br /></code><br /><br />3. Duck-type check whether it responds to :each method<br /><br /><code>def f(a)<br /> a = [a] unless a.respond_to? :each<br /> ...<br />end<br /></code><br /><br />4. Use splat operator. This also does the job. It is concise but less readable.<br /><br /><code>def f(a)<br /> a = [*a]<br /> ...<br />end<br /></code><br /><br />5. Use Array#flatten. I prefer this one. It is readable.<br /><br /><code>def f(a)<br /> a = [a].flatten<br /> ...<br />end<br /></code> <br /><br />Is there any other more way?Weehttp://www.blogger.com/profile/04042457149790214773noreply@blogger.com0