live_record 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -2
  3. data/{lib/live_record/.rspec → .rspec} +0 -0
  4. data/.travis.yml +0 -1
  5. data/Gemfile +3 -1
  6. data/README.md +363 -202
  7. data/Rakefile +5 -0
  8. data/app/assets/javascripts/live_record.coffee +4 -0
  9. data/app/assets/javascripts/live_record/helpers.coffee +4 -0
  10. data/app/assets/javascripts/live_record/helpers/load_records.coffee +13 -7
  11. data/app/assets/javascripts/live_record/helpers/spaceship.coffee +13 -0
  12. data/app/assets/javascripts/live_record/model.coffee +4 -0
  13. data/app/assets/javascripts/live_record/model/create.coffee +96 -27
  14. data/app/assets/javascripts/live_record/plugins.coffee +3 -0
  15. data/app/assets/javascripts/live_record/plugins/live_dom.coffee +5 -19
  16. data/app/assets/javascripts/live_record/plugins/live_dom/apply_to_model.coffee +17 -0
  17. data/app/channels/live_record/base_channel.rb +41 -0
  18. data/app/channels/live_record/changes_channel.rb +59 -0
  19. data/app/channels/live_record/publications_channel.rb +134 -0
  20. data/{lib/live_record/config.ru → config.ru} +0 -0
  21. data/lib/live_record.rb +2 -0
  22. data/lib/live_record/action_view_extensions/view_helper.rb +22 -0
  23. data/lib/live_record/configure.rb +19 -0
  24. data/lib/live_record/generators/install_generator.rb +9 -4
  25. data/lib/live_record/generators/templates/create_live_record_updates.rb +1 -1
  26. data/lib/live_record/generators/templates/index.html.erb +1 -0
  27. data/lib/live_record/generators/templates/model.rb.rb +4 -4
  28. data/lib/live_record/model/callbacks.rb +8 -2
  29. data/lib/live_record/version.rb +1 -1
  30. data/live_record.gemspec +2 -2
  31. data/{lib/live_record/spec → spec}/factories/posts.rb +0 -0
  32. data/spec/features/live_record_syncing_spec.rb +184 -0
  33. data/spec/helpers/wait.rb +19 -0
  34. data/{lib/live_record/spec → spec}/internal/app/assets/config/manifest.js +0 -0
  35. data/{lib/live_record/spec → spec}/internal/app/assets/javascripts/application.js +0 -0
  36. data/{lib/live_record/spec → spec}/internal/app/assets/javascripts/cable.js +0 -0
  37. data/spec/internal/app/assets/javascripts/posts.coffee +8 -0
  38. data/{lib/live_record/spec → spec}/internal/app/channels/application_cable/channel.rb +0 -0
  39. data/{lib/live_record/spec → spec}/internal/app/channels/application_cable/connection.rb +4 -4
  40. data/{lib/live_record/spec → spec}/internal/app/controllers/application_controller.rb +0 -0
  41. data/{lib/live_record/spec → spec}/internal/app/controllers/posts_controller.rb +1 -0
  42. data/{lib/live_record/spec → spec}/internal/app/models/application_record.rb +0 -0
  43. data/{lib/live_record/spec → spec}/internal/app/models/live_record_update.rb +0 -0
  44. data/spec/internal/app/models/post.rb +11 -0
  45. data/{lib/live_record/spec → spec}/internal/app/views/layouts/application.html.erb +0 -0
  46. data/{lib/live_record/spec → spec}/internal/app/views/posts/_form.html.erb +0 -0
  47. data/{lib/live_record/spec → spec}/internal/app/views/posts/_post.json.jbuilder +0 -0
  48. data/{lib/live_record/spec → spec}/internal/app/views/posts/edit.html.erb +0 -0
  49. data/{lib/live_record/spec → spec}/internal/app/views/posts/index.html.erb +6 -5
  50. data/{lib/live_record/spec → spec}/internal/app/views/posts/index.json.jbuilder +0 -0
  51. data/{lib/live_record/spec → spec}/internal/app/views/posts/new.html.erb +0 -0
  52. data/{lib/live_record/spec → spec}/internal/app/views/posts/show.html.erb +0 -0
  53. data/{lib/live_record/spec → spec}/internal/app/views/posts/show.json.jbuilder +0 -0
  54. data/{lib/live_record/spec → spec}/internal/config/cable.yml +0 -0
  55. data/{lib/live_record/spec → spec}/internal/config/database.yml +0 -0
  56. data/{lib/live_record/spec → spec}/internal/config/routes.rb +0 -0
  57. data/{lib/live_record/spec → spec}/internal/db/schema.rb +2 -0
  58. data/{lib/live_record/spec → spec}/internal/public/favicon.ico +0 -0
  59. data/{lib/live_record/spec → spec}/rails_helper.rb +4 -2
  60. data/{lib/live_record/spec → spec}/spec_helper.rb +0 -0
  61. metadata +64 -56
  62. data/app/assets/javascripts/live_record.js +0 -4
  63. data/app/assets/javascripts/live_record/helpers.js +0 -4
  64. data/app/assets/javascripts/live_record/model.js +0 -4
  65. data/app/assets/javascripts/live_record/plugins.js +0 -3
  66. data/lib/live_record/channel/implement.rb +0 -100
  67. data/lib/live_record/spec/features/live_record_syncing_spec.rb +0 -60
  68. data/lib/live_record/spec/internal/app/assets/javascripts/posts.coffee +0 -14
  69. data/lib/live_record/spec/internal/app/channels/live_record_channel.rb +0 -4
  70. data/lib/live_record/spec/internal/app/models/post.rb +0 -11
@@ -1,5 +1,6 @@
1
1
  <script>
2
- LiveRecord.helpers.loadRecords({modelName: 'Post'})
2
+ LiveRecord.helpers.loadRecords({ modelName: 'Post' });
3
+ LiveRecord.Model.all.Post.subscribe({ where: { is_enabled_eq: true, content_eq: 'somecontent' }});
3
4
  </script>
4
5
  <p id="notice"><%= notice %></p>
5
6
 
@@ -16,9 +17,9 @@
16
17
 
17
18
  <tbody>
18
19
  <% @posts.each do |post| %>
19
- <tr data-live-record-destroy-from='Post-<%= post.id %>'>
20
- <td data-live-record-update-from='Post-<%= post.id %>-title'><%= post.title %></td>
21
- <td data-live-record-update-from='Post-<%= post.id %>-content'><%= post.content %></td>
20
+ <tr <%= live_record_destroyable(post) %>>
21
+ <td <%= live_record_updatable(post, :title) %>><%= post.title %></td>
22
+ <td <%= live_record_updatable(post, :content) %>><%= post.content %></td>
22
23
  <td><%= link_to 'Show', post %></td>
23
24
  <td><%= link_to 'Edit', edit_post_path(post) %></td>
24
25
  <td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
@@ -29,4 +30,4 @@
29
30
 
30
31
  <br>
31
32
 
32
- <%= link_to 'New Post', new_post_path %>
33
+ <%= link_to 'New Post', new_post_path %>
@@ -10,7 +10,9 @@ ActiveRecord::Schema.define do
10
10
  create_table "posts", force: :cascade do |t|
11
11
  t.string "title"
12
12
  t.text "content"
13
+ t.boolean "is_enabled"
13
14
  t.datetime "created_at", null: false
14
15
  t.datetime "updated_at", null: false
16
+ t.index ["is_enabled"], name: "index_posts_on_is_enabled"
15
17
  end
16
18
  end
@@ -1,14 +1,16 @@
1
1
  ENV['RAILS_ENV'] ||= 'test'
2
2
  require 'spec_helper'
3
+ Dir[__dir__ + '/helpers/*.rb'].each {|file| require file }
3
4
 
4
5
  ActiveRecord::Migration.maintain_test_schema!
5
6
 
6
- Capybara.javascript_driver = :selenium_chrome_headless
7
+ Capybara.javascript_driver = :selenium_chrome_headless # :selenium_chrome
7
8
  Capybara.server = :puma
8
9
 
9
10
  RSpec.configure do |config|
10
11
  config.include FactoryGirl::Syntax::Methods
11
12
  config.include Capybara::DSL
13
+ config.include SpecHelpers::Wait
12
14
 
13
15
  config.use_transactional_fixtures = true
14
16
  config.infer_spec_type_from_file_location!
@@ -21,7 +23,7 @@ RSpec.configure do |config|
21
23
  DatabaseCleaner.clean
22
24
  DatabaseCleaner.strategy = :deletion
23
25
  end
24
-
26
+
25
27
  config.before(:each) do
26
28
  DatabaseCleaner.start
27
29
  DatabaseCleaner.clean
File without changes
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: live_record
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jules Roman B. Polidario
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-08-18 00:00:00.000000000 Z
11
+ date: 2017-09-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -30,6 +30,20 @@ dependencies:
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
32
  version: '5.2'
33
+ - !ruby/object:Gem::Dependency
34
+ name: rails
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '5.1'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '5.1'
33
47
  - !ruby/object:Gem::Dependency
34
48
  name: bundler
35
49
  requirement: !ruby/object:Gem::Requirement
@@ -184,20 +198,6 @@ dependencies:
184
198
  - - "~>"
185
199
  - !ruby/object:Gem::Version
186
200
  version: '2.7'
187
- - !ruby/object:Gem::Dependency
188
- name: capybara
189
- requirement: !ruby/object:Gem::Requirement
190
- requirements:
191
- - - "~>"
192
- - !ruby/object:Gem::Version
193
- version: '2.15'
194
- type: :development
195
- prerelease: false
196
- version_requirements: !ruby/object:Gem::Requirement
197
- requirements:
198
- - - "~>"
199
- - !ruby/object:Gem::Version
200
- version: '2.15'
201
201
  - !ruby/object:Gem::Dependency
202
202
  name: chromedriver-helper
203
203
  requirement: !ruby/object:Gem::Requirement
@@ -255,30 +255,38 @@ dependencies:
255
255
  - !ruby/object:Gem::Version
256
256
  version: '1.6'
257
257
  description: |-
258
- Auto-syncs records in client-side JS (through a Model DSL) from changes in the backend Rails server through ActionCable.
259
- Auto-updates DOM elements mapped to a record attribute, from changes.
260
- Automatically resyncs after client-side reconnection.
258
+ Auto-syncs records in client-side JS (through a Model DSL) from changes (updates/destroy) in the backend Rails server through ActionCable.
259
+ Also supports streaming newly created records to client-side JS.
260
+ Supports lost connection restreaming for both new records (create), and record-changes (updates/destroy).
261
+ Auto-updates DOM elements mapped to a record attribute, from changes (updates/destroy).
261
262
  email: jrpolidario@gmail.com
262
263
  executables: []
263
264
  extensions: []
264
265
  extra_rdoc_files: []
265
266
  files:
266
267
  - ".gitignore"
268
+ - ".rspec"
267
269
  - ".travis.yml"
268
270
  - Gemfile
269
271
  - README.md
270
- - app/assets/javascripts/live_record.js
271
- - app/assets/javascripts/live_record/helpers.js
272
+ - Rakefile
273
+ - app/assets/javascripts/live_record.coffee
274
+ - app/assets/javascripts/live_record/helpers.coffee
272
275
  - app/assets/javascripts/live_record/helpers/load_records.coffee
273
- - app/assets/javascripts/live_record/model.js
276
+ - app/assets/javascripts/live_record/helpers/spaceship.coffee
277
+ - app/assets/javascripts/live_record/model.coffee
274
278
  - app/assets/javascripts/live_record/model/all.coffee
275
279
  - app/assets/javascripts/live_record/model/create.coffee
276
- - app/assets/javascripts/live_record/plugins.js
280
+ - app/assets/javascripts/live_record/plugins.coffee
277
281
  - app/assets/javascripts/live_record/plugins/live_dom.coffee
282
+ - app/assets/javascripts/live_record/plugins/live_dom/apply_to_model.coffee
283
+ - app/channels/live_record/base_channel.rb
284
+ - app/channels/live_record/changes_channel.rb
285
+ - app/channels/live_record/publications_channel.rb
286
+ - config.ru
278
287
  - lib/live_record.rb
279
- - lib/live_record/.rspec
280
- - lib/live_record/channel/implement.rb
281
- - lib/live_record/config.ru
288
+ - lib/live_record/action_view_extensions/view_helper.rb
289
+ - lib/live_record/configure.rb
282
290
  - lib/live_record/engine.rb
283
291
  - lib/live_record/generators/install_generator.rb
284
292
  - lib/live_record/generators/templates/create_live_record_updates.rb
@@ -290,38 +298,38 @@ files:
290
298
  - lib/live_record/generators/templates/model.rb.rb
291
299
  - lib/live_record/generators/templates/show.html.erb
292
300
  - lib/live_record/model/callbacks.rb
293
- - lib/live_record/spec/factories/posts.rb
294
- - lib/live_record/spec/features/live_record_syncing_spec.rb
295
- - lib/live_record/spec/internal/app/assets/config/manifest.js
296
- - lib/live_record/spec/internal/app/assets/javascripts/application.js
297
- - lib/live_record/spec/internal/app/assets/javascripts/cable.js
298
- - lib/live_record/spec/internal/app/assets/javascripts/posts.coffee
299
- - lib/live_record/spec/internal/app/channels/application_cable/channel.rb
300
- - lib/live_record/spec/internal/app/channels/application_cable/connection.rb
301
- - lib/live_record/spec/internal/app/channels/live_record_channel.rb
302
- - lib/live_record/spec/internal/app/controllers/application_controller.rb
303
- - lib/live_record/spec/internal/app/controllers/posts_controller.rb
304
- - lib/live_record/spec/internal/app/models/application_record.rb
305
- - lib/live_record/spec/internal/app/models/live_record_update.rb
306
- - lib/live_record/spec/internal/app/models/post.rb
307
- - lib/live_record/spec/internal/app/views/layouts/application.html.erb
308
- - lib/live_record/spec/internal/app/views/posts/_form.html.erb
309
- - lib/live_record/spec/internal/app/views/posts/_post.json.jbuilder
310
- - lib/live_record/spec/internal/app/views/posts/edit.html.erb
311
- - lib/live_record/spec/internal/app/views/posts/index.html.erb
312
- - lib/live_record/spec/internal/app/views/posts/index.json.jbuilder
313
- - lib/live_record/spec/internal/app/views/posts/new.html.erb
314
- - lib/live_record/spec/internal/app/views/posts/show.html.erb
315
- - lib/live_record/spec/internal/app/views/posts/show.json.jbuilder
316
- - lib/live_record/spec/internal/config/cable.yml
317
- - lib/live_record/spec/internal/config/database.yml
318
- - lib/live_record/spec/internal/config/routes.rb
319
- - lib/live_record/spec/internal/db/schema.rb
320
- - lib/live_record/spec/internal/public/favicon.ico
321
- - lib/live_record/spec/rails_helper.rb
322
- - lib/live_record/spec/spec_helper.rb
323
301
  - lib/live_record/version.rb
324
302
  - live_record.gemspec
303
+ - spec/factories/posts.rb
304
+ - spec/features/live_record_syncing_spec.rb
305
+ - spec/helpers/wait.rb
306
+ - spec/internal/app/assets/config/manifest.js
307
+ - spec/internal/app/assets/javascripts/application.js
308
+ - spec/internal/app/assets/javascripts/cable.js
309
+ - spec/internal/app/assets/javascripts/posts.coffee
310
+ - spec/internal/app/channels/application_cable/channel.rb
311
+ - spec/internal/app/channels/application_cable/connection.rb
312
+ - spec/internal/app/controllers/application_controller.rb
313
+ - spec/internal/app/controllers/posts_controller.rb
314
+ - spec/internal/app/models/application_record.rb
315
+ - spec/internal/app/models/live_record_update.rb
316
+ - spec/internal/app/models/post.rb
317
+ - spec/internal/app/views/layouts/application.html.erb
318
+ - spec/internal/app/views/posts/_form.html.erb
319
+ - spec/internal/app/views/posts/_post.json.jbuilder
320
+ - spec/internal/app/views/posts/edit.html.erb
321
+ - spec/internal/app/views/posts/index.html.erb
322
+ - spec/internal/app/views/posts/index.json.jbuilder
323
+ - spec/internal/app/views/posts/new.html.erb
324
+ - spec/internal/app/views/posts/show.html.erb
325
+ - spec/internal/app/views/posts/show.json.jbuilder
326
+ - spec/internal/config/cable.yml
327
+ - spec/internal/config/database.yml
328
+ - spec/internal/config/routes.rb
329
+ - spec/internal/db/schema.rb
330
+ - spec/internal/public/favicon.ico
331
+ - spec/rails_helper.rb
332
+ - spec/spec_helper.rb
325
333
  homepage: https://github.com/jrpolidario/live_record
326
334
  licenses:
327
335
  - MIT
@@ -1,4 +0,0 @@
1
- //= require_self
2
- //= require_directory ./live_record/
3
-
4
- this.LiveRecord || (this.LiveRecord = {});
@@ -1,4 +0,0 @@
1
- //= require_self
2
- //= require_directory ./helpers/
3
-
4
- this.LiveRecord.helpers || (this.LiveRecord.helpers = {});
@@ -1,4 +0,0 @@
1
- //= require_self
2
- //= require_directory ./model/
3
-
4
- this.LiveRecord.Model || (this.LiveRecord.Model = {});
@@ -1,3 +0,0 @@
1
- //= require_self
2
-
3
- this.LiveRecord.plugins || (this.LiveRecord.plugins = {});
@@ -1,100 +0,0 @@
1
- module LiveRecord
2
- module Channel
3
- module Implement
4
- def subscribed
5
- find_record_from_params(params) do |record|
6
- authorised_attributes = authorised_attributes(record, current_user)
7
-
8
- if authorised_attributes.present?
9
- stream_for record, coder: ActiveSupport::JSON do |message|
10
- begin
11
- record.reload
12
- rescue ActiveRecord::RecordNotFound
13
- end
14
-
15
- authorised_attributes = authorised_attributes(record, current_user)
16
-
17
- if authorised_attributes.present?
18
- response = filtered_message(message, authorised_attributes)
19
- transmit response if response.present?
20
- else
21
- respond_with_error(:forbidden)
22
- reject_subscription
23
- end
24
- end
25
- else
26
- respond_with_error(:forbidden)
27
- reject
28
- end
29
- end
30
- end
31
-
32
- def sync_record(data)
33
- find_record_from_params(data.symbolize_keys) do |record|
34
- authorised_attributes = authorised_attributes(record, current_user)
35
-
36
- if authorised_attributes.present?
37
- live_record_update = LiveRecordUpdate.where(
38
- recordable_type: record.class.name,
39
- recordable_id: record.id
40
- ).where(
41
- 'created_at >= ?', DateTime.parse(data['stale_since']) - 1.minute
42
- ).order(id: :asc)
43
-
44
- if live_record_update.exists?
45
- message = { 'action' => 'update', 'attributes' => record.attributes }
46
- response = filtered_message(message, authorised_attributes)
47
- transmit response if response.present?
48
- end
49
- else
50
- respond_with_error(:forbidden)
51
- reject_subscription
52
- end
53
- end
54
- end
55
-
56
- def unsubscribed
57
- # Any cleanup needed when channel is unsubscribed
58
- end
59
-
60
- private
61
-
62
- def authorised_attributes(record, current_user)
63
- return record.class.live_record_whitelisted_attributes(record, current_user).map(&:to_s)
64
- end
65
-
66
- def filtered_message(message, filters)
67
- if message['attributes'].present?
68
- message['attributes'].slice!(*filters)
69
- end
70
- message
71
- end
72
-
73
- def find_record_from_params(params)
74
- model_class = params[:model_name].safe_constantize
75
-
76
- if model_class && model_class < ApplicationRecord
77
- record = model_class.find_by(id: params[:record_id])
78
-
79
- if record.present?
80
- yield record
81
- else
82
- transmit 'action' => 'destroy'
83
- end
84
- else
85
- respond_with_error(:bad_request, 'Not a correct model name')
86
- reject_subscription
87
- end
88
- end
89
-
90
- def respond_with_error(type, message = nil)
91
- case type
92
- when :forbidden
93
- transmit error: { 'code' => 'forbidden', 'message' => (message || 'You are not authorised') }
94
- when :bad_request
95
- transmit error: { 'code' => 'bad_request', 'message' => (message || 'Invalid request parameters') }
96
- end
97
- end
98
- end
99
- end
100
- end
@@ -1,60 +0,0 @@
1
- require 'rails_helper'
2
-
3
- RSpec.feature 'LiveRecord Syncing', type: :feature do
4
- let(:post1) { create(:post) }
5
- let(:post2) { create(:post) }
6
- let(:post3) { create(:post) }
7
- let!(:posts) { [post1, post2, post3] }
8
-
9
- scenario 'User sees live changes (updates) of post records', js: true do
10
- visit '/posts'
11
-
12
- post1_title_td = find('td', text: post1.title, wait: 10)
13
- post2_title_td = find('td', text: post2.title, wait: 10)
14
- post3_title_td = find('td', text: post3.title, wait: 10)
15
-
16
- post1.update!(title: 'post1newtitle')
17
- post2.update!(title: 'post2newtitle')
18
-
19
- expect(post1_title_td).to have_content('post1newtitle', wait: 10)
20
- expect(post2_title_td).to have_content('post2newtitle', wait: 10)
21
- expect(post3_title_td).to have_content(post3.title, wait: 10)
22
- end
23
-
24
- scenario 'User sees live changes (destroy) post records', js: true do
25
- visit '/posts'
26
-
27
- expect{find('td', text: post1.title, wait: 10)}.to_not raise_error
28
- expect{find('td', text: post2.title, wait: 10)}.to_not raise_error
29
- expect{find('td', text: post3.title, wait: 10)}.to_not raise_error
30
-
31
- post1.destroy
32
- post2.destroy
33
-
34
- expect{find('td', text: post1.title)}.to raise_error Capybara::ElementNotFound
35
- expect{find('td', text: post2.title)}.to raise_error Capybara::ElementNotFound
36
- expect{find('td', text: post3.title)}.to_not raise_error
37
- end
38
-
39
- scenario 'User sees live changes (updates) of post records, but only changes from whitelisted authorised attributes', js: true do
40
- visit '/posts'
41
-
42
- post1_title_td = find('td', text: post1.title, wait: 10)
43
- post1_content_td = find('td', text: post1.content, wait: 10)
44
- post2_title_td = find('td', text: post2.title, wait: 10)
45
- post2_content_td = find('td', text: post2.content, wait: 10)
46
- post3_title_td = find('td', text: post3.title, wait: 10)
47
- post3_content_td = find('td', text: post3.content, wait: 10)
48
-
49
- post1.update!(title: 'post1newtitle', content: 'post1newcontent')
50
- post2.update!(title: 'post2newtitle', content: 'post2newcontent')
51
- post3.update!(title: 'post3newtitle', content: 'post3newcontent')
52
-
53
- expect(post1_title_td).to have_content('post1newtitle', wait: 10)
54
- expect(post1_content_td).to_not have_content('post1newcontent')
55
- expect(post2_title_td).to have_content('post2newtitle', wait: 10)
56
- expect(post2_content_td).to_not have_content('post2newcontent')
57
- expect(post3_title_td).to have_content('post3newtitle', wait: 10)
58
- expect(post3_content_td).to_not have_content('post3newcontent')
59
- end
60
- end