Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
2e4c0b8
Restructure into ably-pubsub-core and ably-pubsub-server gems
umair-ably Sep 1, 2026
e694f24
Fix agent param spec to accept multi-digit version segments
umair-ably Sep 1, 2026
8847f7b
Stamp the side-declaring agent entry without a version
umair-ably Sep 2, 2026
d24039b
Add lockstep release workflow, update contributing and migration docs
umair-ably Sep 1, 2026
79cde4c
Add release dry-run job to CI
umair-ably Sep 1, 2026
11b3330
Rename the SDK agent identifier to ably-pubsub-ruby
umair-ably Sep 2, 2026
f18bcbe
Trim release workflow header comment
umair-ably Sep 2, 2026
97f823f
Add gem-packaging specs for the core/server split
umair-ably Sep 2, 2026
a057dcc
Merge remote-tracking branch 'origin/main' into pubsub-split/restructure
umair-ably Sep 3, 2026
b57ccd5
Update server gemspec homepage for the repo rename
umair-ably Sep 3, 2026
8a46390
Merge branch 'pubsub-split/restructure' into pubsub-split/release-too…
umair-ably Sep 3, 2026
ddc408a
Signpost the old ably gem's API reference from the new docs front page
umair-ably Sep 4, 2026
c6736af
Merge pull request #453 from ably/pubsub-split/restructure
ttypic Sep 15, 2026
ba6aae8
Merge pull request #454 from ably/pubsub-split/release-tooling
ttypic Sep 15, 2026
6a8a6d5
Refactor client instantiation to enforce side-specific construction t…
ttypic Sep 14, 2026
60b4e36
Fix REST presence query params to use correct camelCase format (clien…
ttypic Sep 14, 2026
6e6123f
Move client namespaces under Ably::PubSub and rename Rest to Http
ttypic Sep 16, 2026
4e857c7
Merge pull request #459 from ably/integration/split-renaming
ttypic Sep 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,43 @@ jobs:
github-token: ${{ secrets.GITHUB_TOKEN }}
flag-name: ruby-${{ matrix.ruby }}-${{ matrix.protocol }}-${{ matrix.type }}
parallel: true
# Dry run of the release pipeline: the same consistency assertions release.yml's
# pre-flight makes, plus a build of both gems — so a version-file or gemspec
# regression surfaces on every PR instead of at release time. Publishes nothing.
release-dry-run:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2
with:
persist-credentials: false
- uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0
with:
ruby-version: '3.3'
bundler-cache: false
- name: 'Versions and pin must agree (mirrors release.yml pre-flight)'
run: |
ruby <<'RUBY'
require_relative 'core/lib/ably/version'
require_relative 'server/lib/ably/pubsub/server/version'

errors = []
errors << "core Ably::VERSION (#{Ably::VERSION}) != server Ably::PubSub::Server::VERSION (#{Ably::PubSub::Server::VERSION})" unless Ably::VERSION == Ably::PubSub::Server::VERSION

server_spec = Gem::Specification.load('server/ably-pubsub-server.gemspec')
core_dep = server_spec.dependencies.find { |d| d.name == 'ably-pubsub-core' }
errors << "server gemspec pins ably-pubsub-core '#{core_dep&.requirement}', expected '= #{Ably::VERSION}'" unless core_dep&.requirement.to_s == "= #{Ably::VERSION}"

abort errors.join("\n") unless errors.empty?
puts "Version consistency OK: #{Ably::VERSION}"
RUBY
- name: Build both gems
run: |
set -euo pipefail
(cd core && gem build ably-pubsub-core.gemspec)
(cd server && gem build ably-pubsub-server.gemspec)

finish:
needs: check
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[submodule "lib/submodules/ably-common"]
path = lib/submodules/ably-common
path = core/lib/submodules/ably-common
url = https://github.com/ably/ably-common.git
2 changes: 2 additions & 0 deletions .yardopts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
core/lib/**/*.rb
server/lib/**/*.rb
41 changes: 29 additions & 12 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,48 @@
# Contributing

This repository hosts two gems, released in lockstep at the same version:

- [`core/`](./core) — `ably-pubsub-core`: the shared implementation. An internal package; only Ably packages depend on it.
- [`server/`](./server) — `ably-pubsub-server`: the public server-side package. Its factory functions (`Ably::PubSub::Server.create_http_client` / `.create_realtime_client`) are the only entry points: the client classes refuse direct construction, and these factories reach them through `Ably::Internal`.

## Development

1. Fork it
2. When pulling to local, make sure to also pull the `ably-common` repo (`git submodule init && git submodule update`)
3. Create your feature branch (`git checkout -b my-new-feature`)
4. Commit your changes (`git commit -am 'Add some feature'`)
5. Ensure you have added suitable tests and the test suite is passing(`bundle exec rspec`)
5. Ensure you have added suitable tests and the test suite is passing (`bundle exec rspec`) — the root `Gemfile` wires both gems up as path dependencies, so a single `bundle install` at the root covers everything
6. Push to the branch (`git push origin my-new-feature`)
7. Create a new Pull Request

---

## Release process

This library uses [semantic versioning](http://semver.org/). For each release, the following needs to be done:
This library uses [semantic versioning](http://semver.org/). `ably-pubsub-core` and `ably-pubsub-server` always release together at the same version: the release workflow refuses to publish them independently.

For each release, the following needs to be done:

1. Create a branch for the release, named like `release/1.2.3` (where `1.2.3` is the new version number)
2. Update the version number in [version.rb](./lib/ably/version.rb) and commit the change.
1. Create a branch for the release, named like `release/2.0.1` (where `2.0.1` is the new version number)
2. Update the version number in **all three places**, which must agree (the release workflow's pre-flight enforces this):
- `Ably::VERSION` in [core/lib/ably/version.rb](./core/lib/ably/version.rb)
- `Ably::PubSub::Server::VERSION` in [server/lib/ably/pubsub/server/version.rb](./server/lib/ably/pubsub/server/version.rb)
- the exact-version `ably-pubsub-core` pin in [server/ably-pubsub-server.gemspec](./server/ably-pubsub-server.gemspec) (derived from the version constant, so it normally follows automatically)
3. Run [`github_changelog_generator`](https://github.com/github-changelog-generator/github-changelog-generator) to automate the update of the [CHANGELOG](./CHANGELOG.md). This may require some manual intervention, both in terms of how the command is run and how the change log file is modified. Your mileage may vary:
- The command you will need to run will look something like this: `github_changelog_generator -u ably -p ably-pubsub-ruby --since-tag v1.2.3 --output delta.md --token $GITHUB_TOKEN_WITH_REPO_ACCESS`. Generate token [here](https://github.com/settings/tokens/new?description=GitHub%20Changelog%20Generator%20token).
- The command you will need to run will look something like this: `github_changelog_generator -u ably -p ably-pubsub-ruby --since-tag v2.0.0 --output delta.md --token $GITHUB_TOKEN_WITH_REPO_ACCESS`. Generate token [here](https://github.com/settings/tokens/new?description=GitHub%20Changelog%20Generator%20token).
- Using the command above, `--output delta.md` writes changes made after `--since-tag` to a new file
- The contents of that new file (`delta.md`) then need to be manually inserted at the top of the `CHANGELOG.md`, changing the "Unreleased" heading and linking with the current version numbers
- Also ensure that the "Full Changelog" link points to the new version tag instead of the `HEAD`
4. Commit this change: `git add CHANGELOG.md && git commit -m "Update change log."`
5. Ideally, run `rake doc:spec` to generate a new [spec file](./SPEC.md). Then commit these changes.
6. Make a PR against `main`. Once the PR is approved, merge it into `main`.
7. Add a tag to the new `main` head commit and push to origin such as `git tag v1.0.3 && git push origin v1.0.3`.
8. Visit [https://github.com/ably/ably-pubsub-ruby/tags](https://github.com/ably/ably-pubsub-ruby/tags) and `Add release notes` for the release including links to the changelog entry.
9. Run `rake release` to publish the gem to [Rubygems](https://rubygems.org/gems/ably).
10. Release the [REST-only library `ably-ruby-rest`](https://github.com/ably/ably-ruby-rest#release-process).
11. Create the entry on the [Ably Changelog](https://changelog.ably.com/) (via [headwayapp](https://headwayapp.co/)).
7. Add a tag to the new `main` head commit and push to origin such as `git tag v2.0.1 && git push origin v2.0.1`.
8. Visit [the tags page](https://github.com/ably/ably-pubsub-ruby/tags) and `Add release notes` for the release including links to the changelog entry.
9. Run the [Release workflow](./.github/workflows/release.yml) (Actions → Release → Run workflow) with the version number. It publishes `ably-pubsub-core` and then `ably-pubsub-server` to RubyGems via trusted publishing — no local credentials involved. A failed run is safe to re-run with the same version: already-published gems are skipped.
10. Create the entry on the [Ably Changelog](https://changelog.ably.com/) (via [headwayapp](https://headwayapp.co/)).

### Trusted publishing

The workflow authenticates to RubyGems with [trusted publishing](https://guides.rubygems.org/trusted-publishing/) (GitHub OIDC): both gems have a Trusted Publisher configured on rubygems.org bound to this repository and `.github/workflows/release.yml`. There are no long-lived RubyGems API keys anywhere. If the repository is renamed, both bindings must be reconfigured on rubygems.org or publishing fails.

### The legacy `ably` gem

The `ably` gem is in its maintenance window (security and critical fixes only, released from the maintenance branch) and is **not** released from `main`. The `ably-rest` gem (from the `ably-ruby-rest` repo) is likewise in maintenance and no longer part of this release process.
7 changes: 5 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
source 'https://rubygems.org'

# Specify your gem's dependencies in ably.gemspec
gemspec
# This repo hosts two gems, released in lockstep at the same version (PDR-091b):
# - core/ ably-pubsub-core: the shared implementation (internal package)
# - server/ ably-pubsub-server: the public server-side package (factory entry points)
gemspec path: 'core'
gemspec path: 'server'
5 changes: 5 additions & 0 deletions INTRO.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Ably `Ruby` Client Library SDK API Reference

> **Using the `ably` gem?** This is the API reference for the `ably-pubsub-server` gem, which replaces it.
> The API reference for the maintenance-only `ably` gem remains available at
> [sdk.ably.com/builds/ably/ably-ruby/main/docs](https://sdk.ably.com/builds/ably/ably-ruby/main/docs/)
> until its end of life.

The `Ruby` Client Library SDK supports a realtime and a REST interface.

The realtime interface enables a client to maintain a persistent connection to Ably and publish, subscribe and be present on channels.
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,10 @@ To use the Ably Realtime SDK in Ruby, the `EventMachine` reactor loop must be ru
Wrap your code inside a `EventMachine.run` block:

```ruby
require 'ably'
require 'ably/pubsub/server'

EventMachine.run do
client = Ably::Realtime.new(key: 'your-api-key')
client = Ably::PubSub::Server.create_realtime_client(key: 'your-api-key')

client.connection.connect do
puts "Connected with connection ID: #{client.connection.id}"
Expand All @@ -87,7 +87,7 @@ The following code connects to Ably's realtime messaging service, subscribes to

```ruby
# Initialize Ably Realtime client
realtime_client = Ably::Realtime.new(key: 'your-ably-api-key', client_id: 'me')
realtime_client = Ably::PubSub::Server.create_realtime_client(key: 'your-ably-api-key', client_id: 'me')

# Wait for connection to be established
realtime_client.connection.on(:connected) do
Expand Down
6 changes: 3 additions & 3 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require 'rubygems'
require 'bundler/setup'
require 'bundler/gem_tasks'

require 'json'

require 'yard'
Expand Down Expand Up @@ -32,8 +32,8 @@ begin

desc 'Generate error code constants from ably-common: https://github.com/ably/ably-common/issues/32'
task :generate_error_codes do
errors_json_path = File.join(File.dirname(__FILE__), 'lib/submodules/ably-common/protocol/errors.json')
module_path = File.join(File.dirname(__FILE__), 'lib/ably/modules/exception_codes.rb')
errors_json_path = File.join(File.dirname(__FILE__), 'core/lib/submodules/ably-common/protocol/errors.json')
module_path = File.join(File.dirname(__FILE__), 'core/lib/ably/modules/exception_codes.rb')
max_length = 0

errors = JSON.parse(File.read(errors_json_path)).each_with_object({}) do |(key, val), hash|
Expand Down
107 changes: 107 additions & 0 deletions UPDATING.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,112 @@
# Upgrade / Migration Guide

## Version 1.x (`ably` gem) to 2.0.0 (`ably-pubsub-server` gem)

### Gemfile

```ruby
# 1.x
gem 'ably'

# 2.0
gem 'ably-pubsub-server'
```

### Require

```ruby
# 1.x
require 'ably'

# 2.0
require 'ably/pubsub/server'
```

### HTTP client

```ruby
# 1.x
client = Ably::Rest::Client.new(key: ENV['ABLY_API_KEY'])
client = Ably::Rest::Client.new('key.id:secret')
client = Ably::Rest::Client.new(token: 'token')
client = Ably::Rest.new(key: ENV['ABLY_API_KEY'])

# 2.0
client = Ably::PubSub::Server.create_http_client(key: ENV['ABLY_API_KEY'])
client = Ably::PubSub::Server.create_http_client('key.id:secret')
client = Ably::PubSub::Server.create_http_client(token: 'token')
```

### Realtime client

```ruby
# 1.x
client = Ably::Realtime::Client.new(key: ENV['ABLY_API_KEY'])
client = Ably::Realtime::Client.new('key.id:secret')
client = Ably::Realtime.new(key: ENV['ABLY_API_KEY'])

# 2.0
client = Ably::PubSub::Server.create_realtime_client(key: ENV['ABLY_API_KEY'])
client = Ably::PubSub::Server.create_realtime_client('key.id:secret')
```

Both factories accept an options `Hash`, an API key `String`, or a token `String`.

`Ably::PubSub::Http::Client.new` and `Ably::PubSub::Realtime::Client.new` raise
`Ably::Exceptions::DirectConstructionNotSupported`.

### Namespaces

| 1.x | 2.0 |
| --- | --- |
| `Ably::Rest::*` | `Ably::PubSub::Http::*` |
| `Ably::Realtime::*` | `Ably::PubSub::Realtime::*` |

```ruby
# 1.x
client.is_a?(Ably::Rest::Client)
Ably::Rest::Client::MAX_MESSAGE_SIZE
Ably::Realtime::Channel::STATE.Attached
Ably::Realtime::Connection::STATE.Connected

# 2.0
client.is_a?(Ably::PubSub::Http::Client)
Ably::PubSub::Http::Client::MAX_MESSAGE_SIZE
Ably::PubSub::Realtime::Channel::STATE.Attached
Ably::PubSub::Realtime::Connection::STATE.Connected
```

`Ably::Rest` and `Ably::Realtime` raise `NameError` in 2.0.

### Unchanged

Everything after construction:

```ruby
client = Ably::PubSub::Server.create_http_client(key: ENV['ABLY_API_KEY'])

channel = client.channels.get('example')
channel.publish 'event', 'payload'
channel.history
channel.presence.get

client.auth.request_token
client.stats
client.time
```

```ruby
client = Ably::PubSub::Server.create_realtime_client(key: ENV['ABLY_API_KEY'])

client.connection.on(:connected) { }

channel = client.channels.get('example')
channel.attach
channel.subscribe { |message| }
channel.publish 'event', 'payload'
channel.presence.enter
```

## Version 1.1.8 to 1.2.0

### Notable Changes
Expand Down
20 changes: 20 additions & 0 deletions core/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# ably-pubsub-core

Internal implementation package for Ably's own Pub/Sub Ruby packages.

**This gem is not intended for direct external use.** It is published only so that
Ably's public packages can depend on it, and it constructs nothing on its own:
`Ably::PubSub::Http::Client.new` and `Ably::PubSub::Realtime::Client.new` raise
`Ably::Exceptions::DirectConstructionNotSupported`. The package a client is created from
is what declares its side to the platform, and a directly constructed client declares
none. Ably's per-side packages construct clients through `Ably::Internal`, which is not
public API.

Use [`ably-pubsub-server`](../server) instead, which exposes the supported entry points:

```ruby
client = Ably::PubSub::Server.create_http_client(key) # stateless HTTP client
client = Ably::PubSub::Server.create_realtime_client(key) # stateful realtime client
```

`ably-pubsub-core` and `ably-pubsub-server` are released in lockstep at the same version.
16 changes: 8 additions & 8 deletions ably.gemspec → core/ably-pubsub-core.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'ably/version'

Gem::Specification.new do |spec|
spec.name = 'ably'
spec.name = 'ably-pubsub-core'
spec.version = Ably::VERSION
spec.authors = ['Lewis Marshall', "Matthew O'Riordan"]
spec.email = ['lewis@lmars.net', 'matt@ably.io']
spec.description = %q{A Ruby client library for ably.io realtime messaging}
spec.summary = %q{A Ruby client library for ably.io realtime messaging implemented using EventMachine}
spec.authors = ['Ably']
spec.email = ['support@ably.com']
spec.description = %q{Internal implementation package for Ably's own Pub/Sub packages. Not intended for direct external use: depend on ably-pubsub-server instead.}
spec.summary = %q{Shared core implementation for Ably Pub/Sub Ruby SDKs (internal)}
spec.homepage = 'https://github.com/ably/ably-pubsub-ruby'
spec.license = 'Apache-2.0'

spec.files = `git ls-files`.split($/)
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
spec.files = Dir.chdir(File.expand_path(__dir__)) do
`git ls-files -z lib`.split("\x0").reject { |f| f.start_with?('lib/submodules') }
end
spec.require_paths = ['lib']

spec.add_runtime_dependency 'eventmachine', '~> 1.2.6'
Expand Down
8 changes: 6 additions & 2 deletions lib/ably.rb → core/lib/ably.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@
end

require 'ably/auth'
require 'ably/pubsub'
require 'ably/exceptions'
require 'ably/logger'
require 'ably/realtime'
require 'ably/rest'
require 'ably/pubsub/realtime'
require 'ably/pubsub/http'

# Requires the client classes above: it constructs them.
require 'ably/internal'
8 changes: 8 additions & 0 deletions core/lib/ably/agent.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module Ably
# The SDK family identifier renamed from `ably-ruby` with the per-side package split, so the
# identifier alone partitions the fleet: `ably-ruby/*` is legacy-gem traffic,
# `ably-pubsub-ruby/*` is new-package traffic. It names the family rather than any one
# published gem; the side a client declares travels as a separate versionless agent entry
# (see Ably::PubSub::Server and the agents registry in ably-common).
AGENT = "ably-pubsub-ruby/#{Ably::VERSION} ruby/#{RUBY_VERSION}"
end
Loading
Loading