Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 19 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,25 @@ jobs:
github-token: ${{ secrets.GITHUB_TOKEN }}
flag-name: ruby-${{ matrix.ruby }}-${{ matrix.protocol }}-${{ matrix.type }}
parallel: true
package:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2
with:
submodules: 'recursive'
persist-credentials: false
- uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0
with:
ruby-version: '3.3'
bundler-cache: true
# The packaging metadata for the Pub/Sub gems is otherwise only exercised at release time,
# where a mistake is expensive. spec/unit/pubsub/packaging_spec.rb covers what each gem
# ships and that the three stay on one version; this covers that they build at all.
- name: 'Check that every gem builds'
run: bundle exec rake build packages:build

finish:
needs: check
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions .yardopts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
lib/**/*.rb packages/*/lib/**/*.rb
35 changes: 33 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,43 @@

---

## Repository layout

This repository builds three gems, released together on the same version. They all install into the one `Ably` namespace, so what you require never tells you which gem shipped it:

| Gem | Source | Required as | Role |
|-----|--------|-------------|------|
| `ably` | [`lib/`](./lib) | `ably` | The shared core, containing all of the implementation |
| `ably-pubsub-server` | [`packages/ably-pubsub-server/`](./packages/ably-pubsub-server) | `ably/pubsub/server` | The server-side factories |
| `ably-pubsub-device` | [`packages/ably-pubsub-device/`](./packages/ably-pubsub-device) | `ably/pubsub/device` | The device-side factory |

Each side adds factories that return the core's clients unchanged, so that the gem a caller installs names the side their application runs on. They pin the core exactly, and requiring either makes the whole `Ably` namespace available.

Two rules keep that arrangement working, and both are covered by [`spec/unit/pubsub/packaging_spec.rb`](./spec/unit/pubsub/packaging_spec.rb):

- **Neither Pub/Sub gem may define `lib/ably/pubsub.rb`.** Both would have to ship it, and whichever came first on the load path would be the one required, hiding the other's. `Ably::PubSub` is opened by each side's own entry point instead.
- **Each Pub/Sub gem ships only its own subtree**, and the core gem ships none of `packages/`, so that no file is shipped by two gems.

The [`Gemfile`](./Gemfile) points at both Pub/Sub gems by path, so `bundle exec rspec` exercises them against the core in this checkout rather than a published version of it. Their specs are in [`spec/unit/pubsub/`](./spec/unit/pubsub) and need no network.

To build all three gems into `pkg/`:

```shell
bundle exec rake build packages:build
```

A gemspec's file list is relative to the working directory, so each Pub/Sub gem is built from its own directory — which `rake packages:build` takes care of.

---

## Release process

This library uses [semantic versioning](http://semver.org/). For each release, the following needs to be done:

`ably`, `ably-pubsub-server` and `ably-pubsub-device` are released in lockstep on the same version, because the Pub/Sub gems pin the core exactly — a partial release is an unusable one.

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.
2. Update the version number in all three of [`lib/ably/version.rb`](./lib/ably/version.rb), [`packages/ably-pubsub-server/lib/ably/pubsub/server/version.rb`](./packages/ably-pubsub-server/lib/ably/pubsub/server/version.rb) and [`packages/ably-pubsub-device/lib/ably/pubsub/device/version.rb`](./packages/ably-pubsub-device/lib/ably/pubsub/device/version.rb), and commit the change. The specs in [`spec/unit/pubsub/packaging_spec.rb`](./spec/unit/pubsub/packaging_spec.rb) fail if any of these drift apart, so run them before moving on.
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-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).
- Using the command above, `--output delta.md` writes changes made after `--since-tag` to a new file
Expand All @@ -26,6 +57,6 @@ This library uses [semantic versioning](http://semver.org/). For each release, t
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-ruby/tags](https://github.com/ably/ably-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).
9. Run `rake release` to publish the core gem to [Rubygems](https://rubygems.org/gems/ably), then `rake packages:release` to publish [`ably-pubsub-server`](https://rubygems.org/gems/ably-pubsub-server) and [`ably-pubsub-device`](https://rubygems.org/gems/ably-pubsub-device). The Pub/Sub gems pin the core exactly, so publish them in that order — the core first, or their dependency cannot be resolved.
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/)).
5 changes: 5 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@ source 'https://rubygems.org'

# Specify your gem's dependencies in ably.gemspec
gemspec

# The Pub/Sub gems released alongside this one, so that the test suite exercises them against the
# core in this checkout rather than a published version of it.
gem 'ably-pubsub-server', path: 'packages/ably-pubsub-server'
gem 'ably-pubsub-device', path: 'packages/ably-pubsub-device'
31 changes: 26 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,24 @@ Ably aims to support a wide range of platforms and browsers. If you experience a

## Installation

To get started with your project, install the package:
Install the gem for the side your application runs on. Each pulls in `ably` and adds an entry point under `Ably::PubSub` naming that side:

```sh
# Create a new Gemfile
echo "source 'https://rubygems.org'" > Gemfile
echo "gem 'ably'" >> Gemfile

# Trusted server environments — publishing, token issuing, backend subscribers
echo "gem 'ably-pubsub-server'" >> Gemfile # provides Ably::PubSub::Server

# End-user devices — desktop apps, CLIs, IoT and embedded clients
echo "gem 'ably-pubsub-device'" >> Gemfile # provides Ably::PubSub::Device

# Install the gem
bundle install
```

Installing `ably` on its own also still works, and remains fully supported. It is the shared core both build on, and the clients they return are its clients unchanged.

> [!NOTE]
Install [Ruby](https://www.ruby-lang.org/en/documentation/installation/) version 2.7 or greater.

Expand All @@ -69,14 +75,15 @@ 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/device'

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

client.connection.connect do
puts "Connected with connection ID: #{client.connection.id}"
end
end
```

---
Expand All @@ -87,7 +94,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::Device.create_client(key: 'your-ably-api-key', client_id: 'me')

# Wait for connection to be established
realtime_client.connection.on(:connected) do
Expand All @@ -108,6 +115,20 @@ end

```

On a server, use `Ably::PubSub::Server.create_realtime_client` for the same client over a persistent connection, or `Ably::PubSub::Server.create_http_client` when publish, history, presence reads, stats and token issuing over HTTP are enough. The HTTP client is synchronous and needs no EventMachine reactor.

### Migrating from the client constructors

Constructing `Ably::Rest::Client` or `Ably::Realtime::Client` directly — including through the `Ably::Rest.new` and `Ably::Realtime.new` shorthands — still works and is not scheduled for removal, but it emits a deprecation warning naming the factory for your side:

| Before | After |
|--------|-------|
| `Ably::Realtime.new(...)` on a device | `Ably::PubSub::Device.create_client(...)` |
| `Ably::Realtime.new(...)` on a server | `Ably::PubSub::Server.create_realtime_client(...)` |
| `Ably::Rest.new(...)` | `Ably::PubSub::Server.create_http_client(...)` |

The factories take the same options as the constructors they replace and behave identically to them, so migrating is a change of entry point only.

---

## Releases
Expand Down
28 changes: 28 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,37 @@ require 'bundler/setup'
require 'bundler/gem_tasks'
require 'json'

require_relative 'lib/ably/version'

require 'yard'
YARD::Rake::YardocTask.new

# The ably-pubsub-server and ably-pubsub-device gems, released alongside this one — see
# CONTRIBUTING.md. `rake build` and `rake release`, from bundler/gem_tasks, cover the core gem.
PUBSUB_GEMS = %w(ably-pubsub-server ably-pubsub-device).freeze

namespace :packages do
pkg_path = File.expand_path('pkg', __dir__)

desc 'Build the Pub/Sub gems into pkg/'
task :build do
mkdir_p pkg_path
PUBSUB_GEMS.each do |gem_name|
# A gemspec's files are relative to the working directory, so each is built from its own
Dir.chdir("packages/#{gem_name}") do
sh "gem build #{gem_name}.gemspec --output #{pkg_path}/#{gem_name}-#{Ably::VERSION}.gem"
end
end
end

desc 'Build and push the Pub/Sub gems to Rubygems'
task :release => :build do
PUBSUB_GEMS.each do |gem_name|
sh "gem push #{pkg_path}/#{gem_name}-#{Ably::VERSION}.gem"
end
end
end

begin
require 'rspec/core/rake_task'

Expand Down
4 changes: 3 additions & 1 deletion ably.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ Gem::Specification.new do |spec|
spec.homepage = 'http://github.com/ably/ably-ruby'
spec.license = 'Apache-2.0'

spec.files = `git ls-files`.split($/)
# packages/ holds the ably-pubsub-server and ably-pubsub-device gems, each of which ships its
# own subtree and is released alongside this one — see CONTRIBUTING.md.
spec.files = `git ls-files`.split($/).reject { |file| file.start_with?('packages/') }
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
spec.require_paths = ['lib']
Expand Down
4 changes: 4 additions & 0 deletions lib/ably/realtime.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ module Ably
module Realtime
# Convenience method providing an alias to {Ably::Realtime::Client} constructor.
#
# @deprecated Use {Ably::PubSub::Server.create_realtime_client}, from the `ably-pubsub-server`
# gem, or {Ably::PubSub::Device.create_client}, from the `ably-pubsub-device` gem, whichever
# names the side your application runs on.
#
# @param (see Ably::Realtime::Client#initialize)
# @option options (see Ably::Realtime::Client#initialize)
#
Expand Down
15 changes: 15 additions & 0 deletions lib/ably/realtime/client.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
require 'uri'
require 'ably/realtime/channel/publisher'
require 'ably/realtime/recovery_key_context'
require 'ably/util/deprecation'

module Ably
module Realtime
# A client that extends the functionality of the {Ably::Realtime::Client} and provides additional realtime-specific features.
#
# @deprecated Use {Ably::PubSub::Server.create_realtime_client}, from the `ably-pubsub-server`
# gem, or {Ably::PubSub::Device.create_client}, from the `ably-pubsub-device` gem, whichever
# names the side your application runs on.
#
class Client
include Ably::Modules::AsyncWrapper
include Ably::Realtime::Channel::Publisher
Expand Down Expand Up @@ -81,6 +86,10 @@ class Client
#
# @spec RSC1
#
# @deprecated Use {Ably::PubSub::Server.create_realtime_client} or
# {Ably::PubSub::Device.create_client}, which take the same options and return this same
# client.
#
# @param (see {Ably::Rest::Client#initialize})
# @option options (see Ably::Rest::Client#initialize) An options {Hash} object.
# @option options [Proc] :auth_callback when provided, the Proc will be called with the token params hash as the first argument, whenever a new token is required.
Expand Down Expand Up @@ -118,6 +127,12 @@ def initialize(options)
end
end

Ably::Util::Deprecation.warn_constructor_deprecated(
'Ably::Realtime::Client.new',
'Ably::PubSub::Server.create_realtime_client, from the ably-pubsub-server gem, or ' \
'Ably::PubSub::Device.create_client, from the ably-pubsub-device gem'
)

@transport_params = options.delete(:transport_params).to_h.each_with_object({}) do |(key, value), acc|
acc[key.to_s] = value.to_s
end
Expand Down
3 changes: 3 additions & 0 deletions lib/ably/rest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ module Ably
module Rest
# Convenience method providing an alias to {Ably::Rest::Client} constructor.
#
# @deprecated Use {Ably::PubSub::Server.create_http_client}, from the `ably-pubsub-server`
# gem, which names the side your application runs on.
#
# @param (see Ably::Rest::Client#initialize)
# @option options (see Ably::Rest::Client#initialize)
#
Expand Down
15 changes: 15 additions & 0 deletions lib/ably/rest/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@
require 'faraday/typhoeus'

require 'ably/rest/middleware/exceptions'
require 'ably/util/deprecation'

module Ably
module Rest
# A client that offers a simple stateless API to interact directly with Ably's REST API.
#
# @deprecated Use {Ably::PubSub::Server.create_http_client}, from the `ably-pubsub-server`
# gem, which names the side your application runs on.
#
class Client
include Ably::Modules::Conversions
include Ably::Modules::HttpHelpers
Expand Down Expand Up @@ -129,6 +133,9 @@ class Client
#
# @spec RSC1
#
# @deprecated Use {Ably::PubSub::Server.create_http_client}, from the `ably-pubsub-server`
# gem, which takes the same options and returns this same client.
#
# @param [Hash,String] options an options Hash or String used to configure the client and the authentication, or String with an API key or Token ID
# @option options [Boolean] :tls (true) When false, TLS is disabled. Please note Basic Auth is disallowed without TLS as secrets cannot be transmitted over unsecured connections.
# @option options [String] :key API key comprising the key name and key secret in a single string
Expand Down Expand Up @@ -185,6 +192,14 @@ def initialize(options)
end
end

# A realtime client builds its REST client with itself as :realtime_client, and warns
# about its own constructor, so only direct use of this one is deprecated here.
unless options[:realtime_client]
Ably::Util::Deprecation.warn_constructor_deprecated(
'Ably::Rest::Client.new', 'Ably::PubSub::Server.create_http_client, from the ably-pubsub-server gem'
)
end

@agent = options.delete(:agent) || Ably::AGENT
@realtime_client = options.delete(:realtime_client)
@tls = options.delete_with_default(:tls, true)
Expand Down
Loading
Loading