Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/dubadub/sync_bumper
https://github.com/dubadub/sync_bumper
Last synced: 17 days ago
JSON representation
- Host: GitHub
- URL: https://github.com/dubadub/sync_bumper
- Owner: dubadub
- License: other
- Created: 2014-03-06T14:12:07.000Z (almost 11 years ago)
- Default Branch: master
- Last Pushed: 2014-03-20T17:56:20.000Z (almost 11 years ago)
- Last Synced: 2024-12-24T06:36:45.706Z (18 days ago)
- Language: Ruby
- Size: 125 KB
- Stars: 0
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# 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 < ApplicationControllerenable_sync only: [:create, :update, :destroy]
...
endclass Todo < ActiveRecord::Base
belongs_to :project, counter_cache: true
has_many :comments, dependent: :destroysync :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 hooksSync 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.ViewbeforeInsert: ($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
enddef update
@user = User.find(params[:id])
if user.save
…
end# Sync updates to any partials listening for this user
sync_update @userredirect_to users_path, notice: "Saved!"
enddef destroy
@user = User.find(params[:id])
@user.destroy# Sync destroy, telling client to remove all dom elements containing this user
sync_destroy @userrespond_to do |format|
format.html { redirect_to users_url }
format.json { head :no_content }
end
end
end
```