Thursday, April 19, 2007

Experience of an International Amazon Virgin

Recently I ordered 12 books from Amazon. It was my first time.

The process started really well - quite easy and pleasant to find the books I was after. Not to mention that amazing range and the great option of getting cheaper second hand books. Adding to the shopping cart was also a breeze.

I was pretty impressed, good prices, nice process. But then the honeymoon was over. Time to check out - stream of consciousness. First, I need to enter address details. Fine, as expected. Then I get a message (from memory) "There is a slight problem with your order. Some of the books you have chosen cannot be shipped to your address. Change your delivery address or change the quantity to 0 on these books.". Not happy! One third of my books (second hand ones) cannot be sent. That means I need to cancel the check out process, remove 4 books from my cart and then try and find the same books from other more expensive suppliers which can be shipped international. So I try again, adding the same book from multiple suppliers to my cart, in the hope of finding one which can deliver to Australia. Then it's back to the checkout process again.. Problem - I missed one book and have to cancel the process and go back to basket process again. Great, all books are OK, finally time to complete the order. So I get to review my order, and it says at the top something like "With an Amazon credit card, this order would be $324 rather than $368". No other total including postage is provided. So is my order $368? Maybe? Further screens finally confirm that this is the case. Nowhere is it possible to see how much postage is per book - you have to work it out yourself doing best guesses and following the Amazon formula. Maybe it would have been better to get a new book rather than a second hand book, as second hand books have twice the postage charge.. ah well, too late now, I'm not going to go through the whole process yet another time! So finally I can check out and my credit card is charged. However, since my credit card is hit by a multitude of different vendors that use Amazon as a front, within seconds of each other, some transactions are rejected as my credit card does not allow too many transactions in too short a time (some sort of security feature?). Finally, after getting a few emails from Amazon saying the card could not be charged, and then telling Amazon to retry, my order is at last paid for and on the way.

Okay, so what could be done to make this better?
  1. Allow buyers to filter their results so they only see books that can be delivered to their addresses.
  2. Do not use patronising messages like "there is a slight problem".
  3. Do not suggest that people change their delivery address to another country.. that is clearly not going to happen!
  4. Show the cost of postage all throughout the process. Book buyers know they are going to have to pay postage and want to optimise their orders taking it into account.
  5. Do not show the order total including postage for the first time as a confusing advertisement ("With an Amazon credit card, this order would be $324 rather than $368"). Instead, provide a simple breakdown in a table, including postage on each book.
  6. Hit the credit card once per order, and divvy up the money at Amazon internally, rather than allowing each book vendor to do it and having credit card rejections as a result.
UPDATE: My books arrived about two weeks after I ordered them. Delivery was smooth and on time. Unfortunately, one of the CDs that came with a book was broken. Amazon has kindly agreed to replace it and the book.

Thursday, April 12, 2007

Tips for Developing Mephisto Plugins with Liquid and Rails

When I was writing a contact form plugin for Mephisto, I had a lot of trouble finding documentation and ended up reading lots of code and experimenting. That was fun, but fairly slow, so I hope this post can save future plugin developers time, and help them avoid some of the gotchas I stumbled over.

Repository Directory Structure
At the most macro level, your repository needs to have a 'plugins' directory, and then a directory named after your plugin. Eg,
.../plugins/my_new_plugin/...
If this is not set up correctly, your plugin will not be able to be installed via 'ruby script/install plugin ' method.

Liquid Plugins Directory and Init.rb
As you probably know, Mephisto uses Liquid for page templates. Liquid can be extended with new tags/blocks. The way to do this in a plugin is to set up a 'mephisto/liquid' directory with your extensions in it. See example here. So that's great, but you also need to register it in init.rb. Here's the contact form's init.rb - check out the line about 'register_tag'.

Mephisto Plugin Class
Mephisto trunk now has a base class for plugins - Mephisto::Plugin. Inheriting from this allows you to set up routes to brand new controllers you create. See contact form example here. This opens the door to writing Mephisto plugins which do postback and processing. It is also possible to add in tabs and forms in the administration interface. Rick's feedback plugin shows how to do this.

Using Liquid Templates from Your Plugin
One of the trickiest bits was getting the plug-in controller to render a liquid template. This is important if you want your additions to Mephisto to have the same layout and colours as the rest of the site. The way I'll outline below works fine, but it is not ideal. Hopefully there is a better way to do this (eg, some sort of Liquid API for Mephisto plugins).. if you know how a better way, please let me know!

I had my plugin controller inherit from the Mephisto ApplicationController to gain access to the method 'render_liquid_template_for'. You can see the code here. However, this led to thorny problems where the plug-in classes were getting loaded only once when the server started, but Mephisto (and the ApplicationController) were getting reloaded for every request. First request worked fine, but nasty errors were spat out on the second and subsequent requests. To resolve this, I removed the plug-in from the 'load_once_paths'. You can see how to do this in the init.rb.

Models, Views & Controllers Directories and Init.rb
Okay, this is open to personal taste. I like to have similar directories in my plugin to a normal app. Eg, separate directories for controllers, model, etc. This causes a bit more work, as you need to add the extra directories to various global path variables. For an example of how to do this, take a look at 'models_path' and 'controllers_path' in this init.rb and the physical directory structure of the contact form's lib directory.

Tuesday, April 03, 2007

Improve Rails Performance Through Eternal Browser Caching of Assets

I've been working on a rails app which has got quite a number of pages that share the same two css files and 3 javascript files. However, every time I visited any page of the app, all of javascripts and css files were being loaded from the server. Not good - site was very slow. Mucking around with 'about:cache' command in Firefox revealed that the css and javascript files had expiry dates set in the past - ie, no caching of them at all. Also, all the links to sylesheets and javascript files generated by rails had ?[some long number] after them. Some research on the web revealed that this is a new rails feature for caching - the long number is a timestamp for when the asset was last modified.

Okay, so why were these assets not being cached? A quick check with wget --save-headers revealed that the web server was sending a nocache directive to the browser. This seems to be the default setup for webrick and also for my shared apache hosting on railsplayground. Considering the new rails asset management system with the ?[last modified timestamp] in the URLs, nocache seems wrong. The browser should never expire the cache since rails will handle cache invalidation by updating the asset url with a new timestamp.

So, how can we implement no/very long cache expiry? In apache, you can use mod_expires or mod_headers to do this. My shared hosting does not support mod_expires, so I went for mod_headers in my .htaccess file.

Using mod_headers:
<FilesMatch "\.*$">
Header set Cache-Control "max-age=29030400"
</FilesMatch>


OR using mod_expires:
ExpiresActive On
ExpiresDefault "access plus 1 year"

Either of the the above will set up a cache expiry time of one year for all content (best you only do this for your rails app directories).

With a cache expiry time of one year in place, my rails apps run much much faster.

Naked Economics by Charles Wheelan

Just finished reading 'Naked Economics: Undressing the dismal science'. It was a present from a friend, and I've been meaning to read it for a while. Glad I finally got around to it.

From the title, I assumed the book aimed to point out the failures of economics as a science. Not so at all - it was written by an economist and provides a high level overview of capitalism in layman's terms.

Here's some interesting questions and explanations from the book:
  • Why do we have money? So that we can indirectly swap our labour or goods for the things we want, even if the person with the things we want is not interested in our labour or goods. Without money, we would need to barter. That's fine if you're swapping chickens for rice. But what happens if you do web design, and you want meat for dinner, and the butcher does not want a website?
  • Money has value only because we all believe it does. We have faith that if we sell something (ie, convert it to the common value unit), we will then be able to swap that money for something we want.
  • Why have markets anyway? Markets produce what people want - ie, what people are willing to pay for (or at least what we are convinced into wanting through advertising etc).
  • Why not set the price of everything rather than letting it get worked out in a market? Well, it would be an enormous job, and things would not reflect the cost of production. Eg, bird flu wipes out half of the chickens in the world. There are now less chickens to go around so chicken becomes more expensive. People who really want chicken can still get it, but it costs them more. People who don't care as much or can't afford it eat fish or beef instead. If the cost of chicken was a constant mandated by the state, chicken distribution would need to be mandated in some other way. If there's not enough chicken for everyone who wants it, who should get it? First come first served? Political clout? Personal connections?
  • Markets destroy. A new way to mechanize weaving may make thousands unemployed and destroy towns and communities. But according to Wheelan, the country as a whole is better off as we are able to produce more for less cost, hence increasing our standard of living. Ie, as a consumer, you may now be able to buy a shirt for half the price.
  • In politics, small motivated groups often drive policy. Eg, the general population does not care much one way or other on a subsidy on growing alfalfa. It might cost each person in Australia 0.01c per year. However, if the subsidy was to be removed, and the alfalfa farmers would care a lot. It may well mean their livelihoods so they would demonstrate, make campaign donations, vote as a block and generally make as much fuss as possible to make sure the subsidy was not removed.
  • Why do people work in sweatshops? According to Wheelan, the pay is generally better than for other jobs available - ie, sweatshops are not the cause of the problem, rather a symptom of the general poverty and lack of opportunity in the area.
  • Why is free trade good? So that everyone can do what they do best - ie, specialise to the max. The idea being that if everyone works on what they are most good at, then productivity overall is higher.
  • Why are tariffs bad? Because they support local industries that are not viable - ie, a poor uses of resources.
  • Why do we need governments? To provide the rails for capitalism. Eg, to enforce laws (so you can't just kill me and take my stuff), to regulate the excesses of the free market and to provide goods and services that people need but that free markets will never provide. Also, to do things that individuals cannot do alone, but are in the interest of the population as a whole - eg, build infrastructure.
  • Care for the environment is a luxury good - ie, if you and your family are starving, cutting down trees to sell for food seems like a pretty good idea.
  • Companies destroy the environment because the current monetary cost of environmental destruction is usually minimal. If the full cost of environmental destruction was factored into the market (eg, companies have to pay for pollution and destruction), then environmental destruction would slow dramatically.
  • Who can create new money? The reserve bank, and it does it by buying bonds from banks with money that did not previously exist.
  • How can the reserve bank change interest rates? It can sell government bonds at its target rate and it can buy bonds (with brand new money) or sell bonds from/to trading banks to influence the amount of cash the banks have to lend. Eg, lots of cash at a trading bank means they'll lower the interest rates so as to rent out the money.
  • How come the economy can go into recession for no real reason? If people are worried, they don't spend. If people don't spend, then companies can't afford new/current investment. People get sacked and then can spend even less. People get more worried and the cycle continues.
  • Why is the level of savings in a country important? Money in the bank means it can be lent out to people who want to use it to create new businesses or expand current businesses. Access to capital allows growth.
I wouldn't take these on face value, and I would certainly question some of the assumptions on which they are based. However, they provide some interesting areas for further thought.