{"id":25390214,"url":"https://github.com/irphilli/activerecord-batch_touching","last_synced_at":"2025-04-09T22:54:07.204Z","repository":{"id":41425072,"uuid":"508918516","full_name":"irphilli/activerecord-batch_touching","owner":"irphilli","description":"Batch up your ActiveRecord \"touch\" operations for better performance.","archived":false,"fork":false,"pushed_at":"2024-07-09T00:46:58.000Z","size":63,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-09T22:54:04.137Z","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/irphilli.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"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":"2022-06-30T03:19:23.000Z","updated_at":"2025-02-05T11:30:32.000Z","dependencies_parsed_at":"2024-07-09T04:24:13.637Z","dependency_job_id":"c137f515-a1d7-4572-9c04-d85a1f112b9c","html_url":"https://github.com/irphilli/activerecord-batch_touching","commit_stats":null,"previous_names":["irphilli/activerecord-batch_touching","productplan/activerecord-batch_touching"],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/irphilli%2Factiverecord-batch_touching","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/irphilli%2Factiverecord-batch_touching/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/irphilli%2Factiverecord-batch_touching/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/irphilli%2Factiverecord-batch_touching/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/irphilli","download_url":"https://codeload.github.com/irphilli/activerecord-batch_touching/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248125632,"owners_count":21051766,"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":"2025-02-15T14:38:11.162Z","updated_at":"2025-04-09T22:54:07.186Z","avatar_url":"https://github.com/irphilli.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ActiveRecord::BatchTouching\n [![Gem Version](https://badge.fury.io/rb/activerecord-batch_touching.svg)](http://badge.fury.io/rb/activerecord-batch_touching)\n[![Build Status](https://github.com/irphilli/activerecord-batch_touching/actions/workflows/ruby-tests.yml/badge.svg?branch=main)](https://github.com/irphilli/activerecord-batch_touching/actions)\n[![Maintainability](https://api.codeclimate.com/v1/badges/decb339d5e259910c8f4/maintainability)](https://codeclimate.com/github/irphilli/activerecord-batch_touching/maintainability)\n[![Test Coverage](https://api.codeclimate.com/v1/badges/decb339d5e259910c8f4/test_coverage)](https://codeclimate.com/github/irphilli/activerecord-batch_touching/test_coverage)\n \nBatch up your ActiveRecord \"touch\" operations for better performance.\n\nThis gem is derivative of [activerecord-delay_touching](https://github.com/godaddy/activerecord-delay_touching) and [@BMorearty](https://github.com/BMorearty)'s subsequent PR to merge this functionality into Rails with https://github.com/rails/rails/pull/18824.\n \n## Why?\nDoesn't ActiveRecord already consolidate touches?\n\nYes, and no! Let's dig in!\n\nThe examples below build upon the following setup:\n\n```\nclass Person \u003c ActiveRecord::Base\n  has_many :pets\n  accepts_nested_attributes_for :pets\nend\n\nclass Pet \u003c ActiveRecord::Base\n   belongs_to :person, touch: true\nend\n```\n\n### One touch per object\nJust like with the current ActiveModel functionality, `batch_touching` will prevent this simple `update` in the controller from calling`@person.touch` N times, where N is the number of pets that were updated via nested attributes. That's N-1 unnecessary round-trips to the database:\n\n```\nclass PeopleController \u003c ApplicationController\n  def update\n    ...\n    @person.update(person_params)\n    ...\n  end\nend\n\n# SQL (0.1ms)  UPDATE \"people\" SET \"updated_at\" = '2014-07-09 19:48:07.137158' WHERE \"people\".\"id\" = 1\n# SQL (0.1ms)  UPDATE \"people\" SET \"updated_at\" = '2014-07-09 19:48:07.138457' WHERE \"people\".\"id\" = 1\n# SQL (0.1ms)  UPDATE \"people\" SET \"updated_at\" = '2014-07-09 19:48:07.140088' WHERE \"people\".\"id\" = 1\n```\n\nWith `batch_touching`, @person is touched only once:\n\n    @person.update(person_params)\n\t# SQL (0.1ms)  UPDATE \"people\" SET \"updated_at\" = '2014-07-09 19:48:07.140088' WHERE \"people\".\"id\" = 1\n\nNothing to see here! The next two sections are where this gem differentiates itself from the current ActiveRecord implementation.\n\n### Consolidate Touches Per Table\n\nIn the following example, a person gives his pet to another person. ActiveRecord automatically touches the old person and the new person. The current ActiveRecord implementation has a SQL UPDATE _per_ individual record touched. With  `batch_touching`, this will only make a  _single_  round-trip to the database, setting  `updated_at`  for all Person records in a single SQL UPDATE statement. Not a big deal when there are only two touches, but when you're updating records en masse and have a cascade of hundreds touches, it really is a big deal.\n\n```\nclass Pet \u003c ActiveRecord::Base\n  belongs_to :person, touch: true\n\n  def give(to_person)\n    self.person = to_person\n    save! # touches old person and new person in a single SQL UPDATE.\n  end\nend\n```\n### Deadlock Prevention\n`batch_touching` will sort the consolidated SQL updates by model name, and then commit touches in their own transaction. The separate transaction in a predictable order for updates should help mitigate potential database deadlocking.\n\nFor example, if two transactions happen to touch records in the following order, there is a potential for a deadlock:\n\n**Transaction 1:**\n```\nActiveRecord::Base.transaction do\n  person1.touch\n  pet1.touch\nend\n# SQL (0.1ms)  UPDATE \"people\" SET \"updated_at\" = '2014-07-09 19:48:07.140088' WHERE \"people\".\"id\" = 1\n# SQL (0.1ms)  UPDATE \"pets\" SET \"updated_at\" = '2014-07-09 19:48:07.140088' WHERE \"pets\".\"id\" = 1\n```\n\n**Transaction 2**:\n```\nActiveRecord::Base.transaction do\n  pet1.touch\n  person1.touch\nend\n# SQL (0.1ms)  UPDATE \"pets\" SET \"updated_at\" = '2014-07-09 19:48:07.140088' WHERE \"pets\".\"id\" = 1\n# SQL (0.1ms)  UPDATE \"people\" SET \"updated_at\" = '2014-07-09 19:48:07.140088' WHERE \"people\".\"id\" = 1\n```\n\n`batch_touching` will have both transactions update in the same order, regardless of the order `.touch` was called.\n\nIt's dangerous to go alone! Take `batch_touching`.\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n    gem 'activerecord-batch_touching'\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself:\n\n    $ gem install activerecord-batch_touching\n\n## Usage\n\nOnce installed, all transactions will automatically have `batch_touching` enabled.\n\n## Other tidbits\n\nSome additional information or gotchas to be aware of!\n\n### Cascading Touches\n\nWhen `batch_touching` runs through and touches everything, it captures additional  `touch` calls that might be called as side-effects. (E.g., in `after_touch`  handlers.) Then it makes a second pass, batching up those touches as well.\n\nIt keeps doing this until there are no more touches, or until the sun swallows up the earth. Whichever comes first.\n\n### Gotchas\n\n* `after_touch` callbacks are still fired for every instance, but not until the block is exited. As a result, the ordering of the callbacks may be different than the default ActiveRecord implementation.\n* If you call `person1.touch` and then `person2.touch`, and they are two separate instances with the same id, only person1's `after_touch` handler will be called.\n\n## Contributing\n\n1. Fork it ( [https://github.com/irphilli/activerecord-batch_touching/fork](https://github.com/ProductPlan/activerecord-batch_touching/fork) )\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 a new Pull Request\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Firphilli%2Factiverecord-batch_touching","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Firphilli%2Factiverecord-batch_touching","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Firphilli%2Factiverecord-batch_touching/lists"}