cubism 0.1.0.pre9 → 0.1.0.pre12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +15 -4
- data/app/channels/cubism/presence_channel.rb +14 -5
- data/app/channels/cubism/presence_channel.rb~ +14 -5
- data/app/helpers/cubism_helper.rb +22 -7
- data/app/helpers/cubism_helper.rb~ +36 -0
- data/app/models/concerns/cubism/presence.rb +11 -3
- data/app/models/concerns/cubism/presence.rb~ +7 -0
- data/lib/cubism/broadcaster.rb +15 -6
- data/lib/cubism/broadcaster.rb~ +14 -14
- data/lib/cubism/cubicle_block_store.rb~ +34 -5
- data/lib/cubism/cubicle_source_store.rb~ +0 -0
- data/lib/cubism/cubicle_store.rb +141 -0
- data/lib/cubism/cubicle_store.rb~ +141 -0
- data/lib/cubism/engine.rb +4 -0
- data/lib/cubism/engine.rb~ +7 -0
- data/lib/cubism/parser.rb~ +0 -0
- data/lib/cubism/preprocessor.rb +33 -0
- data/lib/cubism/preprocessor.rb~ +30 -0
- data/lib/cubism/version.rb +1 -1
- data/lib/cubism/version.rb~ +1 -1
- data/lib/cubism.rb +5 -2
- data/lib/cubism.rb~ +3 -1
- metadata +10 -3
- data/lib/cubism/cubicle_block_store.rb +0 -35
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8cd7c7ee681cabc05fd5329e7e6c17fd030d037beb100440faf1ef190a614ddf
|
4
|
+
data.tar.gz: a772708b9dc6a32c5a37766d203ea6a4b119eac57368378121e6b4da9f2b50f6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b47525bcc5545b289624615b66215b68467c2a2ba35a7b5335c944bdfacc68d0c6d60c430c93ad1b18470db7e7d3a58313f232b35bc32a34b21847f4a28f086b
|
7
|
+
data.tar.gz: 4595ebf829643d556613ebc5ca03e19f35cc6601a8c435f0dfea3c4e9cc7c08966c94ae69996e87d0e8c572d1d0decc8e6bd1cfa9ee7840a7abad29909f3ab7f
|
data/README.md
CHANGED
@@ -4,7 +4,9 @@
|
|
4
4
|
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
5
5
|
[![Twitter follow](https://img.shields.io/twitter/follow/julian_rubisch?style=social)](https://twitter.com/julian_rubisch)
|
6
6
|
|
7
|
-
Lightweight Resource-Based Presence Solution with CableReady
|
7
|
+
Lightweight Resource-Based Presence Solution with CableReady.
|
8
|
+
|
9
|
+
`Cubism` provides real-time updates of who is viewing or interacting with whatever resources you need. Whether you want Slack's "X is typing..." indicator or an e-commerce "5 other customers are viewing this item" notice, `Cubism` gives you everything you need "under the hood" so that you can focus on what really matters—end-user functionality.
|
8
10
|
|
9
11
|
## Table of Contents
|
10
12
|
|
@@ -12,6 +14,7 @@ Lightweight Resource-Based Presence Solution with CableReady
|
|
12
14
|
- [Usage](#usage)
|
13
15
|
- [Installation](#installation)
|
14
16
|
- [API](#api)
|
17
|
+
- [Limitations](#limitations)
|
15
18
|
- [Gotchas](#gotchas)
|
16
19
|
- [Contributing](#contributing)
|
17
20
|
- [License](#license)
|
@@ -54,6 +57,8 @@ Using the `cubicle_for` helper, you can set up a presence indicator. It will
|
|
54
57
|
<% end %>
|
55
58
|
```
|
56
59
|
|
60
|
+
**Important!** due to technical limitations the cubism block does _not_ act as a closure, i.e. it has _only_ access to the `users` variable passed to it - think of it more as a self-contained component.
|
61
|
+
|
57
62
|
## Installation
|
58
63
|
Add this line to your application's Gemfile:
|
59
64
|
|
@@ -91,12 +96,18 @@ CableReady.initialize({ consumer });
|
|
91
96
|
|
92
97
|
The `cubicle_for` helper accepts the following options as keyword arguments:
|
93
98
|
|
94
|
-
- `
|
95
|
-
- `
|
96
|
-
- `
|
99
|
+
- `scope`: declare a scope in which presence indicators should appear. For example, if you want to divide between index and show views, do `scope: :index` and `scope: :show` respectively (default: `""`).
|
100
|
+
- `exclude_current_user (true|false)`: Whether or not to exclude the current user from the list of present users broadcasted to the view. Useful e.g. for "typing..." indicators (default: `true`).
|
101
|
+
- `appear_trigger`: JavaScript event names (e.g. `["focus", "debounced:input]`) to use. (Can also be a singular string, which will be converted to an array). The default is `:connect`, i.e. register a user as "appeared"/"present" when the element connects to the DOM. Another special value is `:intersect`, which fires when the `trigger_root` comes into the viewport.
|
102
|
+
- `disappear_trigger`: a JavaScript event name (e.g. `:blur`) to use. (Can also be a singular string, which will be converted to an array). The default is `:disconnect`, i.e. remove a user form the present users list when the element disconnects from the DOM. Analoguous to above, `:intersect` means that `disappear` will fire when the `trigger_root` is scrolled out of the viewport.
|
97
103
|
- `trigger_root`: a CSS selector to attach the appear/disappear events to. Defaults to the `cubicle-element` itself.
|
98
104
|
- `html_options` are passed to the TagBuilder.
|
99
105
|
|
106
|
+
## Limitations
|
107
|
+
|
108
|
+
### Supported Template Handlers
|
109
|
+
- ERB
|
110
|
+
|
100
111
|
## Gotchas
|
101
112
|
|
102
113
|
### Usage with ViewComponent
|
@@ -3,7 +3,7 @@ class Cubism::PresenceChannel < ActionCable::Channel::Base
|
|
3
3
|
|
4
4
|
def subscribed
|
5
5
|
if resource.present?
|
6
|
-
stream_from
|
6
|
+
stream_from element_id
|
7
7
|
resource.cubicle_element_ids << element_id
|
8
8
|
resource.excluded_user_id_for_element_id[element_id] = user.id if exclude_current_user?
|
9
9
|
else
|
@@ -20,11 +20,11 @@ class Cubism::PresenceChannel < ActionCable::Channel::Base
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def appear
|
23
|
-
resource.
|
23
|
+
resource.set_present_users_for_scope(resource.present_users_for_scope(scope).add(user.id), scope) if scope
|
24
24
|
end
|
25
25
|
|
26
26
|
def disappear
|
27
|
-
resource.
|
27
|
+
resource.set_present_users_for_scope(resource.present_users_for_scope(scope).delete(user.id), scope) if scope
|
28
28
|
end
|
29
29
|
|
30
30
|
private
|
@@ -35,7 +35,15 @@ class Cubism::PresenceChannel < ActionCable::Channel::Base
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def user
|
38
|
-
|
38
|
+
block_container&.user
|
39
|
+
end
|
40
|
+
|
41
|
+
def scope
|
42
|
+
block_container&.scope
|
43
|
+
end
|
44
|
+
|
45
|
+
def block_container
|
46
|
+
Cubism.block_store[element_id]
|
39
47
|
end
|
40
48
|
|
41
49
|
def exclude_current_user?
|
@@ -43,7 +51,8 @@ class Cubism::PresenceChannel < ActionCable::Channel::Base
|
|
43
51
|
end
|
44
52
|
|
45
53
|
def element_id
|
46
|
-
params[:element_id]
|
54
|
+
/cubicle-(?<element_id>.+)/ =~ params[:element_id]
|
55
|
+
element_id
|
47
56
|
end
|
48
57
|
|
49
58
|
def url
|
@@ -3,7 +3,7 @@ class Cubism::PresenceChannel < ActionCable::Channel::Base
|
|
3
3
|
|
4
4
|
def subscribed
|
5
5
|
if resource.present?
|
6
|
-
stream_from
|
6
|
+
stream_from element_id
|
7
7
|
resource.cubicle_element_ids << element_id
|
8
8
|
resource.excluded_user_id_for_element_id[element_id] = user.id if exclude_current_user?
|
9
9
|
else
|
@@ -20,11 +20,11 @@ class Cubism::PresenceChannel < ActionCable::Channel::Base
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def appear
|
23
|
-
resource.
|
23
|
+
resource.set_present_users_for_scope(resource.present_users_for_scope(scope).add(user.id), scope)
|
24
24
|
end
|
25
25
|
|
26
26
|
def disappear
|
27
|
-
resource.
|
27
|
+
resource.set_present_users_for_scope(resource.present_users_for_scope(scope).delete(user.id), scope)
|
28
28
|
end
|
29
29
|
|
30
30
|
private
|
@@ -35,7 +35,15 @@ class Cubism::PresenceChannel < ActionCable::Channel::Base
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def user
|
38
|
-
|
38
|
+
block_container.user
|
39
|
+
end
|
40
|
+
|
41
|
+
def scope
|
42
|
+
block_container.scope
|
43
|
+
end
|
44
|
+
|
45
|
+
def block_container
|
46
|
+
Cubism.block_store[element_id]
|
39
47
|
end
|
40
48
|
|
41
49
|
def exclude_current_user?
|
@@ -43,7 +51,8 @@ class Cubism::PresenceChannel < ActionCable::Channel::Base
|
|
43
51
|
end
|
44
52
|
|
45
53
|
def element_id
|
46
|
-
params[:element_id]
|
54
|
+
/cubicle-(?<element_id>.+)/ =~ params[:element_id]
|
55
|
+
element_id
|
47
56
|
end
|
48
57
|
|
49
58
|
def url
|
@@ -1,18 +1,33 @@
|
|
1
1
|
module CubismHelper
|
2
2
|
include CableReady::StreamIdentifier
|
3
3
|
|
4
|
-
def cubicle_for(resource, user, html_options: {}, appear_trigger: :connect, disappear_trigger: nil, trigger_root: nil, exclude_current_user: true, &block)
|
5
|
-
|
6
|
-
|
4
|
+
def cubicle_for(resource, user, scope: "", html_options: {}, appear_trigger: :connect, disappear_trigger: nil, trigger_root: nil, exclude_current_user: true, &block)
|
5
|
+
block_location = block.source_location.join(":")
|
6
|
+
block_source = Cubism::BlockSource.find_or_create(
|
7
|
+
location: block_location,
|
8
|
+
view_context: self
|
9
|
+
)
|
10
|
+
|
11
|
+
resource_gid = resource.to_gid.to_s
|
12
|
+
|
13
|
+
block_container = Cubism::BlockContainer.new(
|
14
|
+
block_location: block_location,
|
15
|
+
block_source: block_source,
|
16
|
+
resource_gid: resource_gid,
|
17
|
+
user_gid: user.to_gid.to_s,
|
18
|
+
scope: scope
|
19
|
+
)
|
20
|
+
|
21
|
+
digested_block_key = block_container.digest
|
22
|
+
|
23
|
+
Cubism.block_store.fetch(digested_block_key, block_container)
|
7
24
|
|
8
|
-
Cubism.store[digested_id] = Cubism::BlockStoreItem.new(context: self, block: block.dup)
|
9
25
|
tag.cubicle_element(
|
10
|
-
identifier: signed_stream_identifier(
|
11
|
-
user: user.to_sgid.to_s,
|
26
|
+
identifier: signed_stream_identifier(resource_gid),
|
12
27
|
"appear-trigger": Array(appear_trigger).join(","),
|
13
28
|
"disappear-trigger": disappear_trigger,
|
14
29
|
"trigger-root": trigger_root,
|
15
|
-
id: "cubicle-#{
|
30
|
+
id: "cubicle-#{digested_block_key}",
|
16
31
|
"exclude-current-user": exclude_current_user,
|
17
32
|
**html_options
|
18
33
|
)
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module CubismHelper
|
2
|
+
include CableReady::StreamIdentifier
|
3
|
+
|
4
|
+
def cubicle_for(resource, user, scope: "", html_options: {}, appear_trigger: :connect, disappear_trigger: nil, trigger_root: nil, exclude_current_user: true, &block)
|
5
|
+
block_location = block.source_location.join(":")
|
6
|
+
block_source = Cubism::BlockSource.find_or_create(
|
7
|
+
location: block_location,
|
8
|
+
view_context: self
|
9
|
+
)
|
10
|
+
|
11
|
+
resource_gid = resource.to_gid.to_s
|
12
|
+
|
13
|
+
block_container = Cubism::BlockContainer.new(
|
14
|
+
block_location: block_location,
|
15
|
+
block_source: block_source,
|
16
|
+
resource_gid: resource_gid,
|
17
|
+
user_gid: user.to_gid.to_s,
|
18
|
+
scope: scope
|
19
|
+
)
|
20
|
+
|
21
|
+
digested_block_key = block_container.digest
|
22
|
+
|
23
|
+
Cubism.block_store.fetch(digested_block_key, block_container)
|
24
|
+
|
25
|
+
tag.cubicle_element(
|
26
|
+
identifier: signed_stream_identifier(resource_gid),
|
27
|
+
"appear-trigger": Array(appear_trigger).join(","),
|
28
|
+
"disappear-trigger": disappear_trigger,
|
29
|
+
"trigger-root": trigger_root,
|
30
|
+
scope: scope,
|
31
|
+
id: "cubicle-#{digested_block_key}",
|
32
|
+
"exclude-current-user": exclude_current_user,
|
33
|
+
**html_options
|
34
|
+
)
|
35
|
+
end
|
36
|
+
end
|
@@ -2,7 +2,7 @@ module Cubism::Presence
|
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
4
|
included do
|
5
|
-
|
5
|
+
kredis_hash :present_users, after_change: :stream_presence
|
6
6
|
kredis_set :cubicle_element_ids
|
7
7
|
kredis_hash :excluded_user_id_for_element_id
|
8
8
|
end
|
@@ -11,8 +11,16 @@ module Cubism::Presence
|
|
11
11
|
Cubism::Broadcaster.new(resource: self).broadcast
|
12
12
|
end
|
13
13
|
|
14
|
-
def
|
15
|
-
|
14
|
+
def present_users_for_scope(scope = "")
|
15
|
+
present_users[scope].present? ? Marshal.load(present_users[scope]) : Set.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def set_present_users_for_scope(user_ids, scope = "")
|
19
|
+
present_users[scope] = Marshal.dump(Set.new(user_ids))
|
20
|
+
end
|
21
|
+
|
22
|
+
def present_users_for_element_id_and_scope(element_id, scope = "")
|
23
|
+
users = Cubism.user_class.find(present_users_for_scope(scope).to_a)
|
16
24
|
users.reject! { |user| user.id == excluded_user_id_for_element_id[element_id].to_i }
|
17
25
|
|
18
26
|
users
|
@@ -10,4 +10,11 @@ module Cubism::Presence
|
|
10
10
|
def stream_presence
|
11
11
|
Cubism::Broadcaster.new(resource: self).broadcast
|
12
12
|
end
|
13
|
+
|
14
|
+
def present_users_for_element_id(element_id)
|
15
|
+
users = Cubism.user_class.find(present_users.members)
|
16
|
+
users.reject! { |user| user.id == excluded_user_id_for_element_id[element_id].to_i }
|
17
|
+
|
18
|
+
users
|
19
|
+
end
|
13
20
|
end
|
data/lib/cubism/broadcaster.rb
CHANGED
@@ -13,16 +13,25 @@ module Cubism
|
|
13
13
|
|
14
14
|
def broadcast
|
15
15
|
resource.cubicle_element_ids.to_a.each do |element_id|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
block_container = Cubism.block_store[element_id]
|
17
|
+
|
18
|
+
next if block_container.blank?
|
19
|
+
|
20
|
+
present_users = resource.present_users_for_element_id_and_scope(element_id, block_container.scope)
|
21
|
+
|
22
|
+
block_source = block_container.block_source
|
23
|
+
|
24
|
+
html = ApplicationController.render(inline: block_source.source, locals: {"#{block_source.variable_name}": present_users})
|
25
|
+
|
26
|
+
selector = "cubicle-element#cubicle-#{element_id}[identifier='#{signed_stream_identifier(resource.to_global_id.to_s)}']"
|
20
27
|
|
21
28
|
cable_ready[element_id].inner_html(
|
22
|
-
selector:
|
29
|
+
selector: selector,
|
23
30
|
html: html
|
24
|
-
)
|
31
|
+
)
|
25
32
|
end
|
33
|
+
|
34
|
+
cable_ready.broadcast
|
26
35
|
end
|
27
36
|
end
|
28
37
|
end
|
data/lib/cubism/broadcaster.rb~
CHANGED
@@ -13,25 +13,25 @@ module Cubism
|
|
13
13
|
|
14
14
|
def broadcast
|
15
15
|
resource.cubicle_element_ids.to_a.each do |element_id|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
block_container = Cubism.block_store[element_id]
|
17
|
+
|
18
|
+
next if block_container.blank?
|
19
|
+
|
20
|
+
present_users = resource.present_users_for_element_id_and_scope(element_id, block_container.scope)
|
21
|
+
|
22
|
+
block_source = block_container.block_source
|
23
|
+
|
24
|
+
html = ApplicationController.render(inline: block_source.source, locals: {"#{block_source.variable_name}": present_users})
|
25
|
+
|
26
|
+
selector = "cubicle-element#cubicle-#{element_id}[identifier='#{signed_stream_identifier(resource.to_global_id.to_s)}'][scope='#{block_container.scope}']"
|
20
27
|
|
21
28
|
cable_ready[element_id].inner_html(
|
22
|
-
selector:
|
29
|
+
selector: selector,
|
23
30
|
html: html
|
24
|
-
)
|
31
|
+
)
|
25
32
|
end
|
26
|
-
end
|
27
|
-
|
28
|
-
private
|
29
|
-
|
30
|
-
def users_for(resource, element_id)
|
31
|
-
users = Cubism.user_class.find(resource.present_users.members)
|
32
|
-
users.reject! { |user| user.id == resource.excluded_user_id_for_element_id[element_id].to_i }
|
33
33
|
|
34
|
-
|
34
|
+
cable_ready.broadcast
|
35
35
|
end
|
36
36
|
end
|
37
37
|
end
|
@@ -1,21 +1,32 @@
|
|
1
1
|
module Cubism
|
2
2
|
class CubicleBlockStore
|
3
|
-
|
3
|
+
delegate_missing_to :@blocks
|
4
4
|
|
5
5
|
def initialize
|
6
|
-
@blocks =
|
6
|
+
@blocks = Kredis.hash "cubism-blocks"
|
7
7
|
end
|
8
8
|
|
9
9
|
def [](key)
|
10
|
-
@blocks[key]
|
10
|
+
Marshal.load(@blocks[key]) if @blocks[key]
|
11
11
|
end
|
12
12
|
|
13
13
|
def []=(key, value)
|
14
14
|
mutex.synchronize do
|
15
|
-
@blocks[key] = value
|
15
|
+
@blocks[key] = Marshal.dump value
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
+
def clear
|
20
|
+
mutex.synchronize do
|
21
|
+
# kredis #remove
|
22
|
+
@blocks.remove
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def size
|
27
|
+
@blocks.to_h.size
|
28
|
+
end
|
29
|
+
|
19
30
|
private
|
20
31
|
|
21
32
|
def mutex
|
@@ -23,5 +34,23 @@ module Cubism
|
|
23
34
|
end
|
24
35
|
end
|
25
36
|
|
26
|
-
BlockStoreItem = Struct.new(:
|
37
|
+
BlockStoreItem = Struct.new(:block_location, :block_source, :user_gid, :resource_gid, keyword_init: true) do
|
38
|
+
def user
|
39
|
+
GlobalID::Locator.locate self[:user_gid]
|
40
|
+
end
|
41
|
+
|
42
|
+
def resource
|
43
|
+
GlobalID::Locator.locate self[:resource_gid]
|
44
|
+
end
|
45
|
+
|
46
|
+
def marshal_dump
|
47
|
+
to_h
|
48
|
+
end
|
49
|
+
|
50
|
+
def marshal_load(serialized_item)
|
51
|
+
%i[block_location block_source user_gid resource_gid].each do |arg|
|
52
|
+
send("#{arg}=", serialized_item[arg])
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
27
56
|
end
|
File without changes
|
@@ -0,0 +1,141 @@
|
|
1
|
+
module Cubism
|
2
|
+
class CubicleStore
|
3
|
+
delegate_missing_to :@blocks
|
4
|
+
|
5
|
+
def initialize(key)
|
6
|
+
@blocks = Kredis.hash key
|
7
|
+
end
|
8
|
+
|
9
|
+
def [](key)
|
10
|
+
Marshal.load(@blocks[key]) if @blocks[key]
|
11
|
+
end
|
12
|
+
|
13
|
+
def []=(key, value)
|
14
|
+
mutex.synchronize do
|
15
|
+
@blocks[key] = Marshal.dump value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def fetch(key, value = nil, &block)
|
20
|
+
if self[key].nil?
|
21
|
+
yield value if block
|
22
|
+
self[key] = value
|
23
|
+
end
|
24
|
+
|
25
|
+
self[key]
|
26
|
+
end
|
27
|
+
|
28
|
+
def clear
|
29
|
+
mutex.synchronize do
|
30
|
+
# kredis #remove
|
31
|
+
@blocks.remove
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def size
|
36
|
+
@blocks.to_h.size
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def mutex
|
42
|
+
@mutex ||= Mutex.new
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Container for cubicle blocks
|
47
|
+
BlockContainer = Struct.new(
|
48
|
+
:block_location,
|
49
|
+
:block_source,
|
50
|
+
:user_gid,
|
51
|
+
:resource_gid,
|
52
|
+
:scope,
|
53
|
+
keyword_init: true
|
54
|
+
) do
|
55
|
+
def initialize(*args)
|
56
|
+
super
|
57
|
+
|
58
|
+
@filename, _lineno = block_location.split(":")
|
59
|
+
end
|
60
|
+
|
61
|
+
def user
|
62
|
+
GlobalID::Locator.locate self[:user_gid]
|
63
|
+
end
|
64
|
+
|
65
|
+
def resource
|
66
|
+
GlobalID::Locator.locate self[:resource_gid]
|
67
|
+
end
|
68
|
+
|
69
|
+
def scope
|
70
|
+
self[:scope] || ""
|
71
|
+
end
|
72
|
+
|
73
|
+
def digest
|
74
|
+
resource_user_key = [resource_gid, user_gid, scope].join(":")
|
75
|
+
|
76
|
+
ActiveSupport::Digest.hexdigest("#{block_location}:#{File.read(@filename)}:#{resource_user_key}")
|
77
|
+
end
|
78
|
+
|
79
|
+
def marshal_dump
|
80
|
+
to_h.merge(block_source: block_source.digest)
|
81
|
+
end
|
82
|
+
|
83
|
+
def marshal_load(serialized_item)
|
84
|
+
members.excluding(:block_source).each do |arg|
|
85
|
+
send("#{arg}=", serialized_item[arg])
|
86
|
+
end
|
87
|
+
|
88
|
+
self.block_source = Cubism.source_store[serialized_item[:block_source]]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Container for cubicle block sources
|
93
|
+
BlockSource = Struct.new(
|
94
|
+
:location,
|
95
|
+
:source,
|
96
|
+
:variable_name,
|
97
|
+
:view_context,
|
98
|
+
keyword_init: true
|
99
|
+
) do
|
100
|
+
def self.find_or_create(location:, view_context:)
|
101
|
+
instance = new(location: location, view_context: view_context)
|
102
|
+
|
103
|
+
Cubism.source_store.fetch(instance.digest, instance) do |instance|
|
104
|
+
instance.parse!
|
105
|
+
end
|
106
|
+
|
107
|
+
instance
|
108
|
+
end
|
109
|
+
|
110
|
+
def initialize(*args)
|
111
|
+
super
|
112
|
+
|
113
|
+
@filename, @lineno = location.split(":")
|
114
|
+
@lineno = @lineno.to_i
|
115
|
+
end
|
116
|
+
|
117
|
+
def parse!
|
118
|
+
return if location.start_with?("inline template")
|
119
|
+
|
120
|
+
lines = File.readlines(@filename)[@lineno - 1..]
|
121
|
+
|
122
|
+
preprocessor = Cubism::Preprocessor.new(source: lines.join.squish, view_context: view_context)
|
123
|
+
self.variable_name = preprocessor.block_variable_name
|
124
|
+
self.source = preprocessor.process
|
125
|
+
end
|
126
|
+
|
127
|
+
def digest
|
128
|
+
ActiveSupport::Digest.hexdigest("#{location}:#{File.read(@filename)}")
|
129
|
+
end
|
130
|
+
|
131
|
+
def marshal_dump
|
132
|
+
to_h.except(:view_context)
|
133
|
+
end
|
134
|
+
|
135
|
+
def marshal_load(serialized_item)
|
136
|
+
members.excluding(:view_context).each do |arg|
|
137
|
+
send("#{arg}=", serialized_item[arg])
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
module Cubism
|
2
|
+
class CubicleStore
|
3
|
+
delegate_missing_to :@blocks
|
4
|
+
|
5
|
+
def initialize(key)
|
6
|
+
@blocks = Kredis.hash key
|
7
|
+
end
|
8
|
+
|
9
|
+
def [](key)
|
10
|
+
Marshal.load(@blocks[key]) if @blocks[key]
|
11
|
+
end
|
12
|
+
|
13
|
+
def []=(key, value)
|
14
|
+
mutex.synchronize do
|
15
|
+
@blocks[key] = Marshal.dump value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def fetch(key, value = nil, &block)
|
20
|
+
if self[key].nil?
|
21
|
+
yield value if block
|
22
|
+
self[key] = value
|
23
|
+
end
|
24
|
+
|
25
|
+
self[key]
|
26
|
+
end
|
27
|
+
|
28
|
+
def clear
|
29
|
+
mutex.synchronize do
|
30
|
+
# kredis #remove
|
31
|
+
@blocks.remove
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def size
|
36
|
+
@blocks.to_h.size
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def mutex
|
42
|
+
@mutex ||= Mutex.new
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Container for cubicle blocks
|
47
|
+
BlockContainer = Struct.new(
|
48
|
+
:block_location,
|
49
|
+
:block_source,
|
50
|
+
:user_gid,
|
51
|
+
:resource_gid,
|
52
|
+
:scope,
|
53
|
+
keyword_init: true
|
54
|
+
) do
|
55
|
+
def initialize(*args)
|
56
|
+
super
|
57
|
+
|
58
|
+
@filename, _lineno = block_location.split(":")
|
59
|
+
end
|
60
|
+
|
61
|
+
def user
|
62
|
+
GlobalID::Locator.locate self[:user_gid]
|
63
|
+
end
|
64
|
+
|
65
|
+
def resource
|
66
|
+
GlobalID::Locator.locate self[:resource_gid]
|
67
|
+
end
|
68
|
+
|
69
|
+
def scope
|
70
|
+
self[:scope] || ""
|
71
|
+
end
|
72
|
+
|
73
|
+
def digest
|
74
|
+
resource_user_key = [resource_gid, user_gid].join(":")
|
75
|
+
|
76
|
+
ActiveSupport::Digest.hexdigest("#{block_location}:#{File.read(@filename)}:#{resource_user_key}")
|
77
|
+
end
|
78
|
+
|
79
|
+
def marshal_dump
|
80
|
+
to_h.merge(block_source: block_source.digest)
|
81
|
+
end
|
82
|
+
|
83
|
+
def marshal_load(serialized_item)
|
84
|
+
members.excluding(:block_source).each do |arg|
|
85
|
+
send("#{arg}=", serialized_item[arg])
|
86
|
+
end
|
87
|
+
|
88
|
+
self.block_source = Cubism.source_store[serialized_item[:block_source]]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Container for cubicle block sources
|
93
|
+
BlockSource = Struct.new(
|
94
|
+
:location,
|
95
|
+
:source,
|
96
|
+
:variable_name,
|
97
|
+
:view_context,
|
98
|
+
keyword_init: true
|
99
|
+
) do
|
100
|
+
def self.find_or_create(location:, view_context:)
|
101
|
+
instance = new(location: location, view_context: view_context)
|
102
|
+
|
103
|
+
Cubism.source_store.fetch(instance.digest, instance) do |instance|
|
104
|
+
instance.parse!
|
105
|
+
end
|
106
|
+
|
107
|
+
instance
|
108
|
+
end
|
109
|
+
|
110
|
+
def initialize(*args)
|
111
|
+
super
|
112
|
+
|
113
|
+
@filename, @lineno = location.split(":")
|
114
|
+
@lineno = @lineno.to_i
|
115
|
+
end
|
116
|
+
|
117
|
+
def parse!
|
118
|
+
return if location.start_with?("inline template")
|
119
|
+
|
120
|
+
lines = File.readlines(@filename)[@lineno - 1..]
|
121
|
+
|
122
|
+
preprocessor = Cubism::Preprocessor.new(source: lines.join.squish, view_context: view_context)
|
123
|
+
self.variable_name = preprocessor.block_variable_name
|
124
|
+
self.source = preprocessor.process
|
125
|
+
end
|
126
|
+
|
127
|
+
def digest
|
128
|
+
ActiveSupport::Digest.hexdigest("#{location}:#{File.read(@filename)}")
|
129
|
+
end
|
130
|
+
|
131
|
+
def marshal_dump
|
132
|
+
to_h.except(:view_context)
|
133
|
+
end
|
134
|
+
|
135
|
+
def marshal_load(serialized_item)
|
136
|
+
members.excluding(:view_context).each do |arg|
|
137
|
+
send("#{arg}=", serialized_item[arg])
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
data/lib/cubism/engine.rb
CHANGED
File without changes
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Cubism
|
2
|
+
class Preprocessor
|
3
|
+
attr_reader :block_variable_name
|
4
|
+
|
5
|
+
def initialize(source:, view_context:)
|
6
|
+
match_data = /<%=\s+cubicle_for.+?\|(\w+)\|\s+%>/.match(source)
|
7
|
+
start_pos = match_data&.end(0) || 0
|
8
|
+
@block_variable_name = match_data[1] if match_data
|
9
|
+
@source = source[start_pos..]
|
10
|
+
@view_context = view_context
|
11
|
+
end
|
12
|
+
|
13
|
+
def process
|
14
|
+
begin
|
15
|
+
do_parse
|
16
|
+
rescue NameError
|
17
|
+
# we ignore any name errors from unset instance variables or local assigns here
|
18
|
+
end
|
19
|
+
|
20
|
+
@source
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def do_parse
|
26
|
+
ActionView::Template::Handlers::ERB::Erubi.new(@source).evaluate(@view_context)
|
27
|
+
rescue SyntaxError
|
28
|
+
end_at_end = /(<%\s+end\s+%>)\z/.match(@source)
|
29
|
+
@source = end_at_end ? @source[..-(end_at_end[0].length + 1)] : @source[..-2]
|
30
|
+
do_parse
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Cubism
|
2
|
+
class Preprocessor
|
3
|
+
def initialize(source:, view_context:)
|
4
|
+
match_data = /<%=\s+cubicle_for.+\|.+\|\s+%>/.match(source)
|
5
|
+
start_pos = match_data&.end(0) || 0
|
6
|
+
@source = source[start_pos..]
|
7
|
+
@view_context = view_context
|
8
|
+
end
|
9
|
+
|
10
|
+
def process
|
11
|
+
begin
|
12
|
+
do_parse
|
13
|
+
rescue NameError
|
14
|
+
# we ignore any name errors from unset instance variables or local assigns here
|
15
|
+
end
|
16
|
+
|
17
|
+
@source
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def do_parse
|
23
|
+
ActionView::Template::Handlers::ERB::Erubi.new(@source).evaluate(@view_context)
|
24
|
+
rescue SyntaxError
|
25
|
+
end_at_end = /(<%\s+end\s+%>)\z/.match(@source)
|
26
|
+
@source = end_at_end ? @source[..-(end_at_end[0].length + 1)] : @source[..-2]
|
27
|
+
do_parse
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/cubism/version.rb
CHANGED
data/lib/cubism/version.rb~
CHANGED
data/lib/cubism.rb
CHANGED
@@ -3,14 +3,17 @@ require "kredis"
|
|
3
3
|
require "cubism/version"
|
4
4
|
require "cubism/engine"
|
5
5
|
require "cubism/broadcaster"
|
6
|
-
require "cubism/
|
6
|
+
require "cubism/cubicle_store"
|
7
|
+
require "cubism/preprocessor"
|
7
8
|
|
8
9
|
module Cubism
|
9
10
|
extend ActiveSupport::Autoload
|
10
11
|
|
11
12
|
autoload :Broadcaster, "cubism/broadcaster"
|
13
|
+
autoload :Preprocessor, "cubism/preprocessor"
|
12
14
|
|
13
15
|
mattr_accessor :user_class, instance_writer: false, instance_reader: false
|
14
16
|
|
15
|
-
|
17
|
+
mattr_accessor :block_store, instance_reader: false
|
18
|
+
mattr_accessor :source_store, instance_reader: false
|
16
19
|
end
|
data/lib/cubism.rb~
CHANGED
@@ -4,13 +4,15 @@ require "cubism/version"
|
|
4
4
|
require "cubism/engine"
|
5
5
|
require "cubism/broadcaster"
|
6
6
|
require "cubism/cubicle_block_store"
|
7
|
+
require "cubism/preprocessor"
|
7
8
|
|
8
9
|
module Cubism
|
9
10
|
extend ActiveSupport::Autoload
|
10
11
|
|
11
12
|
autoload :Broadcaster, "cubism/broadcaster"
|
13
|
+
autoload :Preprocessor, "cubism/preprocessor"
|
12
14
|
|
13
15
|
mattr_accessor :user_class, instance_writer: false, instance_reader: false
|
14
16
|
|
15
|
-
|
17
|
+
mattr_accessor :block_store, instance_reader: false
|
16
18
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cubism
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.0.
|
4
|
+
version: 0.1.0.pre12
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Julian Rubisch
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-01
|
11
|
+
date: 2022-03-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -135,6 +135,7 @@ files:
|
|
135
135
|
- app/channels/cubism/presence_channel.rb
|
136
136
|
- app/channels/cubism/presence_channel.rb~
|
137
137
|
- app/helpers/cubism_helper.rb
|
138
|
+
- app/helpers/cubism_helper.rb~
|
138
139
|
- app/models/concerns/cubism/presence.rb
|
139
140
|
- app/models/concerns/cubism/presence.rb~
|
140
141
|
- app/models/concerns/cubism/user.rb
|
@@ -144,9 +145,15 @@ files:
|
|
144
145
|
- lib/cubism.rb~
|
145
146
|
- lib/cubism/broadcaster.rb
|
146
147
|
- lib/cubism/broadcaster.rb~
|
147
|
-
- lib/cubism/cubicle_block_store.rb
|
148
148
|
- lib/cubism/cubicle_block_store.rb~
|
149
|
+
- lib/cubism/cubicle_source_store.rb~
|
150
|
+
- lib/cubism/cubicle_store.rb
|
151
|
+
- lib/cubism/cubicle_store.rb~
|
149
152
|
- lib/cubism/engine.rb
|
153
|
+
- lib/cubism/engine.rb~
|
154
|
+
- lib/cubism/parser.rb~
|
155
|
+
- lib/cubism/preprocessor.rb
|
156
|
+
- lib/cubism/preprocessor.rb~
|
150
157
|
- lib/cubism/version.rb
|
151
158
|
- lib/cubism/version.rb~
|
152
159
|
- lib/tasks/cubism_tasks.rake
|
@@ -1,35 +0,0 @@
|
|
1
|
-
module Cubism
|
2
|
-
class CubicleBlockStore
|
3
|
-
include Singleton
|
4
|
-
|
5
|
-
delegate_missing_to :@blocks
|
6
|
-
|
7
|
-
def initialize
|
8
|
-
@blocks = {}
|
9
|
-
end
|
10
|
-
|
11
|
-
def [](key)
|
12
|
-
@blocks[key]
|
13
|
-
end
|
14
|
-
|
15
|
-
def []=(key, value)
|
16
|
-
mutex.synchronize do
|
17
|
-
@blocks[key] = value
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def clear
|
22
|
-
mutex.synchronize do
|
23
|
-
@blocks.clear
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
private
|
28
|
-
|
29
|
-
def mutex
|
30
|
-
@mutex ||= Mutex.new
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
BlockStoreItem = Struct.new(:context, :block, keyword_init: true)
|
35
|
-
end
|