--- 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