cubism 0.1.0.pre9 → 0.1.0.pre12

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 579dec94521951d33b64eb0c211a210fe86cc89f927a79489cd05258593e3634
4
- data.tar.gz: 21d48a1e4c42ad33e27e11c1128930d22c2103120307a8bd0351d5321d520f9a
3
+ metadata.gz: 8cd7c7ee681cabc05fd5329e7e6c17fd030d037beb100440faf1ef190a614ddf
4
+ data.tar.gz: a772708b9dc6a32c5a37766d203ea6a4b119eac57368378121e6b4da9f2b50f6
5
5
  SHA512:
6
- metadata.gz: 57c779b3efd7deae25de93339ab19d4a673e2bfae83e3f0d02e62741780b8320d4f1e1dd1c2a4c07bfec3a42c417eb5c59e5fbdfddd43a586c28ed9f945e546a
7
- data.tar.gz: 999b2cae4e2d0c506fd08a02dad104c7e66f50f863fd9f5a58fa600e4fa2d0409ad81d88819905541423b68fccb9d80589a03dafc75c6c6a510aed2966f164fb
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
- - `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.
95
- - `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.
96
- - `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.
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 params[:element_id]
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.present_users.add(user.id)
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.present_users.remove(user.id)
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
- GlobalID::Locator.locate_signed(params[:user])
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 params[:element_id]
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.present_users.add(user.id)
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.present_users.remove(user.id)
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
- GlobalID::Locator.locate_signed(params[:user])
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
- key = "#{block.source_location.join(":")}:#{resource.to_gid}:#{user.to_gid}"
6
- digested_id = ActiveSupport::Digest.hexdigest(key)
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(resource.to_gid.to_s),
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-#{digested_id}",
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
- kredis_set :present_users, after_change: :stream_presence
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 present_users_for_element_id(element_id)
15
- users = Cubism.user_class.find(present_users.members)
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
@@ -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
- /cubicle-(?<block_key>.+)/ =~ element_id
17
- block = Cubism.store[block_key].block
18
- view_context = Cubism.store[block_key].context
19
- html = view_context.capture(resource.present_users_for_element_id(element_id), &block)
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: "cubicle-element##{element_id}[identifier='#{signed_stream_identifier(resource.to_global_id.to_s)}']",
29
+ selector: selector,
23
30
  html: html
24
- ).broadcast
31
+ )
25
32
  end
33
+
34
+ cable_ready.broadcast
26
35
  end
27
36
  end
28
37
  end
@@ -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
- /cubicle-(?<block_key>.+)/ =~ element_id
17
- block = Cubism.store[block_key].block
18
- view_context = Cubism.store[block_key].context
19
- html = view_context.capture(users_for(resource, element_id), &block)
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: "cubicle-element##{element_id}[identifier='#{signed_stream_identifier(resource.to_global_id.to_s)}']",
29
+ selector: selector,
23
30
  html: html
24
- ).broadcast
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
- users
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
- include Singleton
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(:context, :block, keyword_init: true)
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
@@ -1,4 +1,8 @@
1
1
  module Cubism
2
2
  class Engine < ::Rails::Engine
3
+ initializer "cubism.stores" do
4
+ Cubism.block_store = Cubism::CubicleStore.new("cubism-blocks")
5
+ Cubism.source_store = Cubism::CubicleStore.new("cubism-source")
6
+ end
3
7
  end
4
8
  end
@@ -0,0 +1,7 @@
1
+ module Cubism
2
+ class Engine < ::Rails::Engine
3
+ initializer "cubism.block_store" do
4
+ Cubism.block_store = Cubism::CubicleBlockStore.new
5
+ end
6
+ end
7
+ end
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
@@ -1,3 +1,3 @@
1
1
  module Cubism
2
- VERSION = "0.1.0.pre9"
2
+ VERSION = "0.1.0.pre12"
3
3
  end
@@ -1,3 +1,3 @@
1
1
  module Cubism
2
- VERSION = "0.1.0.pre8"
2
+ VERSION = "0.1.0.pre11"
3
3
  end
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/cubicle_block_store"
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
- mattr_reader :store, instance_reader: false, default: Cubism::CubicleBlockStore.instance
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
- mattr_reader :store, instance_reader: false, default: Cubism::CubicleBlockStore.instance
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.pre9
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-09 00:00:00.000000000 Z
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