Making Dependable #2

Christopher Pitt
Laravel 5 Tutorials
4 min readMay 27, 2015

Let’s start pulling repositories, branches and tags!

Why I’m Not Testing Yet…

We’re beginning to write code, and it’s at this point I would usually write some tests. I am a firm believer in the value of writing tests before (or at the same time) as application code.

There are a couple of reasons why I’m going to hold off on them.

Please know that my message to you is to write tests before (or at the same time) as your application code. I’m not advocating test-free code. I feel obligated to explain why this project is missing tests, because I consider testing essential.

  1. The only code we’ve written (and the code we will continue writing today) depends on OAuth. I’ve never written integration tests to test OAuth before. I could write unit tests to test everything else, but there is no value in testing ORM code right now. When I figure out how to test OAuth and refactor the application code to use a service layer, then tests will add more value to the design.
  2. Laravel 5.1 will have a greatly improved set of tools for integration testing. Right now I’m using 5.0, and I don’t want to use dev-master. I’d rather wait till a refactoring cycle to begin integration testing.

Please test your applications. When this project moves on (from a proof of concept to a real thing) then I will write tests.

Storing Repositories

Let’s figure out how to store repositories. To recap, we get a list of URLs when we connect via Socialite:

Route::get("connect/github", function () {
$data = Socialite::with("github")->user();

// ...data includes URLs
});

This happens in ~/app/Http/routes.php

Laravel\Socialite\Two\User Object
(
...
[user] => Array
(
...
[followers_url] => ...
[following_url] => ...
[gists_url] => ...
[starred_url] => ...
[subscriptions_url] => ...
[organizations_url] => ...
[repos_url] => ...
[events_url] => ...
[received_events_url] => ...
...
)
)

We don’t want to connect Github every time we want that repos_url. We also don’t want to store it in the database because it can change. That’s the point of Github returning it on every connection!

Fortunately, I can make a GET request (http://api.github.com) and will see:

{
...
user_url: "https://api.github.com/users/{user}",
...
}

I can use this to find the list of repositories (for any nickname I know). When I make a GET request to https://api.github.com/users/assertchris, I see:

{
login: "assertchris",
...
repos_url: "https://api.github.com/users/assertchris/repos",
...
}

When I make a GET request to that URL, I see a list of repositories. There are also URLs for branches and tags.

So there are many steps to get the most current list of repositories, branches, and tags. We don’t want to have to run through these steps every time. Let’s make some assumptions about how we store repositories, branches, and tags:

  1. Developers should be able to select which repositories they want to show, when building a dependency map. Think about how Travis manages this. They have switches next to each repository, and there’s a “refresh” button. We can batch the first 3 requests in this area. Repositories won’t be listed if they’re not enabled and they wont be refreshed without a developer clicking the “refresh” button.
  2. A GET request to http://api.github.com will list a repository_url. We already know the nickname and we can store the names of each repository. So we can use that URL. We can even cache the details of http://api.github.com.

So which fields should I store? I can get the repository name from Github. I also need a way to set whether the repository can be used (like that switch in Travis), and a way to label them:

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateRepositoryTable extends Migration
{
/**
* Run the migrations.
*/
public function up()
{
Schema::create("repository", function (Blueprint $table) {
$table->increments("id");
$table->string("label")->nullable();
$table->string("github_name");
$table->string("is_active")->default(false);
$table->integer("developer_id");

$table->timestamps();

$table->foreign("developer_id")
->references("id")->on("developer")
->onDelete("cascade");

});
}

/**
* Reverse the migrations.
*/
public function down()
{
Schema::dropIfExists("repository");
}
}

This happens in ~/database/migrations/
2015_05_27_000000_create_repository_table.php

Before I can import repositories, I need to be able to link them to a developer. That’s why I added the developer_id foreign key…

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Repository extends Model
{
/**
* @var string
*/
protected $table = "repository";

/**
* @var array
*/
protected $fillable = [
"label",
"github_name",
"is_active",
"developer_id",
];


/**
* @return BelongsTo
*/
public function developer()
{
return $this->belongsTo(
Developer::class, "developer_id"
);
}

}

This happens in ~/app/Models/Repository.php

/**
* @return HasMany
*/
public function repositories()
{
return $this->hasMany(
Repository::class, "developer_id"
);
}

This happens in ~/app/Models/Developer.php

There’s still work to be done before we can have a working login, so for now I’ll just reply on there being at least 1 connected developer, and see if I can implement the repository storage:

Route::get("repositories", function () {
$repositories = Developer::first()->repositories;

return view("repositories", [
"repositories" => $repositories
]);
});

This happens in ~/app/Http/routes.php

<h1>Repositories</h1>
<ol>
@foreach($repositories as $repository)
<li>{{ $repository->github_name }}</li>
@endforeach

</ol>
<a href="{{ url("repositories/refresh") }}">Refresh</a>

This happens in ~/resources/views/repositories.blade.php

When I view this page, in a browser, there’s not much to see. I haven’t started storing the repositories yet, but at least there will be a list of them when I do. Let’s fetch them from Github:

Route::get("repositories/refresh", function () {
$developer = Developer::first();

$client = new GuzzleHttp\Client();


if (!Cache::has("github.urls")) {
try {
$response = $client->get("https://api.github.com");

Cache::put("github.urls", $response->json(), 5);

} catch (Exception $e) {
abort(500);
}
}

$urls = Cache::get("github.urls");
$repositories_url = null;

try {
$url = str_replace(
"{user}",
$developer->github_nickname,
$urls["user_url"]
);

$response = $client->get($url);

$developer_urls = $response->json();

$repositories_url = $developer_urls["repos_url"];

} catch (Exception $e) {
abort(500);
}

$repositories = [];

try {
$response = $client->get($repositories_url);

$repositories = $response->json();

} catch (Exception $e) {
abort(500);
}

foreach ($repositories as $repository) {
$existing = Repository::where(
"github_name", $repository["name"]
)->first();

if (!$existing) {
Repository::create([
"github_name" => $repository["name"],
"developer_id" => $developer->id,
]);
}
}

return redirect("repositories");

});

This happens in ~/app/Http/routes.php

That sure is an ugly bunch of code. We’ll clean it up later…

--

--