Like so many other things in life, starting a new feature is the hardest part. We're creating something out of nothing. It's not an easy task to define how that "something" should appear. But we'll try. Let's begin with one example of how the system should behave, using TDD.
Let's drop down a level and write a test to ensure that, when experience is awarded to a user, an announcement (or event) is made to the rest of our application. Later, we'll listen for this announcement and grant any necessary achievement badges to the user.
It sounds like we'll need an
Achievement Eloquent model, as well as its associated database table. Let's use TDD to construct both.
In this episode, we'll construct the relationship between a user and their respective achievements. As you'll see, this is a prime use-case for a generic pivot table.
Leading up to this episode, I've been doing some thinking. Yes, we can store achievement badges within the database, but where do we put the logic that determines if the given user should be granted that badge? This led me to considering a different path we might consider. In this episode, let's tinker around with that idea. If we like it, we'll keep it. If we don't, we'll revert!
In the previous episode, we tinkered with the idea of not using a database at all for determining and fetching a user's acquired achievement badges. Let's talk about that refactor - including the pros and cons to such an approach - before ultimately reverting in favor of a better solution for our needs.
Now that we've reverted the previous lesson's work, we can move on to a clean approach. In this lesson, we'll build the necessary system to register and sync achievements with any given user, based on newly awarded experience.
In this episode, we'll discuss two equally valid ways to organize all of the achievement-related files, including service providers, events, listeners, etc.
In this lesson, let's use TDD to provide a sensible default
name for each achievement, based upon the class name.
It might be nice to offer an Artisan command to quickly scaffold new achievement classes. Though it provides only a small speed boost, conveniences like this are always useful and appreciated.
When we glance at our
app/Achievements directory, it's difficult to tell which of the classes are achievement types that can be awarded to a user. Let's move all of those related files to their own directory in this lesson.
Much of this feature is now working, so let's finally view it in the browser. We'll start by preparing a few sample achievement badges, and then seeding and fetching a user's achievements. Once we have this information, it should be as simple as looping through all available achievements, and toggling a CSS class that indicates if the current user has been awarded the related badge.
In this lesson, we'll construct a view that displays which achievements the current user has and has not been awarded.
Once we deploy this new feature, initially, it will appear as if all users have zero achievements. This is because the logic that syncs a user's achievements does not take place until they've accrued new experience points. To fix this, let's use TDD to construct an Artisan command to seed all user achievements in our system.
I will now begin constructing the actual achievement badges that you can earn here at Laracasts. The badge designs are ready, so come along as I add them.
In the previous episode, we noticed that an achievement's icon file name is almost always the kebab-case version of the class name. With that in mind, let's make that the default. Next, we'll turn the achievement's description property into a dedicated method.
Did you notice that we have a pesky N+1 issue at the moment? Let's resolve it in this episode by caching all achievements forever. If at some point in the future we need to add a new achievement, we'll simply make a note to clear out the cache during our deployment process.
In some situations, you may find it beneficial to return a custom collection class for your various Eloquent queries. No problem: overriding the
newCollection method on your model makes this task a cinch to complete.
At the moment, all achievements exist on the same playing field, which we designate using a blue background. Let's add the necessary logic to specify a beginner, intermediate, or advanced skill level for each achievement type. Then, in our UI, we can visualize these with yellow, blue, and red background colors, respectively.
I'd like to sort all achievements from beginner to advanced. It sounds like we'll need custom sorting logic. Let's tackle that in this lesson by building a lookup table to translate each skill level to a respective number. Finally, we'll finish the lesson by discussing object responsibility and where logic like this should live.
Take a moment to think how much we've covered in this series. These techniques can be applied to any new component or feature that you're working on. I hope you've found a technique or two to throw into your toolbelt. Thanks for coming along!