Archive

jQuery Datepicker – the Instance Data bug

The jQuery UI datepicker does strange things with the DOM, which causes undocumented brittleness. For instance, consider the following operations on a page that contains a single input element:

$('input').datepicker().attr('id','the-input');

This will cause no error, and clicking on the input will correctly summon the date picking dialog, but clicking on a date in that dialog will fail with the following error:

missing instance data for this datepicker

The diagnosis is quite simple: the jQuery UI datepicker stores additional “instance data” based on the id attribute of the element, so changing the id attribute manually causes that instance data to be lost. This unexpected brittleness forced me to spend some time hacking my code so that the identifier is attributed before the datepicker is enabled, but at least this solved the problem.

Two related problems would be:

  • If you have several input elements with the same identifier, and apply the datepicker on the second element, the search-by-id will return the first element and cause the same error as above.
  • If you apply the hasDatepicker CSS class on an element, and then apply the datepicker plugin, it will assume that the instance data has already been initialized, and will fail.

Having a Strong Opinion

Many blogs about technical hiring will at one point state something about buzzwords and programmer flexibility. One of the original trendsetters, Joel Spolsky, said:

The recruiters-who-use-grep, by the way, are ridiculed here, and for good reason. I have never met anyone who can do Scheme, Haskell, and C pointers who can’t pick up Java in two days, and create better Java code than people with five years of experience in Java, but try explaining that to the average HR drone.

And this is not only a point about elite languages like Scheme-Haskell-C versus mundane languages like Java-PHP-whatever : flexibility, the ability to switch languages and to adapt to new interfaces and libraries, is almost always presented as a prerequisite to being competent. John can perform miracles with PHP but cannot easily learn Ruby ? Then John is not a competent programmer, he is just a competent PHP programmer.

Maybe there is some truth to this characterization. Maybe there is indeed something about good programmers that lets them shine in a language-independent way, with languages as mere details of their day-to-day miracles. But I am vaguely uncomfortable with that notion. And not for personal reasons — my current language of choice is one of those elite functional languages that would hypothetically place me at the apex of the competence food chain.

I believe the critical element of programming competence is not ability but passion. What makes you a good programmer is how much you care about software development. Does John have a nine-to-five PHP programming job and hardly touch the computer outside of work, or does he do small projects on the side, or contribute to Open Source PHP software, or answer technical PHP questions on Stack Overflow, or perform any other number of PHP-related activities that do not have professional rewards as their main objective? Does he unconsciously try to do the right thing in his code, even though it will be harder than writing a dirty hack to make his boss happy?

I have seen people, many of them high-ranking academics, with the intellectual firepower to outgun me in any programming-related endeavor, but a striking lack of passion that let their applications crippled, hideous and unreliable. And I have no doubts that, had they cared about those things, they could have done better.

I have seen people, many of them students, with a genuine passion for software development, who would spend their free time hacking together video games or dynamic websites or clever hacks, who would notice after a while that their abilities were stagnating and, unable to improve, would give up programming rather than live with the frustration of writing software worthy of their expectations.

And when you care about programming, you tend to have strong opinions about how it should be done.

Some of these opinions are trivial. My hair stands on end whenever I have to read badly formatted code — I don’t care about the opening-brace-position flame wars, any convention is fine by me as long as it is consistently followed — and the authors often wonder why I would care about such a silly thing. I have a strong opinion about how code should look like, and I dislike working with people who do not share that opinion.

Yes, I am one of those Scheme-Haskell-C elite programmers, and I can pick up Java in a few days and outperform experienced Java-only programmers. I have done it several times in the past. And every single time I did so, I felt dirty and miserable, because Java goes against several of my opinions about what software development should be like.

In fact, I am not really surprised about the popular success of Python and Ruby on Rails — not in terms of how many projects are written, but in terms of how outspoken the technical advocates are. This is because those two have something that appeals to people who can become passionate about them : a clean core philosophy you can agree or disagree with.

Python zealots flock around the Zen of Python :

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren’t special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one– and preferably only one –obvious way to do it.
Although that way may not be obvious at first unless you’re Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it’s a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea — let’s do more of those!

Ruby on Rails fanboys have a similar set of core beliefs, the Rails Way:

DRY – “Don’t Repeat Yourself” – suggests that writing the same code over and over again is a bad thing.
Convention Over Configuration – means that Rails makes assumptions about what you want to do and how you’re going to d o it, rather than requiring you to specify every little thing through endless configuration files.
REST is the best pattern for web applications – organizing your application around resources and standard HTTP verbs is the fastest way to go.

So, if you happen to wholeheartedly agree with the Ruby on Rails way, then by using it you are certain to find both a technical environment in which you can feel happy, and a community that shares you strong opinions about software development. It is any wonder, then, that people passionate about the RoR values would flock to RoR and, inevitably, start advocating its use?

Going a little bit further, if you are hiring for your software company, would you rather hire someone with weak opinions on most topics because they are «flexible» or someone with strong opinions that match the strong opinions of your company? Given the choice, I would certainly hire the latter.

I have my own «core philosophy» that I apply to the way I write my own code. These would be, by order of decreasing importance:

  1. It is better to have a correct program with few features, than a buggy program with many features.
    If possible, take the time to design your code and your interface so that errors cannot happen. If not, explicitly detect and display all errors as they happen. If possible, have a programming language and a programming style that can eliminate by design many errors, rather than a programming language or programming style that improves productivity at the cost of having more errors.
  2. It is better to prove the correctness of a program, than to test for the existence of bugs.
    Tests cannot prove that the software is correct, they may only prove the existence of bugs. A proven program contains no bugs, there is no worry about having enough code coverage and enough test cases. This is a special case of “fail early” : better to fail at the compilation stage, than to fail during tests or at runtime.
  3. It is better to accept that code will have to be rewritten, than to future-proof a complex design.
    Future-proof code will likely be larger, and contains more untested pieces, than normal code. This increases the probability of bugs, without completely eliminating the possibility of a completely unforeseen design change that still involves a rewrite. Preparing your code for a rewrite, by splitting it up into clean independent self-documenting modules and creating automated correctness checks for these, is the best way to make it flexible.
  4. It is better to enforce data constraints through types, than to enforce it through code.
    Attempting to store data that violates the constraints fails earlier if the type cannot represent that data, especially in a statically typed language. Doing things this way might take longer than just keeping a flexible data type and performing the constraint checks in the code, but the odds of it being correct are higher.
  5. It is better to have the computer do work for you, than for you to do that work yourself.
    Why write trivial unit tests when you can harness the type system to perform those checks? Why define or configure things by hand when your framework could define or configure them for you?
  6. It is better to rewrite your code using new concepts, than to insist on using existing but ill-adapted concepts.
    Concepts improve productivity and readability, and by design will prevent some kinds of incorrect usage, but only as long as they match what the software is expected to be doing. Otherwise, at best they will be a useless weight and at worst will have to be tediously worked around to achieve anything. The size of the refactoring is no obstacle: if half the application needs to be adapted to the new concept, then so be it.
  7. It is better to repeat yourself from time to time, than to introduce too many concepts.
    Any repetition can be eliminated by adding a new abstraction through refactoring. That abstraction is usually a mere application of an existing pattern or concept, but might sometimes give flesh to a new concept. While that concept arguably already existed in the non-refactored code, it is easier to understand uncommon concepts by looking at their repeated code, than to give them a sufficiently understandable name.

I don’t know. Maybe someone might agree with me one day.
Article image © Timo Newton-Syms — Flickr

Romania – Days 10 & 11

Where we slept in Sibiu.

As you might remember, we spent two nights at the Cocosul Rosu in Sibiu. On the whole, the stay was pretty pleasant, although the lack of a host-provided breakfast had us walk all the way to the town squares in search of a restaurant that would be open early enough to accomodate us (our final choice is Timi’s, in the southwest corner of the Piata Mare). Also, do not under any circumstances open the window past nightfall, or you will let in an entire menagerie of bloodsucking insects.

On day 10, we started with a quick visit to the Brukenthal museum of art. The exhibits were correct bordering on remarkable, but the maze-like layout of the museum (as with, it seems, all museums we’ve visited in Romania so far) is extremely frustrating. Indeed, navigating involves a fair bit of backtracking, and there are often several forks both within exhibitions and between exhibitions where you will require the assistance of a museum employee, because there are no useful signs or written hints available.

Brukenthal Museum of Art

Our next stop was Brașov. As an interesting bit of trivia, the old name of the city was Corona, and so the modern beer brand Corona lavishly sponsors the city pubs, bars and restaurant terraces.

Near the city is the pass of Bran, overlooked by the castle of Bran, the kind of castle Bram Stoker would have chosen for count Dracula to live in. The castle was renovated, its walls were painted white, and flowers were planted everywhere, so whatever eerie gothic aspect Stoker could have seen there is probably gone, but the peddlers of vampire-themed trinkets right outside the castle doors are there to stay.

Entrance to Bran castle.

A room inside Bran castle.

Secret door in the Bran castle library.

Empty corridor inside Bran castle.

Inside the Bran castle courtyard.

Bran castle is by all definitions a tourist trap. The roads that lead to the city are lined for miles with housing and restaurant advertising, and the city itself is drowned in far more traffic than its single road can allow. Everything here reaches prices beyond anything else in the country. The ice cream parlour in front of the castle serves ice cream that would be costly in a posh Parisian restaurant, though I have no reason to assume that it is actually worth it.

I am lucky to be two meters tall and be able to see above the heads of the hundreds of tourists navigating the (again, maze-like and frustrating) corridors of the castle, although the low ceiling in many of the rooms could prove quite unforgiving if you ever forget it’s there.

Our bed & breakfast in Brașov.

On day 11, we visited Brașov itself. Before I post pictures, here’s some information about the layout of the city: most of the interesting pieces are in a valley in the Carpathes, with the south edge of the valley holding a cable car station and the name “BRASOV” written on the top, Hollywood-style, and the north edge of the valley holding two fortified towers (the white tower and the black tower). Inside the valley is the Council Square (Piata Sfatului) and the Black Church.

The cable car and southern ridge, seen from the Council Square.

Our first trip of the day involved the cable car. The view was so gorgeous, we forgot to take good pictures of it.

Looking down through the letter B.

After we came back down, we visited the white and black towers.

White tower seen from the southern ridge.

Black tower.

Both towers contain museums. The Black tower museum was opened every day 9-6, and we arrived at around 1 pm, so the museum was obviously closed. I swear, all the museums we try to see are closed for unspecified or ridiculous reasons when we get there.

The White tower museum was open, and therefore much more amusing.

First, let it be known that the top floor of the White tower is the best sauna in the entire city, and that is probably unintentional. The top floor of the tower has a dark wooden floor and no ceiling (it burned down in the last city-wide fire) and so it is capped with an array of glass panels that turn it into a glasshouse of sorts. You can feel the heat wrapping from your scalp down to your toes as you climb the last flight of stairs, and five minutes later you can smell yourself roasting.

I Wish.

The public employee in the White tower is responsible for selling tickets, but also has a small shop of various souvenirs, trinkets, postcards, audio CDs, pamphlets and other things that she will try to sell at any occasion, with unerring insistence. She offered me, in no more than two minutes’ time, a presentation pamphlet of the White tower in French (which was actually Spanish) for 2 lei, an unfolding image book of the tower for 7 lei, two postcards for 3 lei, an audio CD about the region for 15 lei, and a small keychain trinket for 5 lei that I did not identify.

I wish I could sell my start-up product like that.

Mountain river below the White tower.

The Black Church.

The city is also home to the Black Church, so named because it was covered in soot after the aforementioned city-wide fire. The church contains several organs collected from surrounding churches in addition to its own beautiful 4000-pipe organ. We went to a concert in the evening, involving some quite elaborate Bach and Liszt pieces.

Romania – Days 8 & 9

On day 8, we drove from Târgu Jiu to Sibiu. Alix was driving for the first half, and I caught this on the side of the road:

A Stork

We stopped for what we expected to be a quick visit at the Hurez monastery near Horezu. There were many people, as August 15th is a religious celebration. While we were visiting, three ladies came up to me, handed me a camera and asked me to take a picture of them. I obliged. We met again near the cloister, where the sisters were giving out some sweets and wine, and the three of them invited us to have lunch with them in the monastery. We agreed.

Lunch was served in the two communal dining rooms of the monastery. We ate in the smaller of the two along with maybe a dozen other people, who were all quite interested in hearing about my Romanian roots and a comparison with French culture. The food was simple: vegetable ciorba, exceptionally tender veal with mashed potatoes, and watermelon.

We were then invited to visit the private areas of the monastery — as one of the three women knew the mother superior quite well — and we enjoyed some coffee, sweets and some slices of delicious telemea cow cheese produced at the monastery, while enjoying this view:

Looking south

We were also allowed to visit the bell tower:

Looking east

And we were also shown the private chapel where the Brâncoveanu family used to pray, when they lived in the monastery, as well as one of the first printed bibles written in Romanian.

We took our leave two hours and a half later than we had expected to, but with a better lunch that we could have hoped to find in Râmnicu Vîlcea. Still, this day brought us more than just free food: knowing that people are willingly and selflessly sharing with others, expecting nothing more than a few words in return; and that the best experience is not one you can buy.

Inside the Hurez monastery

The rest of the day was spent driving up the valley of the Olt river and reaching Sibiu.

Had this on my right for around 50 km

The Bridge of Lies, in Sibiu

We are spending two nights at the Cocosul Rosu bed and breakfast (I kid you not, this means the Red Cock Rooster) before going to Brasov.

Sibiu is a Romanian city with a strong german-speaking community, beautiful medieval architecture and a much better shape than other cities we have visited so far. If I had to pick, I’d rather have Sibiu than Bucharest.

On day nine, we visited the city. The Brukenthal museum of arts was closed, but should be open again tomorrow. Instead, we visited the Brukenthal museum of history (which had a quite enjoyable exhibition about the city guilds), then toured the city under a blistering heat and unforgiving sun.

The other name of Sibiu is Hermannstadt

East view from the clock tower

West view from the clock tower

Paper model of the (old parts of the) city

A street behind the fortified wall

A small medieval street

The orthodox church

Romania – Days 6 & 7

First, the eye candy. This is how day seven ended:

Coloana Infinitului

Coloana Infinitului

On day 6, we started from Drobeta Turnu Severin and rented a car up to Baile Herculane, nested in a valley in the mountains. There, we had a late lunch at the Hotel Ferdinand restaurant, which is beautifully laid out as a series of terraces on a fairly steep slope.

Hercules

Hercules

The hot mountain springs at Baile Herculane were already known by romans (hence the name), and are still active. We tried them out.

Left: cold. Right: hot.

Then, we visited the nearby Iron Gates, a beautiful gorge on the Danube river that separates Romania from Serbia. While driving along the Danube, I actually received a welcome SMS from the Serbian branch of my mobile provider…

Serbian side of the gorge.

The penultimate narrowing on the Danube.

Sadly, the Drobeta Turnu Severin museum was closed, which included the last standing parts of Emperor Traian’s bridge (the one Romans used to bring over enough troops to seize control over Dacia).

Closed for renovation.

Day seven started in Tîrgu Jiu, northeast of Drobeta Turnu Severin. We are staying at the Hotel Restaurant Europa, which happens to have a great restaurant with a great lemonade.

Demand to see life's manager!

We drove west from the city to explore Constantin Brâncuş’s home town of Hobiţa.

Museum Sign

Wooden church erected by Constantin's grandfather.

We pushed further west to Baia de Aramă, visiting the Tismana monastery along the way, where we were caught by rain in a forested valley.

Sun + Rain + Forest

Back in Târgu Jiu, we visited the scluptural ensemble by Constantin Brâncuş dedicated to the solders who died in the Great War. It is composed of the Table of Silence (twelve stone chairs around a large round stone table) followed closely by the Gate of the Kiss (a large stone gate), then the Path of Heroes (an empty path, about 1300 m long) and finally the Column of the Infinite.

The artist designed the Path of Heroes so that a visitor sitting at the Table of Silence and looking through the Gate of the Kiss would see the Column of the Infinite in the distance. This was planned in 1935-1938. However, in 1937, king Carol II authorized the building of an orthodox church in the middle of the Path of Heroes, completely breaking the perspective.

The picture at the top of the article is one of the Column of the Infinite.

My Hat of the Infinite

Romania – Days 4 & 5

Today is our last day in Bucharest this week. The weather was sunny again, so we visited outdoor locations.

Hipster graffiti on the National History Museum.

Vlad the Impaler

Grave of Mihail Eminescu

Grave inscription: 'You. I was what you are. You will be what I am.'

Carol I gardens

This annoys me to no end.

Quite scary. Does this really help sell kids' clothing?

We dined at the Caru cu Bere

The Caru cu Bere is a beautiful location. It’s also extremely loud and filled with cigarette smoke, and the food is not significantly better than other restaurants we sampled. As its name implies, it probably serves good beer, but I did not try.

Night train to Timisoara

The train left us off at the Timisoara station at 7:25 this morning. The trip was quite pleasent, albeit a bit cold.

Timisoara, Victory Square

Our older friend.

Timisoara, Union Square

Traffic lights have timers here. This is pure unadulterated genius.

Blue Dragonfly

Green Dragonfly

We actually witnessed a blue dragonfly saving another blue dragonfly from a green one.

The lawn is absolutely great here.

Staying at the Hotel Central

Romania – Day 3

We cannot seem to find tea — the local “Ceai” always seems to be some kind of fruit- or leaf-based infusion without actual tea, even though it is labeled as “Thé” or “Tea”. I have resorted to Pepsi for my morning caffeine.

We visited the Liceul Mihai Viteazul.

Mihai Viteazul, again.

We also visited the Palace of the Parliament - unofficially 'Casa Poporului'.

National Statistics Institute.

On the wall of the National Statistics Institute.

Dâmbovița, flowing through Bucharest.

National History Museum, beautiful pieces but dreadful setup.

We dined at the Monte Carlo in the Cişmigiu gardens. The food is absolutely excellent, and waiters are orders of magnitude more polite than the ones we have in Paris.

Surprisingly, the crippling heat of the last few weeks turned to freezing downpour tonight, but we managed to stay inside most of the time. In front of the National History Museum, a team of workers that were restoring the pavement did not work yesterday because of the heat, and did not work this afternoon because of the rain. I wonder whether the pavement will be done by the time we leave.

Romania – Day 2



We ate here yesterday night.



View on modern buildings from victory square.



Association of romanian writers.



The boring Goerge Enescu museum.



University of Industrial Chemistry.



Pepsi is the leading soda brand in Romania.



Coca-Cola is fighting for brand recognition here.



The crest of Bucharest.



An apartment from the communist era.



My old elementary school.



I used to play around these parts a lot.



Titan Metro Station



James Bond's conspicuous parking spot.



A street in eastern Bucharest.



Beautiful Park in midtown Bucharest.



Artificial waterfall that fascinated me as a child.



A tree-like cement bridge.



Bust of Romanian Poet Mihail Eminescu.



It is getting late, still a lot to see.



Meat management, apparently.

Romania – Day 1

Châtelet les Halles

Roissy - Aéroport Charles de Gaulle

Otopeni - Aeroport Henri Coanda

Using OCaml from CouchDB views

What follows is directly taken from my latest GitHub project, which provides an adapter for transforming OCaml applications into CouchDB view servers. The programmer writes an OCaml application that exports one or more map and reduce functions using the API found in module CouchAdapter, and creates a CouchDB design document that specifies the application path and the name of the exported functions. The adapter server then receives evaluation requests from CouchDB and passes them to the application, and returns the result back to CouchDB.

The objective of this project is not to support writing OCaml code directly into views! The OCaml code should follow the standard build procedure, the only exception being that the CouchAdapter API is used to export that code and make it available to the adapter server.

Requirements and setup

This adapter uses json-wheel for representing JSON values, and the build process requires OCamlBuild. There are no other direct dependencies. Building the adapter server runServer is fairly straightforward: make byte or make native generates runServer.byte or runServer.native respectively. Move the resulting application to an appropriate location on your system and allow CouchDB to execute it. My suggestion is:

cp runServer.native /usr/bin/couch-ml-adapter
chmod a+x /usr/bin/couch-ml-adapter

I will be assuming this convention for the rest of this manual. Once the server is built and installed, you need to configure CouchDB to actually use that adapter to execute OCaml views. Edit the local.ini configuration file of your CouchDB server (usually found in /etc/couchdb/local.ini) and add the following lines:

[query_servers]
ocaml=/usr/bin/couch-ml-adapter

Depending on your configuration, there might already be a [query_servers] section. If that is the case, add the second line to that section. If you have trouble configuring your query servers, read the CouchDB documentation.

Errors that happen while executing the adapter will appear in the CouchDB logs (usually found in /var/log/couchdb/couch.log).

Architecture

Query Servers

The CouchDB server usually evaluates map and reduce functions only when a design document containing those functions is queried by a client, by following this process:

  • If the query server configured in local.ini is not already running, start it.
  • Send various instructions on the query server’s STDIN, such as “apply the map function F to document D”
  • Read the results on the query server’s STDOUT.

The Adapter Server

The adapter server provided by this project is one such query server. When it must apply a function to a document, it does the following:

  • Determine which application provides the function.
  • If the application is not already running, start it.
  • Send the request to the application’s STDIN, read the answer on its STDOUT.
  • If the application responds with results, send these back to CouchDB.

In short, the overall architecture looks like this:

+---------+         +------------------------+
|         | <-----> |  Haskell Query Server  |
|         |         +------------------------+
|         |
|         |         +------------------------+
| CouchDB | <-----> | Brainfuck Query Server |
|         |         +------------------------+
|         |
|         |         +------------------------+
|         | <-----> |                        | <-----> [ Application /home/nicollet/test ]
+---------+         |  OCaml Adapter Server  |
                    |                        | <-----> [ Application /usr/bin/foo ]
                    +------------------------+

The programmer should therefore write an application which reads the adapter requests on STDIN, runs the requested functions on the provided documents, and sends the results back on STDOUT. All the boilerplate involved is handled by the CouchAdapter module, so that the actual development process you will be following is:

  • Include any modules you might need to use in your view.
  • Define the map or reduce function as an OCaml function.
  • Register that function as being exported with CouchAdapter.export_map and CouchAdapter.export_reduce.
  • Call CouchAdapter.export()

Importing From CouchDB

CouchDB references map and reduce functions in design documents, using the following syntax:

{ "_id" : "_design/..."  ,
  "language" : "...",
  "views" : {
    "foobar" : { "map" : ... }
    "quxbaz" : { "map" : ... , "reduce" : ... }
  }
}

In order to use the OCaml adapter, one must first set the language property to "ocaml". Then, to reference the function "extract_foo" defined in application /usr/bin/foo, one would write:

"views" : {
  "foobar" : { "map" : ["/usr/bin/foo", 1, "extract_foo"] }
}

The same syntax applies for reduce functions as well. The three components of the definition are 1- the absolute path to the application that exports the function (this is how the adapter server knows what application to run), 2- a version number discussed in the next section and 3- the name under which the function is exported from that application.

Function versions

For performance reasons, once an application or query server has been started, it is never shut down. This only causes problems when there’s a new version of the code that needs to be deployed. The adapter server provides a versioning system which automatically detects that a function.

A CouchDB design document requests a function that is at least a certain version. For instance, ["/usr/bin/foo", 42, "extract_foo"] indicates that the adapter server should find version 42 or greater of the function "extract_foo" exported by application usr/bin/foo. If that application is currently running and the function is either missing or older than version 42 then the application is shut down and started anew in a completely transparent fashion.

Note that if rebooting the application still fails to provide an appropriate version of the function, the adapter server will report an error, which CouchDB will propagate to the client. This makes all the views inside the design document unavailable until an appropriate version of the application is deployed.

Failing to manage function versions both in CouchDB and in the application can lead to data inconsistencies, as different documents are processed by different versions of the same function. Only a global version change which prompts a full refresh of the view and reloads the application can ensure data consistency in the face of code changes.

Creating a map function

A map function must follow the signature json -> (json * json) list: the argument is the entire document being processed, and the output is a list of key, value pairs being output by the map function.

For example, suppose you already have an User module in your application, which is used among other things for reading and writing users to the CouchDB database:

type t = {
  active  : bool ;
  name    : string ;
  email   : string ;
  picture : string
}

let of_json = (* ... *)
let to_json = (* ... *)

Then you can rely on that module to define a map function with the above signature, and export it using the CouchAdapter module:

open Json_type

let user_by_email json =
  try let user = User.of_json json in
      [ String user.User.email , Null ]
  with _ -> []

let () =             

  CouchAdapter.export_map
    ~name:"user_by_email"
    ~version:1
    ~body:user_by_email ;

  CouchAdapter.export ()

Should you decide to update the view code, make sure that you also increment the version number:

open Json_type

let user_by_email json =
  try let user = User.of_json json in
      if user.User.active then [ String user.User.email , Null ]
  else []
  with _ -> []

let () =             

  CouchAdapter.export_map
    ~name:"user_by_email"
    ~version:2
    ~body:user_by_email ;

  CouchAdapter.export ()

Creating a reduce function

There is no distinction made between reduce and rereduce. While this causes a slight loss in functionality it also makes writing reduce functions less arduous given the OCaml type system. The signature of reduce functions is simply json list -> json.

For example, let’s assume that an Article module is already defined in your main application:

type t = {
  title : string ;
  html  : string ;
  tags  : string list
}

let of_json = (* ... *)
let to_json = (* ... *)

We now define a map function and a reduce function that counts how many articles are published for every tag.

let by_tag_map json =
  try let article = Article.of_json json in
      List.map (fun tag -> String tag , Int 1) article.Article.tags
  with _ -> []

let by_tag_reduce json =
  Int (List.fold_left (fun acc -> function Int i -> acc + i | _ -> acc) 0 json)

let () =
  CouchAdapter.export_map "by_tag-map" 1 by_tag_map ;
  CouchAdapter.export_reduce "by_tag-reduce" 1 by_tag_reduce ;
  CouchAdapter.export ()

And the CouchDB design document is as follows:

{ "_id" : "_design/article",
  "language" : "ocaml",
  "views" : {
    "by_tag" : { "map"    : ["/path/to/app", 1, "by_tag-map"    ],
                 "reduce" : ["/path/to/app", 1, "by_tag-reduce" ] }
  }
}

Article image © Miriam Rossignoli — Flickr



1150 feed subscribers
(readers who polled a feed this week)