loose_leaf 0.0.1

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.
@@ -0,0 +1,56 @@
1
+ module LooseLeaf
2
+ # An ActionCable channel for TLooseLeaf
3
+ class CollaborationChannel < ApplicationCable::Channel
4
+ attr_reader :client_id
5
+
6
+ def subscribed
7
+ @client_id = SecureRandom.uuid
8
+
9
+ transmit action: 'subscribed', client_id: client_id
10
+ end
11
+
12
+ # Subscribe to changes to a document
13
+ def document(data)
14
+ document = collaborative_model.find_by_collaborative_key(data['id'])
15
+
16
+ @documents ||= []
17
+ @documents << document
18
+
19
+ send_attribute_versions(document)
20
+
21
+ stream_from "loose_leaf.documents.#{document.id}.operations"
22
+ end
23
+
24
+ def operation(data)
25
+ data = ActiveSupport::HashWithIndifferentAccess.new(data)
26
+ document = collaborative_model.find_by_collaborative_key(data[:document_id])
27
+
28
+ version, operation = document.apply_operation(data)
29
+ data[:sent_version] = data[:version]
30
+ data[:version] = version
31
+ data[:operation] = operation.to_a
32
+
33
+ ActionCable.server.broadcast "loose_leaf.documents.#{document.id}.operations", data
34
+ end
35
+
36
+ private
37
+
38
+ def collaborative_model
39
+ raise 'You must override the collaborative_model method to specify your collaborative model'
40
+ end
41
+
42
+ # Send out initial versions
43
+ def send_attribute_versions(document)
44
+ collaborative_model.collaborative_attributes.each do |attribute_name|
45
+ attribute = document.collaborative_attribute(attribute_name)
46
+
47
+ transmit(
48
+ document_id: document.id,
49
+ action: 'attribute',
50
+ attribute: attribute_name,
51
+ version: attribute.version
52
+ )
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,4 @@
1
+ module LooseLeaf
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
data/lib/loose_leaf.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'ot'
2
+ require 'loose_leaf/engine'
3
+ require 'loose_leaf/document'
4
+ require 'loose_leaf/document_attribute'
5
+
6
+ module LooseLeaf
7
+ # Your code goes here...
8
+ end
@@ -0,0 +1,99 @@
1
+ # code base
2
+ # https://github.com/ball-hayden/Collaborate
3
+
4
+ module LooseLeaf
5
+ module Document
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ after_initialize :setup_collaborative_attributes
10
+ end
11
+
12
+ module ClassMethods
13
+ def collaborative_attributes(*attributes)
14
+ return @collaborative_attributes if attributes.size.zero?
15
+
16
+ @collaborative_attributes = attributes.map(&:to_s)
17
+
18
+ bind_collaborative_document_attributes
19
+ end
20
+
21
+ def collaborative_key(attribute = nil)
22
+ return @collaborative_key || :id if attribute.blank?
23
+ @collaborative_key ||= attribute
24
+ end
25
+
26
+ def find_by_collaborative_key(key)
27
+ query = {}
28
+ query[collaborative_key] = key
29
+ find_by(query)
30
+ end
31
+
32
+ private
33
+
34
+ def bind_collaborative_document_attributes
35
+ collaborative_attributes.each do |attribute|
36
+ bind_collaborative_document_attribute(attribute)
37
+ end
38
+ end
39
+
40
+ def bind_collaborative_document_attribute(attribute)
41
+ define_method("collaborative_#{attribute}") do
42
+ collaborative_attribute(attribute).value
43
+ end
44
+
45
+ define_method("collaborative_#{attribute}=") do |value|
46
+ collaborative_attribute(attribute).value = value
47
+ end
48
+
49
+ # Override the normal setter so that we can update our cache
50
+ define_method("#{attribute}=") do |value|
51
+ super(value)
52
+
53
+ collaborative_attribute(attribute).value = value
54
+ end
55
+ end
56
+ end
57
+
58
+ def collaborative_attribute(attribute_name)
59
+ @collaborative_attributes[attribute_name.to_s]
60
+ end
61
+
62
+ def collaborative_id
63
+ @collaborative_id ||= send(self.class.collaborative_key)
64
+ end
65
+
66
+ def apply_operation(data)
67
+ operation = OT::TextOperation.from_a data[:operation]
68
+ attribute = data[:attribute]
69
+ version = data[:version]
70
+
71
+ collaborative_attribute(attribute).apply_operation(operation, version)
72
+ end
73
+
74
+ def commit_collaborative_attributes(*attributes)
75
+ attributes = attributes.flatten.map(&:to_s)
76
+
77
+ # Intersect with allowed attributes
78
+ attributes &= self.class.collaborative_attributes
79
+
80
+ attributes.each do |attribute|
81
+ send("#{attribute}=", send("collaborative_#{attribute}"))
82
+ end
83
+
84
+ save
85
+ end
86
+
87
+ def clear_collaborative_cache(attribute)
88
+ collaborative_attribute(attribute).clear_cache
89
+ end
90
+
91
+ def setup_collaborative_attributes
92
+ @collaborative_attributes = {}
93
+
94
+ self.class.collaborative_attributes.each do |attribute|
95
+ @collaborative_attributes[attribute] = DocumentAttribute.new(self, attribute)
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,90 @@
1
+ module LooseLeaf
2
+ class DocumentAttribute
3
+ attr_reader :document, :attribute
4
+
5
+ def initialize(document, attribute)
6
+ @document = document
7
+ @attribute = attribute
8
+
9
+ @operations = []
10
+ end
11
+
12
+ # Based on https://github.com/Operational-Transformation/ot.js/blob/15d4e7/lib/server.js#L16
13
+ def apply_operation(operation, client_version)
14
+ Rails.logger.debug "Applying client version #{client_version}, server version: #{version}"
15
+
16
+ unless client_version > version
17
+ operation = transform_old_operation(operation, client_version)
18
+ end
19
+
20
+ store_operation(operation)
21
+
22
+ self.value = new_text(operation)
23
+
24
+ [version, operation]
25
+ end
26
+
27
+ def version
28
+ operations.length
29
+ end
30
+
31
+ def value
32
+ cached_value || document.attributes[attribute.to_s]
33
+ end
34
+
35
+ def value=(value)
36
+ return unless document.persisted?
37
+
38
+ Rails.cache.write(value_key, value)
39
+ end
40
+
41
+ def clear_cache
42
+ Rails.cache.delete(value_key)
43
+ end
44
+
45
+ def clear_operations_cache
46
+ Rails.cache.delete(operations_key)
47
+ end
48
+
49
+ private
50
+
51
+ def value_key
52
+ "loose_leaf-#{document.class}-#{document.id}-#{attribute}"
53
+ end
54
+
55
+ def cached_value
56
+ return unless document.persisted?
57
+ Rails.cache.read(value_key)
58
+ end
59
+
60
+ def operations_key
61
+ "loose_leaf-#{document.class}-#{document.id}-#{attribute}-operations"
62
+ end
63
+
64
+ def operations
65
+ Rails.cache.read(operations_key) || []
66
+ end
67
+
68
+ def store_operation(operation)
69
+ Rails.cache.write(operations_key, operations + [operation])
70
+ end
71
+
72
+ def new_text(operation)
73
+ operation.apply(value || '')
74
+ end
75
+
76
+ def transform_old_operation(operation, client_version)
77
+ server_version = version
78
+ concurrent_operations = operations.slice(
79
+ client_version - 1,
80
+ server_version - client_version + 1
81
+ )
82
+
83
+ concurrent_operations.each do |other_operation|
84
+ operation = OT::TextOperation.transform(operation, other_operation).first
85
+ end
86
+
87
+ operation
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,16 @@
1
+ module LooseLeaf
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace LooseLeaf
4
+
5
+ initializer 'loose_leaf.vendor_assets' do |app|
6
+ end
7
+
8
+ config.generators do |g|
9
+ g.test_framework :rspec
10
+ g.factory_girl suffix: 'factory'
11
+ g.helper false
12
+ g.stylesheets false
13
+ g.javascripts false
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ module LooseLeaf
2
+ VERSION = '0.0.1'.freeze
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :loose_leaf do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,342 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: loose_leaf
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Akira Takahashi
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-10-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 5.0.0
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 5.0.0.1
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: 5.0.0
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 5.0.0.1
33
+ - !ruby/object:Gem::Dependency
34
+ name: ot
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rspec
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: rspec-rails
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: rspec-expectations
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: pry
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ - !ruby/object:Gem::Dependency
104
+ name: pry-byebug
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ - !ruby/object:Gem::Dependency
118
+ name: pry-stack_explorer
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ type: :development
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ - !ruby/object:Gem::Dependency
132
+ name: pry-doc
133
+ requirement: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ type: :development
139
+ prerelease: false
140
+ version_requirements: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ - !ruby/object:Gem::Dependency
146
+ name: pry-state
147
+ requirement: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ type: :development
153
+ prerelease: false
154
+ version_requirements: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ - !ruby/object:Gem::Dependency
160
+ name: pry-toys
161
+ requirement: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ type: :development
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - ">="
171
+ - !ruby/object:Gem::Version
172
+ version: '0'
173
+ - !ruby/object:Gem::Dependency
174
+ name: pry-rescue
175
+ requirement: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - ">="
178
+ - !ruby/object:Gem::Version
179
+ version: '0'
180
+ type: :development
181
+ prerelease: false
182
+ version_requirements: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - ">="
185
+ - !ruby/object:Gem::Version
186
+ version: '0'
187
+ - !ruby/object:Gem::Dependency
188
+ name: simplecov
189
+ requirement: !ruby/object:Gem::Requirement
190
+ requirements:
191
+ - - ">="
192
+ - !ruby/object:Gem::Version
193
+ version: '0'
194
+ type: :development
195
+ prerelease: false
196
+ version_requirements: !ruby/object:Gem::Requirement
197
+ requirements:
198
+ - - ">="
199
+ - !ruby/object:Gem::Version
200
+ version: '0'
201
+ - !ruby/object:Gem::Dependency
202
+ name: coveralls
203
+ requirement: !ruby/object:Gem::Requirement
204
+ requirements:
205
+ - - ">="
206
+ - !ruby/object:Gem::Version
207
+ version: '0'
208
+ type: :development
209
+ prerelease: false
210
+ version_requirements: !ruby/object:Gem::Requirement
211
+ requirements:
212
+ - - ">="
213
+ - !ruby/object:Gem::Version
214
+ version: '0'
215
+ - !ruby/object:Gem::Dependency
216
+ name: codeclimate-test-reporter
217
+ requirement: !ruby/object:Gem::Requirement
218
+ requirements:
219
+ - - ">="
220
+ - !ruby/object:Gem::Version
221
+ version: '0'
222
+ type: :development
223
+ prerelease: false
224
+ version_requirements: !ruby/object:Gem::Requirement
225
+ requirements:
226
+ - - ">="
227
+ - !ruby/object:Gem::Version
228
+ version: '0'
229
+ - !ruby/object:Gem::Dependency
230
+ name: rubocop
231
+ requirement: !ruby/object:Gem::Requirement
232
+ requirements:
233
+ - - ">="
234
+ - !ruby/object:Gem::Version
235
+ version: '0'
236
+ type: :development
237
+ prerelease: false
238
+ version_requirements: !ruby/object:Gem::Requirement
239
+ requirements:
240
+ - - ">="
241
+ - !ruby/object:Gem::Version
242
+ version: '0'
243
+ - !ruby/object:Gem::Dependency
244
+ name: guard
245
+ requirement: !ruby/object:Gem::Requirement
246
+ requirements:
247
+ - - ">="
248
+ - !ruby/object:Gem::Version
249
+ version: '0'
250
+ type: :development
251
+ prerelease: false
252
+ version_requirements: !ruby/object:Gem::Requirement
253
+ requirements:
254
+ - - ">="
255
+ - !ruby/object:Gem::Version
256
+ version: '0'
257
+ - !ruby/object:Gem::Dependency
258
+ name: guard-rspec
259
+ requirement: !ruby/object:Gem::Requirement
260
+ requirements:
261
+ - - ">="
262
+ - !ruby/object:Gem::Version
263
+ version: '0'
264
+ type: :development
265
+ prerelease: false
266
+ version_requirements: !ruby/object:Gem::Requirement
267
+ requirements:
268
+ - - ">="
269
+ - !ruby/object:Gem::Version
270
+ version: '0'
271
+ - !ruby/object:Gem::Dependency
272
+ name: guard-rubocop
273
+ requirement: !ruby/object:Gem::Requirement
274
+ requirements:
275
+ - - ">="
276
+ - !ruby/object:Gem::Version
277
+ version: '0'
278
+ type: :development
279
+ prerelease: false
280
+ version_requirements: !ruby/object:Gem::Requirement
281
+ requirements:
282
+ - - ">="
283
+ - !ruby/object:Gem::Version
284
+ version: '0'
285
+ - !ruby/object:Gem::Dependency
286
+ name: gem-release
287
+ requirement: !ruby/object:Gem::Requirement
288
+ requirements:
289
+ - - ">="
290
+ - !ruby/object:Gem::Version
291
+ version: '0'
292
+ type: :development
293
+ prerelease: false
294
+ version_requirements: !ruby/object:Gem::Requirement
295
+ requirements:
296
+ - - ">="
297
+ - !ruby/object:Gem::Version
298
+ version: '0'
299
+ description:
300
+ email:
301
+ - rike422@gmail.com
302
+ executables: []
303
+ extensions: []
304
+ extra_rdoc_files: []
305
+ files:
306
+ - MIT-LICENSE
307
+ - README.md
308
+ - Rakefile
309
+ - app/assets/javascripts/loose_leaf/index.js
310
+ - app/channels/loose_leaf/collaboration_channel.rb
311
+ - app/controllers/loose_leaf/application_controller.rb
312
+ - lib/loose_leaf.rb
313
+ - lib/loose_leaf/document.rb
314
+ - lib/loose_leaf/document_attribute.rb
315
+ - lib/loose_leaf/engine.rb
316
+ - lib/loose_leaf/version.rb
317
+ - lib/tasks/loose_leaf_tasks.rake
318
+ homepage: https://github.com/rike422/loose-leaf
319
+ licenses:
320
+ - MIT
321
+ metadata: {}
322
+ post_install_message:
323
+ rdoc_options: []
324
+ require_paths:
325
+ - lib
326
+ required_ruby_version: !ruby/object:Gem::Requirement
327
+ requirements:
328
+ - - ">="
329
+ - !ruby/object:Gem::Version
330
+ version: '0'
331
+ required_rubygems_version: !ruby/object:Gem::Requirement
332
+ requirements:
333
+ - - ">="
334
+ - !ruby/object:Gem::Version
335
+ version: '0'
336
+ requirements: []
337
+ rubyforge_project:
338
+ rubygems_version: 2.5.1
339
+ signing_key:
340
+ specification_version: 4
341
+ summary: A Rails gem for real-time collaboration using ActionCable
342
+ test_files: []