{"id":13747549,"url":"https://github.com/chrismccord/render_sync","last_synced_at":"2025-12-16T18:33:08.059Z","repository":{"id":7880871,"uuid":"9255872","full_name":"chrismccord/render_sync","owner":"chrismccord","description":"Real-time Rails Partials","archived":false,"fork":false,"pushed_at":"2019-05-25T19:15:38.000Z","size":367,"stargazers_count":1398,"open_issues_count":39,"forks_count":106,"subscribers_count":28,"default_branch":"master","last_synced_at":"2025-04-14T01:52:08.269Z","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":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/chrismccord.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-04-06T05:38:16.000Z","updated_at":"2025-02-28T08:50:16.000Z","dependencies_parsed_at":"2022-08-09T09:15:19.299Z","dependency_job_id":null,"html_url":"https://github.com/chrismccord/render_sync","commit_stats":null,"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chrismccord%2Frender_sync","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chrismccord%2Frender_sync/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chrismccord%2Frender_sync/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chrismccord%2Frender_sync/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chrismccord","download_url":"https://codeload.github.com/chrismccord/render_sync/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254264737,"owners_count":22041791,"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-03T06:01:33.238Z","updated_at":"2025-12-16T18:33:07.965Z","avatar_url":"https://github.com/chrismccord.png","language":"Ruby","readme":"# RenderSync [![Build Status](https://img.shields.io/travis/chrismccord/sync.svg)](https://travis-ci.org/chrismccord/sync) [![Code climate](https://img.shields.io/codeclimate/github/chrismccord/sync.svg)](https://codeclimate.com/github/chrismccord/sync) [![Code coverage](https://img.shields.io/codeclimate/coverage/github/chrismccord/sync.svg)](https://codeclimate.com/github/chrismccord/sync) [![gem version](https://img.shields.io/gem/v/sync.svg)](http://rubygems.org/gems/sync)\n\n\n\u003e This started as a thought experiment that is growing into a viable option for realtime Rails apps without ditching\n  the standard rails stack that we love and are so productive with for a heavy client side MVC framework.\n\n\nReal-time partials with Rails. Sync lets you render partials for models that, with minimal code,\nupdate in realtime in the browser when changes occur on the server.\n\n#### Watch a screencast to see it in action\n[![See it in action](http://chrismccord.com/images/sync/video_thumb.png)](http://chrismccord.com/blog/2013/04/21/sync-realtime-rails-partials/)\n\nIn practice, one simply only needs to replace:\n\n```erb\n\u003c%= render partial: 'user_row', locals: {user: @user} %\u003e\n```\n\nwith:\n\n```erb\n\u003c%= sync partial: 'user_row', resource: @user %\u003e\n```\n\nThen update views realtime automatically with the `sync` DSL or with a simple `sync_update(@user)` in the controller without any extra javascript or\nconfiguration.\n\nIn addition to real-time updates, Sync also provides:\n\n  - Realtime removal of partials from the DOM when the sync'd model is destroyed in the controller via `sync_destroy(@user)`\n  - Realtime appending of newly created model's on scoped channels\n  - JavaScript/CoffeeScript hooks to override and extend element updates/appends/removes for partials\n  - Support for [Faye](http://faye.jcoglan.com/) and [Pusher](http://pusher.com)\n\n## Requirements\n\n  - Ruby \u003e= 1.9.3\n  - Rails 3 \u003e= 3.1 or Rails 4\n  - jQuery \u003e= 1.9\n\n## Upgrading from 0.4.0\n\nThe gem name has changed from `sync` to `render_sync`, so to upgrade you just need to use\nthe new name in your Gemfile:\n```\ngem 'render_sync'\n```\n\n## Installation\n\n#### 1) Add the gem to your `Gemfile`\n\n#### Using Faye\n\n```ruby\ngem 'faye'\ngem 'thin', require: false\ngem 'render_sync'\n```\n\n#### Using Pusher\n\n```ruby\ngem 'pusher'\ngem 'render_sync'\n```\n\n#### Install\n\n```bash\n$ bundle\n$ rails g render_sync:install\n```\n\n#### 2) Require sync in your asset javascript manifest `app/assets/javascripts/application.js`:\n\n```javascript\n//= require sync\n```\n\n#### 3) Add sync's configuration script to your application layout `app/views/layouts/application.html.erb`\n\n```erb\n\u003c%= include_sync_config %\u003e\n```\n\n#### 4) Configure your pubsub server (Faye or Pusher)\n\n\n#### Using [Faye](http://faye.jcoglan.com/) (self hosted)\n\nSet your configuration in the generated `config/sync.yml` file, using the Faye adapter. Then run Faye alongside your app.\n\n```bash\nrackup sync.ru -E production\n```\n\n#### Using [Pusher](http://pusher.com) (SaaS)\n\nSet your configuration in the generated `config/sync.yml` file, using the Pusher adapter. No extra process/setup.\n\n## Current Caveats\nThe current implementation uses a DOM range query (jQuery's `nextUntil`) to match your partial's \"element\" in\nthe DOM. The way this selector works requires your sync'd partial to be wrapped in a root level html tag for that partial file.\nFor example, this parent view/sync partial approach would *not* work:\n\nGiven the sync partial `_todo_row.html.erb`:\n\n```erb\nTitle:\n\u003c%= link_to todo.title, todo %\u003e\n```\n\nAnd the parent view:\n\n```erb\n\u003ctable\u003e\n  \u003ctbody\u003e\n    \u003ctr\u003e\n      \u003c%= sync partial: 'todo_row', resource: @todo %\u003e\n    \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n```\n\n##### The markup *would need to change to*:\n\n\nsync partial `_todo_row.html.erb`:\n\n```erb\n\u003ctr\u003e \u003c!-- root level container for the partial required here --\u003e\n  Title:\n  \u003c%= link_to todo.title, todo %\u003e\n\u003c/tr\u003e\n```\n\nAnd the parent view changed to:\n\n```erb\n\u003ctable\u003e\n  \u003ctbody\u003e\n    \u003c%= sync partial: 'todo_row', resource: @todo %\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n```\n\nI'm currently investigating true DOM ranges via the [Range](https://developer.mozilla.org/en-US/docs/DOM/range) object.\n\n\n## 'Automatic' syncing through the sync model DSL\n\nIn addition to calling explicit sync actions within controller methods, a\n`sync` and `enable_sync` DSL has been added to ActionController::Base and ActiveRecord::Base to automate the syncing\napproach in a controlled, threadsafe way.\n\n### Example Model/Controller\n```ruby\n  class Todo \u003c ActiveRecord::Base\n    sync :all\n  end\n```\n```ruby\n  class TodosController \u003c ApplicationController\n    enable_sync only: [:create, :update, :destroy]\n    ...\n  end\n```\n\nNow, whenever a Todo is created/updated/destroyed inside an action of the `TodosController` changes are automatically pushed to all subscribed clients without manually calling sync actions.\n\n### Updating multiple sets of records with sync scopes\n\nSometimes you might want to display multiple differently scoped todo lists throughout your application and keep them all in sync. For example:\n\n- A global list with all todos\n- A list with all completed todos\n- A list with all todos of a user\n- A list with all todos of a project\n- ...\n\nThis was quite tricky to accomplish in previous versions of sync. Well, now this is going to be dead simple with the help of explicit sync scopes. First, define your desired sync scopes on the model with `sync_scope` like this:\n\n```ruby\nclass Todo \u003c ActiveRecord::Base\n  belongs_to :user\n  belongs_to :project\n\n  sync :all\n\n  sync_scope :active, -\u003e { where(completed: false) }\n  sync_scope :completed, -\u003e { where(completed: true) }\nend\n```\n\nThen in your views display the different sets of todos by passing the `scope` as a parameter like this:\n\n```erb\n\u003c%= sync partial: \"todo\", collection: Todo.active %\u003e\n\u003c%= sync_new partial: \"todo\", resource: Todo.new, scope: Todo.active %\u003e\n\n\u003c%= sync partial: \"todo\", collection: Todo.completed %\u003e\n\u003c%= sync_new partial: \"todo\", resource: Todo.new, scope: Todo.completed %\u003e\n```\n\nNow, whenever a todo is created/updated/destroyed sync will push the appropriate changes to all affected clients. This also works for attribute changes that concern the belonging to a specific scope itself. E.g. if the `completed` flag is set to `true` during an update action sync will automatically push the todo partial to all clients displaying the list of completed todos and remove it from all clients subscribed to the list of active todos.\n\n#### Advanced scoping with parameters\n\nIn order to display lists that are dynamically scoped (e.g. by the `current_user` or a `@project` instance variable) you can setup dynamic sync scopes like this:\n\n```ruby\nsync_scope :by_user, -\u003e(user) { where(user_id: user.id) }\nsync_scope :by_project, -\u003e(project) { where(project_id: project.id) }\n```\n\nNote that the naming of the parameters is very important for sync to do its magic. Be sure to only use names of methods, parent associations or ActiveRecord attributes defined on the model (e.g. in this case `user` and `project`). This way sync will be able to detect changes to the scope.\n\nSetup the rendering of the partials in the views with:\n\n```erb\n\u003c%= sync partial: \"todo\", collection: Todo.by_user(current_user) %\u003e\n\u003c%= sync_new partial: \"todo\", resource: Todo.new, scope: Todo.by_user(current_user) %\u003e\n\n\u003c%= sync partial: \"todo\", collection: Todo.by_project(@project) %\u003e\n\u003c%= sync_new partial: \"todo\", resource: Todo.new, scope: Todo.by_project(@project) %\u003e\n```\n\nBeware that chaining of sync scopes in the view is currently not supported. So the following example would not work as expected:\n\n```erb\n\u003c%= sync_new partial: \"todo\", Todo.new, scope: Todo.by_user(current_user).completed %\u003e\n```\n\nTo work around this just create an explicit sync_scope for your use case:\n\n```ruby\nsync_scope :completed_by_user, -\u003e(user) { completed.by_user(current_user) }\n```\n\n```erb\n\u003c%= sync_new partial: \"todo\", Todo.new, scope: Todo.completed_by_user(current_user) %\u003e\n```\n\n#### Things to keep in mind when using `sync_scope`\n\nPlease keep in mind that the more sync scopes you set up the more sync messages will be send over your pubsub adapter. So be sure to keep the number scopes small and remove scopes you are not using.\n\n#### Automatic updating of parent associations\n\nIf you want to automatically sync the partials of a parent association whenever a record changes you can use the `sync_touch` method. E.g. if you always want to sync the partials of the associated `user` and `project` just add this line to your `Todo` class:\n\n```ruby\nsync_touch :project, :user\n```\n\n### Syncing outside of the controller\n\n`Sync::Actions` can be included into any object wishing to perform sync\npublishes for a given resource. Instead of using the controller as\ncontext for rendering, a Sync::Renderer instance is used. Since the Renderer\nis not part of the request/response/session, it has no knowledge of the\ncurrent session (ie. current_user), so syncing from outside the controller\ncontext will require some care that the partial can be rendered within a\nsessionless context.\n\n### Example Syncing from a background worker or rails console\n```ruby\nclass MyJob\n  include Sync::Actions\n\n  def perform\n    Sync::Model.enable do\n      Todo.first.update title: \"This todo will be sync'd on save\"\n    end\n    Todo.first.update title: \"This todo will NOT be sync'd on save\"\n\n    Sync::Model.enable!\n    Todo.first.update title: \"This todo will be sync'd on save\"\n    Todo.first.update title: \"This todo will be sync'd on save\"\n    Todo.first.update title: \"This todo will be sync'd on save\"\n    Sync::Model.disable!\n    Todo.first.update title: \"This todo will NOT be sync'd on save\"\n  end\nend\n```\n\n## Custom Sync Views and javascript hooks\n\nSync allows you to hook into and override or extend all of the actions it performs when updating partials on the client side. When a sync partial is rendered, sync will instantiate a javascript View class based on the following order of lookup:\n\n 1. The camelized version of the concatenated snake case resource\n    and partial names.\n 2. The camelized version of the snake cased partial name.\n\n#### Examples\n\npartial name 'list_row', resource name 'todo', order of lookup:\n\n 1. Sync.TodoListRow\n 2. Sync.ListRow\n 3. Sync.View (Default fallback)\n\n\nFor example, if you wanted to fade in/out a row in a sync'd todo list instead of the Sync.View default of instant insert/remove:\n\n```coffeescript\nclass Sync.TodoListRow extends Sync.View\n\n  beforeInsert: ($el) -\u003e\n    $el.hide()\n    @insert($el)\n\n  afterInsert: -\u003e @$el.fadeIn 'slow'\n\n  beforeRemove: -\u003e @$el.fadeOut 'slow', =\u003e @remove()\n\n```\n\n## Narrowing sync_new scope\n\nSometimes, you do not want your page to update with every new record. With the `scope` option, you can limit what is being updated on a given page.\n\nOne way of using `scope` is by supplying a String or a Symbol. This is useful for example when you want to only show new records for a given locale:\n\nView:\n```erb\n\u003c%= sync_new partial: 'todo_list_row', resource: Todo.new, scope: I18n.locale %\u003e\n```\n\nController/Model:\n```ruby\nsync_new @todo, scope: @todo.locale\n```\n\nAnother use of `scope` is with a parent resource. This way you can for example update a project page with new todos for this single project:\n\nView:\n```erb\n\u003c%= sync_new partial: 'todo_list_row', resource: Todo.new, scope: @project %\u003e\n```\n\nController/Model:\n```ruby\nsync_new @todo, scope: @project\n```\n\nBoth approaches can be combined. Just supply an Array of Strings/Symbols and/or parent resources to the `scope` option. Note that the order of elements matters. Be sure to use the same order in your view and in your controller/model.\n\n## Scoping by Partial\n\nIf a single resource has a bunch of different sync partials, calling `sync_new` or `sync_update` could be very expensive, as sync would need to render each partial for that resource, even if only one partial would be affected by the update. Because of this, sync allows you to scope these by the name of the partial:\n\n```rb\ndef UsersController \u003c ApplicationController\n  …\n  def create\n    …\n    if @user.save\n      sync_new @user, partial: 'users_count'\n    end\n    …\n  end\nend\n```\n\nIn the above example, only the `sync/users/users_count` partial will be rendered and pushed to subscribed clients.\n\n## Refetching Partials\n\nRefetching allows syncing partials across different users when the partial requires the session's context (ie. current_user).\n\nEx:\n    View: Add `refetch: true` to sync calls, and place partial file in a 'refetch'\n    subdirectory in the model's sync view folder:\n\nThe partial file would be located in `app/views/sync/todos/refetch/_list_row.html.erb`\n```erb\n\u003c% @project.todos.ordered.each do |todo| %\u003e\n  \u003c%= sync partial: 'list_row', resource: todo, refetch: true %\u003e\n\u003c% end %\u003e\n\u003c%= sync_new partial: 'list_row', resource: Todo.new, scope: @project, refetch: true %\u003e\n```\n\n*Notes*\n\nWhile this approach works very well for the cases it's needed, syncing without refetching should be used unless refetching is absolutely necessary for performance reasons. For example,\n\nA sync update request is triggered on the server for a 'regular' sync'd partial with 100 listening clients:\n- number of http requests 1\n- number of renders 1, pushed out to all 100 clients via pubsub server.\n\n\nA sync update request is triggered on the server for a 'refetch' sync'd partial with 100 listening clients:\n- number of http requests 100\n- number of renders 100, rendering each request in clients session context.\n\n## Using with cache_digests (Russian doll caching)\n\nSync has a custom `DependencyTracker::ERBTracker` that can handle `sync` render calls.\nBecause the full partial name is not included, it has to guess the location of\nyour partial based on the name of the `resource` or `collection` passed to it.\nSee the tests to see how it works. If it doesn't work for you, you can always\nuse the [explicit \"Template Dependency\"\nmarkers](https://github.com/rails/cache_digests).\n\nTo enable, add to `config/initializers/cache_digests.rb`:\n\n#### Rails 4\n\n```ruby\nrequire 'action_view/dependency_tracker'\n\nActionView::DependencyTracker.register_tracker :haml, Sync::ERBTracker\nActionView::DependencyTracker.register_tracker :erb, Sync::ERBTracker\n```\n\n#### Rails 3 with [cache_digests](https://github.com/rails/cache_digests) gem\n\n```ruby\nrequire 'cache_digests/dependency_tracker'\n\nCacheDigests::DependencyTracker.register_tracker :haml, Sync::ERBTracker\nCacheDigests::DependencyTracker.register_tracker :erb, Sync::ERBTracker\n```\n\n**Note:** haml support is limited, but it seems to work in most cases.\n\n\n## Serving Faye over HTTPS (with Thin)\n\nCreate a thin configuration file `config/sync_thin.yml` similar to the following:\n\n```yaml\n---\nport: 4443\nssl: true\nssl_key_file: /path/to/server.pem\nssl_cert_file: /path/to/certificate_chain.pem\nenvironment: production\nrackup: sync.ru\n```\n\nThe `certificate_chain.pem` file should contain your signed certificate, followed by intermediate certificates (if any) and the root certificate of the CA that signed the key.\n\nNext reconfigure the `server` and `adapter_javascript_url` in `config/sync.yml` to look like `https://your.hostname.com:4443/faye` and `https://your.hostname.com:4443/faye/faye.js` respectively.\n\nFinally start up Thin from the project root.\n\n```\nthin -C config/sync_thin.yml start\n```\n\n\n## Brief Example or [checkout an example application](https://github.com/chrismccord/sync_example)\n\nView `sync/users/_user_list_row.html.erb`\n\n```erb\n\u003ctr\u003e\n  \u003ctd\u003e\u003c%= link_to user.name, user %\u003e\u003c/td\u003e\n  \u003ctd\u003e\u003c%= link_to 'Edit', edit_user_path(user) %\u003e\u003c/td\u003e\n  \u003ctd\u003e\u003c%= link_to 'Destroy', user, method: :delete, remote: true, data: { confirm: 'Are you sure?' } %\u003e\u003c/td\u003e\n\u003c/tr\u003e\n```\n\nView `users/index.html.erb`\n\n```erb\n\u003ch1\u003eSome Users\u003c/h1\u003e\n\u003ctable\u003e\n  \u003ctbody\u003e\n    \u003c%= sync partial: 'user_list_row', collection: @users %\u003e\n    \u003c%= sync_new partial: 'user_list_row', resource: User.new, direction: :append %\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n```\n\n\nController\n\n```ruby\ndef UsersController \u003c ApplicationController\n  …\n  def create\n    @user = User.new(user_params)\n    if @user.save\n      sync_new @user\n    end\n    respond_to do |format|\n      format.html { redirect_to users_url }\n      format.json { head :no_content }\n    end\n  end\n\n  def update\n    @user = User.find(params[:id])\n    if user.save\n    …\n    end\n\n    # Sync updates to any partials listening for this user\n    sync_update @user\n\n    redirect_to users_path, notice: \"Saved!\"\n  end\n\n  def destroy\n    @user = User.find(params[:id])\n    @user.destroy\n\n    # Sync destroy, telling client to remove all dom elements containing this user\n    sync_destroy @user\n\n    respond_to do |format|\n      format.html { redirect_to users_url }\n      format.json { head :no_content }\n    end\n  end\nend\n```\n\n## Google detecting not found errors\n\nIf you're using [Google Webmaster Tools](https://www.google.com/webmasters/) you may notice that Google detects *lots* of URLs it can't find on your site when using Sync.\nThis is because Google now attempts to discover URLs in JavaScript and some JavaScript we generate looks a little like a URL to Google.\nYou can [safely ignore](https://support.google.com/webmasters/answer/2409439?ctx=MCE\u0026ctx=NF) this problem.\n","funding_links":[],"categories":["Ruby","WebSocket","Tools per Language"],"sub_categories":["Ruby"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchrismccord%2Frender_sync","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchrismccord%2Frender_sync","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchrismccord%2Frender_sync/lists"}