Interns record their work experience hours and projects, and mentors review them.
Interns record their hours each month against a set of work experience subcategories, and record the projects they worked on. Every few months they submit a summary of that period, which is reviewed by their mentor. A work experience report totals everything to date against the minimum hours of each category.
This requires Rails 6+ and Twitter Bootstrap 4 and just works with Devise.
Please first install the effective_datatables gem.
Please download and install the Twitter Bootstrap4
Add to your Gemfile:
gem 'effective_work_experience'Run the bundle command to install it:
bundle installThen run the generator:
rails generate effective_work_experience:installThe generator will install an initializer which describes all configuration options and creates a database migration.
Then migrate the database:
rake db:migrateEffective::WorkExperienceCategory— the top level grouping, with a minimum hours targetEffective::WorkExperienceSubcategory— belongs to a category. Hours are recorded against these.Effective::WorkExperienceRecord— one intern's hours for one monthEffective::WorkExperienceEntry— one row of a record. Five weeks of hours for one subcategory.Effective::WorkExperienceProject— one project an intern worked onEffective::WorkExperienceSummary— one period, submitted by the intern and reviewed by their mentorEffective::WorkExperienceOutsideMentor— a mentor without an account, stored as freeform fieldsEffective::WorkExperienceReport— an ActiveModel report of everything to date
The work experience summary is the only swappable class. Mark your own with effective_work_experience_summary:
module Bcsla
class WorkExperienceSummary < ApplicationRecord
effective_work_experience_summary
end
endand configure it:
config.work_experience_summary_class_name = 'Bcsla::WorkExperienceSummary'Nothing works until there are categories and subcategories to record hours against — a new record
has one row per subcategory, so with none it is an empty form. Every application defines its own,
either from the admin screens or from its own seeds. See db/seeds.rb for an example set.
minimum_hours is optional on both. The work experience report shows a target and a percent
complete for each category and subcategory that has one, and just the hours to date for those that
don't.
An intern's mentor is another user, so this gem needs one column on your users table. The install migration does not add it. Write your own:
class AddWorkExperienceMentorToUsers < ActiveRecord::Migration[8.0]
def change
add_column :users, :work_experience_mentor_id, :integer
end
endThen add the following to your User model:
effective_work_experience_userwhich adds the belongs_to :work_experience_mentor for that column, plus:
work_experience_mentees— the interns I am a mentor forwork_experience_outside_mentor— my mentor when they don't have an account. Ahas_one.work_experience_records,work_experience_entries,work_experience_projects,work_experience_summaries,mentee_work_experience_summaries- the
work_experience_hours,work_experience_hours_by_yearandwork_experience_total_hours_to_datecalculations work_experience_intern?,work_experience_mentor?andwork_experience_outside_mentor?, used by the views and permissions below
An intern with an outside mentor has no work_experience_mentor, so their summaries are reviewed automatically on submit.
The summary's user, mentor and supervisor are polymorphic. A summary assigns its mentor from the user's work_experience_mentor, and its supervisor from the user's supervisor when your User model defines one.
work_experience_intern? is true once a user has any records or summaries. To have someone see the
intern dashboard before they've recorded anything, define intern? on your User model and it will
be used too:
def intern?
membership.present? && membership.category.intern?
endRender the mentor fields from your own admin user form:
= render('admin/work_experience/user_fields', f: f, hint: 'Who can be a mentor')This is the work_experience_mentor select plus an f.has_many builder for the one outside mentor.
The work_experience_mentor_id and work_experience_outside_mentors_attributes params are permitted
by effective_resources for free — there is nothing to add to your effective_resource do block.
Render an intern's records, projects, summaries and report as a tab on the same form:
- if user.work_experience_intern?
= tab Effective::WorkExperienceRecord, label: 'Work Experience' do
= render('admin/work_experience/user_work_experience', user: user)Render the dashboard partials from your own dashboard:
- if current_user.work_experience_intern?
= render 'effective/work_experience/dashboard_intern'
- if current_user.work_experience_mentor?
= render 'effective/work_experience/dashboard_mentor'All configuration options are documented in the config/initializers/effective_work_experience.rb initializer.
All authorization checks are handled via the effective_resources gem found in the config/initializers/effective_resources.rb file.
The permissions you actually want to define are as follows (using CanCan):
if user.persisted?
can([:index, :show, :new, :create], Effective::WorkExperienceRecord) { |record| record.user == user }
can([:edit, :update], Effective::WorkExperienceRecord) { |record| !record.backdated? && !record.was_reviewed? }
can(:destroy, Effective::WorkExperienceRecord) { |record| !record.backdated? && !record.was_submitted? }
can(crud, Effective::WorkExperienceProject) { |project| project.user == user }
can([:new, :create], EffectiveWorkExperience.WorkExperienceSummary) { |summary| summary.user == user }
can(:destroy, EffectiveWorkExperience.WorkExperienceSummary) { |summary| summary.user == user && summary.draft? }
can([:show, :index], EffectiveWorkExperience.WorkExperienceSummary) do |summary|
summary.user == user || (summary.mentor == user && summary.was_submitted?)
end
can(:update, EffectiveWorkExperience.WorkExperienceSummary) do |summary|
(summary.user == user && !summary.was_submitted?) || (summary.mentor == user && summary.was_submitted?)
end
can(:show, Effective::WorkExperienceReport) { |report| report.user == user || report.mentor == user }
if user.work_experience_mentor?
can(:index, EffectiveWorkExperienceReportsReviewDatatable)
end
end
if user.admin?
can :admin, :effective_work_experience
can([:index, :edit, :update], Effective::WorkExperienceCategory)
can([:index, :edit, :update], Effective::WorkExperienceSubcategory)
can(crud - [:show], Effective::WorkExperienceProject)
can(crud - [:show], Effective::WorkExperienceRecord)
can(crud, Effective::WorkExperienceReport)
can(crud, EffectiveWorkExperience.WorkExperienceSummary)
can(:index, Admin::EffectiveWorkExperienceReportsDatatable)
endAdd a link to the admin menu:
- if can? :admin, :effective_work_experience
= nav_link_to Effective::WorkExperienceRecord, effective_work_experience.admin_work_experience_records_path
= nav_link_to Effective::WorkExperienceProject, effective_work_experience.admin_work_experience_projects_path
= nav_link_to EffectiveWorkExperience.WorkExperienceSummary, effective_work_experience.admin_work_experience_summaries_path
= nav_link_to Effective::WorkExperienceReport, effective_work_experience.admin_work_experience_reports_pathMIT License. Copyright Code and Effect Inc.
Run tests by:
rails test- Fork it
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Bonus points for test coverage
- Create new Pull Request