Sunday, October 11, 2009

Ruby: 1.8.x hashes are not sorted


What?
I know, it stinks. But Ruby 1.9.x has sorted Hashes (in insertion oreder)

# Ruby 1.8.7irb(main):001:0> {:a=>"a", :c=>"c", :b=>"b"}=> {:a=>"a", :b=>"b", :c=>"c"}# Ruby 1.9.1 (in insertion order)irb(main):001:0> {:a=>"a", :c=>"c", :b=>"b"}=> {:a=>"a", :c=>"c", :b=>"b"}

More info here: http://www.igvita.com/2009/02/04/ruby-19-internals-ordered-hash/

Wait. I can't migrate over to Ruby 1.9.x. So what?
There are a lot of reasons why you wouldn't migrate, one of which being that after 2 years, Komodo IDE is still unable to debug 1.9 code.
Anyway. There's a trick to have sorted hashes in Ruby 1.8. Just maintain a sorted array of keys, and then each time you want to traverse your hash, do so via the parallel array.
See an example of this technique in my YADL module which I built in order to parse DOS variables. It will be the subject of my next post.

Sunday, July 26, 2009

Build Best Products: The Perfect Marketing Mix?

So it's been more than a year ow that I've switched.
After more than a decade spent with Windows-only desktops and laptops, I gave a Mac a try last year - and never came back.

I'm in the development business. I make a living off my IT skills. So that's fine if my clients are struggling to keep their Windows environment efficient because that's why I'm there.
But for my personal needs, why should I struggle the same?

It's been over a year. And mind you, this has strange collateral effects. Because I could never really set up on a correct Windows machine, I was used to reinstall the OS and all dev software every 6 months. And within a year, I'd buy a new one.
What a shock I had recently, when I realized that I haven't been in the market for a new laptop since I bought my MacBook eighteen months ago!
And if I were, I guess I would be looking for a newer MacBook - mind you, not more efficient. So that's probably why I'm not looking for another one.

From a marketing standpoint, that's terrific. Because it means that the current clients from Apple are faithful on the one hand. On the other, growing means finding new clients because existing ones won't re-equip soon. That my friends is the perfect mix for continued growth. A quick look at the latest stats over Apple latest quarterly results seems to prove the point.

Go buy a Mac, and save money. That's completely contradictory to what the Microsoft ads are trying to demonstrate. The day they understand that the Apple Tax is actually an Apple Saving, they'll start building their own devices.

Sunday, July 12, 2009

EverNote 3 Series - So What?

Introduction - What EverNote 3 is all about

Since it came out last year, EverNote 3 has successfully redefined what note taking applications should be. Now, we all expect them to:
- Offer document access from everywhere: Web browser (IE, FF, Safari), Mobile Phones (WAP), Smartphones (with dedicated applications like the ones for the Apple iPhone or Microsoft's Windows Mobile), Desktops (MacOS and Windows).
- Seamlessly integrate with modern communication tools like taking photographs and uploading them.
- Offer rich document editing capabilities.
- Have built-in search capabilities, including in images.
- Support tagging.
- Support file attachments.
- Offer controlled document sharing (a very recent addition).
- All of this for free (well, most of the features are free but the full monty will set you back 45$ a year and add bigger storage capabilities and SSL).

Having defined the above incredibly rich features set, they have defined their own market and they are way beyond the followers pack. It is very difficult to compete with them I guess. I think today they have more than a million subscribers (not saying all of them are paying customers of course, but I guess their VC funding helps here as well).

Short History

What you might not know is that EverNote is no recent company. And their flagship product is no recent either. At some point in time, there was an EverNote 1.0, then 1.1, then 2.0, etc... At that time, it was a rich Windows application only. And they had a small, but fervent customer base. These versions are no longer available.
At that time, I felt that the company was having a hard time delivering on promises (like having smartphone client applications or a better note editor) while enlarging the customer base which, as everyone knows, is the only way for a company to keep growing. It just looked like a niche player with no real future actually.
So they changed of direction and probably, the power went from some hands to some others and as I hinted above, external money helped define a new strategy.
But, from what I can read here and there, most of their ancient customers are a bit lost with the change of direction the company took. It looks like most advanced features have been dropped, and no one in the company is listening to them.

So what is actually missing then?

Quite a lot actually (remember though that this is *my* opinion):
- Still no decent unified note editor. The web version vs the Mac version vs the Windows version, etc... Not any one of them is good actually, or consistent with the others. The worst is probably on the Mac where you have (for example) to select the wrong font to have the right one applied. OMG.
- Inter-notes linking. This is probably the most required feature they miss. Without it, no wiki-style notes linking. Without it, you are completely unable to link your notes together. Boy, that's a huge hole.
- iPhone version has bells & whistles. But it doesn't have editing capabilities! It can only edit 'text' notes. But real text that is, meaning not even a color or bold or ... nothing. Plain ascii. Wow.
- Tags organization is missing one huge feature: hierarchy. They had that in their previous version. Imagine you select a tag: the UI then lists all the documents tagged by it. Say for example "Project A". You have all documents tagged "Project A" listed. Okay, but these documents have other tags as well. For example, you can have some tags for your colleagues on these meeting minutes notes. So inside "Project A", you also have "Meeting Minutes" and "Larry" and "Cathy". That's a whole new way of having your notes organized and once you start digging into that, you understand it's quite powerful.

Other Issues

I know a lot of people prefer having their laptop with them - they don't trust anyone for storing their documents over the Internet (and maybe for good reasons as we saw recently some big players having server problems). And then, once you are with your laptop, you have all the power you need for editing or organizing your work.
I also know some corporate clients of mine are blocking access to Web Applications like Google Docs and EverNote. They mind about the security of their documents and knowledge and IP and honestly they can't be blamed for that. Maybe EverNote is considering something to address these concerns (if not, then that's a missed opportunity) but not as of now anyway.

Conclusion?

If you are looking for easy (real easy) and efficient (real efficient) internet-based document sharing and ubiquitous access, then look no further. EverNote 3 is what you're looking for.
But beware the limitations and liabilities.

Update as at 8.23

Evernote have recently released a new version of the Mac client, which almost brings it on par to the Windows one. Which means that most painful text editing / formating issues are gone. At last!

Saturday, June 27, 2009

Rails - How to set defaults to an ActiveRecord model's attributes?

Problem to Solve

I want to have a default value for some attributes of an ActiveRecord Model. Of course, I can have the dafault value set in the table itself - but that doesn't cover instance creation (it will only be done after save), plus I cannot have a different default value per say, language and, mostly, it doesn't work for hashes.


First Hit

Obie Fernandez has some proposal here, which involves intercepting the attribute getter / setter on the fly, like so:

class Specification <>
def tolerance
read_attribute(:tolerance) or 'n/a'
end
end
class SillyFortuneCookie <>
def message=(txt)
write_attribute(:message, txt + ' in bed')
end
end

A shorter version would be like so:

class Specification <>
def tolerance
self[:tolerance] or 'n/a'
end
end
class SillyFortuneCookie <>
def message=(txt)
self[:message] = txt + ' in bed'
end
end

But of course, this doesn't work for hashes.


Second Thought

Actually, what would be best is to override the default constructor, to set the hash to some empty value rather than nil.
See this post on 3HV. Here's the skinny:

def initialize (params = nil)
super(params)
self.myhash = {} unless self.myhash
end

And actually, this is a very bad thing to do. Never override initialize, because of the way AR::Base deals with it. Here's an extract of a post from Josh Susser about the issue (very informative, I reckon):

I probably should have been more clear about the problems with overriding initialize, but I figured people already knew not to do that. You can get away with it sometimes, but it's risky and won't always do what you expect. For one thing, your approach won't work right if you also use the block syntax for initialized model object, as the code in your subclass initialize method won't have been run yet when the block is executed. I've definitely run into other issues as well, but it's too early in the morning for me to remember all the details (not a coffee achiever).


Third Thought: using callbaks

There is one callback called after_initialize, and this is the method recommended by some Rails luminaries like Michael Koziarski. Here's what you do:

after_initialize :set_sensible_defaults
private
def set_sensible_defaults
# do stuff
end

But this callback is called only when the object is instanciated from the database (meaning, it's a rough duplicate of after_find callback). And it's a bit late to set defaults.

So no, there's no callback that matches what we need (like after_new). And some debate has been going on for years about that (extract: http://groups.google.com/group/rubyonrails-core/browse_thread/thread/b509a2fe2b62ac5)


Fourth Option: Override after_initialize, with a Cost, and a Protection

Here you go:

def after_initialize
if new_record?
#do stuff
end
end

This one will get called in 2 cases: when the object is first instanciated in memory (which is what we're looking for), and also when the object is later retrieved from the database, which is why we protect ourselves using new_record?

The problem here is: the method will be called in the above 2 cases, meaning each time an object is retrieved from the DB, although you don't need that. Small problem arguably.
Now, after_initialize is called AFTER the object has been created in memory and AFTER attributes have been set potentially (as would result from a call like Object.new(:attr1=>"value1")

So, here's what you should really do:

def after_initialize
if new_record?
# Set default only if the attribute is not set already
self.attr1 = "Default value" if self.attr1.nil?
end
end