Module: Chats
- Defined in:
- lib/chats.rb,
lib/chats/engine.rb,
lib/chats/errors.rb,
lib/chats/macros.rb,
lib/chats/version.rb,
lib/chats/broadcasts.rb,
lib/chats/configuration.rb,
lib/chats/models/message.rb,
lib/chats/models/reaction.rb,
lib/chats/models/participant.rb,
lib/chats/models/conversation.rb,
app/helpers/chats/engine_helper.rb,
lib/chats/models/concerns/messager.rb,
lib/chats/models/application_record.rb,
lib/generators/chats/views_generator.rb,
lib/chats/models/concerns/chat_subject.rb,
lib/generators/chats/install_generator.rb,
app/controllers/chats/messages_controller.rb,
app/controllers/chats/reactions_controller.rb,
app/controllers/chats/application_controller.rb,
app/controllers/chats/conversations_controller.rb
Overview
Chats
A drop-in, real-time messaging engine for Rails: DMs, group chats, reactions, attachments, read receipts — Hotwire-native, polymorphic, adapter-driven.
The public surface is intentionally tiny:
Chats.configure { |config| ... } # one block, in an initializer
acts_as_messager # on any model that can converse
acts_as_chat_subject # on any model conversations can be about
user.chat_with(other) # find-or-create a direct conversation
user.message!(other, "hello!") # ...and say something in one line
Everything else (controllers, views, broadcasts) ships with the engine and is overridable the Devise way (‘rails g chats:views`).
Defined Under Namespace
Modules: Broadcasts, ChatSubject, EngineHelper, Generators, Macros, Messager Classes: ApplicationController, ApplicationRecord, BlockedError, Configuration, ConfigurationError, Conversation, ConversationsController, Engine, Error, Message, MessagesController, NotAllowedError, Participant, Reaction, ReactionsController
Constant Summary collapse
- VERSION =
"0.1.1"
Class Method Summary collapse
- .avatar_for(messager) ⇒ Object
-
.blocked_between?(a, b) ⇒ Boolean
True when
aandbcan’t message each other (either one blocked the other — blocking is enforced bidirectionally, like every serious messaging product). -
.blocked_ids_for(messager) ⇒ Object
The single source of truth for “who can’t talk to whom”.
-
.can_message?(sender, recipient) ⇒ Boolean
Host policy on top of (never instead of) block enforcement.
- .chat_subject_class?(klass) ⇒ Boolean
-
.config ⇒ Object
(also: configuration)
— Configuration ——————————————————–.
- .configure {|config| ... } ⇒ Object
-
.display_name_for(messager) ⇒ Object
— Display helpers (used by the bundled views) ————————–.
-
.logger ⇒ Object
— Internals ————————————————————.
-
.messager_class?(klass) ⇒ Boolean
Whether
klass(a Class or class name) is a registered messager. - .messager_class_names ⇒ Object
-
.messager_key(messager) ⇒ Object
A stable, URL-safe, non-guessy key for a messager, used in DOM data attributes (the Stimulus thread controller compares it to decide own-vs-other bubble alignment) and in direct-conversation keys.
-
.notify(event, **payload) ⇒ Object
Fire a domain event through the host’s notifier hook (no-op by default).
- .register_chat_subject(klass) ⇒ Object
-
.register_messager(klass) ⇒ Object
— Registries ———————————————————–.
-
.reset! ⇒ Object
Reset all global state.
- .subject_class_names ⇒ Object
Class Method Details
.avatar_for(messager) ⇒ Object
153 154 155 156 157 |
# File 'lib/chats.rb', line 153 def avatar_for() return nil if .nil? config..call() end |
.blocked_between?(a, b) ⇒ Boolean
True when a and b can’t message each other (either one blocked the other — blocking is enforced bidirectionally, like every serious messaging product). Only meaningful between messagers of the same class (a User blocks a User); cross-class pairs are never considered blocked.
104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/chats.rb', line 104 def blocked_between?(a, b) return false if a.nil? || b.nil? return false unless a.class.base_class == b.class.base_class blocked_ids = blocked_ids_for(a) if blocked_ids.respond_to?(:exists?) # An AR relation: resolve with one indexed query instead of loading ids. blocked_ids.exists?(b.id) else blocked_ids.include?(b.id) end end |
.blocked_ids_for(messager) ⇒ Object
The single source of truth for “who can’t talk to whom”. Wraps the host-provided ‘config.blocked_messager_ids` proc (no-op by default, or `Moderate.blocked_ids_for(user)` when the moderate gem is wired in) and always returns something usable in a `WHERE id IN (…)` — an Array of ids or an AR relation selecting ids.
94 95 96 97 98 |
# File 'lib/chats.rb', line 94 def blocked_ids_for() return [] if .nil? config..call() || [] end |
.can_message?(sender, recipient) ⇒ Boolean
Host policy on top of (never instead of) block enforcement. The blocked check is hardcoded in the models so a host overriding ‘can_message` cannot accidentally disable Trust & Safety guarantees.
120 121 122 123 124 |
# File 'lib/chats.rb', line 120 def (sender, recipient) return false if blocked_between?(sender, recipient) config..call(sender, recipient) end |
.chat_subject_class?(klass) ⇒ Boolean
83 84 85 |
# File 'lib/chats.rb', line 83 def chat_subject_class?(klass) registered_class?(subject_class_names, klass) end |
.config ⇒ Object Also known as: configuration
— Configuration ——————————————————–
33 34 35 |
# File 'lib/chats.rb', line 33 def config @config ||= Configuration.new end |
.configure {|config| ... } ⇒ Object
39 40 41 42 43 |
# File 'lib/chats.rb', line 39 def configure yield config if block_given? config.validate! config end |
.display_name_for(messager) ⇒ Object
— Display helpers (used by the bundled views) ————————–
147 148 149 150 151 |
# File 'lib/chats.rb', line 147 def display_name_for() return "" if .nil? config..call().to_s end |
.logger ⇒ Object
— Internals ————————————————————
161 162 163 |
# File 'lib/chats.rb', line 161 def logger defined?(::Rails) ? ::Rails.logger : nil end |
.messager_class?(klass) ⇒ Boolean
Whether klass (a Class or class name) is a registered messager. Ancestor-aware so an STI subclass of a messager is accepted too.
79 80 81 |
# File 'lib/chats.rb', line 79 def (klass) registered_class?(, klass) end |
.messager_class_names ⇒ Object
69 70 71 |
# File 'lib/chats.rb', line 69 def @messager_class_names ||= Set.new end |
.messager_key(messager) ⇒ Object
A stable, URL-safe, non-guessy key for a messager, used in DOM data attributes (the Stimulus thread controller compares it to decide own-vs-other bubble alignment) and in direct-conversation keys. GlobalID params are opaque-ish (Base64) and already encode class + id.
169 170 171 |
# File 'lib/chats.rb', line 169 def () .to_global_id.to_param end |
.notify(event, **payload) ⇒ Object
Fire a domain event through the host’s notifier hook (no-op by default). Events (see Chats::Configuration#notifier):
:message_created message: (every persisted, non-system message)
:participant_added participant: (someone added to a group)
Hosts typically point this at a Noticed notifier or a mailer job:
config.notifier = ->(event, **payload) {
NewMessageNotifier.with(**payload).deliver if event == :message_created
}
135 136 137 138 139 140 141 142 143 |
# File 'lib/chats.rb', line 135 def notify(event, **payload) config.notifier.call(event, **payload) rescue StandardError => e # A broken notifier must never break message delivery itself — the # message is already committed; notifications are best-effort fan-out. # Same error-isolation philosophy as pricing_plans' lifecycle callbacks. logger&.error("[chats] notifier raised on #{event}: #{e.class}: #{e.}") nil end |
.register_chat_subject(klass) ⇒ Object
65 66 67 |
# File 'lib/chats.rb', line 65 def register_chat_subject(klass) subject_class_names << klass.name if klass.name end |
.register_messager(klass) ⇒ Object
— Registries ———————————————————–
‘acts_as_messager` / `acts_as_chat_subject` self-register the calling class here. We store class NAMES (strings), not Class objects, so the registry survives Zeitwerk code reloading in development (a reloaded class is a brand-new object; its name is stable).
61 62 63 |
# File 'lib/chats.rb', line 61 def (klass) << klass.name if klass.name end |
.reset! ⇒ Object
Reset all global state. Used by the test suite to keep examples isolated; also handy in a console when experimenting with configuration.
47 48 49 50 51 52 |
# File 'lib/chats.rb', line 47 def reset! @config = Configuration.new @messager_classes = nil @subject_classes = nil self end |
.subject_class_names ⇒ Object
73 74 75 |
# File 'lib/chats.rb', line 73 def subject_class_names @subject_class_names ||= Set.new end |