You have already done everything!
plot(ra, col=ra@data $COLOUR)
Or even as @Spacedman suggested:
plot(ra, col=ra$COLOUR)
What is it!

And if you want to get rid of the borders of the polygon:
plot(ra, col=ra$COLOUR, border=NA)

Edit : An attempt to explain this behavior:
According to ?SpatialPointsDataFrame :
SpatialPolygonsDataFrame with matching identifiers by default checks the row names of the data frame on the polygon identifier slots. Then they must be consistent with each other and be unique (Polygons objects cannot share identifiers); rows of data frames will be redirected, if necessary, to match the identifiers of the polygons.
The value that the polygons are ordered according to their identifier and, therefore, are in the order of the rows of the data frame in the @data slot.
Now, if you look at the plot.SpatialPolygons function (using getAnywhere(plot.SpatialPolygons) ), at some point there are lines like this:
... polys <- slot(x, "polygons") pO <- slot(x, "plotOrder") if (!is.null(density)) { if (missing(col)) col <- par("fg") if (length(col) != n) col <- rep(col, n, n) if (length(density) != n) density <- rep(density, n, n) if (length(angle) != n) angle <- rep(angle, n, n) for (j in pO) .polygonRingHoles(polys[[j]], border = border[j], xpd = xpd, density = density[j], angle = angle[j], col = col[j], pbg = pbg, lty = lty, ...) } ...
The vector entered in col is in the same order as the polygons slot and, therefore, as an ID. plotOrder used to index all of them in the same way.