{"id":22753187,"url":"https://github.com/nebulab/omnes","last_synced_at":"2025-04-30T21:26:03.475Z","repository":{"id":40457485,"uuid":"460786967","full_name":"nebulab/omnes","owner":"nebulab","description":"Pub/Sub for Ruby","archived":false,"fork":false,"pushed_at":"2023-11-10T20:56:14.000Z","size":197,"stargazers_count":60,"open_issues_count":1,"forks_count":2,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-04-18T12:19:45.932Z","etag":null,"topics":["pubsub","ruby"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nebulab.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-02-18T09:22:59.000Z","updated_at":"2024-12-15T19:23:59.000Z","dependencies_parsed_at":"2023-02-14T07:16:06.516Z","dependency_job_id":null,"html_url":"https://github.com/nebulab/omnes","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nebulab%2Fomnes","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nebulab%2Fomnes/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nebulab%2Fomnes/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nebulab%2Fomnes/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nebulab","download_url":"https://codeload.github.com/nebulab/omnes/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251784328,"owners_count":21643262,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["pubsub","ruby"],"created_at":"2024-12-11T06:09:29.333Z","updated_at":"2025-04-30T21:26:03.455Z","avatar_url":"https://github.com/nebulab.png","language":"Ruby","readme":"# Omnes\n\nPub/sub for Ruby.\n\nOmnes is a Ruby library implementing the publish-subscribe pattern. This\npattern allows senders of messages to be decoupled from their receivers. An\nEvent Bus acts as a middleman where events are published while interested\nparties can subscribe to them.\n\n## Installation\n\n`bundle add omnes`\n\n## Usage\n\nThere're two ways to make use of the pub/sub features Omnes provides:\n\n- Standalone, through an [`Omnes::Bus`](lib/omnes/bus.rb) instance:\n\n```ruby\nrequire \"omnes\"\n\nbus = Omnes::Bus.new\n```\n\n- Mixing in the behavior in another class by including the [`Omnes`](lib/omnes.rb) module.\n\n```ruby\nrequire \"omnes\"\n\nclass Notifier\n  include Omnes\nend\n```\n\nThe following examples will use the direct `Omnes::Bus` instance. The only\ndifference for the mixing use case is that the methods are directly called in\nthe including instance.\n\n## Registering events\n\nBefore being able to work with a given event, its name (which must be a\n`Symbol`) must be registered:\n\n```ruby\nbus.register(:order_created)\n```\n\n## Publishing events\n\nAn event can be anything responding to a method `:omnes_event_name`, which must match with a\nregistered name.\n\nTypically, there're two main ways to generate events.\n  \n1. Unstructured events\n\nAn event can be generated at publication time, where you provide its name and a\npayload to be consumed by its subscribers:\n\n```ruby\nbus.publish(:order_created, number: order.number, user_email: user.email)\n```\n\nIn that case, an instance of [`Omnes::UnstructuredEvent`](lib/omnes/unstructured_event.rb) is generated\nunder the hood.\n\nUnstructured events are straightforward to create and use, but they're harder\nto debug as they're defined at publication time. On top of that, other\nfeatures, such as event persistence, can't be reliably built on top of them.\n\n2. Instance-backed events\n\nYou can also publish an instance of a class including\n[`Omnes::Event`](lib/omnes/event.rb). The only fancy thing it provides is an\nOOTB event name generated based on the class name.\n\n```ruby\nclass OrderCreatedEvent\n  include Omnes::Event\n\n  attr_reader :number, :user_email\n  \n  def initialize(number:, user_email:)\n    @number = number\n    @user_email = user_email\n  end\nend\n\nevent = OrderCreatedEvent.new(number: order.number, user_email: user.email)\nbus.publish(event)\n```\n\nBy default, an event name instance equals the event class name downcased,\nunderscored and with the `Event` suffix removed if present (`:order_created` in\nthe previous example). However, you can configure your own name generator based\non the event instance:\n\n```ruby\nevent_name_as_class = -\u003e(event) { event.class.name.to_sym } # :OrderCreatedEvent in the example\nOmnes.config.event.name_builder = event_name_as_class\n```\n\nInstance-backed events provide a well-defined structure, and other features,\nlike event persistence, can be added on top of them.\n\n## Subscribing to events\n\nYou can subscribe to a specific event to run some code whenever it's published.\nThe event is yielded to the subscription block:\n\n```ruby\nbus.subscribe(:order_created) do |event|\n  # ...\nend\n```\n\nFor unstructured events, the published data is made available through the\n`payload` method, although `#[]` can be used as a shortcut:\n\n```ruby\nbus.subscribe(:order_created) do |event|\n  OrderCreationEmail.new.send(number: event[:number], email: event[:user_email])\n  # OrderCreationEmail.new.send(number: event.payload[:number], email: event.payload[:user_email])\nend\n```\n\nOtherwise, use the event instance according to its structure:\n\n```ruby\nbus.subscribe(:order_created) do |event|\n  OrderCreationEmail.new.send(number: event.number, email: event.user_email)\nend\n```\n\nThe subscription code can also be given as anything responding to a method\n`#call`.\n\n```ruby\nclass OrderCreationEmailSubscription\n  def call(event)\n    OrderCreationEmail.new.send(number: event.number, email: event.user_email)\n  end\nend\n\nbus.subscribe(:order_created, OrderCreationEmailSubscription.new)\n```\n\nHowever, see [Event subscribers](#event-subscribers) section bellow for a more powerful way\nto define standalone event handlers.\n\n### Global subscriptions\n\nYou can also create a subscription that will run for all events:\n\n```ruby\nclass LogEventsSubscription\n  attr_reader :logger\n  \n  def initialize(logger: Logger.new(STDOUT))\n    @logger = logger\n  end\n  \n  def call(event)\n    logger.info(\"Event #{event.omnes_event_name} published\")\n  end\nend\n\nbus.subscribe_to_all(LogEventsSubscription.new)\n```\n\n### Custom matcher subscriptions\n\nCustom event matchers can be defined. A matcher is something responding to\n`#call` and taking the event as an argument. It must return `true` or `false`\nto match or ignore the event.\n\n```ruby\nORDER_EVENTS_MATCHER = -\u003e(event) { event.omnes_event_name.start_with?(:order) }\n\nbus.subscribe_with_matcher(ORDER_EVENTS_MATCHER) do |event|\n  # ...\nend\n```\n\n### Referencing subscriptions\n\nFor all subscription methods we've seen, an `Omnes::Subscription` instance is\nreturned. Holding that reference can be useful for [debugging](#debugging) and\n[testing](#testing) purposes.\n\nOften though, you won't have the reference at hand when you need it.\nThankfully, you can provide a subscription identifier on subscription time and\nuse it later to fetch the subscription instance from the bus. A subscription\nidentifier needs to be a `Symbol`:\n\n```ruby\nbus.subscribe(:order_created, OrderCreationEmailSubscription.new, id: :order_created_email)\nsubscription = bus.subscription(:order_created_email)\n```\n\n## Event subscribers\n\nEvents subscribers offer a way to define event subscriptions from a custom\nclass.\n\nIn its simplest form, you can match an event to a method in the class.\n  \n```ruby\nclass OrderCreationEmailSubscriber\n  include Omnes::Subscriber\n  \n  handle :order_created, with: :send_confirmation_email\n  \n  attr_reader :service\n  \n  def initialize(service: OrderCreationEmail.new)\n    @service = service\n  end\n\n  def send_confirmation_email(event)\n    service.send(number: event.number, email: event.user_email)\n  end\nend\n```\n\nYou add the subscriptions by calling the `#subscribe_to` method on an instance:\n\n```ruby\nOrderCreationEmailSubscriber.new.subscribe_to(bus)\n```\n\nEquivalent to the subscribe methods we've seen above, you can also subscribe to\nall events:\n\n```ruby\nclass LogEventsSubscriber\n  include Omnes::Subscriber\n  \n  handle_all with: :log_event\n  \n  attr_reader :logger\n  \n  def initialize(logger: Logger.new(STDOUT))\n    @logger = logger\n  end\n\n  def log_event(event)\n    logger.info(\"Event #{event.omnes_event_name} published\")\n  end\nend\n```\n\nYou can also handle the event with your own custom matcher:\n\n```ruby\nclass OrderSubscriber\n  include Omnes::Subscriber\n  \n  handle_with_matcher ORDER_EVENTS_MATCHER, with: :register_order_event\n  \n  def register_order_event(event)\n    # ...\n  end\nend\n```\n\nLikewise, you can provide [identifiers to reference\nsubscriptions](#referencing-subscriptions):\n\n```ruby\nhandle :order_created, with: :send_confirmation_email, id: :order_creation_email_subscriber\n```\n\nAs you can subscribe multiple instances of a subscriber to the same bus, you\nmight need to create a different identifier for each of them. For those cases,\nyou can pass a lambda taking the subscriber instance:\n\n```ruby\nhandle :order_created, with: :send_confirmation_email, id: -\u003e(subscriber) { :\"#{subscriber.id}_order_creation_email_subscriber\" }\n```\n\n### Autodiscovering event handlers\n\nYou can let the event handlers to be automatically discovered.You need to\nenable the `autodiscover` feature and prefix the event name with `on_` for your\nhandler name.\n\n```ruby\nclass OrderCreationEmailSubscriber\n  include Omnes::Subscriber[\n    autodiscover: true\n  ]\n  \n  # ...\n  \n  def on_order_created(event)\n    # ...\n  end\nend\n```\n\nIf you prefer, you can make `autodiscover` on by default:\n\n```ruby\nOmnes.config.subscriber.autodiscover = true\n```\n\nYou can also specify your own autodiscover strategy. It must be something\ncallable, transforming the event name into the handler name.\n  \n```ruby\nAUTODISCOVER_STRATEGY = -\u003e(event_name) { event_name }\n\nclass OrderCreationEmailSubscriber\n  include Omnes::Subscriber[\n    autodiscover: true,\n    autodiscover_strategy: AUTODISCOVER_STRATEGY\n  ]\n  \n  # ...\n\n  def order_created(event)\n    # ...\n  end\nend\n```\n\nThe strategy can also be globally set:\n\n```ruby\nOmnes.config.subscriber.autodiscover_strategy = AUTODISCOVER_STRATEGY\n```\n\n### Adapters\n\nSubscribers are not limited to use a method as event handler. They can interact\nwith the whole instance context and leverage it to build adapters.\n\nOmnes ships with a few of them.\n\n#### Sidekiq adapter\n\nThe Sidekiq adapter allows creating a subscription to be processed as a\n[Sidekiq](https://sidekiq.org) background job.\n\nSidekiq requires that the argument passed to `#perform` is serializable. By\ndefault, the result of calling `#payload` in the event is taken.\n\n```ruby\nclass OrderCreationEmailSubscriber\n  include Omnes::Subscriber\n  include Sidekiq::Job\n  \n  handle :order_created, with: Adapter::Sidekiq\n  \n  def perform(payload)\n    OrderCreationEmail.send(number: payload[\"number\"], email: payload[\"user_email\"])\n  end\nend\n\nbus = Omnes::Bus.new\nbus.register(:order_created)\nOrderCreationEmailSubscriber.new.subscribe_to(bus)\nbus.publish(:order_created, \"number\" =\u003e order.number, \"user_email\" =\u003e user.email)\n```\n\nHowever, you can configure how the event is serialized thanks to the\n`serializer:` option. It needs to be something callable taking the event as\nargument:\n\n```ruby\nhandle :order_created, with: Adapter::Sidekiq[serializer: :serialized_payload.to_proc]\n```\n\nYou can also globally configure the default serializer:\n\n```ruby\nOmnes.config.subscriber.adapter.sidekiq.serializer = :serialized_payload.to_proc\n```\n\nYou can delay the callback execution from the publication time with the `.in`\nmethod (analogous to `Sidekiq::Job.perform_in`):\n\n```ruby\nhandle :order_created, with: Adapter::Sidekiq.in(60)\n```\n\n#### ActiveJob adapter\n\nThe ActiveJob adapter allows creating a subscription to be processed as an\n[ActiveJob](https://edgeguides.rubyonrails.org/active_job_basics.html)\nbackground job.\n\nActiveJob requires that the argument passed to `#perform` is serializable. By\ndefault, the result of calling `#payload` in the event is taken.\n\n```ruby\nclass OrderCreationEmailSubscriber \u003c ActiveJob\n  include Omnes::Subscriber\n  \n  handle :order_created, with: Adapter::ActiveJob\n  \n  def perform(payload)\n    OrderCreationEmail.send(number: payload[\"number\"], email: payload[\"user_email\"])\n  end\nend\n\nbus = Omnes::Bus.new\nbus.register(:order_created)\nOrderCreationEmailSubscriber.new.subscribe_to(bus)\nbus.publish(:order_created, \"number\" =\u003e order.number, \"user_email\" =\u003e user.email)\n```\n\nHowever, you can configure how the event is serialized thanks to the\n`serializer:` option. It needs to be something callable taking the event as\nargument:\n\n```ruby\nhandle :order_created, with: Adapter::ActiveJob[serializer: :serialized_payload.to_proc]\n```\n\nYou can also globally configure the default serializer:\n\n```ruby\nOmnes.config.subscriber.adapter.active_job.serializer = :serialized_payload.to_proc\n```\n\n#### Custom adapters\n\nCustom adapters can be built. They need to implement a method `#call` taking\nthe instance of `Omnes::Subscriber`, the event and, optionally, the publication\ncontext (see [debugging subscriptions](#subscription)).\n\nHere's a custom adapter executing a subscriber method in a different\nthread (we add an extra argument for the method name, and we partially apply it\nat the definition time to obey the adapter requirements).\n\n```ruby\nTHREAD_ADAPTER = lambda do |method_name, instance, event|\n  Thread.new { instance.method(method_name).call(event) }\nend\n\nclass OrderCreationEmailSubscriber\n  include Omnes::Subscriber\n  \n  handle :order_created, with: THREAD_ADAPTER.curry[:order_created]\n  \n  def order_created(event)\n    # ...\n  end\nend\n```\n\nAlternatively, adapters can be curried and only take the instance as an\nargument, returning a callable taking the event. For instance, we could also\nhave defined the thread adapter like this:\n\n```ruby\nclass ThreadAdapter\n  attr_reader :method_name\n  \n  def initialize(method_name)\n    @method_name = method_name\n  end\n  \n  def call(instance)\n    raise unless instance.respond_to?(method_name)\n    \n    -\u003e(event) { instance.method(:call).(event) }\n  end\nend\n\n# ...\nhandle :order_created, with: ThreadAdapter.new(:order_created)\n# ...\n```\n\n## Unsubscribing \u0026 clearing\n\nYou can unsubscribe a given subscription by passing its\n[reference](#referencing-subscriptions) to `Omnes::Bus#unsubscribe` (see how to\n[reference subscriptions](#referencing-subscriptions)):\n\n```ruby\nsubscription = bus.subscribe(:order_created, OrderCreationEmailSubscription.new)\nbus.unsubscribe(subscription)\n```\n\nSometimes you might need to leave your bus in a pristine state, with no events\nregistered or active subscriptions. That can be useful for autoloading in\ndevelopment:\n\n```ruby\nbus.clear\nbus.registry.event_names # =\u003e []\nbus.subscriptions # =\u003e []\n```\n\n## Debugging\n\n### Registration\n\nWhenever you register an event, you get back an [`Omnes::Registry::Registration`](lib/omnes/registry.rb)\ninstance. It gives access to both the registered `#event_name` and the\n`#caller_location` of the registration.\n\nAn `Omnes::Bus` contains a reference to its registry, which can be used to\nretrieve a registration later on.\n\n```ruby\nbus.registry.registration(:order_created)\n```\n\nYou can also use the registry to retrieve all registered event names:\n\n```ruby\nbus.registry.event_names\n```\n\nSee [`Omnes::Registry`](lib/omnes/registry.rb) for other available methods.\n\n### Publication\n\nWhen you publish an event, you get back an\n[`Omnes::Publication`](lib/omnes/publication.rb) instance. It contains some\nattributes that allow observing what happened:\n\n- `#event` contains the event instance that has been published.\n- `#executions` contains an array of\n  `Omnes::Execution`(lib/omnes/execution.rb). Read more below.\n- `#context` is an instance of\n  [`Omnes::PublicationContext`](lib/omnes/publication_context.rb).\n  \n`Omnes::Execution` represents a subscription individual execution. It contains\nthe following attributes:\n\n- `#subscription` is an instance of [`Omnes::Subscription`](lib/omnes/subscripiton.rb).\n- `#result` contains the result of the execution.\n- `#benchmark` of the operation.\n- `#time` is the time where the execution started.\n\n`Omnes::PublicationContext` represents the shared context for all triggered\nexecutions. See [Subscription][#subscription] for details.\n\n### Subscription\n\nIf your subscription block or callable object takes a second argument, it'll\ncontain an instance of an\n[`Omnes::PublicationContext`](lib/omnes/publication_context.rb). It allows you\nto inspect what triggered a given execution from within that execution code. It\ncontains:\n\n- `#caller_location` refers to the publication caller.\n- `#time` is the time stamp for the publication.\n\n```ruby\nclass OrderCreationEmailSubscriber\n  include Omnes::Subscriber\n  \n  handle :order_created, with: :send_confirmation_email\n\n  def send_confirmation_email(event, publication_context)\n    # debugging\n    abort(publication_context.caller_location.inspect)\n\n    OrderCreationEmail.send(number: event.number, email: event.user_email)\n  end\nend\n```\n\nIn case you're developing your own async adapter, you can call `#serialized` on\nan instance of `Omnes::PublicationContext` to get a serialized version of it.\nIt'll return a `Hash` with `\"caller_location\"` and `\"time\"` keys, and the\nrespective `String` representations as values.\n\n## Testing\n\nIdeally, you wouldn't need big setups to test your event-driven behavior. You\ncould design your subscribers to use lightweight mocks for any external or\noperation at the integration level. Example:\n\n```ruby\nif # test environment\n  bus.subscribe(:order_created, OrderCreationEmailSubscriber.new(service: MockService.new)\nelse\n  bus.subscribe(:order_created, OrderCreationEmailSubscriber.new)\nend\n```\n\nThen, at the unit level, you can test your subscribers as any other class.\n\nHowever, there's also a handy `Omnes::Bus#performing_only` method that allows\nrunning a code block with only a selection of subscriptions as potential\ncallbacks for published events.\n\n```ruby\ncreation_subscription = bus.subscribe(:order_created, OrderCreationEmailSubscriber.new)\ndeletion_subscription = bus.subscribe(:order_deleted, OrderDeletionSubscriber.new)\nbus.performing_only(creation_subscription) do\n  bus.publish(:order_created, number: order.number, user_email: user.email) # `creation_subscription` will run\n  bus.publish(:order_deleted, number: order.number) # `deletion_subscription` won't run\nend\nbus.publish(:order_deleted, number: order.number) # `deletion_subscription` will run\n```\n\nRemember that you can get previous [subscription\nreferences](#referencing-subscriptions) thanks to\nsubscription identifiers.\n\nThere's also a specialized `Omnes::Bus#performing_nothing` method that runs no\nsubscriptions for the duration of the block.\n\n## Configuration\n\nWe've seen the relevant configurable settings in the corresponding sections.\nYou can also access the configuration in the habitual block syntax:\n\n```ruby\nOmnes.configure do |config|\n  config.subscriber.adapter.sidekiq.serializer = :serialized_payload.to_proc\nend\n```\n\nFinally, nested settings can also be set directly from the affected class. E.g.:\n\n```ruby\nOmnes::Subscriber::Adapter::Sidekiq.config.serializer = :serialized_payload.to_proc\n```\n\n## Recipes\n\n### Rails\n\nCreate an initializer in `config/initializers/omnes.rb`:\n\n```ruby\nrequire \"omnes\"\n\nOmnes.config.subscriber.autodiscover = true\n\nBus = Omnes::Bus.new\n\nRails.application.config.to_prepare do\n  Bus.clear\n\n  Bus.register(:order_created)\n\n  OrderCreationEmailSubscriber.new.subscribe_to(Bus)\nend\n```\n\nWe can define `OrderCreationEmailSubscriber` in\n`app/subscribers/order_creation_email_subscriber.rb`:\n\n```ruby\n# frozen_string_literal: true\n\nclass OrderCreationEmailSubscriber\n  include Omnes::Subscriber\n\n  def on_order_created(event)\n    # ...\n  end\nend\n```\n\nIdeally, you'll publish your event in a [custom service\nlayer](https://www.toptal.com/ruby-on-rails/rails-service-objects-tutorial). If\nthat's not possible, you can publish it in the controller.\n\nWe strongly discourage publishing events as part of an `ActiveRecord` callback.\nSubscribers should run code that is independent of the main business\ntransaction. As such, they shouldn't run within the same database transaction,\nand they should be decoupled of persistence responsibilities altogether.\n\n## Why is it called Omnes?\n\nWhy an Event Bus is called an _Event Bus_? It's a long story:\n\n- The first leap leaves us with the hardware computer buses. They move data from one hardware component to another.\n- The name leaked to the software to describe architectures that communicate parts by sending messages, like an Event Bus.\n- That was given as an analogy of buses as vehicles, where not data but people are transported.\n- _Bus_ is a clipped version of the Latin _omnibus_. That's what buses used to be called (and they're still called like that in some places, like Argentina).\n- _Bus_ stands for the preposition _for_, while _Omni_ means _all_. That's _for\n  all_, but, for some reason, we decided to keep the part void of meaning.\n- Why were they called _omnibus_? Let's move back to 1823 and talk about a man named Stanislas Baudry.\n- Stanislas lived in a suburb of Nantes, France. There, he ran a corn mill.\n- Hot water was a by-product of the mill, so Stanislas decided to build a spa business.\n- As the mill was on the city's outskirts, he arranged some horse-drawn\n  transportation to bring people to his spa.\n- It turned out that people weren't interested in it, but they did use the carriage to go to and fro.\n- The first stop of the service was in front of the shop of a hatter called\n  __Omnes__.\n- Omnes was a witty man. He'd named his shop with a pun on his Latin-sounding\n  name: _Omnes Omnibus_. That means something like _everything for everyone_.\n- Therefore, people in Nantes started to call _Omnibus_ to the new service.\n\nSo, it turns out we call it the \"Event Bus\" because presumably, the parents of\nOmnes gave him that name. So, the name of this library, it's a tribute to\nOmnes, the hatter.\n\nBy the way, in case you're wondering, Stanislas, the guy of the mill, closed\nboth it and the spa to run his service.\nEventually, he moved to Paris to earn more money in a bigger city.\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. Then, run\n`rake spec` to run the tests. You can also run `bin/console` for an interactive\nprompt that will allow you to experiment.\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/nebulab/omnes. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/nebulab/omnes/blob/master/CODE_OF_CONDUCT.md).\n\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n\n## Code of Conduct\n\nEveryone interacting in the Omnes project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/nebulab/omnes/blob/master/CODE_OF_CONDUCT.md).\n","funding_links":[],"categories":["Ruby"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnebulab%2Fomnes","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnebulab%2Fomnes","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnebulab%2Fomnes/lists"}