hyper-store 0.2.0

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,91 @@
1
+ module HyperStore
2
+ class StateWrapper < BaseStoreClass
3
+ module ArgumentValidator
4
+ class InvalidOptionError < StandardError; end
5
+
6
+ def validate_args!(klass, *args, &block)
7
+ name, initial_value, opts = parse_arguments(*args, &block)
8
+
9
+ opts[:scope] ||= default_scope(klass)
10
+ opts[:initializer] = validate_initializer(initial_value, klass, opts)
11
+ opts[:block] = block if block
12
+
13
+ if opts[:reader]
14
+ opts[:reader] = opts[:reader] == true ? name : opts[:reader]
15
+ end
16
+
17
+ [name, opts]
18
+ end
19
+
20
+ private
21
+
22
+ def invalid_option(message)
23
+ raise InvalidOptionError, message
24
+ end
25
+
26
+ # Parses the arguments given to get the name, initial_value (if any), and options
27
+ def parse_arguments(*args)
28
+ # If the only argument is a hash, the first key => value is name => inital_value
29
+ if args.first.is_a?(Hash)
30
+ # If the first key passed in is not the name, raise an error
31
+ if [:reader, :initializer, :scope].include?(args.first.keys.first.to_sym)
32
+ message = 'The name of the state must be specified first as '\
33
+ "either 'state :name' or 'state name: nil'"
34
+ invalid_option(message)
35
+ end
36
+
37
+ name, initial_value = args[0].shift
38
+ # Otherwise just the name is passed in by itself first
39
+ else
40
+ name = args.shift
41
+ end
42
+
43
+ # [name, initial_value (can be nil), args (if nil then return an empty hash)]
44
+ [name, initial_value, args[0] || {}]
45
+ end
46
+
47
+ # Converts the initialize option to a Proc
48
+ def validate_initializer(initial_value, klass, opts) # rubocop:disable Metrics/MethodLength
49
+ # If we pass in the name as a hash with a value ex: state foo: :bar,
50
+ # we just put that value inside a Proc and return that
51
+ if initial_value
52
+ dup_or_return_intial_value(initial_value)
53
+ # If we pass in the initialize option
54
+ elsif opts[:initializer]
55
+ # If it's a Symbol we convert to to a Proc that calls the method on the instance
56
+ if [Symbol, String].include?(opts[:initializer].class)
57
+ method_name = opts[:initializer]
58
+ if [:class, :shared].include?(opts[:scope])
59
+ -> { klass.send(:"#{method_name}") }
60
+ else
61
+ ->(instance) { instance.send(:"#{method_name}") }
62
+ end
63
+ # If it is already a Proc we do nothing and just return what was given
64
+ elsif opts[:initializer].is_a?(Proc)
65
+ opts[:initializer]
66
+ # If it's not a Proc or a String we raise an error and return an empty Proc
67
+ else
68
+ invalid_option("'state' option 'initialize' must either be a Symbol or a Proc")
69
+ -> {}
70
+ end
71
+ # Otherwise if it's not specified we just return an empty Proc
72
+ else
73
+ -> {}
74
+ end
75
+ end
76
+
77
+ # Dup the initial value if possible, otherwise just return it
78
+ # Ruby has no nice way of doing this...
79
+ def dup_or_return_intial_value(value)
80
+ value =
81
+ begin
82
+ value.dup
83
+ rescue
84
+ value
85
+ end
86
+
87
+ -> { value }
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,3 @@
1
+ module HyperStore
2
+ VERSION = '0.2.0'
3
+ end
@@ -0,0 +1,34 @@
1
+ module Hyperloop
2
+ # insure at least a stub of operation is defined. If
3
+ # Hyperloop::Operation is already loaded it will have
4
+ # defined these.
5
+ class Operation
6
+ class << self
7
+ def on_dispatch(&block)
8
+ receivers << block
9
+ end
10
+
11
+ def receivers
12
+ @receivers ||= []
13
+ end
14
+ end
15
+ end unless defined? Operation
16
+ class Application
17
+ class Boot < Operation
18
+ class ReactDummyParams
19
+ attr_reader :context
20
+ def initialize(context)
21
+ @context = context
22
+ end
23
+ end
24
+ def self.run(context: nil)
25
+ params = ReactDummyParams.new(context)
26
+ receivers.each do |receiver|
27
+ receiver.call params
28
+ end
29
+ rescue Exception => e
30
+ puts "called Boot.run and she broke #{e}"
31
+ end
32
+ end unless defined? Boot
33
+ end
34
+ end
@@ -0,0 +1,12 @@
1
+ module Hyperloop
2
+ class Store
3
+ class << self
4
+ def inherited(child)
5
+ child.include(Mixin)
6
+ end
7
+ end
8
+ def initialize
9
+ init_store
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,21 @@
1
+ module Hyperloop
2
+ class Store
3
+ module Mixin
4
+ class << self
5
+ def included(base)
6
+ base.include(HyperStore::InstanceMethods)
7
+ base.extend(HyperStore::ClassMethods)
8
+ base.extend(HyperStore::DispatchReceiver)
9
+
10
+ base.singleton_class.define_singleton_method(:__state_wrapper) do
11
+ @__state_wrapper ||= Class.new(HyperStore::StateWrapper)
12
+ end
13
+
14
+ base.singleton_class.define_singleton_method(:state) do |*args, &block|
15
+ __state_wrapper.define_state_methods(base, *args, &block)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,29 @@
1
+ module React
2
+ class Observable
3
+ def initialize(value, on_change = nil, &block)
4
+ @value = value
5
+ @on_change = on_change || block
6
+ end
7
+
8
+ def method_missing(method_sym, *args, &block)
9
+ @value.send(method_sym, *args, &block).tap { |result| @on_change.call @value }
10
+ end
11
+
12
+ def respond_to?(method, *args)
13
+ if [:call, :to_proc].include? method
14
+ true
15
+ else
16
+ @value.respond_to? method, *args
17
+ end
18
+ end
19
+
20
+ def call(new_value)
21
+ @on_change.call new_value
22
+ @value = new_value
23
+ end
24
+
25
+ def to_proc
26
+ lambda { |arg = @value| @on_change.call arg }
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,158 @@
1
+ module React
2
+ class State
3
+
4
+ ALWAYS_UPDATE_STATE_AFTER_RENDER = true
5
+ @rendering_level = 0
6
+
7
+ class << self
8
+ attr_reader :current_observer
9
+
10
+ def has_observers?(object, name)
11
+ !observers_by_name[object][name].empty?
12
+ end
13
+
14
+ def bulk_update
15
+ saved_bulk_update_flag = @bulk_update_flag
16
+ @bulk_update_flag = true
17
+ yield
18
+ ensure
19
+ @bulk_update_flag = saved_bulk_update_flag
20
+ end
21
+
22
+ def set_state2(object, name, value, updates, exclusions = nil)
23
+ # set object's name state to value, tell all observers it has changed.
24
+ # Observers must implement update_react_js_state
25
+ object_needs_notification = object.respond_to? :update_react_js_state
26
+ observers_by_name[object][name].dup.each do |observer|
27
+ next if exclusions && exclusions.include?(observer)
28
+ updates[observer] += [object, name, value]
29
+ object_needs_notification = false if object == observer
30
+ end
31
+ updates[object] += [nil, name, value] if object_needs_notification
32
+ end
33
+
34
+ def initialize_states(object, initial_values) # initialize objects' name/value pairs
35
+ states[object].merge!(initial_values || {})
36
+ end
37
+
38
+ def get_state(object, name, current_observer = @current_observer)
39
+ # get current value of name for object, remember that the current object depends on this state,
40
+ # current observer can be overriden with last param
41
+ if current_observer && !new_observers[current_observer][object].include?(name)
42
+ new_observers[current_observer][object] << name
43
+ end
44
+ if @delayed_updates && @delayed_updates[object][name]
45
+ @delayed_updates[object][name][1] << current_observer
46
+ end
47
+ states[object][name]
48
+ end
49
+
50
+ def set_state(object, name, value, delay=ALWAYS_UPDATE_STATE_AFTER_RENDER)
51
+ states[object][name] = value
52
+ if delay || @bulk_update_flag
53
+ @delayed_updates ||= Hash.new { |h, k| h[k] = {} }
54
+ @delayed_updates[object][name] = [value, Set.new]
55
+ @delayed_updater ||= after(0.001) do
56
+ delayed_updates = @delayed_updates
57
+ @delayed_updates = Hash.new { |h, k| h[k] = {} } # could this be nil???
58
+ @delayed_updater = nil
59
+ updates = Hash.new { |hash, key| hash[key] = Array.new }
60
+ delayed_updates.each do |object, name_hash|
61
+ name_hash.each do |name, value_and_set|
62
+ set_state2(object, name, value_and_set[0], updates, value_and_set[1])
63
+ end
64
+ end
65
+ updates.each { |observer, args| observer.update_react_js_state(*args) }
66
+ end
67
+ elsif @rendering_level == 0
68
+ updates = Hash.new { |hash, key| hash[key] = Array.new }
69
+ set_state2(object, name, value, updates)
70
+ updates.each { |observer, args| observer.update_react_js_state(*args) }
71
+ end
72
+ value
73
+ end
74
+
75
+ def notify_observers(object, name, value)
76
+ object_needs_notification = object.respond_to? :update_react_js_state
77
+ observers_by_name[object][name].dup.each do |observer|
78
+ observer.update_react_js_state(object, name, value)
79
+ object_needs_notification = false if object == observer
80
+ end
81
+ object.update_react_js_state(nil, name, value) if object_needs_notification
82
+ end
83
+
84
+ def notify_observers_after_thread_completes(object, name, value)
85
+ (@delayed_updates ||= []) << [object, name, value]
86
+ @delayed_updater ||= after(0) do
87
+ delayed_updates = @delayed_updates
88
+ @delayed_updates = []
89
+ @delayed_updater = nil
90
+ delayed_updates.each { |args| notify_observers(*args) }
91
+ end
92
+ end
93
+
94
+ def will_be_observing?(object, name, current_observer)
95
+ current_observer && new_observers[current_observer][object].include?(name)
96
+ end
97
+
98
+ def is_observing?(object, name, current_observer)
99
+ current_observer && observers_by_name[object][name].include?(current_observer)
100
+ end
101
+
102
+ def update_states_to_observe(current_observer = @current_observer) # should be called after the last after_render callback, currently called after components render method
103
+ raise "update_states_to_observer called outside of watch block" unless current_observer
104
+ current_observers[current_observer].each do |object, names|
105
+ names.each do |name|
106
+ observers_by_name[object][name].delete(current_observer)
107
+ end
108
+ end
109
+ observers = current_observers[current_observer] = new_observers[current_observer]
110
+ new_observers.delete(current_observer)
111
+ observers.each do |object, names|
112
+ names.each do |name|
113
+ observers_by_name[object][name] << current_observer
114
+ end
115
+ end
116
+ end
117
+
118
+ def remove # call after component is unmounted
119
+ raise "remove called outside of watch block" unless @current_observer
120
+ current_observers[@current_observer].each do |object, names|
121
+ names.each do |name|
122
+ observers_by_name[object][name].delete(@current_observer)
123
+ end
124
+ end
125
+ current_observers.delete(@current_observer)
126
+ end
127
+
128
+ def set_state_context_to(observer, rendering = nil) # wrap all execution that may set or get states in a block so we know which observer is executing
129
+ if `typeof Opal.global.reactive_ruby_timing !== 'undefined'`
130
+ @nesting_level = (@nesting_level || 0) + 1
131
+ start_time = Time.now.to_f
132
+ observer_name = (observer.class.respond_to?(:name) ? observer.class.name : observer.to_s) rescue "object:#{observer.object_id}"
133
+ end
134
+ saved_current_observer = @current_observer
135
+ @current_observer = observer
136
+ @rendering_level += 1 if rendering
137
+ return_value = yield
138
+ return_value
139
+ ensure
140
+ @current_observer = saved_current_observer
141
+ @rendering_level -= 1 if rendering
142
+ @nesting_level = [0, @nesting_level - 1].max if `typeof Opal.global.reactive_ruby_timing !== 'undefined'`
143
+ return_value
144
+ end
145
+
146
+ def states
147
+ @states ||= Hash.new { |h, k| h[k] = {} }
148
+ end
149
+
150
+ [:new_observers, :current_observers, :observers_by_name].each do |method_name|
151
+ define_method(method_name) do
152
+ instance_variable_get("@#{method_name}") ||
153
+ instance_variable_set("@#{method_name}", Hash.new { |h, k| h[k] = Hash.new { |h, k| h[k] = [] } })
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
metadata ADDED
@@ -0,0 +1,293 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hyper-store
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - catmando
8
+ - adamcreekroad
9
+ autorequire:
10
+ bindir: exe
11
+ cert_chain: []
12
+ date: 2017-03-13 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: hyperloop-config
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: bundler
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '1.12'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '1.12'
42
+ - !ruby/object:Gem::Dependency
43
+ name: hyper-react
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: 0.12.0
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: 0.12.0
56
+ - !ruby/object:Gem::Dependency
57
+ name: hyper-spec
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: listen
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ - !ruby/object:Gem::Dependency
85
+ name: opal
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ - !ruby/object:Gem::Dependency
99
+ name: opal-browser
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ - !ruby/object:Gem::Dependency
113
+ name: opal-rails
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: pry-byebug
128
+ requirement: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ type: :development
134
+ prerelease: false
135
+ version_requirements: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ - !ruby/object:Gem::Dependency
141
+ name: rails
142
+ requirement: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ type: :development
148
+ prerelease: false
149
+ version_requirements: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ - !ruby/object:Gem::Dependency
155
+ name: rake
156
+ requirement: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - "~>"
159
+ - !ruby/object:Gem::Version
160
+ version: '10.0'
161
+ type: :development
162
+ prerelease: false
163
+ version_requirements: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - "~>"
166
+ - !ruby/object:Gem::Version
167
+ version: '10.0'
168
+ - !ruby/object:Gem::Dependency
169
+ name: react-rails
170
+ requirement: !ruby/object:Gem::Requirement
171
+ requirements:
172
+ - - "<"
173
+ - !ruby/object:Gem::Version
174
+ version: 1.10.0
175
+ type: :development
176
+ prerelease: false
177
+ version_requirements: !ruby/object:Gem::Requirement
178
+ requirements:
179
+ - - "<"
180
+ - !ruby/object:Gem::Version
181
+ version: 1.10.0
182
+ - !ruby/object:Gem::Dependency
183
+ name: rspec
184
+ requirement: !ruby/object:Gem::Requirement
185
+ requirements:
186
+ - - ">="
187
+ - !ruby/object:Gem::Version
188
+ version: '0'
189
+ type: :development
190
+ prerelease: false
191
+ version_requirements: !ruby/object:Gem::Requirement
192
+ requirements:
193
+ - - ">="
194
+ - !ruby/object:Gem::Version
195
+ version: '0'
196
+ - !ruby/object:Gem::Dependency
197
+ name: rspec-steps
198
+ requirement: !ruby/object:Gem::Requirement
199
+ requirements:
200
+ - - ">="
201
+ - !ruby/object:Gem::Version
202
+ version: '0'
203
+ type: :development
204
+ prerelease: false
205
+ version_requirements: !ruby/object:Gem::Requirement
206
+ requirements:
207
+ - - ">="
208
+ - !ruby/object:Gem::Version
209
+ version: '0'
210
+ - !ruby/object:Gem::Dependency
211
+ name: sqlite3
212
+ requirement: !ruby/object:Gem::Requirement
213
+ requirements:
214
+ - - ">="
215
+ - !ruby/object:Gem::Version
216
+ version: '0'
217
+ type: :development
218
+ prerelease: false
219
+ version_requirements: !ruby/object:Gem::Requirement
220
+ requirements:
221
+ - - ">="
222
+ - !ruby/object:Gem::Version
223
+ version: '0'
224
+ - !ruby/object:Gem::Dependency
225
+ name: rubocop
226
+ requirement: !ruby/object:Gem::Requirement
227
+ requirements:
228
+ - - ">="
229
+ - !ruby/object:Gem::Version
230
+ version: '0'
231
+ type: :development
232
+ prerelease: false
233
+ version_requirements: !ruby/object:Gem::Requirement
234
+ requirements:
235
+ - - ">="
236
+ - !ruby/object:Gem::Version
237
+ version: '0'
238
+ description:
239
+ email:
240
+ - mitch@catprint.com
241
+ executables: []
242
+ extensions: []
243
+ extra_rdoc_files: []
244
+ files:
245
+ - ".gitignore"
246
+ - ".rubocop.yml"
247
+ - CODE_OF_CONDUCT.md
248
+ - Gemfile
249
+ - Gemfile.lock
250
+ - LICENSE.txt
251
+ - README.md
252
+ - Rakefile
253
+ - bin/console
254
+ - bin/setup
255
+ - hyper-store.gemspec
256
+ - lib/hyper-store.rb
257
+ - lib/hyper-store/class_methods.rb
258
+ - lib/hyper-store/dispatch_receiver.rb
259
+ - lib/hyper-store/instance_methods.rb
260
+ - lib/hyper-store/mutator_wrapper.rb
261
+ - lib/hyper-store/state_wrapper.rb
262
+ - lib/hyper-store/state_wrapper/argument_validator.rb
263
+ - lib/hyper-store/version.rb
264
+ - lib/hyperloop/application/boot.rb
265
+ - lib/hyperloop/store.rb
266
+ - lib/hyperloop/store/mixin.rb
267
+ - lib/react/observable.rb
268
+ - lib/react/state.rb
269
+ homepage: https://ruby-hyperloop.io
270
+ licenses:
271
+ - MIT
272
+ metadata: {}
273
+ post_install_message:
274
+ rdoc_options: []
275
+ require_paths:
276
+ - lib
277
+ required_ruby_version: !ruby/object:Gem::Requirement
278
+ requirements:
279
+ - - ">="
280
+ - !ruby/object:Gem::Version
281
+ version: '0'
282
+ required_rubygems_version: !ruby/object:Gem::Requirement
283
+ requirements:
284
+ - - ">="
285
+ - !ruby/object:Gem::Version
286
+ version: '0'
287
+ requirements: []
288
+ rubyforge_project:
289
+ rubygems_version: 2.5.1
290
+ signing_key:
291
+ specification_version: 4
292
+ summary: Flux Stores and more for Hyperloop
293
+ test_files: []