isomorfeus-redux 4.0.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,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3f0fa508494a00cb6c953ec0660d5a25fcdfeeadfe860b521cc60568da02357d
4
+ data.tar.gz: 7b318ed593527284d0e946067c278dceed3a4eaa0e8fbc0941b111c1f49d688a
5
+ SHA512:
6
+ metadata.gz: fb7e38b768252ae140861adb4fe56afdec8b6e74704a710efec364eeaa168a849e07b66dad85b59bb4af5fbaa80d685064d27ecaedbe6abfb5b68fb027ba9d15
7
+ data.tar.gz: 93e6ee7ae5bb629f0d480b71e87c96c49299102f37b41875e670911da5987619707ae1077d4e57abc4e508cbaa2d77fd2bc4f9b26f10d4c155fe09fab7d5879c
data/Gemfile ADDED
@@ -0,0 +1 @@
1
+ gemspec
@@ -0,0 +1,94 @@
1
+ # isomorfeus-redux
2
+
3
+ Redux for Opal Ruby.
4
+
5
+ ## Versioning
6
+ isomorfeus-redux version follows the Redux version which features and API it implements.
7
+ Isomorfeus-redux 4.0.x implements features and the API of Redux 4.0 and should be used with Redux4.0
8
+
9
+ ## Installation
10
+ To install redux with the matching version:
11
+ ```
12
+ yarn add redux@4.0.0
13
+ ```
14
+ then add to the Gemfile:
15
+ ```ruby
16
+ gem 'isomorfeus-redux'
17
+ ```
18
+ then `bundle install`
19
+ and to your client code add:
20
+ ```ruby
21
+ require 'isomorfeus-redux'
22
+ ```
23
+ ## Usage
24
+ Because isomorfeus-redux follows closely the Redux principles/implementation/API and Documentation, most things of the official Redux documentation
25
+ apply, but in the Ruby way, see:
26
+ - https://redux.js.org
27
+
28
+ Redux and accompanying libraries must be imported and available in the global namespace in the application javascript entry file,
29
+ with webpack this can be ensured by assigning them to the global namespace:
30
+ ```javascript
31
+ import * as Redux from 'redux';
32
+ global.Redux = Redux;
33
+ ```
34
+
35
+ Following features are presented with its differences to the Javascript Redux implementation.
36
+ Of course, in Ruby the naming is underscored, eg. `Redux.createStore` in javascript becomes `Redux.create_store` in Ruby.
37
+
38
+ ### Creating a Store
39
+ A store can be created using:
40
+ ```ruby
41
+ store = Redux::Store.new(reducer, preloaded_state, enhancer)
42
+ ```
43
+ or:
44
+ ```ruby
45
+ store = Redux.create_store(reducer, preloaded_state, enhancer)
46
+ ```
47
+ - **reducer** is a javascript function. Isomorfeus provides a helper to create a reducer, see below.
48
+ - **preloaded_state** can be a Ruby Hash or a native javascript object.
49
+ - **enhancer** is a javascript function.
50
+
51
+ ### Creating a Reducer
52
+ Its possible to use native javascript functions for creating a store. To use ruby conveniently for reducers a helper is provided:
53
+ ```ruby
54
+ reducer = Redux.create_reducer do |prev_state, action|
55
+ # do something here
56
+ {}.merge(prev_state)
57
+ end
58
+ ```
59
+ This helper wraps the ruby code block in a javascript function that takes care of converting Opal Hashes to javascript
60
+ objects and the other way around. The resulting reducer is simply javascript function, suitable for creating a store.
61
+
62
+ ### Adding a reducer to the global store
63
+ The reducer must be a Javascript function and can be created with Redux.create_reducer as above:
64
+ ```ruby
65
+ Redux::Store.add_reducer(your_store_key_name: reducer)
66
+ ```
67
+
68
+ ### Global Store
69
+ The store is available from anywhere within Opal Ruby context as:
70
+ ```ruby
71
+ Isomorfeus.store
72
+ ```
73
+ To get the native store from within Javascript context:
74
+ ```javascript
75
+ Opal.Isomorfeus.store.native
76
+ ```
77
+ ### Other Rubyfications
78
+ - `dispatch` accepts a Ruby Hash
79
+ - `get_state` returns a Ruby Hash
80
+ - `subscribe` accepts a ruby block as listener:
81
+ ```ruby
82
+ Isomorfeus.store.subscribe do
83
+ # something useful here
84
+ end
85
+ ```
86
+ ### Setup
87
+ This is done automatically within Isomorfeus. If isomorfeus-redux is used in isolation, these methods can be used:
88
+ ```ruby
89
+ Redux::Store.add_middleware(middleware) # middleware must be Javascript function
90
+ Redux::Store.init! # initializes the global store
91
+ ```
92
+
93
+ ### Development Tools
94
+ The Redux Development Tools allow for detailed debugging of store/state changes: https://github.com/zalmoxisus/redux-devtools-extension
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require_relative 'lib/redux/version.rb'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'isomorfeus-redux'
6
+ s.version = Redux::VERSION
7
+
8
+ s.authors = ['Jan Biedermann']
9
+ s.email = ['jan@kursator.com']
10
+ s.homepage = 'http://isomorfeus.com'
11
+ s.summary = 'Redux for Opal Ruby.'
12
+ s.license = 'MIT'
13
+ s.description = 'Use a global store and write reducers for it in Opal Ruby.'
14
+
15
+ s.files = `git ls-files`.split("\n").reject { |f| f.match(%r{^(gemfiles|s)/}) }
16
+ # s.test_files = `git ls-files -- {test,s,features}/*`.split("\n")
17
+ s.require_paths = ['lib']
18
+
19
+ s.add_dependency 'opal', '>= 0.11.0', '< 0.12.0'
20
+ end
@@ -0,0 +1,22 @@
1
+ if RUBY_ENGINE == 'opal'
2
+ require 'native'
3
+ require 'promise'
4
+ require 'redux/version'
5
+ require 'redux'
6
+ require 'redux/store'
7
+ # store initialization happens in Isomorfeus.init, see isomorfeus-react/lib/isomorfeus/config.rb
8
+ else
9
+ require 'opal'
10
+ require 'isomorfeus/promise'
11
+ require 'redux/version'
12
+
13
+ Opal.append_path(__dir__.untaint)
14
+
15
+ if Dir.exist?(File.join('app', 'isomorfeus'))
16
+ # Opal.append_path(File.expand_path(File.join('app', 'isomorfeus', 'components')))
17
+ Opal.append_path(File.expand_path(File.join('app', 'isomorfeus'))) unless Opal.paths.include?(File.expand_path(File.join('app', 'isomorfeus')))
18
+ elsif Dir.exist?('isomorfeus')
19
+ # Opal.append_path(File.expand_path(File.join('isomorfeus', 'components')))
20
+ Opal.append_path(File.expand_path('isomorfeus')) unless Opal.paths.include?(File.expand_path('isomorfeus'))
21
+ end
22
+ end
@@ -0,0 +1,350 @@
1
+ # taken from https://github.com/ruby-hyperloop/isomorfeus-operation/blob/edge/lib/isomorfeus-operation/promise.rb
2
+ # TODO: This is used only server side and may be used by others too and should be a separate gem maybe
3
+
4
+ class Promise
5
+ def self.value(value)
6
+ new.resolve(value)
7
+ end
8
+
9
+ def self.error(value)
10
+ new.reject(value)
11
+ end
12
+
13
+ def self.when(*promises)
14
+ When.new(promises)
15
+ end
16
+
17
+ attr_reader :error, :prev, :next
18
+
19
+ def initialize(action = {})
20
+ @action = action
21
+
22
+ @realized = false
23
+ @exception = false
24
+ @value = nil
25
+ @error = nil
26
+ @delayed = false
27
+
28
+ @prev = nil
29
+ @next = []
30
+ end
31
+
32
+ def value
33
+ if Promise === @value
34
+ @value.value
35
+ else
36
+ @value
37
+ end
38
+ end
39
+
40
+ def act?
41
+ @action.has_key?(:success) || @action.has_key?(:always)
42
+ end
43
+
44
+ def action
45
+ @action.keys
46
+ end
47
+
48
+ def exception?
49
+ @exception
50
+ end
51
+
52
+ def realized?
53
+ !!@realized
54
+ end
55
+
56
+ def resolved?
57
+ @realized == :resolve
58
+ end
59
+
60
+ def rejected?
61
+ @realized == :reject
62
+ end
63
+
64
+ def pending?
65
+ !realized?
66
+ end
67
+
68
+ def ^(promise)
69
+ promise << self
70
+ self >> promise
71
+
72
+ promise
73
+ end
74
+
75
+ def <<(promise)
76
+ @prev = promise
77
+
78
+ self
79
+ end
80
+
81
+ def >>(promise)
82
+ @next << promise
83
+
84
+ if exception?
85
+ promise.reject(@delayed[0])
86
+ elsif resolved?
87
+ promise.resolve(@delayed ? @delayed[0] : value)
88
+ elsif rejected?
89
+ if !@action.has_key?(:failure) || Promise === (@delayed ? @delayed[0] : @error)
90
+ promise.reject(@delayed ? @delayed[0] : error)
91
+ elsif promise.action.include?(:always)
92
+ promise.reject(@delayed ? @delayed[0] : error)
93
+ end
94
+ end
95
+
96
+ self
97
+ end
98
+
99
+ def resolve(value = nil)
100
+ if realized?
101
+ raise ArgumentError, 'the promise has already been realized'
102
+ end
103
+
104
+ if Promise === value
105
+ return (value << @prev) ^ self
106
+ end
107
+
108
+ begin
109
+ if block = @action[:success] || @action[:always]
110
+ value = block.call(value)
111
+ end
112
+
113
+ resolve!(value)
114
+ rescue Exception => e
115
+ exception!(e)
116
+ end
117
+
118
+ self
119
+ end
120
+
121
+ def resolve!(value)
122
+ @realized = :resolve
123
+ @value = value
124
+
125
+ if @next.any?
126
+ @next.each { |p| p.resolve(value) }
127
+ else
128
+ @delayed = [value]
129
+ end
130
+ end
131
+
132
+ def reject(value = nil)
133
+ if realized?
134
+ raise ArgumentError, 'the promise has already been realized'
135
+ end
136
+
137
+ if Promise === value
138
+ return (value << @prev) ^ self
139
+ end
140
+
141
+ begin
142
+ if block = @action[:failure] || @action[:always]
143
+ value = block.call(value)
144
+ end
145
+
146
+ if @action.has_key?(:always)
147
+ resolve!(value)
148
+ else
149
+ reject!(value)
150
+ end
151
+ rescue Exception => e
152
+ exception!(e)
153
+ end
154
+
155
+ self
156
+ end
157
+
158
+ def reject!(value)
159
+ @realized = :reject
160
+ @error = value
161
+
162
+ if @next.any?
163
+ @next.each { |p| p.reject(value) }
164
+ else
165
+ @delayed = [value]
166
+ end
167
+ end
168
+
169
+ def exception!(error)
170
+ @exception = true
171
+
172
+ reject!(error)
173
+ end
174
+
175
+ def then(&block)
176
+ self ^ Promise.new(success: block)
177
+ end
178
+
179
+ def then!(&block)
180
+ there_can_be_only_one!
181
+ self.then(&block)
182
+ end
183
+
184
+ alias do then
185
+ alias do! then!
186
+
187
+ def fail(&block)
188
+ self ^ Promise.new(failure: block)
189
+ end
190
+
191
+ def fail!(&block)
192
+ there_can_be_only_one!
193
+ fail(&block)
194
+ end
195
+
196
+ alias rescue fail
197
+ alias catch fail
198
+ alias rescue! fail!
199
+ alias catch! fail!
200
+
201
+ def always(&block)
202
+ self ^ Promise.new(always: block)
203
+ end
204
+
205
+ def always!(&block)
206
+ there_can_be_only_one!
207
+ always(&block)
208
+ end
209
+
210
+ alias finally always
211
+ alias ensure always
212
+ alias finally! always!
213
+ alias ensure! always!
214
+
215
+ def trace(depth = nil, &block)
216
+ self ^ Trace.new(depth, block)
217
+ end
218
+
219
+ def trace!(*args, &block)
220
+ there_can_be_only_one!
221
+ trace(*args, &block)
222
+ end
223
+
224
+ def there_can_be_only_one!
225
+ if @next.any?
226
+ raise ArgumentError, 'a promise has already been chained'
227
+ end
228
+ end
229
+
230
+ def inspect
231
+ result = "#<#{self.class}(#{object_id})"
232
+
233
+ if @next.any?
234
+ result += " >> #{@next.inspect}"
235
+ end
236
+
237
+ if realized?
238
+ result += ": #{(@value || @error).inspect}>"
239
+ else
240
+ result += ">"
241
+ end
242
+
243
+ result
244
+ end
245
+
246
+ class Trace < self
247
+ def self.it(promise)
248
+ current = []
249
+
250
+ if promise.act? || promise.prev.nil?
251
+ current.push(promise.value)
252
+ end
253
+
254
+ if prev = promise.prev
255
+ current.concat(it(prev))
256
+ else
257
+ current
258
+ end
259
+ end
260
+
261
+ def initialize(depth, block)
262
+ @depth = depth
263
+
264
+ super success: proc {
265
+ trace = Trace.it(self).reverse
266
+ trace.pop
267
+
268
+ if depth && depth <= trace.length
269
+ trace.shift(trace.length - depth)
270
+ end
271
+
272
+ block.call(*trace)
273
+ }
274
+ end
275
+ end
276
+
277
+ class When < self
278
+ def initialize(promises = [])
279
+ super()
280
+
281
+ @wait = []
282
+
283
+ promises.each {|promise|
284
+ wait promise
285
+ }
286
+ end
287
+
288
+ def each(&block)
289
+ raise ArgumentError, 'no block given' unless block
290
+
291
+ self.then {|values|
292
+ values.each(&block)
293
+ }
294
+ end
295
+
296
+ def collect(&block)
297
+ raise ArgumentError, 'no block given' unless block
298
+
299
+ self.then {|values|
300
+ When.new(values.map(&block))
301
+ }
302
+ end
303
+
304
+ def inject(*args, &block)
305
+ self.then {|values|
306
+ values.reduce(*args, &block)
307
+ }
308
+ end
309
+
310
+ alias map collect
311
+
312
+ alias reduce inject
313
+
314
+ def wait(promise)
315
+ unless Promise === promise
316
+ promise = Promise.value(promise)
317
+ end
318
+
319
+ if promise.act?
320
+ promise = promise.then
321
+ end
322
+
323
+ @wait << promise
324
+
325
+ promise.always {
326
+ try if @next.any?
327
+ }
328
+
329
+ self
330
+ end
331
+
332
+ alias and wait
333
+
334
+ def >>(*)
335
+ super.tap {
336
+ try
337
+ }
338
+ end
339
+
340
+ def try
341
+ if @wait.all?(&:realized?)
342
+ if promise = @wait.find(&:rejected?)
343
+ reject(promise.error)
344
+ else
345
+ resolve(@wait.map(&:value))
346
+ end
347
+ end
348
+ end
349
+ end
350
+ end
@@ -0,0 +1,45 @@
1
+ module Redux
2
+
3
+ def self.create_store(reducer, preloaded_state = nil, enhancer = nil)
4
+ Redux::Store.new(reducer, preloaded_state, enhancer)
5
+ end
6
+
7
+ def self.combine_reducers(reducers)
8
+ %x{
9
+ var real_reducers;
10
+ if (typeof reducers.$class === "function") {
11
+ real_reducers = reducers.$to_n();
12
+ } else {
13
+ real_reducers = reducers;
14
+ }
15
+ return Redux.combineReducers(real_reducers);
16
+ }
17
+ end
18
+
19
+ def self.apply_middleware(*middlewares)
20
+ if middlewares.size == 1
21
+ `Redux.applyMiddleware.apply(null, middlewares[0])`
22
+ else
23
+ `Redux.applyMiddleware.apply(null, middlewares)`
24
+ end
25
+ end
26
+
27
+ def self.bind_action_creators(*args)
28
+ dispatch = args.pop()
29
+ `Redux.bindActionCreators(args, dispatch)`
30
+ end
31
+
32
+ def self.compose(*functions)
33
+ `Redux.compose(functions)`
34
+ end
35
+
36
+ def self.create_reducer(&block)
37
+ %x{
38
+ return (function(previous_state, action) {
39
+ var new_state = block.$call(Opal.Hash.$new(previous_state), Opal.Hash.$new(action));
40
+ if (typeof new_state.$class === "function") { new_state = new_state.$to_n(); }
41
+ return new_state;
42
+ });
43
+ }
44
+ end
45
+ end
@@ -0,0 +1,133 @@
1
+ module Redux
2
+ class Store
3
+ include Native::Wrapper
4
+
5
+ def self.add_middleware(middleware)
6
+ if Isomorfeus.store
7
+ `console.warning("Adding middleware after Store initialization may have side effects! Saving state and initializing new store with restored state!")`
8
+ middlewares << middleware
9
+ preloaded_state = Isomorfeus.store.get_state
10
+ init!
11
+ else
12
+ middlewares << middleware
13
+ end
14
+ end
15
+
16
+ def self.add_reducer(reducer)
17
+ if Isomorfeus.store
18
+ # if the store has been initalized already, add the reducer to the instance
19
+ Isomorfeus.store.add_reducer(reducer)
20
+ else
21
+ # otherwise just add it to the reducers, so that they will be used when initializing the store
22
+ preloaded_state[reducer.keys.first] = {} unless preloaded_state.has_key?(reducer.keys.first)
23
+ reducers.merge!(reducer)
24
+ end
25
+ end
26
+
27
+ def self.add_reducers(new_reducers)
28
+ if Isomorfeus.store
29
+ # if the store has been initalized already, add the reducer to the instance
30
+ Isomorfeus.store.add_reducers(new_reducers)
31
+ else
32
+ # otherwise just add it to the reducers, so that they will be used when initializing the store
33
+ new_reducers.each do |key, value|
34
+ add_reducer(key => value)
35
+ end
36
+ end
37
+ end
38
+
39
+ # called from Isomorfeus.init
40
+ def self.init!
41
+ next_reducer = Redux.combine_reducers(@reducers)
42
+ if middlewares.any?
43
+ enhancer = Redux.apply_middleware(middlewares)
44
+ Redux::Store.new(next_reducer, preloaded_state, enhancer)
45
+ else
46
+ Redux::Store.new(next_reducer, preloaded_state)
47
+ end
48
+ end
49
+
50
+ def self.middlewares
51
+ @middlewares ||= []
52
+ end
53
+
54
+ def self.preloaded_state_merge!(ruby_hash)
55
+ preloaded_state.merge!(ruby_hash)
56
+ end
57
+
58
+ def self.preloaded_state
59
+ @preloaded_state ||= {}
60
+ end
61
+
62
+ def self.preloaded_state=(ruby_hash)
63
+ @preloaded_state = ruby_hash
64
+ end
65
+
66
+ def self.reducers
67
+ @reducers ||= {}
68
+ end
69
+
70
+ def initialize(reducer, preloaded_state = `null`, enhancer = `null`)
71
+ %x{
72
+ var real_preloaded_state;
73
+ if (typeof preloaded_state.$class === "function" && preloaded_state.$class() == "Hash") {
74
+ if (preloaded_state.$size() == 0) {
75
+ real_preloaded_state = null;
76
+ } else {
77
+ real_preloaded_state = preloaded_state.$to_n();
78
+ }
79
+ } else if (preloaded_state == nil) {
80
+ real_preloaded_state = null;
81
+ } else {
82
+ real_preloaded_state = preloaded_state;
83
+ }
84
+ if (enhancer && real_preloaded_state) {
85
+ this.native = Redux.createStore(reducer, real_preloaded_state, enhancer);
86
+ } else if (real_preloaded_state) {
87
+ this.native = Redux.createStore(reducer, real_preloaded_state);
88
+ } else if (enhancer) {
89
+ this.native = Redux.createStore(reducer, enhancer);
90
+ } else {
91
+ this.native = Redux.createStore(reducer);
92
+ }
93
+ }
94
+ end
95
+
96
+ def add_reducer(reducer)
97
+ self.class.reducers << reducer
98
+ next_reducer = Redux.combine_reducers(*self.class.reducers)
99
+ self.replace_reducer = next_reducer
100
+ end
101
+
102
+ def add_reducers(new_reducers)
103
+
104
+ self.class.reducers.merge!(new_reducers)
105
+
106
+ next_reducer = Redux.combine_reducers(self.class.reducers)
107
+ replace_reducer(next_reducer)
108
+ end
109
+
110
+ def dispatch(action)
111
+ %x{
112
+ if (typeof action.$class === "function" && action.$class() == "Hash") {
113
+ this.native.dispatch(action.$to_n());
114
+ } else {
115
+ this.native.dispatch(action);
116
+ }
117
+ }
118
+ end
119
+
120
+ def get_state
121
+ Hash.new(`this.native.getState()`)
122
+ end
123
+
124
+ def replace_reducer(next_reducer)
125
+ `this.native.replaceReducer(next_reducer)`
126
+ end
127
+
128
+ # returns function needed to unsubscribe the listener
129
+ def subscribe(&listener)
130
+ `this.native.subscribe(function() { return listener$.call(); })`
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,3 @@
1
+ module Redux
2
+ VERSION = '4.0.0'
3
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: isomorfeus-redux
3
+ version: !ruby/object:Gem::Version
4
+ version: 4.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jan Biedermann
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-10-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: opal
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 0.11.0
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: 0.12.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 0.11.0
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: 0.12.0
33
+ description: Use a global store and write reducers for it in Opal Ruby.
34
+ email:
35
+ - jan@kursator.com
36
+ executables: []
37
+ extensions: []
38
+ extra_rdoc_files: []
39
+ files:
40
+ - Gemfile
41
+ - README.md
42
+ - isomorfeus-redux.gemspec
43
+ - lib/isomorfeus-redux.rb
44
+ - lib/isomorfeus/promise.rb
45
+ - lib/redux.rb
46
+ - lib/redux/store.rb
47
+ - lib/redux/version.rb
48
+ homepage: http://isomorfeus.com
49
+ licenses:
50
+ - MIT
51
+ metadata: {}
52
+ post_install_message:
53
+ rdoc_options: []
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ requirements: []
67
+ rubyforge_project:
68
+ rubygems_version: 2.7.6
69
+ signing_key:
70
+ specification_version: 4
71
+ summary: Redux for Opal Ruby.
72
+ test_files: []