May 12, 2014
Table of contents:
The build up towards launching a new consumer web application is almost as important as the actual application itself. Launching to the world without any kind of groundswell will more often than not be a failure.
Building an invitation process pre-launch is certainly not a new idea. Many different types of applications have launched with a clever invite system to both success and failure.
In my opinion, one of the best features of a good invite system is the incentive for people who request an invite to share their link with their followers on Twitter and Facebook. By sharing their link they will get into the application quicker, and it’s always good to be the person who knows the new hot thing.
In this week’s tutorial I’m going to show you how to build this into the functionality of the Cribbb invitation system. If you haven’t already read the first two tutorials, go read them now!
So just to be clear, here is how I envision the Cribbb invitation system will work.
When a new user requests an invite, they submit their email address to get on the invitation list.
This will present them with a url that they can share on Twitter and Facebook to spread the word and jump the queue.
When someone clicks on the link, the referral code will be recorded in the session. If the person requests an invite too, the original invite will increase in rank.
Once I’m ready to start accepting new users, I will sort the invites
table by the number of referrals to find the next batch of users to invite.
The first thing I will do is to update the invites
table with some additional columns:
$table->string("invitation_code");
$table->string("referral_code");
$table->integer("referral_count")->default(0);
Here I’ve added a referral_code
string field and a referral_count
integer field that defaults to 0. You will also notice that I’ve renamed the code
field to invitation_code
to differentiate between the two codes. This meant I also had to update the getValidInviteByCode()
in the EloquentInviteRepository
class.
Now that I need to generate two codes for the model when it is created instead of one, I can tweak the existing boot()
and generateCode()
methods:
/**
* Register the model events
*
* @return void
*/
protected static function boot()
{
parent::boot();
static::creating(function($model) {
$model->invitation_code = $model->generateCode();
$model->referral_code = $model->generateCode();
});
}
/**
* Generate a code
*
* @return string
*/
protected function generateCode()
{
return bin2hex(openssl_random_pseudo_bytes(16));
}
This should be pretty self explanatory as I’m simply calling the generateCode()
method twice and assigning the returned value to the model’s class properties before it is saved to the database.
When a user requests an invite, they will be presented with a url in the form of https://cribbb.com?referral=referral_code. To keep things simple, I’m only going to store the referral_code
in the session from the HomeController
:
/**
* Index
*
* @return View
*/
public function index()
{
$this->storeReferralCode();
return View::make('home.index');
}
/**
* Store Referral code
*
* @return void
*/
protected function storeReferralCode()
{
if (Input::has('referral')) {
Session::put('referral_code', Input::get('referral'));
}
}
If you wanted to check for a referral code in the query string and store it in the session for every page of your application, or even just a handful of pages you would be better of moving this to either the App::before
filter or into the __construct()
method of the Controller class.
Now that the referral_code
will be stored in the session when a new user is referred, I need to update the Request
class for creating a new invitation.
Firstly, in the InviteController
update the store()
method to submit the referral_code
from the session if it is available:
/**
* Create a new invite
*
* @return Response
*/
public function store()
{
$invite = $this->requester->create(Input::all(), Session::get('referral_code', null));
}
The null
value as the second parameter to the get()
method is a default value if the value you are looking for is not available.
Next I can update the create()
method on the Requester
class:
/**
* Create a new Invite
*
* @param array $data
* @return Illuminate\Database\Eloquent\Model
*/
public function create(array $data, $referral = null)
{
if ($this->runValidationChecks($data)) {
if ($referral) {
$referer = $this->inviteRepository->getBy('referral_code', $referral)->first();
if ($referer) {
$referer->increment('referral_count');
}
}
return $this->inviteRepository->create($data);
}
}
Firstly I’ve set the $referral
as null
by default so this method does not have to be hampered by requiring a $referral
.
If the $data
passes the validation checks the method will check to see if a $referral
has been passed. Next It will attempt to find the $referer
from the $inviteRepository
by the referral_code
.
If a $referer
is found, the referrals
counter is incremented.
Finally the invitation is created in the InviteRepository
and returned.
To ensure that the process works correctly, I can use the following test:
public function testRequestSentByReferral()
{
$requester = App::make('Cribbb\Inviters\Requester');
$invite1 = $requester->create(array('email' => 'name@domain.com.com'));
$invite2 = $requester->create(array('email' => 'other@domain.com'), $invite1->referral_code);
$invite1 = Invite::find($invite1->id);
$this->assertEquals(1, count($invite1->referral_count));
}
In this test I create an invitation and then use the referral_code
to create a second invitation.
The test here is to ensure that the invitation’s referral_count
is incremented when it’s referal_code
is used to refer another user.
The final aspect of the invitation system that I wanted to build was to be able to generate a “family tree” of how user’s were invited by other users. I really like the way in Dribbble you can see which users were invited by another user.
A user will be invited by one user, and they might subsequently invited many other users. This is a classic one to many relationship, but we are linking two entities from the same table.
So the first thing to do is to add a column to the users
to store the invited_by
id.
$table->integer("invited_by")->nullable();
This column will store the id
of the user who invited the current user. A user will only have a invited_by
if they were invited by another user and so I will make this field nullable.
Again as I mentioned last week, if you haven’t shipped your application yet, just throw this line into the current users
table migration.
In order to know who invited the new user when they are registering for an account, I need to be able to track who give them the invitation. Add the following line to your invites
table migration:
$table->integer("referrer_id")->nullable();
Next I need to set the referrer_id
when an existing users invites another user.
In Inviter.php
update the create()
method to merge in the referrer_id
form the User
entity:
/**
* Create a new Invite
*
* @param array User
* @param array $data
* @return Illuminate\Database\Eloquent\Model
*/
public function create(User $user, $data)
{
$data = array_merge($data, ['referrer_id' => $user->id]);
foreach ($this->policies as $policy) {
if ($policy instanceof Policy) {
$policy->run($user);
} else {
throw new Exception("{$policy} is not an instance of Cribbb\Inviters\Policies\Policy");
}
}
if ($this->runValidationChecks($data)) {
return $this->inviteRepository->create($data);
}
}
Finally I can update the test to ensure this functionality is working correctly:
public function testUserInviteAnotherUser()
{
$inviter = App::make('Cribbb\Inviters\Inviter');
$user = User::create(['username' => 'philipbrown', 'email' => 'name@domain.com']);
$user->invitations = 1;
$invite = $inviter->create($user, ['email' => 'other@domain.com']);
$this->assertInstanceOf('Invite', $invite);
$this->assertEquals(1, $invite->referrer_id);
}
In this test I’m asserting that the referrer_id
has been set correctly.
Now when a new user uses an invitation to register with Cribbb I can take the referrer_id
and set the invited_by
column on the new user entity.
Within Cribbb I probably use these family tree like structure to display how users know each other on a user’s profile page, although there are many other possible things you could do with this kind of data.
This is the final tutorial in the mini series of building an invitation system. Hopefully over the last couple of weeks you will have seen my iterative process for creating a process such as this.
I think it’s very important to take these kinds of things one step at a time. In the first tutorial, my code was very basic, but it did do the job it was intending to do.
Over the last three weeks I’ve taken each part of the process and either updated it or introduced my desired functionality. I think when you try to bite off more than you can chew, you end up in the weeds of implementation details and you lose sight of what you are actually trying to accomplish.
By breaking it down into much smaller, achievable chunks we can make progress and end up with easier to understand and maintain code.
This is a series of posts on building an entire Open Source application called Cribbb. All of the tutorials will be free to web, and all of the code is available on GitHub.