{"id":21958670,"url":"https://github.com/solid-process/solid-adapters","last_synced_at":"2025-10-05T12:44:27.387Z","repository":{"id":245708126,"uuid":"819019865","full_name":"solid-process/solid-adapters","owner":"solid-process","description":"Write interface contracts using pure Ruby.","archived":false,"fork":false,"pushed_at":"2024-06-24T02:33:02.000Z","size":38,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-08-12T16:51:57.801Z","etag":null,"topics":[],"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/solid-process.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-06-23T14:48:43.000Z","updated_at":"2025-03-06T02:09:37.000Z","dependencies_parsed_at":"2025-03-22T19:44:17.047Z","dependency_job_id":"d263478f-f927-4f5c-924e-e0885eae386a","html_url":"https://github.com/solid-process/solid-adapters","commit_stats":null,"previous_names":["solid-process/solid-adapters"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/solid-process/solid-adapters","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/solid-process%2Fsolid-adapters","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/solid-process%2Fsolid-adapters/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/solid-process%2Fsolid-adapters/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/solid-process%2Fsolid-adapters/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/solid-process","download_url":"https://codeload.github.com/solid-process/solid-adapters/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/solid-process%2Fsolid-adapters/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278457486,"owners_count":25989954,"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","status":"online","status_checked_at":"2025-10-05T02:00:06.059Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":[],"created_at":"2024-11-29T09:16:41.296Z","updated_at":"2025-10-05T12:44:27.366Z","avatar_url":"https://github.com/solid-process.png","language":"Ruby","readme":"\u003cp align=\"center\"\u003e\n  \u003ch1 align=\"center\" id=\"-solidadapters\"\u003e🧩 Solid::Adapters\u003c/h1\u003e\n  \u003cp align=\"center\"\u003e\u003ci\u003eWrite interface contracts using pure Ruby.\u003c/i\u003e\u003c/p\u003e\n  \u003cp align=\"center\"\u003e\n    \u003ca href=\"https://codeclimate.com/github/solid-process/solid-adapters/maintainability\"\u003e\u003cimg src=\"https://api.codeclimate.com/v1/badges/b94564ac2686bc8d5feb/maintainability\" /\u003e\u003c/a\u003e\n    \u003ca href=\"https://codeclimate.com/github/solid-process/solid-adapters/test_coverage\"\u003e\u003cimg src=\"https://api.codeclimate.com/v1/badges/b94564ac2686bc8d5feb/test_coverage\" /\u003e\u003c/a\u003e\n    \u003cimg src=\"https://img.shields.io/badge/Ruby%20%3E%3D%202.7%2C%20%3C%3D%20Head-ruby.svg?colorA=444\u0026colorB=333\" alt=\"Ruby\"\u003e\n  \u003c/p\u003e\n\u003c/p\u003e\n\n## 📚 Table of Contents \u003c!-- omit from toc --\u003e\n\n- [Supported Ruby](#supported-ruby)\n- [Examples](#examples)\n- [Installation](#installation)\n- [Usage](#usage)\n  - [`Solid::Adapters::Interface`](#solidadaptersinterface)\n    - [Dynamic proxies](#dynamic-proxies)\n  - [`Solid::Adapters::Proxy`](#solidadaptersproxy)\n- [Configuration](#configuration)\n  - [Non-toggleable features](#non-toggleable-features)\n  - [Solid::Adapters.configuration(freeze: false)](#solidadaptersconfigurationfreeze-false)\n  - [Solid::Adapters.config](#solidadaptersconfig)\n  - [`Solid::Adapters::Interface` versus `Solid::Adapters::Proxy`](#solidadaptersinterface-versus-solidadaptersproxy)\n  - [`Solid::Adapters::Configurable`](#solidadaptersconfigurable)\n    - [Configuration](#configuration-1)\n- [About](#about)\n- [Development](#development)\n- [Contributing](#contributing)\n- [License](#license)\n- [Code of Conduct](#code-of-conduct)\n\n## Supported Ruby\n\nThis library is tested against:\n\nCoverage | 2.7 | 3.0 | 3.1 | 3.2 | 3.3 | Head\n---- | --- | --- | --- | --- | --- | ---\n100%  | ✅  | ✅  | ✅  | ✅  | ✅  | ✅\n\n## Examples\n\nCheck the [examples](examples) directory to see different applications of `solid-adapters`.\n\n\u003e **Attention:** Each example has its own **README** with more details.\n\n1. [Ports and Adapters](examples/ports_and_adapters) - Implements the Ports and Adapters pattern. It uses [**`Solid::Adapters::Interface`**](#solidadaptersinterface) to provide an interface from the application's core to other layers.\n\n2. [Anti-Corruption Layer](examples/anti_corruption_layer) - Implements the Anti-Corruption Layer pattern. It uses the [**`Solid::Adapters::Proxy`**](#solidadapterstproxy) to define an interface for a set of adapters, which will translate an external interface (`vendors`) to the application's core interface.\n\n3. [Solid::Rails::App](https://github.com/solid-process/solid-rails-app/tree/solid-process-4?tab=readme-ov-file#-solid-rails-app-) - A Rails application (Web and REST API) made with  `solid-adapters` + [`solid-process`](https://github.com/solid-process/solid-process) that uses the Ports and Adapters (Hexagonal) architectural pattern to decouple the application's core from the framework.\n\n\u003cp align=\"right\"\u003e\u003ca href=\"#-solidadapters\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e\u003c/p\u003e\n\n## Installation\n\nInstall the gem and add to the application's Gemfile by executing:\n\n    $ bundle add solid-adapters\n\nIf bundler is not being used to manage dependencies, install the gem by executing:\n\n    $ gem install solid-adapters\n\nAnd require it in your code:\n\n    require 'solid/adapters'\n\n\u003cp align=\"right\"\u003e\u003ca href=\"#-solidadapters\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e\u003c/p\u003e\n\n## Usage\n\n### `Solid::Adapters::Interface`\n\nThis feature allows the creation of a module that will be used as an interface.\n\nIt will check if the class that includes it or the object that extends it implements all the expected methods.\n\n```ruby\nmodule User::Repository\n  include ::Solid::Adapters::Interface\n\n  module Methods\n    def create(name:, email:)\n      name =\u003e String\n      email =\u003e String\n\n      super.tap { _1 =\u003e ::User::Data[id: Integer, name: String, email: String] }\n    end\n  end\nend\n```\n\nLet's break down the example above.\n\n1. The `User::Repository` module includes `Solid::Adapters::Interface`.\n2. Defines the `Methods` module. It is mandatory, as these will be the methods to be implemented.\n3. The `create` method is defined inside the `Method`s' module.\n   1. This method receives two arguments: `name` and `email`.\n   2. The arguments are checked using the `=\u003e` pattern matching operator.\n   3. `super` is called to invoke the `create` method of the superclass. Which will be the class/object that includes/extends the `User::Repository` module.\n   4. The `super` output is checked using pattern matching under the `tap` method.\n\nNow, let's see how to use it in a class.\n\n```ruby\nclass User::Record::Repository\n  include User::Repository\n\n  def create(name:, email:)\n    record = Record.create(name:, email:)\n\n    ::User::Data.new(id: record.id, name: record.name, email: record.email)\n  end\nend\n```\n\nAnd how to use it in a module with singleton methods.\n\n```ruby\nmodule User::Record::Repository\n  extend User::Repository\n\n  def self.create(name:, email:)\n    record = Record.create(name:, email:)\n\n    ::User::Data.new(id: record.id, name: record.name, email: record.email)\n  end\nend\n```\n\n**What happend when an interface module is included/extended?**\n\n1. An instance of the class will be a `User::Repository`.\n2. The module, class, object, that extended the interface will be a `User::Repository`.\n\n```ruby\nclass User::Record::Repository\n  include User::Repository\nend\n\nmodule UserTest::RepositoryInMemory\n  extend User::Repository\n  # ...\nend\n\nUser::Record::Repository.new.is_a?(User::Repository) # true\n\nUserTest::RepositoryInMemory.is_a?(User::Repository) # true\n```\n\n**Why this is useful?**\n\nYou can use `=\u003e` pattern matching or `is_a?` to ensure that the class/object implements the expected methods as it includes/extends the interface.\n\n```ruby\nclass User::Creation\n  def initialize(repository)\n    repository =\u003e User::Repository\n\n    @repository = repository\n  end\n\n  # ...\nend\n```\n\n\u003e Access the [**Ports and Adapters example**](examples/ports_and_adapters) to see, test, and run something that uses the `Solid::Adapters::Interface`\n\n\u003cp align=\"right\"\u003e\u003ca href=\"#-solidadapters\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e\u003c/p\u003e\n\n#### Dynamic proxies\n\nThe `Solid::Adapters::Interface` can be used to create dynamic proxies. To do this, you must use the `.[]` method to wrap an object in a proxy that will check if the object implements the interface methods.\n\nThe advantage of dynamic proxies is that you can create a proxy for any object. Therefore, you don't need to include/extend the interface module to perform the checkings.\n\n```ruby\nclass User::Repository\n  include ::Solid::Adapters::Interface\n\n  module Methods\n    def create(name:, email:)\n      name =\u003e String\n      email =\u003e String\n\n      super.tap { _1 =\u003e ::User::Data[id: Integer, name: String, email: String] }\n    end\n  end\nend\n\n## Real object example\n\nclass User::Record::Repository\n  def create(name:, email:)\n    ::User::Data.new(id: 1, name: name, email: email)\n  end\nend\n\nrepository = User::Repository[User::Record::Repository.new]\n\n## Mock example\n\nmock_repository = double\n\nallow(mock_repository)\n  .to receive(:create)\n  .with(name: 'John', email: 'john@email.com')\n  .and_return(::User::Data.new(id: 1, name: 'John', email: 'john@email.com'))\n\nrepository = User::Repository[mock_repository]\n```\n\n\u003cp align=\"right\"\u003e\u003ca href=\"#-solidadapters\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e\u003c/p\u003e\n\n### `Solid::Adapters::Proxy`\n\nThis feature allows the creation of a class that will be used as a proxy for another objects.\n\nThe idea is to define an interface for the object that will be proxied.\n\nLet's implement the example from the [previous section](#solidadaptersinterface) using a proxy.\n\n```ruby\nclass User::Repository \u003c Solid::Adapters::Proxy\n  def create(name:, email:)\n    name =\u003e String\n    email =\u003e String\n\n    object.create(name:, email:).tap do\n      _1 =\u003e ::User::Data[id: Integer, name: String, email: String]\n    end\n  end\nend\n```\n\n**How to use it?**\n\nInside the proxy you will use `object` to access the proxied object. This means the proxy must be initialized with an object. And the object must implement the methods defined in the proxy.\n\n```ruby\nclass User::Record::Repository\n  # ...\nend\n\nmodule UserTest::RepositoryInMemory\n  extend self\n  # ...\nend\n\n# The proxy must be initialized with an object that implements the expected methods\n\nmemory_repository = User::Repository.new(UserTest::RepositoryInMemory)\n\nrecord_repository = User::Repository.new(User::Record::Repository.new)\n```\n\n\u003e Access the [**Anti-Corruption Layer**](examples/anti_corruption_layer) to see, test, and run something that uses the `Solid::Adapters::Proxy`\n\n\u003cp align=\"right\"\u003e\u003ca href=\"#-solidadapters\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e\u003c/p\u003e\n\n## Configuration\n\nBy default, the `Solid::Adapters` enables all its features. You can disable them by setting the configuration.\n\n```ruby\nSolid::Adapters.configuration do |config|\n  dev_or_test = ::Rails.env.local?\n\n  config.proxy_enabled = dev_or_test\n  config.interface_enabled = dev_or_test\nend\n\n# PS: You can use .configure is an alias for .configuration\n```\n\nIn the example above, the `Solid::Adapters::Proxy`, `Solid::Adapters::Interface` will be disabled in production.\n\n\u003cp align=\"right\"\u003e\u003ca href=\"#-solidadapters\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e\u003c/p\u003e\n\n###  Non-toggleable features\n\nThe following variants are always enabled. You cannot disable them through the configuration.\n\n#### `Solid::Adapters::Proxy::AlwaysEnabled` \u003c!-- omit from toc --\u003e\n\n```ruby\nclass User::Repository\n  include ::Solid::Adapters::Interface::AlwaysEnabled\n\n  module Methods\n    # ...\n  end\nend\n```\n\n#### `Solid::Adapters::Interface::AlwaysEnabled` \u003c!-- omit from toc --\u003e\n\n```ruby\nclass User::Repository \u003c Solid::Adapters::Proxy::AlwaysEnabled\n  # ...\nend\n```\n\n\u003cp align=\"right\"\u003e\u003ca href=\"#-solidadapters\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e\u003c/p\u003e\n\n### Solid::Adapters.configuration(freeze: false)\n\nBy default, the configuration is frozen after the block is executed. This means you cannot change the configuration after the application boot. If you need to change the configuration after the application boot, you can set the `freeze` option to `false`.\n\n```ruby\nSolid::Adapters.configuration(freeze: false) do |config|\n  config.proxy_enabled = false\n  config.interface_enabled = ::Rails.env.local?\nend\n```\n\n\u003cp align=\"right\"\u003e\u003ca href=\"#-solidadapters\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e\u003c/p\u003e\n\n### Solid::Adapters.config\n\nYou can access or change (if the configuration is not frozen) the configuration by using the `Solid::Adapters.config` method.\n\n\u003cp align=\"right\"\u003e\u003ca href=\"#-solidadapters\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e\u003c/p\u003e\n\n### `Solid::Adapters::Interface` versus `Solid::Adapters::Proxy`\n\nThe main difference between the interface and the proxy is when the settings take effect.\n\n`Solid::Adapters::Interface` modules are applied with the application boot. So, you must ensure that the `Solid::Adapters.configuration` runs before loading the code. On the other hand, proxies dynamically check the configuration every time a proxy instance is generated, allowing for the possibility of turning `Solid::Adapters::Proxy` post-application boot.\n\nI recommend using interfaces, as they can be included/extended directly and because they dynamically produce proxies. In other words, they are more versatile. But please remember you have different feature toggles in the configuration for using both adapters based on your application needs.\n\n\u003cp align=\"right\"\u003e\u003ca href=\"#-solidadapters\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e\u003c/p\u003e\n\n### `Solid::Adapters::Configurable`\n\nThe `Solid::Adapters::Configurable` module can be included in a class to provide a configuration block. This is useful when you want to inject/define dependencies into a namespace dynamically.\n\nFirst you need to include the module in the class. And define the configurations that you want to expose.\n\n```ruby\nmodule User::Adapters\n  extend Solid::Adapters::Configurable\n\n  config.repository = nil\nend\n```\n\nThen you can use the `configure` method to set the configurations. Lets use a Rails initializer to set the repository.\n\n```ruby\n# config/initializers/user_adapters.rb\n\nUser::Adapters.configuration do |config|\n  config.repository = User::Record::Repository.new\nend\n```\n\nSo you can access the repository in some place like this:\n\n```ruby\nclass User::Creation\n  def initialize\n    @repository = User::Adapters.config.repository\n  end\n\n  def create(name:, email:)\n    @repository.create(name: name, email: email)\n  end\nend\n```\n\n\u003cp align=\"right\"\u003e\u003ca href=\"#-solidadapters\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e\u003c/p\u003e\n\n#### Configuration\n\nFirst, the `Solid::Adapters.configuration` does not affect the `Solid::Adapters::Configurable` configurations. This means you can use both features together.\n\nSecond, as the `Solid::Adapters.configuration` method, the `Solid::Adapters::Configurable` configurations are frozen by default. You can change this behavior by setting the `freeze` option to `false`.\n\n```ruby\n# config/initializers/user_adapters.rb\n\nUser::Adapters.configuration(freeze: false) do |config|\n  config.repository = User::Record::Repository.new\nend\n\n# PS: You can use .configure is an alias for .configuration\n```\n\n\u003e Access the [Solid::Rails::App](https://github.com/solid-process/solid-rails-app/tree/solid-process-4?tab=readme-ov-file#-solid-rails-app-) versions 3 and 4 to see, test, and run something that uses the `Solid::Adapters::Configurable`.\n\n\u003cp align=\"right\"\u003e\u003ca href=\"#-solidadapters\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e\u003c/p\u003e\n\n## About\n\n[Rodrigo Serradura](https://github.com/serradura) created this project. He is the Solid Process creator and has already made similar gems like the [u-case](https://github.com/serradura/u-case) and [kind](https://github.com/serradura/kind/blob/main/lib/kind/result.rb). This gem can be used independently, but it also contains essential features that facilitate the adoption of Solid Process (the method) in code.\n\n\u003cp align=\"right\"\u003e\u003ca href=\"#-solidadapters\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e\u003c/p\u003e\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.\n\nTo install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/solid-process/solid-adapters. 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/solid-process/solid-adapters/blob/master/CODE_OF_CONDUCT.md).\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 Solid::Adapters project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/solid-adapters/blob/master/CODE_OF_CONDUCT.md).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsolid-process%2Fsolid-adapters","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsolid-process%2Fsolid-adapters","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsolid-process%2Fsolid-adapters/lists"}