# Sync
> This started as a thought experiment that is growing into a viable option for realtime Rails apps without ditching
the standard rails stack that we love and are so productive with for a heavy client side MVC framework.
Real-time partials with Rails. Sync lets you render partials for models that, with minimal code,
update in realtime in the browser when changes occur on the server.
#### Watch a screencast to see it in action
[![See it in action](http://chrismccord.com/images/sync/video_thumb.png)](http://chrismccord.com/blog/2013/04/21/sync-realtime-rails-partials/)
In practice, one simply only needs to replace:
```erb
<%= render partial: 'user_row', locals: {user: @user} %>
```
with:
```erb
<%= sync partial: 'user_row', resource: @user %>
```
Then update views realtime automatically with the `sync` DSL or with a with a simple `sync_update(@user)` in the controller without any extra javascript or
configuration.
In addition to real-time updates, Sync also provides:
- Realtime removal of partials from the DOM when the sync'd model is destroyed in the controller via `sync_destroy(@user)`
- Realtime appending of newly created model's on scoped channels
- JavaScript/CoffeeScript hooks to override and extend element updates/appends/removes for partials
- Support for [Faye](http://faye.jcoglan.com/) and [Pusher](http://pusher.com)
## Requirements
- Ruby >= 1.9.2
- Rails 3 >= 3.1 or Rails 4
- jQuery >= 1.9
## Installation
#### 1) Add the gem to your `Gemfile`
#### Using Faye
```ruby
gem 'faye'
gem 'thin', require: false
gem 'sync'
```
#### Using Pusher
```ruby
gem 'pusher'
gem 'sync'
```
#### Install
```bash
$ bundle
$ rails g sync:install
```
#### 2) Require sync in your asset javascript manifest `app/assets/javascripts/application.js`:
```javascript
//= require sync
```
#### 3) Add the pubsub adapter's javascript to your application layout `app/views/layouts/application.html.erb`
```erb
<%= javascript_include_tag Sync.adapter_javascript_url %>
```
#### 4) Configure your pubsub server (Faye or Pusher)
#### Using [Faye](http://faye.jcoglan.com/) (self hosted)
Set your configuration in the generated `config/sync.yml` file, using the Faye adapter. Then run Faye alongside your app.
```bash
rackup sync.ru -E production
```
#### Using [Pusher](http://pusher.com) (SaaS)
Set your configuration in the generated `config/sync.yml` file, using the Pusher adapter. No extra process/setup.
## Current Caveats
The current implementation uses a DOM range query (jQuery's `nextUntil`) to match your partial's "element" in
the DOM. The way this selector works requires your sync'd partial to be wrapped in a root level html tag for that partial file.
For example, this parent view/sync partial approach would *not* work:
Given the sync partial `_todo_row.html.erb`:
```erb
Title:
<%= link_to todo.title, todo %>
```
And the parent view:
```erb
<%= sync partial: 'todo_row', resource: @todo %>
```
##### The markup *would need to change to*:
sync partial `_todo_row.html.erb`:
```erb
Title:
<%= link_to todo.title, todo %>
```
And the parent view changed to:
```erb
<%= sync partial: 'todo_row', resource: @todo %>
```
I'm currently investigating true DOM ranges via the [Range](https://developer.mozilla.org/en-US/docs/DOM/range) object.
## 'Automatic' syncing through the sync DSL
In addition to calling explicit sync actions within controller methods, a
`sync` and `enable_sync` DSL has been added to ActionController::Base and ActiveRecord::Base to automate the syncing
approach in a controlled, threadsafe way.
### Example Controller/Model
```ruby
class TodosController < ApplicationController
enable_sync only: [:create, :update, :destroy]
...
end
class Todo < ActiveRecord::Base
belongs_to :project, counter_cache: true
has_many :comments, dependent: :destroy
sync :all, scope: :project
end
```
### Syncing outside of the controller
`Sync::Actions` can be included into any object wishing to perform sync
publishes for a given resource. Instead of using the the controller as
context for rendering, a Sync::Renderer instance is used. Since the Renderer
is not part of the request/response/session, it has no knowledge of the
current session (ie. current_user), so syncing from outside the controller
context will require some care that the partial can be rendered within a
sessionless context.
### Example Syncing from a background worker or rails console
```ruby
# Inside some script/worker
Sync::Model.enable do
Todo.first.update title: "This todo will be sync'd on save"
end
Todo.first.update title: "This todo will NOT be sync'd on save"
Sync::Model.enable!
Todo.first.update title: "This todo will be sync'd on save"
Todo.first.update title: "This todo will be sync'd on save"
Todo.first.update title: "This todo will be sync'd on save"
Sync::Model.disable!
Todo.first.update title: "This todo will NOT be sync'd on save"
```
## Custom Sync Views and javascript hooks
Sync 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:
1. The camelized version of the concatenated snake case resource
and partial names.
2. The camelized version of the snake cased partial name.
#### Examples
partial name 'list_row', resource name 'todo', order of lookup:
1. Sync.TodoListRow
2. Sync.ListRow
3. Sync.View (Default fallback)
For 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:
```coffeescript
class Sync.TodoListRow extends Sync.View
beforeInsert: ($el) ->
$el.hide()
@insert($el)
afterInsert: -> @$el.fadeIn 'slow'
beforeRemove: -> @$el.fadeOut 'slow', => @remove()
```
## Narrowing sync_new scope
Sometimes, 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.
One 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:
View:
```erb
<%= sync_new partial: 'todo_list_row', resource: Todo.new, scope: I18n.locale %>
```
Controller/Model:
```ruby
sync_new @todo, scope: @todo.locale
```
Another 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:
View:
```erb
<%= sync_new partial: 'todo_list_row', resource: Todo.new, scope: @project %>
```
Controller/Model:
```ruby
sync_new @todo, scope: @project
```
Both 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.
## Refetching Partials
Refetching allows syncing partials across different users when the partial requires the session's context (ie. current_user).
Ex:
View: Add `refetch: true` to sync calls, and place partial file in a 'refetch'
subdirectory in the model's sync view folder:
The partial file would be located in `app/views/sync/todos/refetch/_list_row.html.erb`
```erb
<% @project.todos.ordered.each do |todo| %>
<%= sync partial: 'list_row', resource: todo, refetch: true %>
<% end %>
<%= sync_new partial: 'list_row', resource: Todo.new, scope: @project, refetch: true %>
```
*Notes*
While 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,
A sync update request is triggered on the server for a 'regular' sync'd partial with 100 listening clients:
- number of http requests 1
- number of renders 1, pushed out to all 100 clients via pubsub server.
A sync update request is triggered on the server for a 'refetch' sync'd partial with 100 listening clients:
- number of http requests 100
- number of renders 100, rendering each request in clients session context.
## Using with cache_digests (Russian doll caching)
Sync has a custom `DependencyTracker::ERBTracker` that can handle `sync` render calls.
Because the full partial name is not included, it has to guess the location of
your partial based on the name of the `resource` or `collection` passed to it.
See the tests to see how it works. If it doesn't work for you, you can always
use the [explicit "Template Dependency"
markers](https://github.com/rails/cache_digests).
To enable, add to `config/initializers/cache_digests.rb`:
#### Rails 4
```ruby
require 'action_view/dependency_tracker'
ActionView::DependencyTracker.register_tracker :haml, Sync::ERBTracker
ActionView::DependencyTracker.register_tracker :erb, Sync::ERBTracker
```
#### Rails 3 with [cache_digests](https://github.com/rails/cache_digests) gem
```ruby
require 'cache_digests/dependency_tracker'
CacheDigests::DependencyTracker.register_tracker :haml, Sync::ERBTracker
CacheDigests::DependencyTracker.register_tracker :erb, Sync::ERBTracker
```
**Note:** haml support is limited, but it seems to work in most cases.
## Serving Faye over HTTPS (with Thin)
Create a thin configuration file `config/sync_thin.yml` similar to the following:
```yaml
---
port: 4443
ssl: true
ssl_key_file: /path/to/server.pem
ssl_cert_file: /path/to/certificate_chain.pem
environment: production
rackup: sync.ru
```
The `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.
Next 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.
Finally start up Thin from the project root.
```
thin -C config/sync_thin.yml start
```
## Brief Example or [checkout an example application](https://github.com/chrismccord/sync_example)
View `sync/users/_user_list_row.html.erb`
```erb
<%= link_to user.name, user %> |
<%= link_to 'Edit', edit_user_path(user) %> |
<%= link_to 'Destroy', user, method: :delete, remote: true, data: { confirm: 'Are you sure?' } %> |
```
View `users/index.html.erb`
```erb
Some Users
<%= sync partial: 'user_list_row', collection: @users %>
<%= sync_new partial: 'user_list_row', resource: User.new, direction: :append %>
```
Controller
```ruby
def UsersController < ApplicationController
…
def create
@user = User.new(user_params)
if @user.save
sync_new @user
end
respond_to do |format|
format.html { redirect_to users_url }
format.json { head :no_content }
end
end
def update
@user = User.find(params[:id])
if user.save
…
end
# Sync updates to any partials listening for this user
sync_update @user
redirect_to users_path, notice: "Saved!"
end
def destroy
@user = User.find(params[:id])
@user.destroy
# Sync destroy, telling client to remove all dom elements containing this user
sync_destroy @user
respond_to do |format|
format.html { redirect_to users_url }
format.json { head :no_content }
end
end
end
```