---
# System prepended metadata

title: Candidate Grouping
tags: [Devex]

---

---
title: Candidate Grouping
tags: Devex
---

# Candidate Groupings

## Intro

- Curated Candidate Lists (aka `CL`)
- Candidate Folders (aka `CF`)

Nowadays CL's and CF's are `Tags`/`Taggins`, the proposal is to split the logic on this two to help maintanability and makign it easier to add features in the future. At the begginig they seamed nearly the same, but we've concluded that it is better to separate them.

So to proceed, we'll need to create 2 new endpoints and deprecate the taggins one:

- `api/curated_lists` (and for the admin?)
- `api/folders` (and for the admin?)


These endpoints will point to the two newly created controllers.

This change comes also with changes in the database level, so new tables will be created for these two new models, and the existing data will be migrated.

## Models

### `app/models/tagging.rb`

```rb
# Table name: ng_taggings

id            :integer          not null, primary key
tag_type      :string(255)      default("Folder")
taggable_type :string(255)
created_at    :datetime
updated_at    :datetime
ng_tag_id     :integer
taggable_id   :integer
```

#### `app/models/tag.rb`

```rb
# Table name: ng_tags

id             :integer          not null, primary key
curated        :boolean          default(FALSE)
description    :text
is_favorite    :boolean          default(FALSE)
name           :string(255)
shared         :boolean          default(TRUE)
taggings_count :integer
created_at     :datetime
updated_at     :datetime
company_id     :integer          not null
consultant_id  :integer
```

## Questions

- Differences and similarities between CL and CF


> :green_heart: same
> :red_circle: different

|              | similarity    | Folders      | Curated Candidate Lists | 
| ------------ | ------------- |------------- | ----------------------- |
| Visibility   | :red_circle:  | Private      | Public                  |
| Managed by   | :red_circle:  | Themselves   | Devex                   |
| Lists        | :green_heart: | Candidates   | Candidates              |
| CA Card      | :red_circle:  | not visible  | Visible                 |
| Association  | :green_heart: | many-to-many | many-to-many            |

- When searching candidates, RA will need a new filter for CCL's (FE)?
- Candidate API response will split taggins in json into Folders and CCL
- CRUD'S:
    - `:create`
    - `:update`
    - `:delete`
    - `:add_candidates_to(Groupable)`
    - `:remove_candidates_from(Groupable)` 
- Which are the current endpoints?
    ```rb
         POST /public/current_user/taggings(.json)
    DELETE /public/current_user/taggings(.json)
       GET /public/current_user/taggings(.json)
      POST /public/current_user/tags(.json)
       GET /public/current_user/tags(.json)
    DELETE /public/current_user/tags/:slug(.json)
     PATCH /public/current_user/tags/:slug(.json)
       GET /public/current_user/tags/:slug(.json)
       GET /public/current_user/tags/curated(.json)
       GET /public/current_user/tags/editable(.json)
    ```
    
- How/where are the CCL going to be managed? a New admin section?

## The BE release plan

1. create a **FeatureFlag** `split_candidate_taggins_into_folders_and_curated_lists`
2. Create **Controllers** that proxy the existing logic.
3. Create or modify new **endpoints**.
4. tackle **`CuratedList`**:
    1. Create new **model** `CuratedList`
    2. Create "shared logic" **helpers/concers** (`:add_to`, `:remove_from`)
    3. Refactor **`CuratedListController`** to use its own logic
    4. Rake task to migrate (copy) all `Tag.curated` data to `CuratedList`.
    5. FE point to new CuratedList `endpoints`
5. tackle **`Folder`**:
    1. Create new **model** `Folder`
    3. Refactor **`FoldersController`** to use its own logic
    4. Rake task to migrate (copy) all `Tag.folder` (new scope) data to `Folder`.
    5. FE point to new Folder `endpoints`
6. Clean up legacy:
    1. delete `Tag/Taggin` data
    2. remove unused code: models, controllers ...
    3. clean FF`split_candidate_taggins_into_folders_and_curated_lists`

## The 'NEW' Model associations

As said above, all model associations must be many-to-many.
Only the relevant association declarations are shown.

### The `CuratedList` Model Proposal

We set a [has_many :through association](https://guides.rubyonrails.org/association_basics.html#the-has-many-through-association) to set up a many-to-many connection with `Consultant` model as *candidates*. 
There's no need to associate with a `Company` or `Recruiter` since only Devexers can manage the lists, and can be done with the existing permission system.
This association indicates that the `CuratedList` model can be matched with zero or more instances of `Consultant` by proceeding through `ConsultantCuratedList` model.

```rb 
class CuratedList < ActiveRecord::Base
  has_many :consultant_curated_lists
  has_many :candidates, through: :consultant_curated_lists, source: :consultant
end

# in between 
class ConsultantCuratedList < ActiveRecord::Base
  belongs_to :curated_list
  belongs_to :consultant
end


class Consultant < ActiveRecord::Base
  has_many :consultant_curated_lists
  has_many :curated_lists, through: :consultant_curated_lists
end
```
### The `Folder` Model Proposal

Both the Recruiter and candidates come from the same model: `Consultant` but have different roles, to tackle this we can have different approaches:

- option 1: a single join table for both roles.
- option 2: each role has its own join table. 

#### Option 1, one join table for Recruiters & Candidates

```rb 
class Folder < ActiveRecord::Base
  belongs_to :company
  belongs_to :consultant

  has_many :consultant_folders
  has_many :consultants, through: :consultant_folders
  
  has_many :consultant_folder_recruiters, -> { recruiters }, class_name: "ConsultantFolder"
  has_many :consultant_folder_candidates, -> { candidates }, class_name: "ConsultantFolder"

  has_many :recruiters, through: :consultant_folder_recruiters, source: :consultant
  has_many :candidates, through: :consultant_folder_candidates, source: :consultant
  
  def shared?
    company.present?
  end
  
  def personal?
    # favorite folder
    consultant.present?
  end
end

# in between 
class ConsultantFolder < ActiveRecord::Base
  # role           :string(255)
  # is_owner :boolean
  
  belongs_to :folder
  belongs_to :consultant
  validates :role, inclusion: { in: ["recruiter", "candidate"] }

  scope :recruiters, -> { where(role: "recruiter") }
  scope :candidates, -> { where(role: "candidate") }
end

class Consultant < ActiveRecord::Base
  has_many :consultant_folders
  has_many :folders, through: :consultant_folders
end
```

#### Option 2, two join table for each role

```rb 
class Folder < ActiveRecord::Base
  belongs_to :company
  validates :company_id, presence: true
  # owner_id :consultant_id, optional: true

  has_many :folder_candidates
  has_many :candidates, through: :folder_candidates, source: :consultant
  
  def personal?
    # favorite folder
    consultant.present?
  end
end

# in between
class FolderCandidates < ActiveRecord::Base
  belongs_to :folder
  belongs_to :consultant
end

class Consultant < ActiveRecord::Base
  has_many :folder_recruiters
  has_many :managed_folders, through: :folder_recruiters
  
  has_many :folder_candidates
  has_many :member_folders, through: :folder_candidates
end
```

# Ccl features

- MUST have the option to have CCL as drafts
    - other proposal like a pending approval to belong to a CCL?

- May have an owner, but since is managed by 2 persons is not a great deal by now.

# Sprint 5

- [Nuts Development - Agile Board - Jira](https://mydevex.atlassian.net/jira/software/c/projects/NUT/boards/111/backlog?view=detail&selectedIssue=NUT-1361&issueLimit=100)

- [Devex Curated Candidate Lists: How to add profiles to CCLs?](https://docs.google.com/document/d/1_wfhMP6S1TtxGOI8Zr9Yy8AieJVgtTYWygE5uwfJ9PY/edit)
- 
# Resources 

- [CCL Badges – Figma](https://www.figma.com/file/s7e2sbvV59u8sm13PT9kBw/CCL-Badges?node-id=31%3A52445&t=mgdRY1Uz0XC1Lb1r-0)

Documentation:

- [2.4 The has\_many :through Association - guides.rubyonrails](https://guides.rubyonrails.org/association_basics.html#the-has-many-through-association)
- [Creating a many to many relationship in Rails - Stack Overflow](https://stackoverflow.com/a/55612199)
- [4.3 has_many Association Reference methods - guides.rubyonrails](https://guides.rubyonrails.org/association_basics.html#has-many-association-reference)
- [ActiveRecord::Associations::ClassMethods](https://api.rubyonrails.org/v7.0.4.2/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many)
- https://coderwall.com/p/muvy9w/forget-counter-cache-for-many-to-many-associations-in-rails
- https://www.eyupatis.com/rails-has-many-through-association-with-counter-cache-argument/
- https://gist.github.com/O-I/3a9b57159089239e4003

- Add a privilege: https://github.com/Devex/grape-api/compare/master...agustibr:grape-api:NUT-1360-create-GET_curated_candidate_lists_endpoint?expand=1
- Grape-api AUTHENTICATION: https://github.com/Devex/grape-api/blob/master/AUTHENTICATION.md

# PR

- update workflow: https://github.com/Devex/grape-api/pull/11463

- add CCL model: https://github.com/Devex/grape-api/compare/master...agustibr:grape-api:NUT-1359-create-curated_candidate_list-model?expand=1

----

- https://github.com/Devex/grape-api/compare/master...agustibr:grape-api:NUT-1360-create-GET_curated_candidate_lists_endpoint?expand=1
- https://mydevex.atlassian.net/browse/NUT-1360?atlOrigin=eyJpIjoiMmM2NjExOWJmZWQ3NDE4MGIxNTQ0NjlhMmFhMDU1NTUiLCJwIjoiaiJ9