{"id":13880226,"url":"https://github.com/fnando/voltage","last_synced_at":"2025-04-17T00:51:58.055Z","repository":{"id":6595315,"uuid":"7838194","full_name":"fnando/voltage","owner":"fnando","description":"A simple observer implementation on POROs (Plain Old Ruby Object) and ActiveRecord objects.","archived":false,"fork":false,"pushed_at":"2023-09-05T01:06:08.000Z","size":90,"stargazers_count":31,"open_issues_count":1,"forks_count":3,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-08T18:54:51.180Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/fnando.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["fnando"],"custom":["https://paypal.me/nandovieira/🍕"]}},"created_at":"2013-01-26T13:38:37.000Z","updated_at":"2023-07-30T17:17:52.000Z","dependencies_parsed_at":"2024-10-20T12:39:55.881Z","dependency_job_id":null,"html_url":"https://github.com/fnando/voltage","commit_stats":{"total_commits":53,"total_committers":3,"mean_commits":"17.666666666666668","dds":"0.37735849056603776","last_synced_commit":"e75d3083e87ef66431f6b65c19d6035d06871573"},"previous_names":["fnando/signal"],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fnando%2Fvoltage","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fnando%2Fvoltage/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fnando%2Fvoltage/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fnando%2Fvoltage/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fnando","download_url":"https://codeload.github.com/fnando/voltage/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249294927,"owners_count":21246010,"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":[],"created_at":"2024-08-06T08:02:52.822Z","updated_at":"2025-04-17T00:51:58.034Z","avatar_url":"https://github.com/fnando.png","language":"Ruby","funding_links":["https://github.com/sponsors/fnando","https://paypal.me/nandovieira/🍕"],"categories":["Ruby"],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/fnando/voltage/\"\u003e\n    \u003cimg width=\"250\" src=\"https://github.com/fnando/voltage/raw/main/voltage.svg\" alt=\"voltage\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  A simple observer implementation for POROs (Plain Old Ruby Object)\n  \u003cbr\u003e\n  and ActiveRecord objects.\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/fnando/voltage/actions/workflows/ruby-tests.yml\"\u003e\n    \u003cimg src=\"https://github.com/fnando/voltage/actions/workflows/ruby-tests.yml/badge.svg\" alt=\"Tests\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://rubygems.org/gems/voltage\"\u003e\n    \u003cimg src=\"https://img.shields.io/gem/v/voltage.svg\" alt=\"Gem\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://rubygems.org/gems/voltage\"\u003e\n    \u003cimg src=\"https://img.shields.io/gem/dt/voltage.svg\" alt=\"Gem\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/fnando/voltage/actions/workflows/codeql-analysis.yml\"\u003e\n    \u003cimg src=\"https://github.com/fnando/voltage/actions/workflows/codeql-analysis.yml/badge.svg\" alt=\"CodeQL\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://tldrlegal.com/license/mit-license\"\u003e\n    \u003cimg src=\"https://img.shields.io/:License-MIT-blue.svg\" alt=\"License: MIT\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n---\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem \"voltage\"\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install voltage\n\n## Usage\n\nYou can use Voltage with PORO (Plain Old Ruby Object) and ActiveRecord.\n\n### Plain Ruby\n\nAll you have to do is including the `Voltage` module. Then you can add listeners\nand trigger events.\n\n```ruby\nclass Status\n  include Voltage\n\n  def ready!\n    emit(:ready)\n  end\nend\n\nstatus = Status.new\nstatus.before(:ready) { puts \"Before the ready event!\" }\nstatus.on(:ready) { puts \"I'm ready!\" }\nstatus.after(:ready) { puts \"After the ready event!\" }\nstatus.ready!\n#=\u003e Before the ready event!\n#=\u003e I'm ready!\n#=\u003e After the ready event!\n```\n\nYou can also pass objects that implement methods like `before_*`, `on_*` and\n`after_*`.\n\n```ruby\nclass MyListener\n  def before_ready\n    puts \"Before the ready event!\"\n  end\n\n  def on_ready\n    puts \"I'm ready!\"\n  end\n\n  def after_ready\n    puts \"After the ready event!\"\n  end\nend\n\nStatus.new\n  .add_listener(MyListener.new)\n  .ready!\n#=\u003e Before the ready event!\n#=\u003e I'm ready!\n#=\u003e After the ready event!\n```\n\nExecuted blocks don't switch context. You always have to emit the object you're\ninterested in. The follow example uses `emit(:output, self)` to send the\n`Contact` instance to all listeners.\n\n```ruby\nclass Contact\n  include Voltage\n\n  attr_reader :name, :email\n\n  def initialize(name, email)\n    @name, @email = name, email\n  end\n\n  def output!\n    emit(:output, self)\n  end\nend\n\ncontact = Contact.new('John Doe', 'john@example.org')\ncontact.on(:output) {|contact| puts contact.name, contact.email }\ncontact.output!\n#=\u003e John Doe\n#=\u003e john@example.org\n```\n\nYou can provide arguments while emitting a voltage:\n\n```ruby\nclass Arguments\n  include Voltage\nend\n\nclass MyListener\n  def on_args(a, b)\n    puts a, b\n  end\nend\n\nArguments.new\n  .on(:args) {|a, b| puts a, b }\n  .add_listener(MyListener.new)\n  .emit(:args, 1, 2)\n```\n\n### ActiveRecord\n\nYou can use Voltage with ActiveRecord, which will give you some default events\nlike `:create`, `:update`, `:remove` and `:validation`.\n\n```ruby\nclass Thing \u003c ActiveRecord::Base\n  include Voltage.active_record\n\n  validates_presence_of :name\nend\n\nthing = Thing.new(:name =\u003e \"Stuff\")\nthing.on(:create) {|thing| puts thing.updated_at, thing.name }\nthing.on(:update) {|thing| puts thing.updated_at, thing.name }\nthing.on(:remove) {|thing| puts thing.destroyed? }\nthing.on(:validation) {|thing| p thing.errors.full_messages }\n\nthing.save!\n#=\u003e 2013-01-26 10:32:39 -0200\n#=\u003e Stuff\n\nthing.update_attributes(:name =\u003e \"Updated stuff\")\n#=\u003e 2013-01-26 10:33:11 -0200\n#=\u003e Updated stuff\n\nthing.update_attributes(:name =\u003e nil)\n#=\u003e [\"Name can't be blank\"]\n\nthing.destroy\n#=\u003e true\n```\n\nThese are the available events:\n\n- `before(:create)`: triggered before creating the record (record is valid).\n- `on(:create)`: triggered after `before(:create)` event.\n- `after(:create)`: triggered after the `on(:create)` event.\n- `before(:update)`: triggered before updating the record (record is valid).\n- `on(:update)`: triggered when the `before(:update)` event.\n- `after(:update)`: triggered after the `on(:update)` event.\n- `before(:remove)`: triggered before removing the record.\n- `on(:remove)`: triggered after the `before(:remove)`.\n- `after(:remove)`: triggered after the `on(:remove)` event.\n- `before(:validation)`: triggered before validating record.\n- `on(:validation)`: triggered when record is invalid.\n- `after(:validation)`: triggered after validating record.\n\n### Inside Rails\n\nAlthough there's no special code for Rails, here's just an example of how you\ncan use it:\n\n```ruby\nclass UsersController \u003c ApplicationController\n  def create\n    @user = User.new(user_params)\n\n    Signup.new(@user)\n      .on(:success) { redirect_to login_path, notice: 'Welcome to MyApp!' }\n      .on(:failure) { render :new }\n      .call\n  end\nend\n```\n\nIf you're using plain ActiveRecord, just do something like the following:\n\n```ruby\nclass UsersController \u003c ApplicationController\n  def create\n    @user = User.new(user_params)\n    @user\n      .on(:create) { redirect_to login_path, notice: 'Welcome to MyApp!' }\n      .on(:validation) { render :new }\n      .save\n  end\nend\n```\n\n### Voltage::Call\n\nYou can include `Voltage.call` instead, so you can have a common interface for\nyour observable object. This will add the `.call()` method to the target class,\nwhich will delegate attributes to the observable's `initialize` method and call\nits `call` method.\n\n```ruby\nclass Contact\n  include Voltage.call\n\n  attr_reader :name, :email\n\n  def initialize(name, email)\n    @name, @email = name, email\n  end\n\n  def call\n    emit(:output, self)\n  end\nend\n\nContact.call('John', 'john@example.com') do |o|\n  o.on(:output) {|contact| puts contact }\nend\n```\n\nNotice that you don't have to explicit call the instance's `call` method;\n`Contact.call` will initialize the object with all the provided parameters and\ncall `Contact#call` after the block has been executed.\n\n### Testing\n\n`Voltage::Mock` can be helpful for most test situations where you don't want to\nbring other mock libraries.\n\n```ruby\nrequire \"voltage/mock\"\n\nclass SomeTest \u003c Minitest::Test\n  def test_some_test\n    mock = Voltage::Mock.new\n\n    # Using listener\n    sum = Sum.new\n    sum.add_listener(mock)\n\n    # Calling `mock.on(event_name)` is required because\n    # the handler doesn't receive the event name, just the\n    # arguments.\n    sum = Sum.new\n    sum.on(:result, \u0026mock.on(:result))\n\n    # Using with Voltage.call\n    Sum.call(1, 2, \u0026mock)\n\n    assert mock.received?(:result)\n    assert mock.received?(:result, times: 1)\n    assert mock.received?(:result, with: [3])\n    assert mock.received?(:result, with: -\u003e(result) { result == 3 } )\n  end\nend\n\n```\n\n## Contributing\n\n1. Fork it\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create new Pull Request\n\n## License\n\nCopyright (c) 2013 Nando Vieira\n\nMIT License\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sub-license, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffnando%2Fvoltage","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffnando%2Fvoltage","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffnando%2Fvoltage/lists"}