Question

I have a form set up with a text input and a multi-select input. The multi-select input has a list of tags. The tags are set up as a many-to-many polymorphic relation. To help make things clear, below is an outline of my tables and models:

Tables:

org
   id - integer
   name - text
   ...

tags
   id - integer
   name - text

taggables
   tag_id - integer
   taggable_id - integer
   taggable_type - text

Models:

class Org extends Eloquent
{
    ...

    public function tags()
    {
        return $this->morphToMany('Tag', 'taggable');
    }
}


class Tag extends Eloquent
{
    ...

    public function org() 
    {
        return $this->morphedByMany('Org', 'taggable');
    }
}

In my routes.php file, I am trying to perform a search like so...

$search = Input::get('keyword');
$searchTerms = explode(' ', $search);
$query = Org::query();
$fields = array('name', 'description', 'additional_info', 'website');

foreach ($searchTerms as $term)
{
    foreach ($fields as $field)
    {
        $query->orWhere($field, 'LIKE', '%'. $term .'%');
    }
}

if(Input::get('tags'))
{
    $tags = Input::get('tags');
    $query
        ->join('taggables', 'taggables.taggable_id', '=', 'org.id')
        ->where('taggables.taggable_type', '=', 'Org')
        ->join('tags', 'taggables.tag_id', '=', 'tags.id')
        ->groupBy('org.id');

    foreach ($tags as $tag)
    {
        $query->orWhere('tags.id', '=', $tag);
    }
}

$org = $query->get();

However, this of course doesn't work. The keyword search works fine, but I am not sure how to add a query that will retrieve only the rows that have the specified associated tags. The $tags variable is an array containing the IDs of tags.

I want to return only the rows from the org table that have at least one of the tags specified in the array.

UPDATE: I have updated my code, and it kind of works, but it seems to return tags and not the associated organizations - though I feel I'm getting close. Here is the relevant code I added (or just look at it above):

$query
    ->join('taggables', 'taggables.taggable_id', '=', 'org.id')
    ->join('tags', 'taggables.tag_id', '=', 'tags.id')
    ->where('taggables.taggable_type', '=', 'Org')
    ->whereIn('tags.id', $tags);

UPDATE 2: This seems to work how I want it, but for some reason, it retrieves the name of the tag instead of the organization name...

$tags = Input::get('tags');
$query
    ->join('taggables', 'taggables.taggable_id', '=', 'org.id')
    ->where('taggables.taggable_type', '=', 'Org')
    ->join('tags', 'taggables.tag_id', '=', 'tags.id')
    ->groupBy('org.id');

foreach ($tags as $tag)
{
    $query->orWhere('tags.id', '=', $tag);
}
Was it helpful?

Solution

I got the keyword + tag search working in the following way:

$query = Org::query();

// Search by keyword(s)
if(Input::get('keyword'))
{
    $search = Input::get('keyword');
    $searchTerms = explode(' ', $search);
    $fields = array('org.name', 'org.description', 'org.additional_info', 'org.website');

    foreach ($searchTerms as $term)
    {
        foreach ($fields as $field)
        {
            $query->orWhere($field, 'LIKE', '%'. $term .'%');
        }
    }
}

// Search for tag(s)
if(Input::get('tags'))
{
    $tags = Input::get('tags');
    $query
        ->join('taggables', 'taggables.taggable_id', '=', 'org.id')
        ->where('taggables.taggable_type', '=', 'Org')
        ->whereIn('taggables.tag_id', $tags)
        ->groupBy('org.id')
        ->orderBy(DB::raw('count(*)'), 'desc')
        ->orderBy('name', 'asc');
}

$org = $query->get();

If a keyword and two tags are entered for the search (for example), this will return those organizations which have one or more of the tags, contain words which are like the entered keyword, and sort the results so that organizations with the most matching tags will appear first.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top