Class: Bible270::Reader
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- Bible270::Reader
- Defined in:
- app/models/bible270/reader.rb
Overview
A reader identity. Either self-contained (created via OmniAuth) or bridged to one of the host application's users through the polymorphic :owner.
Class Method Summary collapse
-
.for_owner(owner, display_name:, email: nil, avatar_url: nil) ⇒ Object
Find or create a reader bridged to a host user (or any model).
-
.from_email(email, display_name: nil) ⇒ Object
Find or create the reader behind a verified email address.
-
.from_omniauth(auth) ⇒ Object
Build/refresh a reader from an OmniAuth auth hash.
Instance Method Summary collapse
-
#calendar_day ⇒ Object
The plan day that today corresponds to (clamped into range), or nil when undated.
-
#checked_counts ⇒ Object
=> number of tracks checked off.
- #clear_start_date! ⇒ Object
- #completion_percent ⇒ Object
-
#current_day ⇒ Object
First day not yet fully complete (where the reader "is").
- #date_for_day(day) ⇒ Object
- #dated? ⇒ Boolean
- #day_complete?(day) ⇒ Boolean
- #days_completed ⇒ Object
-
#days_off_pace ⇒ Object
How many days behind (positive) or ahead (negative) of the calendar the reader's actual progress is.
- #days_read_in(track) ⇒ Object
-
#effective_start_date ⇒ Object
The start date that actually governs this reader.
-
#ensure_started! ⇒ Object
Called when a reader first participates.
- #initials ⇒ Object
- #not_started_yet? ⇒ Boolean
-
#own_start_date? ⇒ Boolean
Whether this reader is following a personal date or the shared cohort one.
- #past_end_date? ⇒ Boolean
- #plan_end_date ⇒ Object
-
#raw_calendar_day ⇒ Object
Raw, unclamped — lets callers distinguish "not started yet" / "finished".
- #read?(day, track) ⇒ Boolean
- #read_tracks_for(day) ⇒ Object
- #start_date ⇒ Object
-
#start_date=(value) ⇒ Object
Set or change this reader's own start date.
- #update_start_date!(value) ⇒ Object
Class Method Details
.for_owner(owner, display_name:, email: nil, avatar_url: nil) ⇒ Object
Find or create a reader bridged to a host user (or any model).
70 71 72 73 74 75 76 77 |
# File 'app/models/bible270/reader.rb', line 70 def self.for_owner(owner, display_name:, email: nil, avatar_url: nil) reader = find_or_initialize_by(owner: owner) reader.display_name = display_name.presence || reader.display_name || "Reader" reader.email ||= email reader.avatar_url ||= avatar_url reader.save! reader end |
.from_email(email, display_name: nil) ⇒ Object
Find or create the reader behind a verified email address. Uses the same provider/uid identity columns as OmniAuth, with provider "email", so an email reader is indistinguishable from any other downstream.
39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'app/models/bible270/reader.rb', line 39 def self.from_email(email, display_name: nil) address = EmailSignIn.normalize_email(email) return nil if address.nil? reader = find_or_initialize_by(provider: "email", uid: address) chosen = first_present(display_name, reader.display_name, EmailSignIn.display_name_from(address), "Reader") reader.display_name = chosen reader.email = address reader.save reader end |
.from_omniauth(auth) ⇒ Object
Build/refresh a reader from an OmniAuth auth hash. Tolerant of the various shapes strategies return (OmniAuth::AuthHash, plain Hash, missing info).
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# File 'app/models/bible270/reader.rb', line 17 def self.from_omniauth(auth) provider = dig_auth(auth, :provider).to_s uid = dig_auth(auth, :uid).to_s return nil if provider.empty? || uid.empty? info = dig_auth(auth, :info) || {} name = first_present(dig_auth(info, :name), dig_auth(info, :nickname), dig_auth(info, :first_name), dig_auth(info, :email)) reader = find_or_initialize_by(provider: provider, uid: uid) reader.display_name = first_present(name, reader.display_name, "Reader") email = dig_auth(info, :email) image = first_present(dig_auth(info, :image), dig_auth(info, :avatar_url)) reader.email = email if email.present? reader.avatar_url = image if image.present? reader.save reader end |
Instance Method Details
#calendar_day ⇒ Object
The plan day that today corresponds to (clamped into range), or nil when undated.
144 145 146 |
# File 'app/models/bible270/reader.rb', line 144 def calendar_day Plan.day_for(Date.current, effective_start_date) end |
#checked_counts ⇒ Object
=> number of tracks checked off
84 85 86 |
# File 'app/models/bible270/reader.rb', line 84 def checked_counts @checked_counts ||= checkoffs.group(:day).count end |
#clear_start_date! ⇒ Object
196 197 198 |
# File 'app/models/bible270/reader.rb', line 196 def clear_start_date! update!(started_on: nil) end |
#completion_percent ⇒ Object
109 110 111 |
# File 'app/models/bible270/reader.rb', line 109 def completion_percent (days_completed.to_f / Plan::DAYS * 100).round end |
#current_day ⇒ Object
First day not yet fully complete (where the reader "is").
114 115 116 |
# File 'app/models/bible270/reader.rb', line 114 def current_day (1..Plan::DAYS).find { |d| !day_complete?(d) } || Plan::DAYS end |
#date_for_day(day) ⇒ Object
165 166 167 |
# File 'app/models/bible270/reader.rb', line 165 def date_for_day(day) Plan.date_for(day, effective_start_date) end |
#dated? ⇒ Boolean
134 135 136 |
# File 'app/models/bible270/reader.rb', line 134 def dated? effective_start_date.present? end |
#day_complete?(day) ⇒ Boolean
96 97 98 99 |
# File 'app/models/bible270/reader.rb', line 96 def day_complete?(day) n = checked_counts[day].to_i n.positive? && n >= Plan.required_track_count(day) end |
#days_completed ⇒ Object
101 102 103 |
# File 'app/models/bible270/reader.rb', line 101 def days_completed checked_counts.count { |day, n| n >= Plan.required_track_count(day) } end |
#days_off_pace ⇒ Object
How many days behind (positive) or ahead (negative) of the calendar the reader's actual progress is. Nil when undated.
171 172 173 174 175 176 |
# File 'app/models/bible270/reader.rb', line 171 def days_off_pace today = calendar_day return nil unless today today - days_completed end |
#days_read_in(track) ⇒ Object
105 106 107 |
# File 'app/models/bible270/reader.rb', line 105 def days_read_in(track) checkoffs.where(track: track.to_s).count end |
#effective_start_date ⇒ Object
The start date that actually governs this reader. A reader's own started_on wins when per-reader dates are allowed; otherwise (or if they haven't got one) the community-wide config.start_date applies. Nil means the plan is undated for this reader and no calendar mapping exists.
125 126 127 128 129 130 131 132 |
# File 'app/models/bible270/reader.rb', line 125 def effective_start_date config = Bible270.config if config.allow_reader_start_date && started_on started_on else config.start_date end end |
#ensure_started! ⇒ Object
Called when a reader first participates. Only stamps a personal start date when per-reader dates are enabled and a shared date isn't already in force.
202 203 204 205 206 207 208 209 |
# File 'app/models/bible270/reader.rb', line 202 def ensure_started! config = Bible270.config return unless config.allow_reader_start_date return if started_on.present? return if config.start_date.present? update!(started_on: Date.current) end |
#initials ⇒ Object
79 80 81 |
# File 'app/models/bible270/reader.rb', line 79 def initials display_name.to_s.split(/\s+/).first(2).map { |w| w[0] }.join.upcase.presence || "?" end |
#not_started_yet? ⇒ Boolean
153 154 155 |
# File 'app/models/bible270/reader.rb', line 153 def not_started_yet? Plan.before_start?(Date.current, effective_start_date) end |
#own_start_date? ⇒ Boolean
Whether this reader is following a personal date or the shared cohort one.
139 140 141 |
# File 'app/models/bible270/reader.rb', line 139 def own_start_date? Bible270.config.allow_reader_start_date && started_on.present? end |
#past_end_date? ⇒ Boolean
157 158 159 |
# File 'app/models/bible270/reader.rb', line 157 def past_end_date? Plan.after_end?(Date.current, effective_start_date) end |
#plan_end_date ⇒ Object
161 162 163 |
# File 'app/models/bible270/reader.rb', line 161 def plan_end_date Plan.end_date_for(effective_start_date) end |
#raw_calendar_day ⇒ Object
Raw, unclamped — lets callers distinguish "not started yet" / "finished".
149 150 151 |
# File 'app/models/bible270/reader.rb', line 149 def raw_calendar_day Plan.day_for(Date.current, effective_start_date, clamp: false) end |
#read?(day, track) ⇒ Boolean
92 93 94 |
# File 'app/models/bible270/reader.rb', line 92 def read?(day, track) read_tracks_for(day).include?(track.to_s) end |
#read_tracks_for(day) ⇒ Object
88 89 90 |
# File 'app/models/bible270/reader.rb', line 88 def read_tracks_for(day) checkoffs.where(day: day).pluck(:track) end |
#start_date ⇒ Object
183 184 185 |
# File 'app/models/bible270/reader.rb', line 183 def start_date started_on end |