Structuring Reviews

Reviews have been on my to-do list for a long, long, looong time. Back on version two of theAdhocracy I would compile monthly review roundups and post them that way, but it was an imperfect system fraught with irritation. When I migrated the site to Craft during the version three redesign, I set up a whole content type for reviews and began fleshing it out. That's where, for the past (almost exactly) year, I've been writing reviews of the films, TV shows, books (hah), and video games that I've variously consumed. The big issue? No one else can see them. That's still the case, but today I took a big step towards creating a solution: I set up the API endpoint for my reviews. *Fist punch*

The problem with media these days

The main reason that's taken so long is that media categorisation is a lot more complex than I initially thought. On the face of it, I want to write a review and publish it to a page with a URL that looks something like:

/review/avengers-end-game

So when I set up the original content type, I gave it a category field for "media type" and a title and figured that was that. But when I began reviewing my old MiM roundups I realised I also tracked when I re-watched something. Craft has the Matrix field, which effectively allows you to set up a reusable, repeatable block of fields within a piece of content. So I moved all the review fields into a Matrix and then every time I wanted to add a rewatch I just used a new Matrix block. That worked.

Well, it did until I came to add a review for the second season of a TV show... oh! Should that now be a whole new entry? If so, would it be under /review/game-of-thrones-season-two or would it be better under /review/game-of-thrones/season-two? Or do I want all seasons of a show to just be under one entry i.e. /review/game-of-thrones? The last option is definitely the easiest to find by URL modification and it means that you aren't stuck with the slightly annoying issue of season one shows being the "main URL" and everything else having "season" in them. Plus, I already had Matrix blocks, so I could piggyback off that, right? Right.

So I added a "season" field. But then I began reading the Game of Thrones books and suddenly I had two more issues:

  1. I want to group the books together into a series, but that won't fit a numbered sequence like TV seasons;
  2. Shouldn't the books also go under /review/game-of-thrones? Or does that need to be game-of-thrones-books? The books came first, so they should definitely get disambiguation priority, but the reviews are already written and so the URL is effectively taken.

On top of which, what about things like Robin Hood. That could be referencing one of several books, several films, several TV shows, or even a graphic novel! Right, first things first, URL structure solves a lot of this, let's just add the content type there:

/review/film/robin-hood
/review/tv/robin-hood

As for multiples of the same media type, I'll just try and be good about it, so if I know there's a more popular version I'll name the entry correspondingly, even if there isn't a review for the more popular item i.e. /film/robin-hood-2018. For anything else, it'll operate on a first-come, first-served basis. That sorted out the URL confusion, but what about the series/season issue? Looks like a new field is needed, so I added a "series" field and (later) an "expansion" field for video games to operate in the same way as "season" would. Yes, it's getting a bit complex, but there we go.

At this point, I could cope well enough with various media types and group things like TV seasons or video game expansions all under a single entry in the CMS, which felt right (and still does). And then I rewatched the entirety of Brooklyn 99 and... what on earth do I do now?! Sure, in the CMS it all still makes some sense:

  • I have an entry for Brooklyn 99 which contains four reviews, one for each season (at that point) of the show;
  • Each review has a "season" field that states which season it is;
  • I can just add four more reviews and continue using the "season" field to be specific and then have a toggle field that states "this is a rewatch" called "rewatch".

Obviously, my reviews were just living in the CMS, so it felt okay to keep extending out the fields like this, but it was getting near impossible to work out how I would ever convert that data structure into a useable API endpoint. By this stage, I'd need to query each entry for the media type, then pass it to a specific logic flow. For stuff like TV shows, that would need to first sort an array of reviews into season arrays, including any rewatches, then sort those again so that rewatch reviews were clearly demarcated. There'd need to be similar (yet different) logic in place for the other "grouping" fields, like series and expansion. It was (very) messy, but probably still doable.

Except... on top of all of that, there are other, broader categories that media would want to fit into. For example, take the MCU. They're sort of sequels to one another, but you also have literal sequels within the greater group (Iron Man, Iron Man 2, Iron Man 3) and then they're also technically sorted into phases. And anyway, if I ever wanted to create a single page for the MCU it would require a huge number of tabs or subdivisions. And what about films which are both related and not, things like spin-offs or side-quels (*shudder*), or the fact that technically Agents of S.H.I.E.L.D and the Netflix superheroes are all parts of the MCU, but then you're mixing media types...

Reviewing my review structure

So I gave up. For almost a year I just continued using a system I knew would never work but which was all I had. However, considering the extra spare time I now have and the huge amount of work I've been doing on the rest of the content here, I decided to sit down and sketch out, roughly, how I would want a review API to work. Here's the structure I figured made the most sense, taking into account every pitfall I've come across[1]:

{
    type
    title
    desc
    overallRating
    series [
        seriesTitle
        review
        rating
        date
        information [...]
        rewatch [
            review
            rating
            date
        ]
    ]
    collections [
        collectionTitle
        desc
    ]
}

That's it, pretty much. The information array is a reserved space for any additional, media-type-specific stuff that I like to track and largely not relevant, but the rest is actually fairly simple. I'm sure there's a better name for series but it felt the most inclusive and will gather together season, expansion, and (of course) series into one place. And yes, this means even solitary reviews – such as films with no wider universe or sequels or reboots or anything complicated – even those will be sat within a series array. Sure, that's a little redundant but, if anything, it probably makes the frontend implementation slightly simpler. At least my typing will be consistent that way.

The least fleshed out part was the collections array, which I wanted to work a bit like a tagging system. I'd create a new collection, likely as its own content type, and then assign specific pieces of media to it. That would give me the flexibility to have multiple media types within a single category and to create groups like the MCU under a single URL if I wanted.

Making plans into reality

Understandably, actually making that structure a reality was a little difficult. After all, if it was obvious I'd probably have hit on it first time around. The largest hurdle was the nesting of a rewatch array within the series array. For that to work, I needed to rethink the way I was using the Matrix field; effectively, rewatch reviews needed to be physically a part of the original review, to allow for seasons etc. Effectively, my structure demanding a repeatable fieldset within a repeatable fieldset, or nested Matrix fields. Unfortunately, right now Craft doesn't allow Matrix nesting (though it's planned for Craft 4). Fortunately, there are some community-made solutions, including the wonderful SuperTable plugin, which can fill the gap until then. I'm still not sure why SuperTable is free but I'm very grateful, because it solved my problem perfectly.

I'd actually toyed around with using a native table field already, with columns for each of the necessary fields. It worked, but it didn't work well. Table fields in Craft won't accept rich text, so formatting and useful things like emojis were out, plus the cell output would have required a whitespace conversion. A minor issue with an easy fix, but still work that I'd preferably avoid.

SuperTable cuts past all of those issues by allowing you to use any field type within a table cell, including other plugins like Redactor, my rich-text editor. Better yet, whilst it's likely still a table under the hood (isn't everything in a database?) you can display a SuperTable field like a native Matrix, effectively mimicking the workflow I'm already used to. It took a little bit of finessing to get it to play nicely with the Elements API plugin I use to generate endpoints, but with a little help I got it into a useable format.

Once that was ticked off, I had to go back over my existing reviews and move data around into the new format, then update a few of the remaining fields to better fit the new structure and the API endpoint was almost done.

The final step was working out collections. As I've said above, I thought the easiest way to do this would be through related entries and a distinct "Collection" content type. I was wrong. It's a system which would work and has some merits, but ultimately it's a bad user pattern on Craft's end. Creating the content type is simple and once you have one, quickly assigning a large number of reviews to it through a Related Entry field is very easy, but actually using it in an ongoing manner is problematic. Let's say I've got an "MCU" collection and have just been to see Black Widow, so I create a new review entry and write it up. Obviously, I want to add that review to my collection, but to do so I have to:

  1. Save the review;
  2. Go to the Collections entry panel and find the MCU entry;
  3. Open it and add the Black Widow review to its list of related entries;
  4. Save the Collection.

That's a lot of steps. It would be better if I could just select the Collection from the review. Unfortunately, Craft doesn't have that kind of parent/child field (at least, not as far as I can see) which is a shame. However it does have categories, and these can both be assigned from an entry and even edited/created from there too, making it a pretty seamless workflow. Categories do have the opposite issue, which is that if you want to retroactively create a collection and assign a number of reviews to it, you'd need to edit each review in turn. That's a shame and a genuine negative to this setup, but it's more easily worked around with the help of bulk editing plugins so it's what I went with.

So, either way I'd need to create a new API endpoint for collections, so on that front the two techniques are identical. Actually, the category one seems to be a little easier in some ways here too. With that set up I now had two endpoints which let me see all my reviews and all my collections respectively, and then within those endpoints you can get the information of which reviews are stored in each collection and vice versa. Perfect. I guess all that leaves me with is creating the actual frontend... no big deal then 😉

Explore Other Articles

Older

Thoughts on Disney+

Two months after Disney+ launched I'm still a huge fan of the content catalogue but swing between feeling bemused and exasperated at the actual experience of using the service.

Further Reading & Sources

Conversation

Want to take part?

Comments are powered by Webmentions; if you know what that means, do your thing 👍

Footnotes

  • <p>It turns out that there are a lot of gotchas to creating a clear category structure for media reviews. Well, after a year of messing around with various setups behind the scenes, I think I'm starting to get somewhere.</p>
  • Murray Champernowne.
Article permalink