Skip to content

Commit

Permalink
feature/more-accurate-related-services (#271)
Browse files Browse the repository at this point in the history
* Added depth column to database ERD

* Added migration for creating depth column

* Taxonomy depths calculated in migration

* Added controller logic for taxnomy depth

* Added taxonomy depth to existing unit tests

* Child taxonomy depths updated when parent is changed

* Added test case for sorting related service by lower level taxonomies

Test currently failing as logic still needs to be implemented.

* Added ordering in RelatedController

* Linted code

* Fixed parent call
  • Loading branch information
matthew-inamdar authored May 15, 2020
1 parent c01f3c1 commit 57a8320
Show file tree
Hide file tree
Showing 11 changed files with 422 additions and 56 deletions.
50 changes: 37 additions & 13 deletions app/Http/Controllers/Core/V1/Service/RelatedController.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,36 +25,60 @@ public function __invoke(Request $request, Service $service)
* 1. Eager load needed relationships.
* 2. Filter out the service which they are related to.
* 3. Filter out services with less than 3 taxonomies in common.
* 4. Order by number of taxonomies in common.
* 4. Order by number of taxonomies in common from depths 6 to 1.
* 5. Order by distance (if location provided in request).
* 6. Order by service name.
*/
$taxonomyIds = $service->taxonomies()->pluck('taxonomies.id');
$taxonomyIdsPlaceholder = implode(
',',
mb_str_split(
str_repeat('?', count($taxonomyIds))
)
);

$baseQuery = Service::query()
->where('services.status', '=', Service::STATUS_ACTIVE)
->with('serviceCriterion', 'usefulInfos', 'socialMedias', 'serviceGalleryItems.file', 'taxonomies')
->where('services.id', '!=', $service->id)
->whereHas('serviceTaxonomies', function (Builder $query) use ($service) {
->whereHas('serviceTaxonomies', function (Builder $query) use ($taxonomyIds) {
$query->whereIn(
'service_taxonomies.taxonomy_id',
$service->taxonomies()->pluck('taxonomies.id')
$taxonomyIds
);
}, '>=', 3)
->orderByRaw(
"(SELECT COUNT(*) FROM `service_taxonomies` WHERE `service_taxonomies`.`taxonomy_id` IN ('?')) DESC",
$service->taxonomies()->pluck('taxonomies.id')->implode("','")
)
}, '>=', 3);

foreach (range(6, 1) as $depth) {
$baseQuery->orderByRaw(
"(
SELECT COUNT(*)
FROM `service_taxonomies`
WHERE `service_taxonomies`.`service_id` = `services`.`id`
AND `service_taxonomies`.`taxonomy_id` IN ({$taxonomyIdsPlaceholder})
AND (
SELECT `taxonomies`.`depth`
FROM `taxonomies`
WHERE `taxonomies`.`id` = `service_taxonomies`.`taxonomy_id`
LIMIT 1
) = ?
) DESC",
array_merge($taxonomyIds->all(), [$depth])
);
}

$baseQuery
->when($request->has('location'), function (Builder $query) use ($request) {
$location = new Coordinate(
$request->input('location.lat'),
$request->input('location.lon')
);

$sql = '(ACOS(
COS(RADIANS(?)) *
COS(RADIANS(`services`.`lat`)) *
COS(RADIANS(`services`.`lon`) - RADIANS(?)) +
SIN(RADIANS(?)) *
SIN(RADIANS(`services`.`lat`))
COS(RADIANS(?)) *
COS(RADIANS(`services`.`lat`)) *
COS(RADIANS(`services`.`lon`) - RADIANS(?)) +
SIN(RADIANS(?)) *
SIN(RADIANS(`services`.`lat`))
))';

$query->orderByRaw($sql, [$location->lat(), $location->lon(), $location->lat()]);
Expand Down
18 changes: 16 additions & 2 deletions app/Http/Controllers/Core/V1/TaxonomyCategoryController.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,19 @@ public function index(IndexRequest $request)
public function store(StoreRequest $request)
{
return DB::transaction(function () use ($request) {
$parent = $request->filled('parent_id')
? Taxonomy::query()->findOrFail($request->parent_id)
: Taxonomy::category();

$category = Taxonomy::create([
'parent_id' => $request->parent_id ?? Taxonomy::category()->id,
'parent_id' => $parent->id,
'name' => $request->name,
'order' => $request->order,
'depth' => 0, // Placeholder
]);

$category->updateDepth();

event(EndpointHit::onCreate($request, "Created taxonomy category [{$category->id}]", $category));

return new TaxonomyCategoryResource($category);
Expand Down Expand Up @@ -97,12 +104,19 @@ public function show(ShowRequest $request, Taxonomy $taxonomy)
public function update(UpdateRequest $request, Taxonomy $taxonomy)
{
return DB::transaction(function () use ($request, $taxonomy) {
$parent = $request->filled('parent_id')
? Taxonomy::query()->findOrFail($request->parent_id)
: Taxonomy::category();

$taxonomy->update([
'parent_id' => $request->parent_id ?? Taxonomy::category()->id,
'parent_id' => $parent->id,
'name' => $request->name,
'order' => $request->order,
'depth' => 0, // Placeholder
]);

$taxonomy->updateDepth();

event(EndpointHit::onUpdate($request, "Updated taxonomy category [{$taxonomy->id}]", $taxonomy));

return new TaxonomyCategoryResource($taxonomy);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public function store(StoreRequest $request)
$organisation = Taxonomy::organisation()->children()->create([
'name' => $request->name,
'order' => $request->order,
'depth' => 1,
]);

event(EndpointHit::onCreate($request, "Created taxonomy organisation [{$organisation->id}]", $organisation));
Expand Down
26 changes: 26 additions & 0 deletions app/Models/Taxonomy.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,30 @@ public function touchServices(): Taxonomy

return $this;
}

/**
* @return int
*/
protected function getDepth(): int
{
if ($this->parent_id === null) {
return 0;
}

return 1 + $this->parent->getDepth();
}

/**
* @return self
*/
public function updateDepth(): self
{
$this->update(['depth' => $this->getDepth()]);

$this->children()->each(function (Taxonomy $child) {
$child->updateDepth();
});

return $this;
}
}
Binary file modified database/diagrams/ERD.mwb
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

use App\Models\Taxonomy;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

class AddDepthColumnToTaxonomiesTable extends Migration
{
/**
* Run the migrations.
*/
public function up()
{
Schema::table('taxonomies', function (Blueprint $table) {
$table->unsignedInteger('depth')
->default(0)
->after('order')
->index();
});

// Remove the default value.
Schema::table('taxonomies', function (Blueprint $table) {
$table->unsignedInteger('depth')
->default(null)
->change();
});

// Calculate the depth for all.
DB::table('taxonomies')->orderBy('id')->chunk(
200,
function (Collection $taxonomies) {
$taxonomies->each(function (stdClass $taxonomy) {
DB::table('taxonomies')
->where('id', '=', $taxonomy->id)
->update([
'depth' => $this->getDepth($taxonomy),
]);
});
}
);
}

/**
* @param \stdClass $taxonomy
* @return int
*/
protected function getDepth(stdClass $taxonomy): int
{
if ($taxonomy->parent_id === null) {
return 0;
}

$parentTaxonomy = DB::table('taxonomies')
->where('id', '=', $taxonomy->parent_id)
->first();

return 1 + $this->getDepth($parentTaxonomy);
}

/**
* Reverse the migrations.
*/
public function down()
{
Schema::table('taxonomies', function (Blueprint $table) {
$table->dropIndex(['depth']);
$table->dropColumn('depth');
});
}
}
5 changes: 5 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ services:
MYSQL_PASSWORD: "${DB_PASS}"
networks:
- app-net
command: mysqld --general-log=1 --general-log-file=/var/log/mysql/general-log.log

redis:
image: grokzen/redis-cluster:5.0.5
Expand All @@ -79,6 +80,8 @@ services:
image: docker.elastic.co/elasticsearch/elasticsearch:6.3.2
environment:
discovery-type: single-node
volumes:
- elasticsearch-data:/usr/share/elasticsearch/data
networks:
- app-net
ports:
Expand All @@ -102,3 +105,5 @@ volumes:
driver: local
redis-data:
driver: local
elasticsearch-data:
driver: local
Loading

0 comments on commit 57a8320

Please sign in to comment.