live_record 0.0.4 → 0.1.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.
- checksums.yaml +4 -4
- data/.gitignore +6 -5
- data/.travis.yml +12 -0
- data/Gemfile +5 -1
- data/Gemfile.lock +199 -1
- data/README.md +38 -9
- data/app/assets/javascripts/live_record/plugins/live_dom.coffee +2 -3
- data/lib/live_record.rb +3 -0
- data/lib/live_record/.rspec +3 -0
- data/lib/live_record/{channel.rb → channel/implement.rb} +1 -4
- data/lib/live_record/config.ru +10 -0
- data/lib/live_record/generators/install_generator.rb +0 -6
- data/lib/live_record/generators/templates/live_record_channel.rb +1 -1
- data/lib/live_record/generators/templates/model.rb.rb +3 -0
- data/lib/live_record/model/callbacks.rb +36 -0
- data/lib/live_record/spec/factories/posts.rb +6 -0
- data/lib/live_record/spec/features/live_record_syncing_spec.rb +60 -0
- data/lib/live_record/spec/internal/app/assets/config/manifest.js +2 -0
- data/lib/live_record/spec/internal/app/assets/javascripts/application.js +17 -0
- data/lib/live_record/spec/internal/app/assets/javascripts/cable.js +12 -0
- data/lib/live_record/spec/internal/app/assets/javascripts/posts.coffee +14 -0
- data/lib/live_record/spec/internal/app/channels/application_cable/channel.rb +4 -0
- data/lib/live_record/spec/internal/app/channels/application_cable/connection.rb +8 -0
- data/lib/live_record/spec/internal/app/channels/live_record_channel.rb +4 -0
- data/lib/live_record/spec/internal/app/controllers/application_controller.rb +3 -0
- data/lib/live_record/spec/internal/app/controllers/posts_controller.rb +74 -0
- data/lib/live_record/spec/internal/app/models/application_record.rb +3 -0
- data/lib/live_record/spec/internal/app/models/live_record_update.rb +3 -0
- data/lib/live_record/spec/internal/app/models/post.rb +11 -0
- data/lib/live_record/spec/internal/app/views/layouts/application.html.erb +13 -0
- data/lib/live_record/spec/internal/app/views/posts/_form.html.erb +27 -0
- data/lib/live_record/spec/internal/app/views/posts/_post.json.jbuilder +2 -0
- data/lib/live_record/spec/internal/app/views/posts/edit.html.erb +6 -0
- data/lib/live_record/spec/internal/app/views/posts/index.html.erb +32 -0
- data/lib/live_record/spec/internal/app/views/posts/index.json.jbuilder +1 -0
- data/lib/live_record/spec/internal/app/views/posts/new.html.erb +5 -0
- data/lib/live_record/spec/internal/app/views/posts/show.html.erb +21 -0
- data/lib/live_record/spec/internal/app/views/posts/show.json.jbuilder +1 -0
- data/lib/live_record/spec/internal/config/cable.yml +8 -0
- data/lib/live_record/spec/internal/config/database.yml +3 -0
- data/lib/live_record/spec/internal/config/routes.rb +3 -0
- data/lib/live_record/spec/internal/db/schema.rb +16 -0
- data/lib/live_record/spec/internal/public/favicon.ico +0 -0
- data/lib/live_record/spec/rails_helper.rb +34 -0
- data/lib/live_record/spec/spec_helper.rb +12 -0
- data/lib/live_record/version.rb +1 -1
- data/live_record.gemspec +19 -3
- metadata +251 -8
- data/lib/live_record/model.rb +0 -36
@@ -9,6 +9,9 @@ class <%= class_name %> < <%= parent_class_name.classify %>
|
|
9
9
|
<% if attributes.any?(&:password_digest?) -%>
|
10
10
|
has_secure_password
|
11
11
|
<% end -%>
|
12
|
+
|
13
|
+
include LiveRecord::Model::Callbacks
|
14
|
+
has_many :live_record_updates, as: :recordable
|
12
15
|
|
13
16
|
def self.live_record_whitelisted_attributes(<%= class_name.underscore %>, current_user)
|
14
17
|
# Add attributes to this array that you would like current_user to have access to.
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module LiveRecord
|
2
|
+
module Model
|
3
|
+
module Callbacks
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
before_update :__live_record_reference_changed_attributes__
|
8
|
+
after_update_commit :__live_record_broadcast_record_update__
|
9
|
+
after_destroy_commit :__live_record_broadcast_record_destroy__
|
10
|
+
|
11
|
+
def self.live_record_whitelisted_attributes(record, current_user)
|
12
|
+
[]
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def __live_record_reference_changed_attributes__
|
18
|
+
@_live_record_changed_attributes = changed
|
19
|
+
end
|
20
|
+
|
21
|
+
def __live_record_broadcast_record_update__
|
22
|
+
included_attributes = attributes.slice(*@_live_record_changed_attributes)
|
23
|
+
@_live_record_changed_attributes = nil
|
24
|
+
message_data = { 'action' => 'update', 'attributes' => included_attributes }
|
25
|
+
LiveRecordChannel.broadcast_to(self, message_data)
|
26
|
+
LiveRecordUpdate.create!(recordable_type: self.class, recordable_id: self.id, created_at: DateTime.now)
|
27
|
+
end
|
28
|
+
|
29
|
+
def __live_record_broadcast_record_destroy__
|
30
|
+
message_data = { 'action' => 'destroy' }
|
31
|
+
LiveRecordChannel.broadcast_to(self, message_data)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,60 @@
|
|
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
|
@@ -0,0 +1,17 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's
|
5
|
+
// vendor/assets/javascripts directory can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// compiled file. JavaScript code in this file should be added after the last require_* statement.
|
9
|
+
//
|
10
|
+
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
|
11
|
+
// about supported directives.
|
12
|
+
//
|
13
|
+
//= require rails-ujs
|
14
|
+
//= require jquery
|
15
|
+
//= require live_record
|
16
|
+
//= require live_record/plugins/live_dom
|
17
|
+
//= require_tree .
|
@@ -0,0 +1,12 @@
|
|
1
|
+
// Action Cable provides the framework to deal with WebSockets in Rails.
|
2
|
+
// You can generate new channels where WebSocket features live using the `rails generate channel` command.
|
3
|
+
//
|
4
|
+
//= require action_cable
|
5
|
+
//= require_self
|
6
|
+
|
7
|
+
(function() {
|
8
|
+
this.App || (this.App = {});
|
9
|
+
|
10
|
+
App.cable = ActionCable.createConsumer();
|
11
|
+
|
12
|
+
}).call(this);
|
@@ -0,0 +1,14 @@
|
|
1
|
+
LiveRecord.Model.create(
|
2
|
+
{
|
3
|
+
modelName: 'Post',
|
4
|
+
plugins: {
|
5
|
+
LiveDOM: true
|
6
|
+
},
|
7
|
+
# See TODO: URL_TO_DOCUMENTATION for supported callbacks
|
8
|
+
# Add Callbacks (callback name => array of functions)
|
9
|
+
# callbacks: {
|
10
|
+
# 'on:disconnect': [],
|
11
|
+
# 'after:update': [],
|
12
|
+
# }
|
13
|
+
}
|
14
|
+
)
|
@@ -0,0 +1,74 @@
|
|
1
|
+
class PostsController < ApplicationController
|
2
|
+
before_action :set_post, only: [:show, :edit, :update, :destroy]
|
3
|
+
|
4
|
+
# GET /posts
|
5
|
+
# GET /posts.json
|
6
|
+
def index
|
7
|
+
@posts = Post.all
|
8
|
+
end
|
9
|
+
|
10
|
+
# GET /posts/1
|
11
|
+
# GET /posts/1.json
|
12
|
+
def show
|
13
|
+
end
|
14
|
+
|
15
|
+
# GET /posts/new
|
16
|
+
def new
|
17
|
+
@post = Post.new
|
18
|
+
end
|
19
|
+
|
20
|
+
# GET /posts/1/edit
|
21
|
+
def edit
|
22
|
+
end
|
23
|
+
|
24
|
+
# POST /posts
|
25
|
+
# POST /posts.json
|
26
|
+
def create
|
27
|
+
@post = Post.new(post_params)
|
28
|
+
|
29
|
+
respond_to do |format|
|
30
|
+
if @post.save
|
31
|
+
format.html { redirect_to @post, notice: 'Post was successfully created.' }
|
32
|
+
format.json { render :show, status: :created, location: @post }
|
33
|
+
else
|
34
|
+
format.html { render :new }
|
35
|
+
format.json { render json: @post.errors, status: :unprocessable_entity }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# PATCH/PUT /posts/1
|
41
|
+
# PATCH/PUT /posts/1.json
|
42
|
+
def update
|
43
|
+
respond_to do |format|
|
44
|
+
if @post.update(post_params)
|
45
|
+
format.html { redirect_to @post, notice: 'Post was successfully updated.' }
|
46
|
+
format.json { render :show, status: :ok, location: @post }
|
47
|
+
else
|
48
|
+
format.html { render :edit }
|
49
|
+
format.json { render json: @post.errors, status: :unprocessable_entity }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# DELETE /posts/1
|
55
|
+
# DELETE /posts/1.json
|
56
|
+
def destroy
|
57
|
+
@post.destroy
|
58
|
+
respond_to do |format|
|
59
|
+
format.html { redirect_to posts_url, notice: 'Post was successfully destroyed.' }
|
60
|
+
format.json { head :no_content }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
# Use callbacks to share common setup or constraints between actions.
|
66
|
+
def set_post
|
67
|
+
@post = Post.find(params[:id])
|
68
|
+
end
|
69
|
+
|
70
|
+
# Never trust parameters from the scary internet, only allow the white list through.
|
71
|
+
def post_params
|
72
|
+
params.require(:post).permit(:title, :content)
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class Post < ApplicationRecord
|
2
|
+
include LiveRecord::Model::Callbacks
|
3
|
+
|
4
|
+
has_many :live_record_updates, as: :recordable
|
5
|
+
|
6
|
+
def self.live_record_whitelisted_attributes(post, current_user)
|
7
|
+
# Add attributes to this array that you would like current_user to have access to.
|
8
|
+
# Defaults to empty array, thereby blocking everything by default, only unless explicitly stated here so.
|
9
|
+
[:title]
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
<%= form_with(model: post, local: true) do |form| %>
|
2
|
+
<% if post.errors.any? %>
|
3
|
+
<div id="error_explanation">
|
4
|
+
<h2><%= pluralize(post.errors.count, "error") %> prohibited this post from being saved:</h2>
|
5
|
+
|
6
|
+
<ul>
|
7
|
+
<% post.errors.full_messages.each do |message| %>
|
8
|
+
<li><%= message %></li>
|
9
|
+
<% end %>
|
10
|
+
</ul>
|
11
|
+
</div>
|
12
|
+
<% end %>
|
13
|
+
|
14
|
+
<div class="field">
|
15
|
+
<%= form.label :title %>
|
16
|
+
<%= form.text_field :title, id: :post_title %>
|
17
|
+
</div>
|
18
|
+
|
19
|
+
<div class="field">
|
20
|
+
<%= form.label :content %>
|
21
|
+
<%= form.text_area :content, id: :post_content %>
|
22
|
+
</div>
|
23
|
+
|
24
|
+
<div class="actions">
|
25
|
+
<%= form.submit %>
|
26
|
+
</div>
|
27
|
+
<% end %>
|
@@ -0,0 +1,32 @@
|
|
1
|
+
<script>
|
2
|
+
LiveRecord.helpers.loadRecords({modelName: 'Post'})
|
3
|
+
</script>
|
4
|
+
<p id="notice"><%= notice %></p>
|
5
|
+
|
6
|
+
<h1>Posts</h1>
|
7
|
+
|
8
|
+
<table>
|
9
|
+
<thead>
|
10
|
+
<tr>
|
11
|
+
<th>Title</th>
|
12
|
+
<th>Content</th>
|
13
|
+
<th colspan="3"></th>
|
14
|
+
</tr>
|
15
|
+
</thead>
|
16
|
+
|
17
|
+
<tbody>
|
18
|
+
<% @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>
|
22
|
+
<td><%= link_to 'Show', post %></td>
|
23
|
+
<td><%= link_to 'Edit', edit_post_path(post) %></td>
|
24
|
+
<td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
|
25
|
+
</tr>
|
26
|
+
<% end %>
|
27
|
+
</tbody>
|
28
|
+
</table>
|
29
|
+
|
30
|
+
<br>
|
31
|
+
|
32
|
+
<%= link_to 'New Post', new_post_path %>
|
@@ -0,0 +1 @@
|
|
1
|
+
json.array! @posts, partial: 'posts/post', as: :post
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<script>
|
2
|
+
LiveRecord.helpers.loadRecords({modelName: 'Post'})
|
3
|
+
</script>
|
4
|
+
<p id="notice"><%= notice %></p>
|
5
|
+
|
6
|
+
<p>
|
7
|
+
<strong>Title:</strong>
|
8
|
+
<span data-live-record-update-from='Post-<%= @post.id %>-title'>
|
9
|
+
<%= @post.title %>
|
10
|
+
</span>
|
11
|
+
</p>
|
12
|
+
|
13
|
+
<p>
|
14
|
+
<strong>Content:</strong>
|
15
|
+
<span data-live-record-update-from='Post-<%= @post.id %>-content'>
|
16
|
+
<%= @post.content %>
|
17
|
+
</span>
|
18
|
+
</p>
|
19
|
+
|
20
|
+
<%= link_to 'Edit', edit_post_path(@post) %> |
|
21
|
+
<%= link_to 'Back', posts_path %>
|
@@ -0,0 +1 @@
|
|
1
|
+
json.partial! "posts/post", post: @post
|