Monday, March 31, 2008

Gotchas of internal iFrame facebook apps and external web apps using Facebooker gem

A while back, I added mobtropolis to facebook as an internal app. I decided to go with using FBML because there was more support in the how-tos about how to use it, and it looked like tighter look and feel and integration.

However, unlike many facebook apps, Mobtropolis also exists as a stand-alone external web app. This decidedly made things a little bit hairier, and I had to write a custom mime-response filter to be able to tell whether a call was coming from a web client (HTML), or as an internal facebook app (FBML), in order to authenticate correctly. I also ended up having to write some custom testing methods for it as well.

Then I revamped the layout of mobtropolis.

It's major suckage to have to maintain two separate views, so I decided to go with an iFrame with the internal facebook app. It took a bit of work to convert it to use iFrames, because authentication gets a little bit more complicated. However, it's something that I only have to deal with once. Subsequent changes to the layout won't affect it as much.

In retrospect, I should have went with using an iFrame from the beginning, though, at the time, mobtropolis was fairly ugly. This is what people call "judgement", and I made the mistake and it cost me about three weeks. The thing is, you just make the best decision you can at the time, and make sure you can change directions easily.

There were a couple gotchas when using iFrames.
  1. Double facebook frames on redirect to install page.
  2. External app's layout is wider than iFrame
  3. Facebook only sends fb params on the first call to your app

Hopefully, I'll save you some time, to whomever's looking for this info.

1) Double facebook frames

When you use ensure_application_is_installed_by_facebook_user or ensure_authenticated_to_facebook, it will automatically reroute the user to an install page if he didn't install your application. Problem is, it assumes that you're not in an iFrame. It ends up that you can override application_is_not_installed_by_facebook_user in your controllers.

def application_is_not_installed_by_facebook_user
redirect_to add_internal_facebook_app_url
end

Where add_internal_facebook_app_url is an action in a controller (say, my_controller), that renders javascript to change the location of the top frame.

def add_internal_facebook_app
render :layout => false, :inline => %Q{<script type="text/javascript">
top.location.href = "<%= session[:facebook_session].install_url -%>"
</script>}
end

You have to make sure you connect it as a route in order to redirect it like I did in the overridden application_is_not_installed_by_facebook_user(), in routes.rb under config/

map.add_internal_facebook_app('add_facebook_internal_app',
:controller => "my_controller",
:action => "add_internal_facebook_app")

2) External app is wider than iFrame

I think there is a way to resize the Facebook iFrame, but I didn't find out about it after I did this. By default, the Facebook iFrame "smartsizes" itself, to fill out rest of the page.

First, I created a stylesheet called fb_internal_layout.css, that had extra stylings that squeezed the interface in a 446px wide iFrame. Then I included it in the headers of my layouts as:
<link href="fb_internal_layout.css" id="fb_internal_layout" media="screen" rel="alternate stylesheet" title="Facebook Internal Layout" type="text/css" />

Make sure you include titles in the link, so that you can actually switch it out.

Then we use javascript to turn on or off this alternate stylesheet depending on whether we're in an iframe or not. You can use something like what's described in A List Apart's article on alternate stylesheets to switch out stylesheets.

To detect if I was in an iFrame, I simply checked whether (frames.top == frames.self). If it was, I turned on the alternate stylesheet.

3) Facebook only sends fb params on the first call to your app

This is actually not a problem if you use FBML. This is also not a problem if you're using iFrames, and you require a user to install your facebook app if they want to see what's on it.

However, even though this is how a lot of facebook apps operate, I don't think this is very user friendly. The user has no way to judge whether they want to install your app or not if they can't even sample it. I would rather have a user add an app because they want to, rather than getting people that add it, but then remove it shortly after. This not only gives you an inaccurate indication of how many people really want to use your app, but also annoys the hell out of them.

But making some pages of an iFrame app to be public is a bit tricky. Only the first click into your facebook app is there fb_params in the request. Every subsequent click by a user is in your iFrame, so looks as if the user is actually on the external webpage.

There are a couple solutions, but I ended up storing session state that the user made a request from an internal app before. You can't override params on subsequent requests, so using old fb_params to authenticate is difficult at best. Using the flag that a user made a request before, this session is likely to be coming from an internal facebook app. When it comes upon a private page, it should be redirected to install mobtropolis, using 1) detailed above. This is not a perfect solution, but it covers all cases correctly.

This, however, doesn't account for the instance where a user that already installed. In that particular case, I just went ahead an got a facebook session on every first request to the facebook app.

Hope that helped, and I hope never to have to mess with this sort of stuff again, and that you don't either. More interesting posts in the future. Tip!

Friday, March 28, 2008

A way to think about design for the naïve hacker

Any technology goes through its phases. First, there's the discovery of what it is, and along with it, the implementation. Just actually getting it to work is exciting. and at this stage, obviously usability is really a second thought. It's really hard enough getting it running in the first place, because of all the details you have to juggle as an innovative maker.

As a result, we get cool things that are hard to use. The first washing machines in the early 1800's didn't actually have a plug, because there were no wall sockets. Where'd you plug it in then? Your light socket hanging from the ceiling. That's right. You'd have to unscrew your lightbulb, and then screw in your washing machine--all the while on a step ladder. If it went haywire, you'd have a heck of a time unplugging it, and that probably wasn't considered very user friendly.

Then there's the phase where we've got a handle on how to build it, and the question becomes, how do we make it easy to use and work well? This is the part where design comes in. In large part, we've become specialized in what we do. The innovative builders make things that weren't yet possible possible, and then designers come along and create and experience around it. Stereotypically, hackers scoff at design. "It's just icing on the cake!" one might say. To that, I say, to scoff at design as a hacker is to scoff at implementation as a theorist--both lack appreciation of what's required.

I don't think it hurts for a hacker to know more about design, and how to think about it. While you may never be as good at putting on the gloss as a professional, you'll be better able to bridge the gap, which will help you be a better hacker. Besides, it will help you when you design APIs or public interfaces to your classes.

On the other hand, you might not need much convincing as a hacker if you are a fan of Apple's products. The Macbook Air, the iPod, the iPhone are widely touted as the stunning examples of design in technology. However, as a hacker, it seems like a strange and touchy-feely territory that relies on "taste" and "intuition". It doesn't have to be that way, as I think there's a good way to approach design, if you can think about it in the right way.

When people think "design", they usually think of superfluous, yet oddly satisfying lines that swoop or swoosh. They think of chrome, reflection, or shiny surfaces. Especially gradients. whoo. Lots of smart people in the actual design world have written entire books on "What is design?". I've never read any of them. However, I don't think they would do a good job of explaining it in a way that's easier for hackers to think about.

While there is inherent joy in design for its own sake, I'm going to only tackle a way to think about design for products. Web products, specifically.

I think of design as a deliberate attention to detail about the exposed public interface, to do two things: 1) to communicate to your user what your product is, and who your user is to others. 2) to control a user's experience

If you have no idea what your product is yet, or is still figuring it out, then there's no point in doing too much design. Design rests very much on what your product is, how it will benefit your user. If you haven't figured that out yet, ignore all the comments about how ugly your web app is, and figure out how to make it something that works, or something that people want to use because it's useful, because remember, design isn't about gradients.

The first part of what I think of as design is communication, usually with little to no words. It answers questions like, "What is it?" "How will it benefit me?" While the questions are deceptively simple, if new users can't tell immediately what it is, and how they'll benefit, they'll move on quickly. This is something I'm still working on, since people, no matter who they are, have limited time and attention to pay to any new thing. Pretend like you're talking to your really smart uncle, that is drunk all the time: Get to the simple point, and make it obvious.

Beyond the first impression, design of the product has to communicate to the user all the time, to answer questions such as, "How do I do [something I want to do]?" or "Can I do [something I wish I could do?" This is why designers talk about affordances of buttons, interfaces, and how the nipple is the only intuitive interface.

Last major point on communication, design should allow the product to communicate to the user what the heck it's doing at any one moment that the user would care about at that moment. If you're at a restaurant, you'd like your waitress to tell you that the food will be arriving in 10 mins, but you don't care what kind of pot the chef is using.

This sort of emphasis is captured in Don Norman's book on Design of Everyday Things, where he goes into detail ad nauseum. I recommend you take a look at it.

The second part, which is the result of communication, is controlling the user experience. Of course, the phrase, "user experience" is throw around a lot, and people don't much say what it is either. One aspect is how a user feels during and after using your product. Do they feel happy? Confident? Like they're having fun? Or do they feel frustrated? Powerless? Incompetent? Or wasting time?

Seems like you can't control whether someone's having a bad day or not, but there are certain tricks that designers employ to conjure up feelings. This works because our brains do a lot in interpreting colors and shapes in our culture. If you see a felt red with a holly green, you'll automatically think of Christmas, and perhaps your feelings that go with Christmas.. Change the tint slightly, and you can be reminded of traffic lights. The designer doesn't actually have to do very much, other than to suggest a mood, and the brain will do the rest. It's a good hack.

Lastly, part of the user experience is also what using the product says about them to their peers. This differs from peer group to peer groups, but they all have the same goal: everyone wants to have their product that brings them higher status in their peer group. This can be achieved by catering to core values of the peer group. If the peer group of your users are business people, they'll likely to value efficiency, reliability, and effectiveness. If the peer group of your users are hackers, they'll likely to value ability, interestingness, and functionality. If the design of your product can communicate that a user is more [insert core values] in their peer group, they'll probably appreciate the product without knowing why.

One word of caution is that it's hard to identify the core values of a group if you're not a part of it. And you can't start with an associated design and work your way back to the core values of a peer group. This is why a lot of people, when designing for girls, automatically start throwing pink everywhere, and then when the design fails, they wonder where their design went wrong. If your design doesn't communicate first what it does for a user, and second who a user is, then it's going to fail, no matter how much pink you put on there.

I recently revamped the layout and the look and feel of mobtropolis, which is why there's this post, and the two week hiatus from posting. You can see a "before" and an "after".





Before (v0.2)



After (v0.6)



While I can't claim to be a designer by any means, I feel that I was fairly successful in changing the direction of the app as well as invoking a better user experience. Communication will be an iterative process, as there will need to be some back and forth with users, before things get completely resolved. Just going through it gave me some thoughts on the matter, and next time, I'll talk about the specific lessons I've learned, and maybe some things to help a hacker or two do basic design. Til then!

Monday, March 24, 2008

The twenty-seven word score club

Like lots of people on facebook, I've been playing scrabulous on facebook. I'm not much of a wordsmith, but I have fun playing people. Justin told me that his goal in life was to span two triple-word scores, to get a 9x word score. So not to be outdone, I wondered what words would be able to give you a 27-word score if you spanned across all three triple-word scores. We would need fifteen lettered words.

Since you can only put down at most seven tiles per turn, there needs to be a word in-between the triple letter scores to help you fill it out. These "bridge words" can't already be on the triple word score already, and they must be between two and six letters long on each side, where the total length of both words has to be greater than eight.

So I wrote a program in a couple hours to find them. I did take into account whether a word was possible to make based on the scrabble tile distribution, as well as taking into account blanks. There's 286 of them thus far in the TWL scrabble dictionary. I didn't find ones that used more than one bridge word on a side. The points aren't completely accurate either.

The first number is the points you'd get, and then the two bridge words. Based simply on the probability of drawing the numbers from a full bag, "irrationalities" is the most likely word. (in reality, this never happens, since you need to draw tile in order to place those words to reach the side.)

459 : irrationalistic : ["ratio", "alist"]

You can score a whopping 459 points with it. The word that has the biggest word score is "coenzymatically"

972 : coenzymatically : ["enzym", "tical"]

Yes. "tical" is a word.

ti·cal –noun, plural
1. a former silver coin and monetary unit of Siam, equal to 100 satang: replaced in 1928 by the baht.


There are quite a number of common words, you wouldn't think, as well as quite a number odd ones. As a note, the point scores aren't exactly accurate. I didn't take into account the double letter scores that might occur if you place a letter one it. But given that the multiplier is 27 here, and I picked the longest bridge words (which usually cover the double letter score), it shouldn't affect it too much. I had held off posting it until I fixed that, but this was sort of a one off amusement and curiosity, rather than anything significant, so I figured I'd just post it. Enjoy!

567 : accountableness : ["count", "lenes"]
648 : accountantships : ["count", "ship"]
486 : administrations : ["minis", "ration"]
648 : ammonifications : ["mon", "cation"]
594 : amorphousnesses : ["morpho", "ness"]
621 : anthropological : ["thro", "logic"]
783 : anthropomorphic : ["thro", "morph"]
594 : antihistaminics : ["his", "aminic"]
540 : antitheoretical : ["tithe", "etic"]
594 : aromatherapists : ["math", "rapist"]
540 : astronautically : ["trona", "tical"]
756 : astrophysically : ["strop", "call"]
675 : astrophysicists : ["strop", "cist"]
540 : atheroscleroses : ["heros", "erose"]
540 : atherosclerosis : ["heros", "eros"]
594 : atherosclerotic : ["heros", "roti"]
540 : authentications : ["then", "cation"]
594 : autoradiographs : ["tora", "graph"]
675 : autoradiography : ["tora", "graph"]
810 : bathymetrically : ["thyme", "call"]
594 : beautifications : ["eau", "cation"]
594 : benightednesses : ["night", "ness"]
675 : blameworthiness : ["lame", "thine"]
540 : brotherlinesses : ["other", "ness"]
486 : businesspersons : ["sines", "person"]
405 : ceaselessnesses : ["easel", "ness"]
729 : chancellorships : ["hance", "ship"]
729 : chemotherapists : ["moth", "rapist"]
810 : cholangiography : ["lang", "graph"]
675 : cholecystitises : ["hole", "titis"]
675 : cholestyramines : ["holes", "amine"]
540 : cholinesterases : ["line", "erase"]
675 : cinematographer : ["nema", "graph"]
729 : cinematographic : ["nema", "graph"]
486 : clandestineness : ["land", "nenes"]
594 : classifications : ["lassi", "cation"]
972 : coenzymatically : ["enzym", "tical"]
648 : comfortableness : ["fort", "lenes"]
567 : commensurations : ["men", "ration"]
594 : commercialistic : ["merc", "alist"]
702 : communistically : ["muni", "tical"]
756 : computerphobias : ["put", "phobia"]
648 : conceivableness : ["once", "lenes"]
567 : concelebrations : ["once", "ration"]
540 : conceptualistic : ["once", "alist"]
540 : conglomerations : ["glom", "ration"]
486 : conglutinations : ["glut", "nation"]
540 : congresspersons : ["res", "person"]
486 : considerateness : ["onside", "ate"]
540 : containerboards : ["tain", "board"]
594 : convertibleness : ["vert", "lenes"]
702 : crashworthiness : ["rash", "thine"]
594 : crotchetinesses : ["rotche", "ness"]
513 : customarinesses : ["stoma", "ness"]
459 : dangerousnesses : ["anger", "ness"]
837 : decarboxylating : ["carbo", "lati"]
810 : decarboxylation : ["carbo", "lati"]
540 : deconcentration : ["once", "ratio"]
540 : deconsecrations : ["cons", "ration"]
621 : dedifferentiate : ["diff", "entia"]
513 : defenestrations : ["fen", "ration"]
513 : delegitimations : ["elegit", "mat"]
567 : delightednesses : ["light", "ness"]
864 : demisemiquavers : ["mise", "quaver"]
810 : denazifications : ["nazi", "cation"]
567 : dialectological : ["alec", "logic"]
486 : diastereoisomer : ["aster", "some"]
648 : dichloroethanes : ["ich", "ethane"]
540 : discontinuances : ["con", "nuance"]
540 : discriminations : ["scrim", "nation"]
486 : disinclinations : ["sin", "nation"]
459 : disintegrations : ["sin", "ration"]
648 : dissatisfactory : ["sati", "factor"]
567 : divertissements : ["vert", "semen"]
675 : dyslogistically : ["slog", "tical"]
567 : eclaircissement : ["lair", "semen"]
486 : educationalists : ["ducat", "alist"]
459 : elaboratenesses : ["labor", "ness"]
459 : emotionlessness : ["motion", "ess"]
756 : encephalographs : ["epha", "graph"]
837 : encephalography : ["epha", "graph"]
675 : enfranchisement : ["franc", "semen"]
621 : epidemiological : ["idem", "logic"]
594 : epistemological : ["piste", "logic"]
540 : epistemologists : ["piste", "gist"]
378 : essentialnesses : ["senti", "ness"]
540 : esterifications : ["rif", "cation"]
594 : eutrophications : ["trop", "cation"]
702 : excrementitious : ["creme", "titi"]
621 : exsanguinations : ["sang", "nation"]
702 : extemporisation : ["tempo", "sati"]
540 : fantastications : ["antas", "cation"]
567 : flibbertigibbet : ["libber", "gib"]
513 : foreordinations : ["ore", "nation"]
567 : fragmentariness : ["ragmen", "rin"]
675 : frightfulnesses : ["right", "ness"]
567 : fundamentalists : ["dame", "alist"]
567 : gentrifications : ["rif", "cation"]
513 : gluconeogeneses : ["cone", "genes"]
513 : gluconeogenesis : ["cone", "genes"]
675 : grotesquenesses : ["rotes", "ness"]
648 : historiographer : ["tori", "graph"]
702 : historiographic : ["tori", "graph"]
432 : houselessnesses : ["ousel", "ness"]
702 : humidifications : ["midi", "cation"]
783 : hypervigilances : ["perv", "lance"]
756 : hypnotherapists : ["not", "rapist"]
567 : identifications : ["dent", "cation"]
459 : illiberalnesses : ["liber", "ness"]
513 : illimitableness : ["limit", "lenes"]
486 : illogicalnesses : ["logic", "ness"]
513 : immaterialities : ["mater", "alit"]
540 : immediatenesses : ["media", "ness"]
459 : inalterableness : ["alter", "lenes"]
459 : inanimatenesses : ["anima", "ness"]
702 : inconsequential : ["cons", "entia"]
567 : inconsiderately : ["cons", "ratel"]
486 : inconsideration : ["cons", "ratio"]
486 : incoordinations : ["coo", "nation"]
567 : incrementalisms : ["creme", "tali"]
513 : incrementalists : ["creme", "alist"]
459 : incuriousnesses : ["curio", "ness"]
567 : indefinableness : ["defi", "lenes"]
486 : indoctrinations : ["doc", "nation"]
540 : indomitableness : ["omit", "lenes"]
648 : inflammableness : ["flam", "lenes"]
513 : instrumentalism : ["strum", "tali"]
459 : instrumentalist : ["strum", "tali"]
540 : instrumentality : ["strum", "tali"]
459 : intolerableness : ["tole", "lenes"]
486 : inviolatenesses : ["viola", "ness"]
459 : irrationalistic : ["ratio", "alist"]
405 : irrationalities : ["ratio", "alit"]
567 : irreconcilables : ["recon", "able"]
729 : kinesthetically : ["nest", "tical"]
648 : lickerishnesses : ["icker", "ness"]
540 : loathsomenesses : ["oaths", "ness"]
621 : magistratically : ["agist", "tical"]
594 : martensitically : ["tens", "tical"]
540 : masterfulnesses : ["aster", "ness"]
621 : mercaptopurines : ["cap", "purine"]
621 : metallographers : ["tall", "raphe"]
702 : methamphetamine : ["eth", "etamin"]
891 : methoxyfluranes : ["ethoxy", "ran"]
729 : methylmercuries : ["ethyl", "curie"]
891 : methylxanthines : ["ethyl", "thine"]
648 : microanalytical : ["roan", "lytic"]
621 : misapplications : ["sap", "cation"]
513 : momentarinesses : ["omenta", "ness"]
486 : monounsaturated : ["nouns", "urate"]
459 : monounsaturates : ["nouns", "urate"]
513 : monumentalities : ["numen", "alit"]
567 : multiplications : ["tip", "cation"]
540 : neurobiological : ["euro", "logic"]
513 : neuroblastomata : ["euro", "stoma"]
783 : neuropsychology : ["euro", "cholo"]
513 : nonbarbiturates : ["barb", "urate"]
486 : nonbelligerents : ["bell", "gerent"]
513 : noncelebrations : ["once", "ration"]
513 : noncooperations : ["coop", "ration"]
594 : nonenforcements : ["one", "cement"]
567 : nonimplications : ["nim", "cation"]
756 : nonphotographic : ["phot", "graph"]
486 : opinionatedness : ["pinion", "ted"]
729 : overextractions : ["ere", "action"]
621 : overmedications : ["med", "cation"]
486 : oversaturations : ["ers", "ration"]
567 : overstimulating : ["verst", "lati"]
540 : overstimulation : ["verst", "lati"]
864 : oxytetracycline : ["tet", "cyclin"]
459 : painterlinesses : ["inter", "ness"]
621 : paragenetically : ["rage", "tical"]
675 : parenthetically : ["rent", "tical"]
648 : parthenocarpies : ["then", "carpi"]
567 : parthenogeneses : ["then", "genes"]
567 : parthenogenesis : ["then", "genes"]
621 : parthenogenetic : ["then", "genet"]
513 : pectinesterases : ["tine", "erase"]
567 : permissibleness : ["miss", "lenes"]
702 : pharmaceuticals : ["harm", "tical"]
729 : pharmacological : ["harm", "logic"]
648 : phenomenalistic : ["nome", "alist"]
675 : photoengravings : ["hot", "raving"]
675 : photoperiodisms : ["tope", "iodism"]
783 : phototelegraphy : ["tote", "graph"]
729 : pithecanthropus : ["theca", "thro"]
648 : planimetrically : ["anime", "call"]
621 : platinocyanides : ["latino", "nide"]
513 : pleasurableness : ["leas", "lenes"]
486 : predestinarians : ["redes", "aria"]
486 : predestinations : ["redes", "nation"]
621 : preestablishing : ["reest", "shin"]
648 : prefabrications : ["ref", "cation"]
594 : preformationist : ["reform", "ion"]
540 : preponderations : ["repo", "ration"]
621 : prepublications : ["rep", "cation"]
513 : presentableness : ["resent", "lenes"]
513 : preterminations : ["rete", "nation"]
594 : prettifications : ["ret", "cation"]
702 : problematically : ["roble", "tical"]
486 : proletarianised : ["role", "anise"]
459 : proletarianises : ["role", "anise"]
675 : proteolytically : ["rote", "tical"]
783 : quantifications : ["anti", "cation"]
729 : rechoreographed : ["chore", "graph"]
621 : recodifications : ["cod", "cation"]
513 : reconcentration : ["once", "ratio"]
513 : reconsecrations : ["cons", "ration"]
486 : reconsideration : ["cons", "ratio"]
459 : redintegrations : ["dint", "ration"]
567 : reductivenesses : ["educt", "ness"]
567 : refrangibleness : ["rang", "lenes"]
513 : regretfulnesses : ["egret", "ness"]
513 : reinvigorations : ["vig", "ration"]
432 : reregistrations : ["egis", "ration"]
567 : respectableness : ["spec", "lenes"]
513 : responsibleness : ["pons", "lenes"]
459 : retroperitoneal : ["trope", "tone"]
540 : retroreflectors : ["ore", "lector"]
594 : rigidifications : ["gid", "cation"]
513 : sacramentalists : ["cram", "alist"]
594 : saponifications : ["apo", "cation"]
810 : saprophytically : ["prop", "tical"]
513 : scintillometers : ["inti", "meter"]
567 : seductivenesses : ["educt", "ness"]
540 : selectivenesses : ["elect", "ness"]
459 : semiterrestrial : ["miter", "stria"]
513 : semitransparent : ["emit", "spare"]
405 : sensationalists : ["sati", "alist"]
459 : sentimentalists : ["time", "alist"]
594 : serviceableness : ["vice", "lenes"]
648 : simplifications : ["imp", "cation"]
594 : slaughterhouses : ["laugh", "house"]
459 : snippersnappers : ["nipper", "napper"]
567 : solidifications : ["lid", "cation"]
594 : solipsistically : ["lips", "tical"]
594 : sophistications : ["phis", "cation"]
540 : spermatogeneses : ["perm", "genes"]
540 : spermatogenesis : ["perm", "genes"]
648 : spinthariscopes : ["pint", "scope"]
567 : sprightlinesses : ["right", "ness"]
540 : stadtholderates : ["tad", "derate"]
864 : straightjackets : ["rai", "jacket"]
540 : stratifications : ["rat", "cation"]
540 : stratovolcanoes : ["rato", "canoe"]
567 : strikebreakings : ["trike", "akin"]
648 : superphenomenon : ["perp", "nomen"]
810 : sympathetically : ["path", "tical"]
351 : tastelessnesses : ["stele", "ness"]
621 : teletypewriters : ["let", "writer"]
567 : thanklessnesses : ["ankle", "ness"]
675 : therapeutically : ["rape", "tical"]
675 : thunderstricken : ["under", "trick"]
432 : toastmistresses : ["oast", "tress"]
432 : traditionalists : ["adit", "alist"]
459 : transaminations : ["ansa", "nation"]
486 : transmigrations : ["ran", "ration"]
621 : trihalomethanes : ["halo", "ethane"]
540 : troubleshooters : ["rouble", "hooter"]
567 : troubleshooting : ["rouble", "hoot"]
513 : troublesomeness : ["rouble", "omen"]
567 : trustworthiness : ["rust", "thine"]
540 : unadulteratedly : ["adult", "rated"]
459 : unalterableness : ["alter", "lenes"]
621 : unanticipatedly : ["antic", "pated"]
513 : unboundednesses : ["bound", "ness"]
729 : unchoreographed : ["chore", "graph"]
459 : uncleanlinesses : ["clean", "ness"]
621 : unclimbableness : ["climb", "lenes"]
702 : uncomplimentary : ["comp", "menta"]
513 : underestimating : ["dere", "matin"]
486 : unearthlinesses : ["earth", "ness"]
702 : unextraordinary : ["extra", "dinar"]
621 : unfavorableness : ["favor", "lenes"]
486 : unguardednesses : ["guard", "ness"]
540 : unrealistically : ["real", "tical"]
513 : unsightlinesses : ["sight", "ness"]
513 : unworldlinesses : ["world", "ness"]
837 : wappenschawings : ["pens", "hawing"]
540 : warrantableness : ["arrant", "lenes"]
486 : westernisations : ["ester", "sati"]
810 : whatchamacallit : ["hatch", "call"]
621 : whippersnappers : ["hipper", "napper"]
621 : wholesomenesses : ["holes", "ness"]
540 : worrisomenesses : ["orris", "ness"]
864 : xeroradiography : ["orad", "graph"]

Saturday, March 22, 2008

What do you take away from it?

This morning, I woke up and read this particular piece from coding horror, the well-known blog about software engineering. Normally, people talking about each others' essays doesn't hold much interest for me to make a comment on. The recent ones that come to mind are Zed Shaw's Rails is a Ghetto and Clifford Heath's Monkeypatching is destroying Ruby. Even if they bring up the finer points of a subject, it often feels like TMZ. So even if I hear about it, I go back to coding (which is why you haven't seen me here).

What prompted me however, is a re-evaluation of the essay--as Jeff Atwood's post made me go back and read Paul Graham's essay to rethink it. In the end, I didn't think his latest essay was the best he's written before, but I don't think his point was to say, "hey you suck ass because you're an employee".

Like people that care about their trade, Paul Graham has a certain philosophy on programmers. Just as the martial arts have different schools of thought of major guiding principles, Paul to has his own school of thought when it comes to programming. As far as I can tell, he's mostly concerned with hackers, which aren't just people that write code, but people with an attitude of subverting the norm and a cultivated curiosity about the world, mainly expressed through programming. I think it's these types of people he's mainly trying to reach. If you're someone that's not like that, but enjoys programming as the way you make a living and don't think about it when you go home the essay simply doesn't apply to you.

That said, there are advantages to working at a big company. Aside for the usual concerns about steady paycheck and health insurance, you get a lot more information thrown your way casually by coworkers if you're paying attention. When you're at a small company, you have to make an effort to read up on industry news--though given how addictive social tech news is, we might not call it 'effort'.

In addition, there are some types of things that need large company resources to do, even though it's cheaper to start certain types of tech company. Bio tech comes to mind, as well as chip design.

Many times, companies are started because someone was at a big company and they had a good view of the industry and saw a particular need that was unfulfilled. If the founders weren't at the big company to begin with, they wouldn't have had the wide view of an industry as easily and they wouldn't have been motivated by their company's lack of interest in the niche to go and start their own.

I venture to guess that Paul writes these sorts of essays on hackers and startups mainly because 1) it's what he knows (and you write best when you write what you know) and 2) there's not enough material out there on it. If you take into account your friends, your parents, guidance counselors, etc, most everyone can tell you how to go get a job. However, not many people can write essays on doing a startup. I believe this particular essay is directed at reaching out to the hackers as described above, and not the general programming audience. In my mind, it's not a bad thing, because, again, you write what you know.

Update:
This comment and response to it is one of the better posts that I've read on there. It's pretty much on the money. I have an admiration for those that can cut to the chase with clarity in their writing.

Friday, March 07, 2008

Nine letter word riddle

It's not too common that I get forwards nowadays. With the advent of social news, all the stupid links have migrated there. But on occasion, I'll get one from the older crowd. This one was a riddle with a movie of the answer attached.
What common English word is 9 letters long, and each time you remove a letter from it, it still remains an English word... from 9 letters all the way down to a single remaining letter?
It was only one answer, however, which it gave as "startling". I ended up wondering if there were more than one, so I wanted to see how fast I could write something to give me the answer. It'd be good practice, since most web programming is design and architectural hard, rather than algorithms hard. Besides, it's been a while since I wrote a recursive function.

Embarrasingly, it took 2.5-3 hours. I thought I'd be able to knock it out in one. I had some problems first removing a single letter from a word. Ever since I came to Ruby, I hardly ever deal with indicies, so finding those methods took a bit of time. Then, the recursion is always a bit of a mind bender when you don't do it often.

I also spent some time looking up what were considered one letter words, but then found out that there's a whole dictionary of one letter words. So I only considered "i" and "a" as valid one letter words. I also threw out all contractions.

See if you can write shorter/faster/better code. It's certainly a better workout than fizzbuzz. Seeing how it took me a bit, and I didn't clean up the code, I set the bar pretty low to beat. There were other things that would optimize it. I didn't throw out shorter words to check in the dictionary if I already checked them in a longer word--ie. I just ran down the list of dictionary words. Try it out yourself, in whatever language. (This sounds like a good beginning problem to write in Arc.) Go ahead. I'll wait.


Got it?


Here's the list I came up with along with their chains. You'll notice that it's actually a tree that branches.

cleansers
[["cleanses", ["cleanse", ["cleans", ["clean", ["clan", ["can", ["an", ["a"]]]], ["lean", ["lea", ["la", ["a"]]]]], ["clans", ["clan", ["can", ["an", ["a"]]]], ["cans", ["can", ["an", ["a"]]]]], ["leans", ["lean", ["lea", ["la", ["a"]]]], ["leas", ["lea", ["la", ["a"]]]]]]]], ["cleanser", ["cleanse", ["cleans", ["clean", ["clan", ["can", ["an", ["a"]]]], ["lean", ["lea", ["la", ["a"]]]]], ["clans", ["clan", ["can", ["an", ["a"]]]], ["cans", ["can", ["an", ["a"]]]]], ["leans", ["lean", ["lea", ["la", ["a"]]]], ["leas", ["lea", ["la", ["a"]]]]]]]]]

discusses
[["discuses", ["discuss", ["discus", ["discs", ["disc", ["dis", ["is", ["i"]]]], ["diss", ["dis", ["is", ["i"]]]]]]]]]

drownings
[["drowning", ["downing", ["owning", ["owing", ["wing", ["win", ["in", ["i"]]]]]]]]]

paintings
[["painting", ["paining", ["pining", ["piing", ["ping", ["pin", ["pi", ["i"]], ["in", ["i"]]], ["pig", ["pi", ["i"]]]]]]]]]

piercings
[["piercing", ["piecing", ["pieing", ["piing", ["ping", ["pin", ["pi", ["i"]], ["in", ["i"]]], ["pig", ["pi", ["i"]]]]]]]]]

prickling
[["pickling", ["picking", ["piking", ["piing", ["ping", ["pin", ["pi", ["i"]], ["in", ["i"]]], ["pig", ["pi", ["i"]]]]]]]], ["pricking", ["picking", ["piking", ["piing", ["ping", ["pin", ["pi", ["i"]], ["in", ["i"]]], ["pig", ["pi", ["i"]]]]]]]]]

restarted
[["restated", ["restate", ["estate", ["state", ["sate", ["sat", ["at", ["a"]]], ["ate", ["at", ["a"]]]]]]]]]

scrapping
[["crapping", ["rapping", ["raping", ["aping", ["ping", ["pin", ["pi", ["i"]], ["in", ["i"]]], ["pig", ["pi", ["i"]]]]]]]]]

sparkling
[["sparking", ["sparing", ["spring", ["sprig", ["prig", ["pig", ["pi", ["i"]]]]]]]]]

splatters
[["platters", ["platter", ["latter", ["later", ["late", ["ate", ["at", ["a"]]]]]]]], ["splatter", ["platter", ["latter", ["later", ["late", ["ate", ["at", ["a"]]]]]]]]]

splitting
[["slitting", ["sitting", ["siting", ["sting", ["sing", ["sin", ["in", ["i"]]]], ["ting", ["tin", ["ti", ["i"]], ["in", ["i"]]]]]]]], ["spitting", ["spiting", ["siting", ["sting", ["sing", ["sin", ["in", ["i"]]]], ["ting", ["tin", ["ti", ["i"]], ["in", ["i"]]]]]]], ["sitting", ["siting", ["sting", ["sing", ["sin", ["in", ["i"]]]], ["ting", ["tin", ["ti", ["i"]], ["in", ["i"]]]]]]]]]

stampeded
[["stampede", ["stamped", ["tamped", ["tamed", ["tame", ["tam", ["am", ["a"]]]]]]]]]

stampedes
[["stampede", ["stamped", ["tamped", ["tamed", ["tame", ["tam", ["am", ["a"]]]]]]]]]

startling
[["starling", ["staring", ["string", ["sting", ["sing", ["sin", ["in", ["i"]]]], ["ting", ["tin", ["ti", ["i"]], ["in", ["i"]]]]]]]], ["starting", ["staring", ["string", ["sting", ["sing", ["sin", ["in", ["i"]]]], ["ting", ["tin", ["ti", ["i"]], ["in", ["i"]]]]]]], ["stating", ["sating", ["sting", ["sing", ["sin", ["in", ["i"]]]], ["ting", ["tin", ["ti", ["i"]], ["in", ["i"]]]]]]]]]

starvings
[["starving", ["staring", ["string", ["sting", ["sing", ["sin", ["in", ["i"]]]], ["ting", ["tin", ["ti", ["i"]], ["in", ["i"]]]]]]]]]

strapping
[["trapping", ["tapping", ["taping", ["aping", ["ping", ["pin", ["pi", ["i"]], ["in", ["i"]]], ["pig", ["pi", ["i"]]]]]]], ["rapping", ["raping", ["aping", ["ping", ["pin", ["pi", ["i"]], ["in", ["i"]]], ["pig", ["pi", ["i"]]]]]]]]]

stringers
[["stingers", ["stinger", ["singer", ["singe", ["sing", ["sin", ["in", ["i"]]]], ["sine", ["sin", ["in", ["i"]]]]]]], ["singers", ["singer", ["singe", ["sing", ["sin", ["in", ["i"]]]], ["sine", ["sin", ["in", ["i"]]]]]], ["singes", ["singe", ["sing", ["sin", ["in", ["i"]]]], ["sine", ["sin", ["in", ["i"]]]]], ["sings", ["sing", ["sin", ["in", ["i"]]]], ["sins", ["sin", ["in", ["i"]]], ["sis", ["is", ["i"]]], ["ins", ["in", ["i"]], ["is", ["i"]]]]]]]], ["stringer", ["stinger", ["singer", ["singe", ["sing", ["sin", ["in", ["i"]]]], ["sine", ["sin", ["in", ["i"]]]]]]]]]

stringier
[["stingier", ["stinger", ["singer", ["singe", ["sing", ["sin", ["in", ["i"]]]], ["sine", ["sin", ["in", ["i"]]]]]]]], ["stringer", ["stinger", ["singer", ["singe", ["sing", ["sin", ["in", ["i"]]]], ["sine", ["sin", ["in", ["i"]]]]]]]]]

trampling
[["tramping", ["tamping", ["taping", ["aping", ["ping", ["pin", ["pi", ["i"]], ["in", ["i"]]], ["pig", ["pi", ["i"]]]]]], ["amping", ["aping", ["ping", ["pin", ["pi", ["i"]], ["in", ["i"]]], ["pig", ["pi", ["i"]]]]]]]]]

trappings
[["trapping", ["tapping", ["taping", ["aping", ["ping", ["pin", ["pi", ["i"]], ["in", ["i"]]], ["pig", ["pi", ["i"]]]]]]], ["rapping", ["raping", ["aping", ["ping", ["pin", ["pi", ["i"]], ["in", ["i"]]], ["pig", ["pi", ["i"]]]]]]]]]

whittlers
[["whittler", ["whitter", ["whiter", ["white", ["whit", ["wit", ["it", ["i"]]], ["hit", ["hi", ["i"]], ["it", ["i"]]]], ["wite", ["wit", ["it", ["i"]]]]]]]]]

wrappings
[["wrapping", ["rapping", ["raping", ["aping", ["ping", ["pin", ["pi", ["i"]], ["in", ["i"]]], ["pig", ["pi", ["i"]]]]]]]]]


And here's my code:


#!/usr/bin/ruby

class Array
def one_less
total = [self[1..-1]]
self.each_with_index do |e, i|
total << self.values_at(0..i, (i+2)..-1)
end
return total[0..-2]
end
end

def sub_words(word)
result = word.split("").one_less.map { |word_array| word_array.join }.uniq
result == [""] ? [] : result
end

def find_sub_word_chain(word)
return nil unless @@dict.include?(word)
return [word] if word.length == 1 && @@dict.include?(word)
valid_sub_words = sub_words(word).reject { |w| !@@dict.include?(w) }
word_chain = valid_sub_words.map do |sub_word|
chain = find_sub_word_chain(sub_word)
if chain.nil?
nil
else
if sub_word.length == 1
chain
else
(chain << sub_word).reverse
end
end
end.compact
word_chain.empty? ? nil : word_chain
end

@@dict = {}
ARGV[0] ||= "/etc/dictionaries-common/words"
words = File.readlines(ARGV[0]).map { |w| w.chomp }
words.each do |word|
if word.length == 1
next unless ["a", "i"].include?(word)
end
@@dict[word] = word
end

words.reject { |e| e.length != 9 }.reject { |word| word =~ /'/ }.map do |word|
chain = find_sub_word_chain(word)
next if chain.nil?
puts word
puts chain.inspect
puts
end

Tuesday, March 04, 2008

Foxy Fixtures and polymorphic tables

Well, I'm behind on everything, which means a bunch of interesting blog posts are queued up. But this one seemed short enough to warrant a small post.

I've always hated fixtures for the same reason that other people hate them, but nonetheless, I've bit the bullet to use them. Along comes Rails 2.0's foxy fixtures, and it becomes a little easier.

What it doesn't detail, however, is how to use your newly foxy fixtures for polymorphic models. If I have a vote model that I can use to vote on any type of table, with the old fixtures, I'd have:

my_vote:
id: 1
account_id: 1
votable_id: 3
votable_type: "Scene"

Normally, you just get rid of the foreign keys since it now checks the belongs_to associations of each model, and you can just use the label names. Same goes with the primary key id. It'll be autogenerated based on a hash of the fixture label.

my_vote:
account: my_account
votable: eat_hotdog
votable_type: "Scene"

Note that you're using the association names, and NOT the foreign key name, so you don't use "_id" anymore (that bit me in the ass for a little bit).

However, you'll find that with polymorphic models, you won't be able to do that. Searching around the good 'ole web lead me to find that Foxy Fixtures originally came from a plugin called Rathole, and at the very end of the README, it states:
Also, sometimes (like when porting older join table fixtures) you'll need to be able to get ahold of Rathole's identifier for a given label. ERB to the rescue:

Go John Barnette! That way, you can simply do this in your fixtures as a fall-back:

my_vote:
account: my_account
votable_id: <%= Fixtures.identify(:eat_hotdog) %>
votable_type: "Scene"

Tip!