isomorfeus-redux 4.0.0

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