live_record 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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