Module: Sessions::EngineHelper
- Defined in:
- app/helpers/sessions/engine_helper.rb
Overview
View helpers for the devices page (and for the host-renderable partials).
Constant Summary collapse
- DEVICE_ICONS =
{ "desktop" => "🖥", "smartphone" => "📱", "tablet" => "📱", "native_ios" => "📱", "native_android" => "📱", "bot" => "🤖" }.freeze
- DEVICE_ICON_NAMES =
Semantic icon names (Heroicons vocabulary — map them onto whatever icon helper your app uses) so custom views don't re-derive the device_type → icon mapping the gem already knows.
{ "desktop" => "computer-desktop", "smartphone" => "device-phone-mobile", "tablet" => "device-tablet", "native_ios" => "device-phone-mobile", "native_android" => "device-phone-mobile", "bot" => "bug-ant" }.freeze
- EVENT_ICON_NAMES =
{ "login" => "check-circle", "failed_login" => "x-circle", "logout" => "arrow-right-on-rectangle", "revoked" => "no-symbol", "expired" => "clock" }.freeze
Class Method Summary collapse
-
.engine_mount_name ⇒ Object
The name of the route that mounts Sessions::Engine in the host app ("sessions" for a plain mount, the
as:value otherwise). - .reset_engine_mount_name! ⇒ Object
Instance Method Summary collapse
- #sessions_device_icon(session) ⇒ Object
- #sessions_device_icon_name(session) ⇒ Object
-
#sessions_devices_for(user, limit: nil) ⇒ Object
Semantic queries for custom/ejected views.
-
#sessions_engine_routes ⇒ Object
The engine's route proxy when it's mounted, nil otherwise — partials rendered inside a host that didn't mount the engine simply omit the revoke buttons.
- #sessions_event_icon_name(event) ⇒ Object
-
#sessions_format_date(date) ⇒ Object
"Signed in May 2, 2026" — localized when the host bundles date formats (rails-i18n or its own locale files), with a safe fallback so a bare host never 500s over a missing
date.formats.long. - #sessions_format_time(time) ⇒ Object
- #sessions_history_for(user, limit: nil) ⇒ Object
-
#sessions_last_active_in_words(session) ⇒ Object
"Active now" within the touch window, else "Active 3 minutes ago".
Class Method Details
.engine_mount_name ⇒ Object
The name of the route that mounts Sessions::Engine in the host app
("sessions" for a plain mount, the as: value otherwise). Memoized
per-process; in development a changed mount name reloads routes and
to_prepare re-touches this helper file, clearing the memo.
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'app/helpers/sessions/engine_helper.rb', line 97 def self.engine_mount_name return @engine_mount_name if defined?(@engine_mount_name) @engine_mount_name = begin route = Rails.application.routes.routes.find do |candidate| # The mount sits behind a Constraints wrapper; unwrap a bounded # number of times, checking BEFORE each unwrap — Rails::Engine # itself responds to #app (its middleware stack), so an unguarded # `while app.respond_to?(:app)` would walk straight past it. app = candidate.app matched = false 3.times do matched = (app == Sessions::Engine) break if matched || !app.respond_to?(:app) app = app.app end matched end route&.name rescue StandardError nil end end |
.reset_engine_mount_name! ⇒ Object
122 123 124 |
# File 'app/helpers/sessions/engine_helper.rb', line 122 def self.reset_engine_mount_name! remove_instance_variable(:@engine_mount_name) if defined?(@engine_mount_name) end |
Instance Method Details
#sessions_device_icon(session) ⇒ Object
36 37 38 |
# File 'app/helpers/sessions/engine_helper.rb', line 36 def sessions_device_icon(session) DEVICE_ICONS.fetch(session.device_type.to_s, "🌐") end |
#sessions_device_icon_name(session) ⇒ Object
40 41 42 |
# File 'app/helpers/sessions/engine_helper.rb', line 40 def sessions_device_icon_name(session) DEVICE_ICON_NAMES.fetch(session.device_type.to_s, "globe-alt") end |
#sessions_devices_for(user, limit: nil) ⇒ Object
Semantic queries for custom/ejected views. Keep liveness and enumeration-safe history semantics in the gem, so host styling changes don't accidentally regress the data contract.
51 52 53 54 55 56 57 58 |
# File 'app/helpers/sessions/engine_helper.rb', line 51 def sessions_devices_for(user, limit: nil) scope = if user.respond_to?(:sessions) user.sessions.live.by_recency else Sessions.session_model.live.where(user: user).by_recency end limit ? scope.limit(limit) : scope end |
#sessions_engine_routes ⇒ Object
The engine's route proxy when it's mounted, nil otherwise — partials
rendered inside a host that didn't mount the engine simply omit the
revoke buttons. The proxy method is named after the mount (sessions
by default, or whatever as: was given), so it's DISCOVERED from the
host's named routes rather than assumed.
88 89 90 91 |
# File 'app/helpers/sessions/engine_helper.rb', line 88 def sessions_engine_routes name = Sessions::EngineHelper.engine_mount_name name && respond_to?(name) ? public_send(name) : nil end |
#sessions_event_icon_name(event) ⇒ Object
44 45 46 |
# File 'app/helpers/sessions/engine_helper.rb', line 44 def sessions_event_icon_name(event) EVENT_ICON_NAMES.fetch(event.event.to_s, "information-circle") end |
#sessions_format_date(date) ⇒ Object
"Signed in May 2, 2026" — localized when the host bundles date
formats (rails-i18n or its own locale files), with a safe fallback so
a bare host never 500s over a missing date.formats.long. nil-safe:
without the guard, I18n.l(nil) raises I18n::ArgumentError and the
rescue would then call nil.strftime — a trap for custom views passing
a nullable column.
132 133 134 135 136 137 138 |
# File 'app/helpers/sessions/engine_helper.rb', line 132 def sessions_format_date(date) return nil unless date I18n.l(date, format: :long) rescue I18n::MissingTranslationData, I18n::ArgumentError date.strftime("%Y-%m-%d") end |
#sessions_format_time(time) ⇒ Object
140 141 142 143 144 145 146 |
# File 'app/helpers/sessions/engine_helper.rb', line 140 def sessions_format_time(time) return nil unless time I18n.l(time, format: :short) rescue I18n::MissingTranslationData, I18n::ArgumentError time.strftime("%Y-%m-%d %H:%M") end |
#sessions_history_for(user, limit: nil) ⇒ Object
60 61 62 63 64 65 66 67 68 69 |
# File 'app/helpers/sessions/engine_helper.rb', line 60 def sessions_history_for(user, limit: nil) scope = if user.respond_to?(:session_history) user.session_history.recent elsif user.respond_to?(:session_events) user.session_events.recent else Sessions::Event.where(authenticatable: user).recent end limit ? scope.limit(limit) : scope end |
#sessions_last_active_in_words(session) ⇒ Object
"Active now" within the touch window, else "Active 3 minutes ago".
72 73 74 75 76 77 78 79 80 81 |
# File 'app/helpers/sessions/engine_helper.rb', line 72 def sessions_last_active_in_words(session) # active_now? owns the window (config.touch_every) — the badge can't # honestly claim more freshness than the throttle records. return t("sessions.devices.active_now") if session.respond_to?(:active_now?) && session.active_now? time = session.last_active_at return nil unless time t("sessions.devices.active_ago", time: time_ago_in_words(time)) end |