hyper-operation 1.0.alpha1.3 → 1.0.alpha1.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -0
  3. data/.travis.yml +1 -0
  4. data/hyper-operation.gemspec +6 -5
  5. data/lib/hyper-operation.rb +2 -1
  6. data/lib/hyper-operation/api.rb +6 -2
  7. data/lib/hyper-operation/async_sleep.rb +23 -0
  8. data/lib/hyper-operation/promise.rb +32 -2
  9. data/lib/hyper-operation/railway/dispatcher.rb +0 -1
  10. data/lib/hyper-operation/railway/run.rb +57 -48
  11. data/lib/hyper-operation/railway/validations.rb +9 -2
  12. data/lib/hyper-operation/server_op.rb +6 -2
  13. data/lib/hyper-operation/transport/client_drivers.rb +41 -11
  14. data/lib/hyper-operation/transport/connection.rb +58 -136
  15. data/lib/hyper-operation/transport/connection_adapter/active_record.rb +113 -0
  16. data/lib/hyper-operation/transport/connection_adapter/active_record/auto_create.rb +26 -0
  17. data/lib/hyper-operation/transport/connection_adapter/active_record/connection.rb +47 -0
  18. data/lib/hyper-operation/transport/connection_adapter/active_record/queued_message.rb +42 -0
  19. data/lib/hyper-operation/transport/connection_adapter/redis.rb +94 -0
  20. data/lib/hyper-operation/transport/connection_adapter/redis/connection.rb +85 -0
  21. data/lib/hyper-operation/transport/connection_adapter/redis/queued_message.rb +34 -0
  22. data/lib/hyper-operation/transport/connection_adapter/redis/redis_record.rb +158 -0
  23. data/lib/hyper-operation/transport/hyperstack.rb +15 -2
  24. data/lib/hyper-operation/transport/hyperstack_controller.rb +5 -1
  25. data/lib/hyper-operation/transport/policy.rb +9 -4
  26. data/lib/hyper-operation/version.rb +1 -1
  27. metadata +66 -45
  28. data/Gemfile.lock +0 -407
  29. data/lib/hyper-operation/delay_and_interval.rb +0 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9684386e717f491348c6acab32279464d69eae7a30dad4177a86e5b05e34b47a
4
- data.tar.gz: 8a7ffd0abd510e70a657ce14ee796265520646a8a709fd4e98b8589c568da42e
3
+ metadata.gz: 89a7c07b2cab1760bb41bcff159333e5ea985d8eca22329f4a39f7890d295e5f
4
+ data.tar.gz: 5955718c34bba9f9af227ea92e0d0869a4f21c131deccd2ad2f0e707c97b8219
5
5
  SHA512:
6
- metadata.gz: a52bbf0183579a349caaa55afc19179d851a900c335bda1a51a13fafc0a9535ec456dd4f2c864bb33766ca2329bf912bde9fba0b13c236a65a5d5bd9763843e1
7
- data.tar.gz: 7a2127f42df5613b3a4f030afa05fc1b3c30e5d0d2afa7b6c32df05ccf33e43678584fa4e54e58562d65caad7936279baf0b700c4fd2c482cecec42ddc0eddf4
6
+ metadata.gz: 215aaf6fa7aa4084551d000b96a5dcf62c1839ff346fd8e0c75b6c66eea32274867e93eca3d1f485ccef0ed0f2cbc5e7442da994016a8446d49daa8156c51832
7
+ data.tar.gz: 18944dce11ebdbd25616d2b4a0d7565ea2ddbbf033ba78f36608102220e95278433f4a7b5e4241032758fda4f5b7a5bec99cb6d1236501f4c6ee729b17bd2736
data/.gitignore CHANGED
@@ -53,3 +53,7 @@ bower.json
53
53
  .idea
54
54
  .vscode
55
55
  *.iml
56
+
57
+ # ignore Gemfile.locks https://yehudakatz.com/2010/12/16/clarifying-the-roles-of-the-gemspec-and-gemfile/
58
+ /spec/test_app/Gemfile.lock
59
+ /Gemfile.lock
data/.travis.yml CHANGED
@@ -7,6 +7,7 @@ rvm:
7
7
  - ruby-head
8
8
  services:
9
9
  - mysql
10
+ - redis-server
10
11
  env:
11
12
  - DRIVER=google-chrome TZ=Europe/Berlin
12
13
  matrix:
@@ -29,24 +29,25 @@ Gem::Specification.new do |spec|
29
29
  spec.add_dependency 'opal-activesupport', '~> 0.3.1'
30
30
  spec.add_dependency 'tty-table'
31
31
 
32
- spec.add_development_dependency 'bundler', ['>= 1.17.3', '< 2.1']
32
+ spec.add_development_dependency 'bundler'
33
33
  spec.add_development_dependency 'chromedriver-helper'
34
34
  spec.add_development_dependency 'database_cleaner'
35
35
  spec.add_development_dependency 'hyper-spec', Hyperstack::Operation::VERSION
36
36
  spec.add_development_dependency 'mysql2'
37
- spec.add_development_dependency 'opal', '>= 0.11.0', '< 0.12.0'
38
37
  spec.add_development_dependency 'opal-browser', '~> 0.2.0'
39
- spec.add_development_dependency 'opal-rails', '~> 0.9.4'
38
+ spec.add_development_dependency 'opal-rails', '>= 0.9.4', '< 2.0'
40
39
  spec.add_development_dependency 'pry-rescue'
40
+ spec.add_development_dependency 'pry-stack_explorer'
41
41
  spec.add_development_dependency 'puma'
42
42
  spec.add_development_dependency 'pusher'
43
43
  spec.add_development_dependency 'pusher-fake'
44
- spec.add_development_dependency 'rails', '>= 4.0.0'
44
+ spec.add_development_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 7.0'
45
45
  spec.add_development_dependency 'rake'
46
46
  spec.add_development_dependency 'react-rails', '>= 2.4.0', '< 2.5.0'
47
+ spec.add_development_dependency 'redis'
47
48
  spec.add_development_dependency 'rspec-rails'
48
49
  spec.add_development_dependency 'rspec-steps', '~> 2.1.1'
49
50
  spec.add_development_dependency 'rspec-wait'
50
- spec.add_development_dependency 'sqlite3'
51
+ spec.add_development_dependency 'sqlite3', '~> 1.4.2' # see https://github.com/rails/rails/issues/35153
51
52
  spec.add_development_dependency 'timecop', '~> 0.8.1'
52
53
  end
@@ -28,6 +28,7 @@ if RUBY_ENGINE == 'opal'
28
28
  require 'hyper-operation/railway/validations'
29
29
  require 'hyper-operation/server_op'
30
30
  require 'hyper-operation/boot'
31
+ require 'hyper-operation/async_sleep'
31
32
  else
32
33
  require 'tty-table'
33
34
  require 'hyperstack-config'
@@ -45,7 +46,7 @@ else
45
46
  require 'hyper-operation/transport/client_drivers'
46
47
  require 'hyper-operation/transport/acting_user'
47
48
  require 'opal-activesupport'
48
- require 'hyper-operation/delay_and_interval'
49
+ require 'hyper-operation/async_sleep'
49
50
  require 'hyper-operation/exception'
50
51
  require 'hyper-operation/promise'
51
52
  require 'hyper-operation/railway'
@@ -46,8 +46,12 @@ module Hyperstack
46
46
  @_railway.process_params(args)
47
47
  @_railway.process_validations
48
48
  @_railway.run
49
- @_railway.dispatch
50
- @_railway.result
49
+ # return the result from dispatch in case there is an error
50
+ if (dispatch_result = @_railway.dispatch).rejected?
51
+ dispatch_result
52
+ else
53
+ @_railway.result
54
+ end
51
55
  end
52
56
  end
53
57
 
@@ -0,0 +1,23 @@
1
+ module Hyperstack
2
+ module AsyncSleep
3
+ if RUBY_ENGINE == 'opal'
4
+ def self.every(*args, &block)
5
+ every(*args, &block)
6
+ end
7
+
8
+ def self.after(*args, &block)
9
+ after(*args, &block)
10
+ end
11
+ else
12
+ extend self
13
+
14
+ def every(time, &block)
15
+ Thread.new { loop { sleep time; block.call } }
16
+ end
17
+
18
+ def after(time, &block)
19
+ Thread.new { sleep time; block.call }
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,3 +1,15 @@
1
+ # Caution. For now Hyperstack maintains its own copy of the Promise class.
2
+ # Eventually the diff between hyperstacks version and the official Opal version
3
+ # should be put into a PR.
4
+
5
+ # A key feature add is the Fail Exception class which is simply there to allow
6
+ # a `always` block to reject without raising an error. To use this see the run.rb
7
+ # module.
8
+
9
+ # Also see exception! method for the part of the code that detects the Fail exception.
10
+
11
+ # See https://github.com/opal/opal/issues/1967 for details.
12
+
1
13
  class Promise
2
14
  def self.value(value)
3
15
  new.resolve(value)
@@ -11,6 +23,13 @@ class Promise
11
23
  When.new(promises)
12
24
  end
13
25
 
26
+ class Fail < StandardError
27
+ attr_reader :result
28
+ def initialize(result)
29
+ @result = result
30
+ end
31
+ end
32
+
14
33
  attr_reader :error, :prev, :next
15
34
 
16
35
  def initialize(action = {})
@@ -104,6 +123,7 @@ class Promise
104
123
 
105
124
  begin
106
125
  if block = @action[:success] || @action[:always]
126
+ @realized = :resolve
107
127
  value = block.call(value)
108
128
  end
109
129
 
@@ -137,6 +157,9 @@ class Promise
137
157
 
138
158
  begin
139
159
  if block = @action[:failure] || @action[:always]
160
+ # temporarily set values so always can determine if this
161
+ # was a reject or resolve
162
+ @realized = :reject
140
163
  value = block.call(value)
141
164
  end
142
165
 
@@ -164,8 +187,15 @@ class Promise
164
187
  end
165
188
 
166
189
  def exception!(error)
167
- @exception = true
168
-
190
+ # If the error is a Promise::Fail, then
191
+ # the error becomes the error.result value
192
+ # this allows code to raise an error on an
193
+ # object that is not an error.
194
+ if error.is_a? Promise::Fail
195
+ error = error.result
196
+ else
197
+ @exception = true
198
+ end
169
199
  reject!(error)
170
200
  end
171
201
 
@@ -1,7 +1,6 @@
1
1
  module Hyperstack
2
2
  class Operation
3
3
  class Railway
4
-
5
4
  def receivers
6
5
  self.class.receivers
7
6
  end
@@ -4,10 +4,13 @@ module Hyperstack
4
4
  class Exit < StandardError
5
5
  attr_reader :state
6
6
  attr_reader :result
7
- def initialize(state, result)
7
+ def initialize(state, result = nil)
8
8
  @state = state
9
9
  @result = result
10
10
  end
11
+ def to_s
12
+ @state
13
+ end
11
14
  end
12
15
 
13
16
  class Railway
@@ -21,7 +24,7 @@ module Hyperstack
21
24
  @tracks ||= []
22
25
  end
23
26
 
24
- def to_opts(tie, args, block)
27
+ def build_tie(tie, args, block)
25
28
  if args.count.zero?
26
29
  { run: block }
27
30
  elsif args[0].is_a?(Hash)
@@ -41,7 +44,7 @@ module Hyperstack
41
44
 
42
45
  [:step, :failed, :async].each do |tie|
43
46
  define_method :"add_#{tie}" do |*args, &block|
44
- tracks << to_opts(tie, args, block)
47
+ tracks << build_tie(tie, args, block)
45
48
  end
46
49
  end
47
50
 
@@ -55,44 +58,36 @@ module Hyperstack
55
58
  end
56
59
 
57
60
  def step(opts)
58
- if @last_result.is_a? Promise
59
- @last_result = @last_result.then do |*result|
60
- @last_result = result
61
- apply(opts, :in_promise)
62
- end
63
- elsif @state == :success
64
- apply(opts)
65
- end
61
+ @promise_chain = @promise_chain
62
+ .then { |result| apply(result, :success, opts) }
66
63
  end
67
64
 
68
65
  def failed(opts)
69
- if @last_result.is_a? Promise
70
- @last_result = @last_result.fail do |e|
71
- @last_result = e
72
- apply(opts, :in_promise)
73
- raise @last_result if @last_result.is_a? Exception
74
- raise e
66
+ @promise_chain = @promise_chain
67
+ .always do |result|
68
+ @state = :failed if @promise_chain.rejected? && @state != :abort
69
+ apply(result, :failed, opts)
75
70
  end
76
- elsif @state == :failed
77
- apply(opts)
78
- end
79
71
  end
80
72
 
81
73
  def async(opts)
82
- apply(opts) if @state != :failed
74
+ @promise_chain = @promise_chain_start = Promise.new
75
+ @promise_chain.resolve(@last_async_result)
76
+ step(opts)
83
77
  end
84
78
 
85
- def apply(opts, in_promise = nil)
79
+ def apply(result, state, opts)
80
+ return result unless @state == state
86
81
  if opts[:scope] == :class
87
- args = [@operation, *@last_result]
82
+ args = [@operation, *result]
88
83
  instance = @operation.class
89
84
  else
90
- args = @last_result
85
+ args = result
91
86
  instance = @operation
92
87
  end
93
88
  block = opts[:run]
94
89
  block = instance.method(block) if block.is_a? Symbol
95
- @last_result =
90
+ last_result =
96
91
  if block.arity.zero?
97
92
  instance.instance_exec(&block)
98
93
  elsif args.is_a?(Array) && block.arity == args.count
@@ -100,40 +95,54 @@ module Hyperstack
100
95
  else
101
96
  instance.instance_exec(args, &block)
102
97
  end
103
- return @last_result unless @last_result.is_a? Promise
104
- raise @last_result.error if @last_result.rejected?
105
- @last_result = @last_result.value if @last_result.resolved?
106
- @last_result
98
+ @last_async_result = last_result unless last_result.is_a? Promise
99
+ last_result
107
100
  rescue Exit => e
108
- @state = e.state
109
- @last_result = (e.state != :failed || e.result.is_a?(Exception)) ? e.result : e
110
- raise e
101
+ # the promise chain ends with an always block which will process
102
+ # any immediate exits by checking the value of @state. All other
103
+ # step/failed/async blocks will be skipped because state will not equal
104
+ # :succeed or :failed
105
+ if e.state == :failed
106
+ @state = :abort
107
+ # exit via the final always block with the exception
108
+ raise e.result.is_a?(Exception) ? e.result : e
109
+ else
110
+ @state = :succeed
111
+ # exit via the final then block with the success value
112
+ e.result
113
+ end
111
114
  rescue Exception => e
112
115
  @state = :failed
113
- @last_result = e
114
- raise e if in_promise
116
+ raise e
115
117
  end
116
118
 
117
119
  def run
118
- if @operation.has_errors? || @state
119
- @last_result ||= ValidationException.new(@operation.instance_variable_get('@errors'))
120
- return if @state # handles abort out of validation
121
- @state = :failed
122
- else
123
- @state = :success
124
- end
125
- tracks.each { |opts| opts[:tie].bind(self).call(opts) }
126
- rescue Exit
120
+ # if @operation.has_errors? || @state
121
+ # @last_result ||= ValidationException.new(@operation.instance_variable_get('@errors'))
122
+ # # following handles abort out of validation. if state is already set then we are aborting
123
+ # # otherwise if state is not set but we have errors then we are failed
124
+ # @state ||= :failed
125
+ # else
126
+ # @state = :success
127
+ # end
128
+ @state ||= :success
129
+ @promise_chain_start = @promise_chain = Promise.new
130
+ @promise_chain_start.resolve(@last_result)
131
+ tracks.each { |opts| opts[:tie].bind(self).call(opts) } unless @state == :abort
127
132
  end
128
133
 
129
134
  def result
130
- return @last_result if @last_result.is_a? Promise
131
- @last_result =
132
- if @state == :success
133
- Promise.new.resolve(@last_result)
135
+ @result ||= @promise_chain.always do |e|
136
+ if %i[abort failed].include? @state
137
+ if e.is_a? Exception
138
+ raise e
139
+ else
140
+ raise Promise::Fail.new(e)
141
+ end
134
142
  else
135
- Promise.new.reject(@last_result)
143
+ e
136
144
  end
145
+ end
137
146
  end
138
147
  end
139
148
  end
@@ -28,6 +28,8 @@ module Hyperstack
28
28
  rescue Exit => e
29
29
  raise e unless e.state == :failed
30
30
  add_error(param, symbol, message)
31
+ # use a bogus exit state which will skip adding
32
+ # a validation error (see catch block in process_validations method)
31
33
  raise Exit.new(:abort_from_add_error, e.result)
32
34
  end
33
35
  end
@@ -47,17 +49,22 @@ module Hyperstack
47
49
  when :failed
48
50
  add_validation_error(i, "param validation #{i+1} aborted")
49
51
  end
50
- @state = :failed
52
+ @state = :abort
51
53
  return # break does not work in Opal
52
54
  rescue AccessViolation => e
53
55
  add_validation_error(i, e)
54
- @state = :failed
56
+ @state = :abort
55
57
  @last_result = e
56
58
  return # break does not work in Opal
57
59
  rescue Exception => e
58
60
  add_validation_error(i, e)
59
61
  end
60
62
  end
63
+ ensure
64
+ if @operation.has_errors?
65
+ @last_result ||= ValidationException.new(@operation.instance_variable_get('@errors'))
66
+ @state ||= :failed
67
+ end
61
68
  end
62
69
  end
63
70
  end
@@ -13,8 +13,8 @@ module Hyperstack
13
13
  hash = serialize_params(hash)
14
14
  Hyperstack::HTTP.post(
15
15
  "#{`window.HyperstackEnginePath`}/execute_remote",
16
- payload: {json: {operation: name, params: hash}.to_json},
17
- headers: {'X-CSRF-Token' => Hyperstack::ClientDrivers.opts[:form_authenticity_token] }
16
+ payload: {hyperstack_secured_json: {operation: name, params: hash}.to_json},
17
+ headers: headers.merge('X-CSRF-Token' => Hyperstack::ClientDrivers.opts[:form_authenticity_token])
18
18
  )
19
19
  .then do |response|
20
20
  deserialize_response response.json[:response]
@@ -129,6 +129,10 @@ module Hyperstack
129
129
  promise.reject e
130
130
  end
131
131
 
132
+ def headers
133
+ {}
134
+ end
135
+
132
136
  def serialize_params(hash)
133
137
  hash
134
138
  end
@@ -35,16 +35,35 @@ module Hyperstack
35
35
 
36
36
 
37
37
  if RUBY_ENGINE == 'opal'
38
+ # Patch in a dummy copy of Model.load in case we are not using models
39
+ # this will be defined properly by hyper-model
40
+ module Model
41
+ def self.load
42
+ Promise.new.tap { |promise| promise.resolve(yield) }
43
+ end unless respond_to?(:load)
44
+ end
45
+
38
46
  def self.connect(*channels)
39
47
  channels.each do |channel|
40
48
  if channel.is_a? Class
41
49
  IncomingBroadcast.connect_to(channel.name)
42
50
  elsif channel.is_a?(String) || channel.is_a?(Array)
43
51
  IncomingBroadcast.connect_to(*channel)
44
- elsif channel.id
45
- IncomingBroadcast.connect_to(channel.class.name, channel.id)
52
+ elsif channel.respond_to?(:id)
53
+ Hyperstack::Model.load do
54
+ channel.id
55
+ end.then do |id|
56
+ raise "Hyperstack.connect cannot connect to #{channel.inspect}. "\
57
+ "The id is nil. This can be caused by connecting to a model "\
58
+ "that is not saved, or that does not exist." unless id
59
+ IncomingBroadcast.connect_to(channel.class.name, id)
60
+ end
46
61
  else
47
- raise "cannot connect to model before it has been saved"
62
+ raise "Hyperstack.connect cannot connect to #{channel.inspect}.\n"\
63
+ "Channels must be either a class, or a class name,\n"\
64
+ "a string in the form 'ClassName-id',\n"\
65
+ "an array in the form [class, id] or [class-name, id],\n"\
66
+ "or an object that responds to the id method with a non-nil value"
48
67
  end
49
68
  end
50
69
  end
@@ -65,19 +84,22 @@ module Hyperstack
65
84
 
66
85
  def self.add_connection(channel_name, id = nil)
67
86
  channel_string = "#{channel_name}#{'-'+id.to_s if id}"
87
+ return if open_channels.include? channel_string
68
88
  open_channels << channel_string
69
89
  channel_string
70
90
  end
71
91
 
72
92
  def self.connect_to(channel_name, id = nil)
73
93
  channel_string = add_connection(channel_name, id)
94
+ return unless channel_string # already connected!
74
95
  if ClientDrivers.opts[:transport] == :pusher
75
96
  channel = "#{ClientDrivers.opts[:channel]}-#{channel_string}"
76
97
  %x{
77
98
  var channel = #{ClientDrivers.opts[:pusher_api]}.subscribe(#{channel.gsub('::', '==')});
78
99
  channel.bind('dispatch', #{ClientDrivers.opts[:dispatch]})
79
- channel.bind('pusher:subscription_succeeded', #{lambda {ClientDrivers.get_queued_data("connect-to-transport", channel_string)}})
100
+ channel.bind('pusher:subscription_succeeded', #{->(*) { ClientDrivers.get_queued_data("connect-to-transport", channel_string)}})
80
101
  }
102
+ @pusher_dispatcher_registered = true
81
103
  elsif ClientDrivers.opts[:transport] == :action_cable
82
104
  channel = "#{ClientDrivers.opts[:channel]}-#{channel_string}"
83
105
  Hyperstack::HTTP.post(ClientDrivers.polling_path('action-cable-auth', channel), headers: { 'X-CSRF-Token' => ClientDrivers.opts[:form_authenticity_token] }).then do |response|
@@ -147,7 +169,7 @@ module Hyperstack
147
169
  config_hash = {
148
170
  transport: Hyperstack.transport,
149
171
  id: id,
150
- acting_user_id: (controller.acting_user && controller.acting_user.id),
172
+ acting_user_id: (controller.acting_user.respond_to?(:id) && controller.acting_user.id),
151
173
  env: ::Rails.env,
152
174
  client_logging: Hyperstack.client_logging,
153
175
  pusher_fake_js: pusher_fake_js,
@@ -163,12 +185,17 @@ module Hyperstack
163
185
  # not sure why the second check is needed. It happens in the test app
164
186
  route.app == Hyperstack::Engine or (route.app.respond_to?(:app) and route.app.app == Hyperstack::Engine)
165
187
  end
166
- raise 'Hyperstack::Engine mount point not found. Check your config/routes.rb file' unless path
167
- path = path.path.spec
168
- "<script type='text/javascript'>\n"\
169
- "window.HyperstackEnginePath = '#{path}';\n"\
170
- "window.HyperstackOpts = #{config_hash.to_json}\n"\
171
- "</script>\n"
188
+ if path
189
+ path = path.path.spec
190
+ "<script type='text/javascript'>\n"\
191
+ "window.HyperstackEnginePath = '#{path}';\n"\
192
+ "window.HyperstackOpts = #{config_hash.to_json}\n"\
193
+ "</script>\n"
194
+ else
195
+ "<script type='text/javascript'>\n"\
196
+ "window.HyperstackOpts = #{config_hash.to_json}\n"\
197
+ "</script>\n"
198
+ end
172
199
  end if RUBY_ENGINE != 'opal'
173
200
 
174
201
  class << self
@@ -222,6 +249,9 @@ module Hyperstack
222
249
 
223
250
  @opts = Hash.new(`window.HyperstackOpts`)
224
251
 
252
+ if opts[:transport] != :none && `typeof(window.HyperstackEnginePath) == 'undefined'`
253
+ raise "No hyperstack mount point found!\nCheck your Rails routes.rb file";
254
+ end
225
255
 
226
256
  if opts[:transport] == :pusher
227
257