hyper-store 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []