{"id":13879937,"url":"https://github.com/krisleech/wisper-activerecord","last_synced_at":"2025-04-05T20:08:48.815Z","repository":{"id":17107696,"uuid":"19873450","full_name":"krisleech/wisper-activerecord","owner":"krisleech","description":"Transparently publish all model changes to subscribers","archived":false,"fork":false,"pushed_at":"2020-05-30T08:01:50.000Z","size":42,"stargazers_count":102,"open_issues_count":2,"forks_count":27,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-29T19:07:31.677Z","etag":null,"topics":["activerecord","ruby","wisper"],"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/krisleech.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-05-16T23:16:09.000Z","updated_at":"2024-12-21T23:25:14.000Z","dependencies_parsed_at":"2022-08-04T16:30:40.381Z","dependency_job_id":null,"html_url":"https://github.com/krisleech/wisper-activerecord","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/krisleech%2Fwisper-activerecord","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/krisleech%2Fwisper-activerecord/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/krisleech%2Fwisper-activerecord/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/krisleech%2Fwisper-activerecord/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/krisleech","download_url":"https://codeload.github.com/krisleech/wisper-activerecord/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247393571,"owners_count":20931813,"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":["activerecord","ruby","wisper"],"created_at":"2024-08-06T08:02:39.827Z","updated_at":"2025-04-05T20:08:48.758Z","avatar_url":"https://github.com/krisleech.png","language":"Ruby","readme":"# Wisper::ActiveRecord\n\n[![Gem Version](https://badge.fury.io/rb/wisper-activerecord.png)](http://badge.fury.io/rb/wisper-activerecord)\n[![Code Climate](https://codeclimate.com/github/krisleech/wisper-activerecord.png)](https://codeclimate.com/github/krisleech/wisper-activerecord)\n[![Build Status](https://travis-ci.org/krisleech/wisper-activerecord.png?branch=master)](https://travis-ci.org/krisleech/wisper-activerecord)\n\nTransparently publish model lifecycle events to subscribers.\n\nUsing Wisper events is a better alternative to ActiveRecord callbacks and Observers.\n\nListeners are subscribed to models at runtime.\n\n## Installation\n\n```ruby\ngem 'wisper-activerecord'\n```\n\n## Usage\n\n### Setup a publisher\n\n```ruby\nclass Meeting \u003c ActiveRecord::Base\n  include Wisper.model\n\n  # ...\nend\n```\n\nIf you wish all models to broadcast events without having to explicitly include\n`Wisper.model` add the following to an initializer:\n\n```ruby\nWisper::ActiveRecord.extend_all\n```\n\n### Subscribing\n\nSubscribe a listener to model instances:\n\n```ruby\nmeeting = Meeting.new\nmeeting.subscribe(Auditor.new)\n```\n\nSubscribe a block to model instances:\n\n```ruby\nmeeting.on(:create_meeting_successful) { |meeting| ... }\n```\n\nSubscribe a listener to _all_ instances of a model:\n\n```ruby\nMeeting.subscribe(Auditor.new)\n```\n\nPlease refer to the [Wisper README](https://github.com/krisleech/wisper) for full details about subscribing.\n\nThe events which are automatically broadcast are:\n\n* `after_create`\n* `after_update`\n* `after_destroy`\n* `create_\u003cmodel_name\u003e_{successful, failed}`\n* `update_\u003cmodel_name\u003e_{successful, failed}`\n* `destroy_\u003cmodel_name\u003e_successful`\n* `\u003cmodel_name\u003e_committed`\n* `after_commit`\n* `after_rollback`\n\n### Reacting to Events\n\nTo receive an event the listener must implement a method matching the name of\nthe event with a single argument, the instance of the model.\n\n```ruby\ndef create_meeting_successful(meeting)\n  # ...\nend\n```\n\n## Example\n\n### Controller\n\n```ruby\nclass MeetingsController \u003c ApplicationController\n  def new\n    @meeting = Meeting.new\n  end\n\n  def create\n    @meeting = Meeting.new(params[:meeting])\n    @meeting.subscribe(Auditor.new)\n    @meeting.on(:create_meeting_successful) { redirect_to meeting_path }\n    @meeting.on(:create_meeting_failed)     { render action: :new }\n    @meeting.save\n  end\n\n  def edit\n    @meeting = Meeting.find(params[:id])\n  end\n\n  def update\n    @meeting = Meeting.find(params[:id])\n    @meeting.subscribe(Auditor.new)\n    @meeting.on(:update_meeting_successful) { redirect_to meeting_path }\n    @meeting.on(:update_meeting_failed)     { render :action =\u003e :edit }\n    @meeting.update_attributes(params[:meeting])\n  end\nend\n```\n\nUsing `on` to subscribe a block to handle the response is optional,\nyou can still use `if @meeting.save` if you prefer.\n\n### Listener\n\n**Which simply records an audit in memory**\n\n```ruby\nclass Auditor\n\n  def after_create(subject)\n    push_audit_for('create', subject)\n  end\n\n  def after_update(subject)\n    push_audit_for('update', subject)\n  end\n\n  def after_destroy(subject)\n    push_audit_for('destroy', subject)\n  end\n\n  def self.audit\n    @audit ||= []\n  end\n\n  private\n\n  def push_audit_for(action, subject)\n    self.class.audit.push(audit_for(action, subject))\n  end\n\n  def audit_for(action, subject)\n    {\n      action: action,\n      subject_id: subject.id,\n      subject_class: subject.class.to_s,\n      changes: subject.previous_changes,\n      created_at: Time.now\n    }\n  end\nend\n```\n\n**Do some CRUD**\n\n```ruby\nMeeting.create(:description =\u003e 'Team Retrospective', :starts_at =\u003e Time.now + 2.days)\n\nmeeting = Meeting.find(1)\nmeeting.starts_at = Time.now + 2.months\nmeeting.save\n```\n\n**And check the audit**\n\n```ruby\nAuditor.audit # =\u003e [...]\n```\n\n## Notes on Testing \n\n### ActiveRecord \u003c= 4.0\n\nThis gem makes use of ActiveRecord's `after_commit` lifecycle hook to broadcast\nevents, which will create issues when testing with transactional fixtures.\nUnless you also include the [test_after_commit gem](https://github.com/grosser/test_after_commit)\nActiveRecord models will not broadcast any lifecycle events within your tests.\n\n## Compatibility\n\nTested on CRuby, Rubinius and JRuby for ActiveRecord ~\u003e 3.0, ~\u003e 4.0, and ~\u003e 5.0.\n\nSee the CI [build status](https://travis-ci.org/krisleech/wisper-activerecord) for more information.\n\n## Contributing\n\nPlease submit a Pull Request with specs.\n\n### Running the specs\n\n```\nbundle exec rspec\n```\n","funding_links":[],"categories":["Ruby"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkrisleech%2Fwisper-activerecord","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkrisleech%2Fwisper-activerecord","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkrisleech%2Fwisper-activerecord/lists"}