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 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