render_sync 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +153 -0
  3. data/Gemfile +3 -0
  4. data/LICENSE +22 -0
  5. data/README.md +521 -0
  6. data/Rakefile +9 -0
  7. data/app/assets/javascripts/sync.coffee +355 -0
  8. data/app/controllers/sync/refetches_controller.rb +56 -0
  9. data/app/helpers/render_sync/config_helper.rb +15 -0
  10. data/config/routes.rb +3 -0
  11. data/config/sync.yml +21 -0
  12. data/lib/generators/render_sync/install_generator.rb +14 -0
  13. data/lib/generators/render_sync/templates/sync.ru +14 -0
  14. data/lib/generators/render_sync/templates/sync.yml +34 -0
  15. data/lib/render_sync.rb +174 -0
  16. data/lib/render_sync/action.rb +39 -0
  17. data/lib/render_sync/actions.rb +114 -0
  18. data/lib/render_sync/channel.rb +23 -0
  19. data/lib/render_sync/clients/dummy.rb +22 -0
  20. data/lib/render_sync/clients/faye.rb +104 -0
  21. data/lib/render_sync/clients/pusher.rb +77 -0
  22. data/lib/render_sync/controller_helpers.rb +33 -0
  23. data/lib/render_sync/engine.rb +24 -0
  24. data/lib/render_sync/erb_tracker.rb +49 -0
  25. data/lib/render_sync/faye_extension.rb +45 -0
  26. data/lib/render_sync/model.rb +174 -0
  27. data/lib/render_sync/model_actions.rb +60 -0
  28. data/lib/render_sync/model_change_tracking.rb +97 -0
  29. data/lib/render_sync/model_syncing.rb +65 -0
  30. data/lib/render_sync/model_touching.rb +35 -0
  31. data/lib/render_sync/partial.rb +112 -0
  32. data/lib/render_sync/partial_creator.rb +47 -0
  33. data/lib/render_sync/reactor.rb +48 -0
  34. data/lib/render_sync/refetch_model.rb +21 -0
  35. data/lib/render_sync/refetch_partial.rb +43 -0
  36. data/lib/render_sync/refetch_partial_creator.rb +21 -0
  37. data/lib/render_sync/renderer.rb +19 -0
  38. data/lib/render_sync/resource.rb +115 -0
  39. data/lib/render_sync/scope.rb +113 -0
  40. data/lib/render_sync/scope_definition.rb +30 -0
  41. data/lib/render_sync/view_helpers.rb +106 -0
  42. data/test/dummy/README.rdoc +28 -0
  43. data/test/dummy/Rakefile +6 -0
  44. data/test/dummy/app/assets/javascripts/application.js +13 -0
  45. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  46. data/test/dummy/app/controllers/application_controller.rb +5 -0
  47. data/test/dummy/app/helpers/application_helper.rb +2 -0
  48. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  49. data/test/dummy/app/views/sync/users/_show.html.erb +1 -0
  50. data/test/dummy/app/views/sync/users/refetch/_show.html.erb +1 -0
  51. data/test/dummy/bin/bundle +3 -0
  52. data/test/dummy/bin/rails +4 -0
  53. data/test/dummy/bin/rake +4 -0
  54. data/test/dummy/config.ru +4 -0
  55. data/test/dummy/config/application.rb +22 -0
  56. data/test/dummy/config/boot.rb +5 -0
  57. data/test/dummy/config/database.yml +8 -0
  58. data/test/dummy/config/environment.rb +5 -0
  59. data/test/dummy/config/environments/development.rb +29 -0
  60. data/test/dummy/config/environments/production.rb +80 -0
  61. data/test/dummy/config/environments/test.rb +36 -0
  62. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  63. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  64. data/test/dummy/config/initializers/inflections.rb +16 -0
  65. data/test/dummy/config/initializers/mime_types.rb +5 -0
  66. data/test/dummy/config/initializers/secret_token.rb +12 -0
  67. data/test/dummy/config/initializers/session_store.rb +3 -0
  68. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  69. data/test/dummy/config/locales/en.yml +23 -0
  70. data/test/dummy/config/routes.rb +56 -0
  71. data/test/dummy/log/test.log +626 -0
  72. data/test/dummy/public/404.html +58 -0
  73. data/test/dummy/public/422.html +58 -0
  74. data/test/dummy/public/500.html +57 -0
  75. data/test/dummy/public/favicon.ico +0 -0
  76. data/test/em_minitest_spec.rb +100 -0
  77. data/test/fixtures/sync_auth_token_missing.yml +6 -0
  78. data/test/fixtures/sync_erb.yml +7 -0
  79. data/test/fixtures/sync_faye.yml +7 -0
  80. data/test/fixtures/sync_pusher.yml +8 -0
  81. data/test/models/group.rb +3 -0
  82. data/test/models/project.rb +2 -0
  83. data/test/models/todo.rb +8 -0
  84. data/test/models/user.rb +82 -0
  85. data/test/sync/abstract_controller.rb +3 -0
  86. data/test/sync/action_test.rb +82 -0
  87. data/test/sync/channel_test.rb +15 -0
  88. data/test/sync/config_test.rb +25 -0
  89. data/test/sync/erb_tracker_test.rb +72 -0
  90. data/test/sync/faye_extension_test.rb +87 -0
  91. data/test/sync/message_test.rb +159 -0
  92. data/test/sync/model_test.rb +315 -0
  93. data/test/sync/partial_creator_test.rb +35 -0
  94. data/test/sync/partial_test.rb +107 -0
  95. data/test/sync/protected_attributes_test.rb +39 -0
  96. data/test/sync/reactor_test.rb +18 -0
  97. data/test/sync/refetch_model_test.rb +26 -0
  98. data/test/sync/refetch_partial_creator_test.rb +16 -0
  99. data/test/sync/refetch_partial_test.rb +74 -0
  100. data/test/sync/renderer_test.rb +19 -0
  101. data/test/sync/resource_test.rb +181 -0
  102. data/test/sync/scope_definition_test.rb +39 -0
  103. data/test/sync/scope_test.rb +113 -0
  104. data/test/test_helper.rb +66 -0
  105. data/test/travis/sync.ru +14 -0
  106. data/test/travis/sync.yml +21 -0
  107. metadata +317 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ea50e504cb67562b8729e58aabf2b6ade03628d5
4
+ data.tar.gz: aebf56cb0648eb79378bccc92ba778c1704592f0
5
+ SHA512:
6
+ metadata.gz: 7def0c581a0683377fbe67b517085a614f0ec0d36fb6c20a2a386af56027f15b1d9e7764f5f552644a392b1228b303b896b65445489f3dfc753d72148631483a
7
+ data.tar.gz: 837a49bf31903a1d6f285fad96129dd91e94f0bb8ab1cadc5d00ec1a70d808376d13fa6bf5826df81eada880ef06951a64efdfda6a38802813181a253a92091e
@@ -0,0 +1,153 @@
1
+ # Version 0.3.5 - January 6, 2015
2
+
3
+ - Removed unnecessary call to `respond_to` since `responders` have been removed from Rails.
4
+
5
+ # Version 0.3.4 - January 6, 2015
6
+
7
+ - `#squish` generated HTML
8
+ - Fix bug when using `sync @resource` with the `:scope` option
9
+
10
+ # Version 0.3.0 - March 3, 2014
11
+
12
+ This release focuses on improving and extending the model DSL for setting up automatic syncing in advanced use cases.
13
+
14
+ - Adds the ability for advanced channel scoping
15
+
16
+ There were multiple feature requests asking for a way to sync differently scoped lists of the same model automatically and interdependently (e.g all todos of a user and all todos of a project). This can now be accomplished by explicitly defining scopes on the model via the new `sync_scope` method:
17
+
18
+ ```ruby
19
+ class Todo < ActiveRecord::Base
20
+ belongs_to :project
21
+ belongs_to :user
22
+
23
+ sync :all
24
+
25
+ sync_scope :by_user(user), -> { where(user_id: user.id) }
26
+ sync_scope :by_project(project), -> { where(project_id: project.id) }
27
+ end
28
+ ```
29
+
30
+ and then use these scopes to narrow the rendering of sync partials like this:
31
+
32
+ ```erb
33
+ <%= sync partial: "todo", resource: Todo.by_user(@user) %>
34
+ <%= sync_new partial: "todo", resource: Todo.new, scope: Todo.by_user(@user) %>
35
+ ```
36
+
37
+ Please take a look at the docs and the readme for a more thorough explanation and examples on how to use this new feature.
38
+
39
+ - Adds the ability to explicitly update parent associations via `sync_touch`
40
+
41
+ ####Breaking Changes:
42
+
43
+ - If you're using the scope feature to narrow the syncing of new records in the `sync_new` call, you will now have to add this scope when calling the `sync` helper method as well:
44
+
45
+ ```erb
46
+ <%= sync partial: 'todo_comment', collection: @comments, scope: @todo %>
47
+ <%= sync_new partial: 'todo_comment', resource: Comment.new, scope: @todo %>
48
+ ```
49
+
50
+ If you're in addition using the controller way of manually syncing partials, you will now also have to add the scope parameter to the sync_destroy call like this:
51
+
52
+ ```ruby
53
+ sync_destroy @comment, scope: @comment.todo
54
+ ```
55
+
56
+ Why is this?
57
+
58
+ Before this version there was only a global destroy channel for every record, so an unscoped `sync_destroy` call was just enough to remove all partials from all subscribed clients when a record has been destroyed. As of 0.3.0 the destroy channel will be used not only to remove partials when a record is destroyed, but also when partials for that record need to be added to/removed from different sets throughout the application when it is updated.
59
+
60
+ - The `:scope` parameter for the `sync` method has been replaced with `:default_scope`. Make sure you update your code accordingly. If you're using the default scope feature, be sure to alway add the corresponding option to your views like this:
61
+
62
+ ```ruby
63
+ class Todo < ActiveRecord::Base
64
+ belongs_to :organization
65
+
66
+ sync :all, default_scope: :organization
67
+ end
68
+ ```
69
+
70
+ ```erb
71
+ <%= sync partial: "todo", resource: @todos, default_scope: @organization %>
72
+ <%= sync_new partial: "todo", resource: Todo.new, default_scope: @organization %>
73
+ ```
74
+
75
+ - The parent model defined by the `:default_scope` parameter will no longer be automatically updated via sync. Please use the new explicit `sync_touch` method instead.
76
+
77
+ Old Syntax:
78
+ ```ruby
79
+ class Todo < ActiveRecord::Base
80
+ belongs_to :project
81
+ belongs_to :user
82
+
83
+ sync :all, scope: :project
84
+ end
85
+ ```
86
+
87
+ New Syntax:
88
+ ```ruby
89
+ class Todo < ActiveRecord::Base
90
+ belongs_to :project
91
+ belongs_to :user
92
+
93
+ sync :all, default_scope: :project
94
+ sync_touch :project, :user
95
+ end
96
+ ```
97
+
98
+ This will sync all partials of the parent model `project` and `user`, whenever a todo is created/updated/deleted.
99
+
100
+ # Version 0.2.7 - Feburary 25, 2014
101
+
102
+ - Fixes https://github.com/chrismccord/sync/issues/54 (Thin complaining about too long query string)
103
+
104
+ # Version 0.2.3 - June 30, 2013
105
+
106
+ - Fixed Turbolinks issue where `page:restore` events no longer evaluate script tags in the body. The workaround re-evaluates all sync sript tags on page restore.
107
+
108
+ # Version 0.2.1 - May 27, 2013
109
+
110
+ - Add ability to narrow scope to custom channel for sync_new publishes
111
+
112
+ Example Usage:
113
+
114
+ View:
115
+ ```erb
116
+ <%= sync_new partial: 'todo_list_row', resource: Todo.new, scope: [@project, :staff] %>
117
+ ```
118
+
119
+ Controller/Model:
120
+ ```ruby
121
+ sync_new @todo, scope: [@project, :staff]
122
+ ```
123
+
124
+
125
+ # Version 0.2.0 - May 26, 2013
126
+
127
+ - Add ability to refetch partial from server to render within session context, ref: https://github.com/chrismccord/sync/issues/44
128
+
129
+ This solves the issues of syncing partials across different users when the partial requires the session's context (ie. current_user).
130
+
131
+ Ex:
132
+ View: Add `refetch: true` to sync calls, and place partial file in a 'refetch'
133
+ subdirectory in the model's sync view folder:
134
+
135
+ The partial file would be located in `app/views/sync/todos/refetch/_list_row.html.erb`
136
+ ```erb
137
+ <% @project.todos.ordered.each do |todo| %>
138
+ <%= sync partial: 'list_row', resource: todo, refetch: true %>
139
+ <% end %>
140
+ <%= sync_new partial: 'list_row', resource: Todo.new, scope: @project, refetch: true %>
141
+ ```
142
+
143
+ *Notes*
144
+ 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,
145
+
146
+ A sync update request is triggered on the server for a 'regular' sync'd partial with 100 listening clients:
147
+ - number of http requests 1
148
+ - number of renders 1, pushed out to all 100 clients via pubsub server.
149
+
150
+
151
+ A sync update request is triggered on the server for a 'refetch' sync'd partial with 100 listening clients:
152
+ - number of http requests 100
153
+ - number of renders 100, rendering each request in clients session context.
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Chris McCord
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ The Software shall be used for Good, not Evil.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,521 @@
1
+ # Sync [![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)
2
+
3
+
4
+ > This started as a thought experiment that is growing into a viable option for realtime Rails apps without ditching
5
+ the standard rails stack that we love and are so productive with for a heavy client side MVC framework.
6
+
7
+
8
+ Real-time partials with Rails. Sync lets you render partials for models that, with minimal code,
9
+ update in realtime in the browser when changes occur on the server.
10
+
11
+ #### Watch a screencast to see it in action
12
+ [![See it in action](http://chrismccord.com/images/sync/video_thumb.png)](http://chrismccord.com/blog/2013/04/21/sync-realtime-rails-partials/)
13
+
14
+ In practice, one simply only needs to replace:
15
+
16
+ ```erb
17
+ <%= render partial: 'user_row', locals: {user: @user} %>
18
+ ```
19
+
20
+ with:
21
+
22
+ ```erb
23
+ <%= sync partial: 'user_row', resource: @user %>
24
+ ```
25
+
26
+ Then update views realtime automatically with the `sync` DSL or with a simple `sync_update(@user)` in the controller without any extra javascript or
27
+ configuration.
28
+
29
+ In addition to real-time updates, Sync also provides:
30
+
31
+ - Realtime removal of partials from the DOM when the sync'd model is destroyed in the controller via `sync_destroy(@user)`
32
+ - Realtime appending of newly created model's on scoped channels
33
+ - JavaScript/CoffeeScript hooks to override and extend element updates/appends/removes for partials
34
+ - Support for [Faye](http://faye.jcoglan.com/) and [Pusher](http://pusher.com)
35
+
36
+ ## Requirements
37
+
38
+ - Ruby >= 1.9.3
39
+ - Rails 3 >= 3.1 or Rails 4
40
+ - jQuery >= 1.9
41
+
42
+
43
+ ## Installation
44
+
45
+ #### 1) Add the gem to your `Gemfile`
46
+
47
+ #### Using Faye
48
+
49
+ ```ruby
50
+ gem 'faye'
51
+ gem 'thin', require: false
52
+ gem 'sync'
53
+ ```
54
+
55
+ #### Using Pusher
56
+
57
+ ```ruby
58
+ gem 'pusher'
59
+ gem 'sync'
60
+ ```
61
+
62
+ #### Install
63
+
64
+ ```bash
65
+ $ bundle
66
+ $ rails g sync:install
67
+ ```
68
+
69
+ #### 2) Require sync in your asset javascript manifest `app/assets/javascripts/application.js`:
70
+
71
+ ```javascript
72
+ //= require sync
73
+ ```
74
+
75
+ #### 3) Add sync's configuration script to your application layout `app/views/layouts/application.html.erb`
76
+
77
+ ```erb
78
+ <%= include_sync_config %>
79
+ ```
80
+
81
+ #### 4) Configure your pubsub server (Faye or Pusher)
82
+
83
+
84
+ #### Using [Faye](http://faye.jcoglan.com/) (self hosted)
85
+
86
+ Set your configuration in the generated `config/sync.yml` file, using the Faye adapter. Then run Faye alongside your app.
87
+
88
+ ```bash
89
+ rackup sync.ru -E production
90
+ ```
91
+
92
+ #### Using [Pusher](http://pusher.com) (SaaS)
93
+
94
+ Set your configuration in the generated `config/sync.yml` file, using the Pusher adapter. No extra process/setup.
95
+
96
+ ## Current Caveats
97
+ The current implementation uses a DOM range query (jQuery's `nextUntil`) to match your partial's "element" in
98
+ 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.
99
+ For example, this parent view/sync partial approach would *not* work:
100
+
101
+ Given the sync partial `_todo_row.html.erb`:
102
+
103
+ ```erb
104
+ Title:
105
+ <%= link_to todo.title, todo %>
106
+ ```
107
+
108
+ And the parent view:
109
+
110
+ ```erb
111
+ <table>
112
+ <tbody>
113
+ <tr>
114
+ <%= sync partial: 'todo_row', resource: @todo %>
115
+ </tr>
116
+ </tbody>
117
+ </table>
118
+ ```
119
+
120
+ ##### The markup *would need to change to*:
121
+
122
+
123
+ sync partial `_todo_row.html.erb`:
124
+
125
+ ```erb
126
+ <tr> <!-- root level container for the partial required here -->
127
+ Title:
128
+ <%= link_to todo.title, todo %>
129
+ </tr>
130
+ ```
131
+
132
+ And the parent view changed to:
133
+
134
+ ```erb
135
+ <table>
136
+ <tbody>
137
+ <%= sync partial: 'todo_row', resource: @todo %>
138
+ </tbody>
139
+ </table>
140
+ ```
141
+
142
+ I'm currently investigating true DOM ranges via the [Range](https://developer.mozilla.org/en-US/docs/DOM/range) object.
143
+
144
+
145
+ ## 'Automatic' syncing through the sync model DSL
146
+
147
+ In addition to calling explicit sync actions within controller methods, a
148
+ `sync` and `enable_sync` DSL has been added to ActionController::Base and ActiveRecord::Base to automate the syncing
149
+ approach in a controlled, threadsafe way.
150
+
151
+ ### Example Model/Controller
152
+ ```ruby
153
+ class Todo < ActiveRecord::Base
154
+ sync :all
155
+ end
156
+ ```
157
+ ```ruby
158
+ class TodosController < ApplicationController
159
+ enable_sync only: [:create, :update, :destroy]
160
+ ...
161
+ end
162
+ ```
163
+
164
+ Now, 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.
165
+
166
+ ### Updating multiple sets of records with sync scopes
167
+
168
+ Sometimes you might want to display multiple differently scoped todo lists throughout your application and keep them all in sync. For example:
169
+
170
+ - A global list with all todos
171
+ - A list with all completed todos
172
+ - A list with all todos of a user
173
+ - A list with all todos of a project
174
+ - ...
175
+
176
+ This 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:
177
+
178
+ ```ruby
179
+ class Todo < ActiveRecord::Base
180
+ belongs_to :user
181
+ belongs_to :project
182
+
183
+ sync :all
184
+
185
+ sync_scope :active, -> { where(completed: false) }
186
+ sync_scope :completed, -> { where(completed: true) }
187
+ end
188
+ ```
189
+
190
+ Then in your views display the different sets of todos by passing the `scope` as a parameter like this:
191
+
192
+ ```erb
193
+ <%= sync partial: "todo", collection: Todo.active %>
194
+ <%= sync_new partial: "todo", resource: Todo.new, scope: Todo.active %>
195
+
196
+ <%= sync partial: "todo", collection: Todo.completed %>
197
+ <%= sync_new partial: "todo", resource: Todo.new, scope: Todo.completed %>
198
+ ```
199
+
200
+ Now, 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.
201
+
202
+ #### Advanced scoping with parameters
203
+
204
+ In 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:
205
+
206
+ ```ruby
207
+ sync_scope :by_user, ->(user) { where(user_id: user.id) }
208
+ sync_scope :by_project, ->(project) { where(project_id: project.id) }
209
+ ```
210
+
211
+ Note 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.
212
+
213
+ Setup the rendering of the partials in the views with:
214
+
215
+ ```erb
216
+ <%= sync partial: "todo", collection: Todo.by_user(current_user) %>
217
+ <%= sync_new partial: "todo", resource: Todo.new, scope: Todo.by_user(current_user) %>
218
+
219
+ <%= sync partial: "todo", collection: Todo.by_project(@project) %>
220
+ <%= sync_new partial: "todo", resource: Todo.new, scope: Todo.by_project(@project) %>
221
+ ```
222
+
223
+ Beware that chaining of sync scopes in the view is currently not supported. So the following example would not work as expected:
224
+
225
+ ```erb
226
+ <%= sync_new partial: "todo", Todo.new, scope: Todo.by_user(current_user).completed %>
227
+ ```
228
+
229
+ To work around this just create an explicit sync_scope for your use case:
230
+
231
+ ```ruby
232
+ sync_scope :completed_by_user, ->(user) { completed.by_user(current_user) }
233
+ ```
234
+
235
+ ```erb
236
+ <%= sync_new partial: "todo", Todo.new, scope: Todo.completed_by_user(current_user) %>
237
+ ```
238
+
239
+ #### Things to keep in mind when using `sync_scope`
240
+
241
+ Please 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.
242
+
243
+ #### Automatic updating of parent associations
244
+
245
+ If 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:
246
+
247
+ ```ruby
248
+ sync_touch :project, :user
249
+ ```
250
+
251
+ ### Syncing outside of the controller
252
+
253
+ `Sync::Actions` can be included into any object wishing to perform sync
254
+ publishes for a given resource. Instead of using the controller as
255
+ context for rendering, a Sync::Renderer instance is used. Since the Renderer
256
+ is not part of the request/response/session, it has no knowledge of the
257
+ current session (ie. current_user), so syncing from outside the controller
258
+ context will require some care that the partial can be rendered within a
259
+ sessionless context.
260
+
261
+ ### Example Syncing from a background worker or rails console
262
+ ```ruby
263
+ class MyJob
264
+ include Sync::Actions
265
+
266
+ def perform
267
+ Sync::Model.enable do
268
+ Todo.first.update title: "This todo will be sync'd on save"
269
+ end
270
+ Todo.first.update title: "This todo will NOT be sync'd on save"
271
+
272
+ Sync::Model.enable!
273
+ Todo.first.update title: "This todo will be sync'd on save"
274
+ Todo.first.update title: "This todo will be sync'd on save"
275
+ Todo.first.update title: "This todo will be sync'd on save"
276
+ Sync::Model.disable!
277
+ Todo.first.update title: "This todo will NOT be sync'd on save"
278
+ end
279
+ end
280
+ ```
281
+
282
+ ## Custom Sync Views and javascript hooks
283
+
284
+ 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:
285
+
286
+ 1. The camelized version of the concatenated snake case resource
287
+ and partial names.
288
+ 2. The camelized version of the snake cased partial name.
289
+
290
+ #### Examples
291
+
292
+ partial name 'list_row', resource name 'todo', order of lookup:
293
+
294
+ 1. Sync.TodoListRow
295
+ 2. Sync.ListRow
296
+ 3. Sync.View (Default fallback)
297
+
298
+
299
+ 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:
300
+
301
+ ```coffeescript
302
+ class Sync.TodoListRow extends Sync.View
303
+
304
+ beforeInsert: ($el) ->
305
+ $el.hide()
306
+ @insert($el)
307
+
308
+ afterInsert: -> @$el.fadeIn 'slow'
309
+
310
+ beforeRemove: -> @$el.fadeOut 'slow', => @remove()
311
+
312
+ ```
313
+
314
+ ## Narrowing sync_new scope
315
+
316
+ 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.
317
+
318
+ 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:
319
+
320
+ View:
321
+ ```erb
322
+ <%= sync_new partial: 'todo_list_row', resource: Todo.new, scope: I18n.locale %>
323
+ ```
324
+
325
+ Controller/Model:
326
+ ```ruby
327
+ sync_new @todo, scope: @todo.locale
328
+ ```
329
+
330
+ 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:
331
+
332
+ View:
333
+ ```erb
334
+ <%= sync_new partial: 'todo_list_row', resource: Todo.new, scope: @project %>
335
+ ```
336
+
337
+ Controller/Model:
338
+ ```ruby
339
+ sync_new @todo, scope: @project
340
+ ```
341
+
342
+ 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.
343
+
344
+ ## Scoping by Partial
345
+
346
+ If 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:
347
+
348
+ ```rb
349
+ def UsersController < ApplicationController
350
+
351
+ def create
352
+
353
+ if @user.save
354
+ sync_new @user, partial: 'users_count'
355
+ end
356
+
357
+ end
358
+ end
359
+ ```
360
+
361
+ In the above example, only the `sync/users/users_count` partial will be rendered and pushed to subscribed clients.
362
+
363
+ ## Refetching Partials
364
+
365
+ Refetching allows syncing partials across different users when the partial requires the session's context (ie. current_user).
366
+
367
+ Ex:
368
+ View: Add `refetch: true` to sync calls, and place partial file in a 'refetch'
369
+ subdirectory in the model's sync view folder:
370
+
371
+ The partial file would be located in `app/views/sync/todos/refetch/_list_row.html.erb`
372
+ ```erb
373
+ <% @project.todos.ordered.each do |todo| %>
374
+ <%= sync partial: 'list_row', resource: todo, refetch: true %>
375
+ <% end %>
376
+ <%= sync_new partial: 'list_row', resource: Todo.new, scope: @project, refetch: true %>
377
+ ```
378
+
379
+ *Notes*
380
+
381
+ 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,
382
+
383
+ A sync update request is triggered on the server for a 'regular' sync'd partial with 100 listening clients:
384
+ - number of http requests 1
385
+ - number of renders 1, pushed out to all 100 clients via pubsub server.
386
+
387
+
388
+ A sync update request is triggered on the server for a 'refetch' sync'd partial with 100 listening clients:
389
+ - number of http requests 100
390
+ - number of renders 100, rendering each request in clients session context.
391
+
392
+ ## Using with cache_digests (Russian doll caching)
393
+
394
+ Sync has a custom `DependencyTracker::ERBTracker` that can handle `sync` render calls.
395
+ Because the full partial name is not included, it has to guess the location of
396
+ your partial based on the name of the `resource` or `collection` passed to it.
397
+ See the tests to see how it works. If it doesn't work for you, you can always
398
+ use the [explicit "Template Dependency"
399
+ markers](https://github.com/rails/cache_digests).
400
+
401
+ To enable, add to `config/initializers/cache_digests.rb`:
402
+
403
+ #### Rails 4
404
+
405
+ ```ruby
406
+ require 'action_view/dependency_tracker'
407
+
408
+ ActionView::DependencyTracker.register_tracker :haml, Sync::ERBTracker
409
+ ActionView::DependencyTracker.register_tracker :erb, Sync::ERBTracker
410
+ ```
411
+
412
+ #### Rails 3 with [cache_digests](https://github.com/rails/cache_digests) gem
413
+
414
+ ```ruby
415
+ require 'cache_digests/dependency_tracker'
416
+
417
+ CacheDigests::DependencyTracker.register_tracker :haml, Sync::ERBTracker
418
+ CacheDigests::DependencyTracker.register_tracker :erb, Sync::ERBTracker
419
+ ```
420
+
421
+ **Note:** haml support is limited, but it seems to work in most cases.
422
+
423
+
424
+ ## Serving Faye over HTTPS (with Thin)
425
+
426
+ Create a thin configuration file `config/sync_thin.yml` similar to the following:
427
+
428
+ ```yaml
429
+ ---
430
+ port: 4443
431
+ ssl: true
432
+ ssl_key_file: /path/to/server.pem
433
+ ssl_cert_file: /path/to/certificate_chain.pem
434
+ environment: production
435
+ rackup: sync.ru
436
+ ```
437
+
438
+ 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.
439
+
440
+ 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.
441
+
442
+ Finally start up Thin from the project root.
443
+
444
+ ```
445
+ thin -C config/sync_thin.yml start
446
+ ```
447
+
448
+
449
+ ## Brief Example or [checkout an example application](https://github.com/chrismccord/sync_example)
450
+
451
+ View `sync/users/_user_list_row.html.erb`
452
+
453
+ ```erb
454
+ <tr>
455
+ <td><%= link_to user.name, user %></td>
456
+ <td><%= link_to 'Edit', edit_user_path(user) %></td>
457
+ <td><%= link_to 'Destroy', user, method: :delete, remote: true, data: { confirm: 'Are you sure?' } %></td>
458
+ </tr>
459
+ ```
460
+
461
+ View `users/index.html.erb`
462
+
463
+ ```erb
464
+ <h1>Some Users</h1>
465
+ <table>
466
+ <tbody>
467
+ <%= sync partial: 'user_list_row', collection: @users %>
468
+ <%= sync_new partial: 'user_list_row', resource: User.new, direction: :append %>
469
+ </tbody>
470
+ </table>
471
+ ```
472
+
473
+
474
+ Controller
475
+
476
+ ```ruby
477
+ def UsersController < ApplicationController
478
+
479
+ def create
480
+ @user = User.new(user_params)
481
+ if @user.save
482
+ sync_new @user
483
+ end
484
+ respond_to do |format|
485
+ format.html { redirect_to users_url }
486
+ format.json { head :no_content }
487
+ end
488
+ end
489
+
490
+ def update
491
+ @user = User.find(params[:id])
492
+ if user.save
493
+
494
+ end
495
+
496
+ # Sync updates to any partials listening for this user
497
+ sync_update @user
498
+
499
+ redirect_to users_path, notice: "Saved!"
500
+ end
501
+
502
+ def destroy
503
+ @user = User.find(params[:id])
504
+ @user.destroy
505
+
506
+ # Sync destroy, telling client to remove all dom elements containing this user
507
+ sync_destroy @user
508
+
509
+ respond_to do |format|
510
+ format.html { redirect_to users_url }
511
+ format.json { head :no_content }
512
+ end
513
+ end
514
+ end
515
+ ```
516
+
517
+ ## Google detecting not found errors
518
+
519
+ If 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.
520
+ This is because Google now attempts to discover URLs in JavaScript and some JavaScript we generate looks a little like a URL to Google.
521
+ You can [safely ignore](https://support.google.com/webmasters/answer/2409439?ctx=MCE&ctx=NF) this problem.