3.7 KiB
type, cluster, universe, status, entity
| type | cluster | universe | status | entity |
|---|---|---|---|---|
| object | gradebook | live | verified | app/models/grade_entry.rb |
GradeEntry
One student's row in one grade-book: two letter grades, attendance days, a perfect-attendance flag. This is where every payout amount is defined.
Verified 2026-08-16 against commit 63732df.
Why this shape
The payout table is five Ruby constants on this model, all in cents
(app/models/grade_entry.rb:9-13):
| Constant | Value | Meaning |
|---|---|---|
EARNINGS_PER_DAY_ATTENDANCE |
20 |
$0.20 per day present |
EARNINGS_FOR_A_GRADE |
3_00 |
$3.00 for any A |
EARNINGS_FOR_B_GRADE |
2_00 |
$2.00 for any B |
EARNINGS_FOR_IMPROVED_GRADE |
2_00 |
$2.00 for improving |
EARNINGS_FOR_PERFECT_ATTENDANCE |
1_00 |
$1.00 bonus |
They are not configuration. Changing what a student earns is a code change and a deploy — there is no admin screen and no database row for these.
GRADE_OPTIONS is ordered best-to-worst on purpose (:15). improved_grade?
compares array indices, treating a lower index as better (:64-67). Reordering or
inserting into that array silently changes every improvement bonus in the app.
There are no validations on this model at all — grades are constrained only by the
<select> in app/views/grade_books/_grade_entry.html.erb:8,17, and the controller
permits the values straight through (app/controllers/grade_books_controller.rb:53-57).
A value outside GRADE_OPTIONS saves fine, then makes improved_grade? compare nil
indices and raise NoMethodError during the next quarter's payout. Grades C through F
earn nothing but are legal; anything not in the list is a latent failure.
Shape
- Table
grade_entries,db/schema.rb:109-121; unique on[grade_book_id, user_id](db/schema.rb:118) — one row per student per book math_grade,reading_grade— plain strings, nullable, unvalidatedattendance_days— bigint, nullable;earnings_for_attendancereturns 0 when blank (:17-21)is_perfect_attendance— boolean, default false,null: false(db/schema.rb:113)belongs_to :grade_book,belongs_to :user(:4-5) —user, notstudent- Earnings readers:
earnings_for_attendance,earnings_for_math,earnings_for_reading,attendance_perfect_earnings,math_improvement_earnings,reading_improvement_earnings(:17-49)
Every earnings method is a pure reader. Nothing here writes money —
DistributeEarnings calls them and creates the deposits.
Connected to
- owns: —
- owned-by: grade-book, user
- joins: —
- looks-like-but-is-not:
math_gradeis a letter ("A+"…"F"), unrelated to grade-level, which is 5–8.
If you change this
- Hits: portfolio-transaction amounts — these
constants are the amounts;
DistributeEarnings(app/services/distribute_earnings.rb:54-73), which sums attendance + math + reading; the finalize-gradebook-earnings movement;AttendanceEntryPresenter. - Does not hit: already-paid deposits. Editing an entry after finalize changes
nothing retroactively — the deposits are independent rows with no link back to the
entry that produced them (
db/schema.rb:170-179has nograde_entry_id). Re-paying would require re-running finalize, which thecompleted?guard blocks.
Surfaces
| Surface | Role |
|---|---|
GradeBooksController#update (+ autosave Stimulus controller) |
teacher writes |
DistributeEarnings |
reads |
AttendanceEntryPresenter |
reads |
See
- Source:
app/models/grade_entry.rb,db/schema.rb:109-121 - As-built:
docs/gradebook-earnings.md