Module: AceMQ::Rails
- Defined in:
- lib/acemq/rails.rb,
lib/acemq/rails/health.rb,
lib/acemq/rails/runner.rb,
lib/acemq/rails/railtie.rb,
lib/acemq/rails/version.rb,
lib/acemq/rails/consumer.rb,
lib/acemq/rails/registry.rb,
lib/acemq/rails/configuration.rb,
lib/acemq/rails/topology_builder.rb
Overview
Rails integration for AceMQ.
One connection for the process, reachable from anywhere in the application; consumers written as classes and run in a process of their own; a health check that composes into whatever the application already exposes.
A note on the name
This module is called Rails and sits inside AceMQ, which means that
inside any file lexically nested in module AceMQ; module Rails, a bare
Rails resolves to this module and not to the framework. Every reference
to the framework in this gem is therefore written ::Rails, and that is not
a style choice — leaving one off gives a NoMethodError on
AceMQ::Rails.application that reads like a bug in the application.
Defined Under Namespace
Modules: Health, Registry, TopologyBuilder Classes: Configuration, Consumer, Railtie, Runner
Constant Summary collapse
- LOCK =
Guards the one connection. Named here rather than inside the singleton class, where a constant would belong to the singleton and not to the module — which compiles, and then cannot be reached by the name it was given.
Mutex.new
- VERSION =
A version line of its own, deliberately.
This gem tracks two release trains: AceMQ's and Rails'. A Rails 8.1 that moves an autoloading hook is a release here and nothing at all in acemq-amqp, and a library release that adds a publishing method is a dependency bump here rather than a new number. Sharing a version with the library would mean one of those two facts had to be lied about.
"0.2.4"
Class Attribute Summary collapse
-
.config ⇒ Configuration
The configuration.
-
.logger ⇒ #info, ...
Where this gem logs.
Class Method Summary collapse
-
.apply_topology ⇒ Object
Declares the topology.
-
.connected? ⇒ Boolean
Whether a connection has been opened.
-
.connection ⇒ AceMQ::AMQP::Connection
The connection this process publishes and consumes on.
-
.connection=(connection) ⇒ Object
Replaces the connection, for a test that wants a fake transport.
-
.consumers ⇒ Array<Class>
The registered consumer classes.
-
.disconnect!(timeout: nil) ⇒ Object
Closes the connection, draining any consumers on it first.
-
.health ⇒ AceMQ::AMQP::Health::Report
What this process can say about its broker, blocked connections included.
-
.intercept(connection) ⇒ Object
private
Registers
config.acemq.interceptorson a freshly opened connection. -
.publish(payload, **options) ⇒ AceMQ::AMQP::Envelope
Publishes a message.
-
.publish_all(payloads, **options) ⇒ Array<AceMQ::AMQP::Envelope>
Publishes a batch in one round trip.
-
.topology ⇒ AceMQ::AMQP::Topology
The topology the configuration declares.
Class Attribute Details
.config ⇒ Configuration
The configuration. Set from config/acemq.yml and config.acemq.* when
a Railtie is loaded, and directly when there is no Rails — a rake task in
a plain Ruby process, or a spec.
55 56 57 |
# File 'lib/acemq/rails.rb', line 55 def config @config ||= Configuration.new end |
.logger ⇒ #info, ...
Where this gem logs. Rails.logger when there is one.
64 65 66 67 68 |
# File 'lib/acemq/rails.rb', line 64 def logger return @logger if defined?(@logger) && @logger defined?(::Rails) && ::Rails.respond_to?(:logger) ? ::Rails.logger : nil end |
Class Method Details
.apply_topology ⇒ Object
Declares the topology. What rake acemq:topology runs, and what the
consumer process does on boot.
157 158 159 160 161 |
# File 'lib/acemq/rails.rb', line 157 def apply_topology return nil unless TopologyBuilder.declared?(config.topology) connection.apply(topology) end |
.connected? ⇒ Boolean
Whether a connection has been opened. Does not open one.
115 |
# File 'lib/acemq/rails.rb', line 115 def connected? = !@connection.nil? |
.connection ⇒ AceMQ::AMQP::Connection
The connection this process publishes and consumes on.
One per process, opened on first use and memoised. A connection is a
socket and a heartbeat; opening one per request would be the single most
expensive mistake available here, and is the reason this exists rather
than every caller writing Connection.open.
Opened lazily by default, which is a decision rather than laziness: a
broker that is down should not stop an application from serving the
pages that never touch it. config.acemq.connect_on_boot = true moves
it to boot for an application that would rather fail fast.
83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/acemq/rails.rb', line 83 def connection # Double-checked, and the outer check is not an optimisation: this is on # the path of every publish in every request, and taking a process-wide # mutex there would serialise a Puma worker's threads behind each other # for the whole life of the process. return @connection if @connection LOCK.synchronize do @connection ||= begin url, = config.connection_arguments intercept(AceMQ::AMQP::Connection.open(url, **)) end end end |
.connection=(connection) ⇒ Object
Replaces the connection, for a test that wants a fake transport.
120 121 122 |
# File 'lib/acemq/rails.rb', line 120 def connection=(connection) LOCK.synchronize { @connection = connection } end |
.consumers ⇒ Array<Class>
The registered consumer classes.
166 |
# File 'lib/acemq/rails.rb', line 166 def consumers = Registry.runnable |
.disconnect!(timeout: nil) ⇒ Object
Closes the connection, draining any consumers on it first.
Safe to call when nothing was ever opened, which is what makes it usable
from an at_exit in a web process that may never have published.
The connection is let go of before it is closed, and not after: a close
that raises — which since acemq-amqp 0.7.0 it does when the drain ran
out of time — must still leave this process without a connection it
thinks it has.
183 184 185 186 187 188 189 190 191 |
# File 'lib/acemq/rails.rb', line 183 def disconnect!(timeout: nil) connection = LOCK.synchronize do taken = @connection @connection = nil taken end timeout ? connection&.close(timeout: timeout) : connection&.close nil end |
.health ⇒ AceMQ::AMQP::Health::Report
What this process can say about its broker, blocked connections included. See Health.
148 |
# File 'lib/acemq/rails.rb', line 148 def health = Health.of(connection) |
.intercept(connection) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Registers config.acemq.interceptors on a freshly opened connection.
Here rather than in an initializer, because an interceptor lives on a connection and an initializer that reaches for one opens the socket during boot — see AceMQ::Rails::Configuration#interceptors. Each is offered to both sides; the library keeps only the hooks the object answers to.
106 107 108 109 110 111 112 |
# File 'lib/acemq/rails.rb', line 106 def intercept(connection) Array(config.interceptors).each do |interceptor| connection.intercept_publish(interceptor) connection.intercept_consume(interceptor) end connection end |
.publish(payload, **options) ⇒ AceMQ::AMQP::Envelope
Publishes a message.
AceMQ::Rails.publish(order.as_json, to: "order.placed",
exchange: "orders-events", type: "order.placed.v2")
A thin delegation, and thin on purpose. The library's publish is
already the right shape, and a wrapper that renamed its keywords would be
a second API to document, a second one to keep in step, and the place
where an integration starts making decisions the library deliberately
left to the caller.
136 |
# File 'lib/acemq/rails.rb', line 136 def publish(payload, **) = connection.publish(payload, **) |
.publish_all(payloads, **options) ⇒ Array<AceMQ::AMQP::Envelope>
Publishes a batch in one round trip. Added to the library in 0.6.0.
142 |
# File 'lib/acemq/rails.rb', line 142 def publish_all(payloads, **) = connection.publish_all(payloads, **) |
.topology ⇒ AceMQ::AMQP::Topology
The topology the configuration declares.
153 |
# File 'lib/acemq/rails.rb', line 153 def topology = TopologyBuilder.build(config.topology) |