hyper-store 1.0.alpha1.8 → 1.0.0.lap28
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -4
- data/.rubocop.yml +107 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/DOCS.md +312 -0
- data/Gemfile +1 -4
- data/LICENSE.txt +21 -0
- data/README.md +80 -0
- data/Rakefile +2 -0
- data/dciy.toml +3 -0
- data/hyper-store.gemspec +18 -20
- data/lib/hyper-store.rb +16 -19
- data/lib/hyper-store/class_methods.rb +32 -0
- data/lib/hyper-store/dispatch_receiver.rb +38 -0
- data/lib/hyper-store/instance_methods.rb +40 -0
- data/lib/hyper-store/mutator_wrapper.rb +71 -0
- data/lib/hyper-store/state_wrapper.rb +107 -0
- data/lib/hyper-store/state_wrapper/argument_validator.rb +91 -0
- data/lib/hyper-store/version.rb +3 -0
- data/lib/hyperloop/application/boot.rb +34 -0
- data/lib/hyperloop/store.rb +12 -0
- data/lib/hyperloop/store/mixin.rb +21 -0
- data/lib/react/observable.rb +29 -0
- data/lib/react/state.rb +143 -0
- metadata +64 -116
- data/lib/hyperstack/internal/store/class_methods.rb +0 -36
- data/lib/hyperstack/internal/store/dispatch_receiver.rb +0 -41
- data/lib/hyperstack/internal/store/instance_methods.rb +0 -45
- data/lib/hyperstack/internal/store/mutator_wrapper.rb +0 -84
- data/lib/hyperstack/internal/store/observable.rb +0 -33
- data/lib/hyperstack/internal/store/state_wrapper.rb +0 -116
- data/lib/hyperstack/internal/store/state_wrapper/argument_validator.rb +0 -93
- data/lib/hyperstack/legacy/store.rb +0 -23
- data/lib/hyperstack/legacy/store/version.rb +0 -7
data/Gemfile
CHANGED
@@ -1,6 +1,3 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
|
-
gem
|
3
|
-
gem 'hyperstack-config', path: '../hyperstack-config'
|
4
|
-
gem 'hyper-component', path: '../hyper-component'
|
5
|
-
gem 'hyper-state', path: '../hyper-state'
|
2
|
+
gem "opal-jquery", git: "https://github.com/opal/opal-jquery.git", branch: "master"
|
6
3
|
gemspec
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Adam Creekroad, Mitch VanDuyn
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
<div class="githubhyperloopheader">
|
2
|
+
|
3
|
+
<p align="center">
|
4
|
+
|
5
|
+
<a href="http://ruby-hyperloop.io/" alt="Hyperloop" title="Hyperloop">
|
6
|
+
<img width="350px" src="http://ruby-hyperloop.io/images/hyperloop-github-logo.png">
|
7
|
+
</a>
|
8
|
+
|
9
|
+
</p>
|
10
|
+
|
11
|
+
<h2 align="center">The Complete Isomorphic Ruby Framework</h2>
|
12
|
+
|
13
|
+
<br>
|
14
|
+
|
15
|
+
<a href="http://ruby-hyperloop.io/" alt="Hyperloop" title="Hyperloop">
|
16
|
+
<img src="http://ruby-hyperloop.io/images/githubhyperloopbadge.png">
|
17
|
+
</a>
|
18
|
+
|
19
|
+
<a href="https://gitter.im/ruby-hyperloop/chat" alt="Gitter chat" title="Gitter chat">
|
20
|
+
<img src="http://ruby-hyperloop.io/images/githubgitterbadge.png">
|
21
|
+
</a>
|
22
|
+
|
23
|
+
[![Build Status](https://travis-ci.org/ruby-hyperloop/hyper-store.svg?branch=master)](https://travis-ci.org/ruby-hyperloop/hyper-store)
|
24
|
+
[![Codeship Status for ruby-hyperloop/hyper-store](https://app.codeship.com/projects/4454c560-d4ea-0134-7c96-362b4886dd22/status?branch=master)](https://app.codeship.com/projects/202301)
|
25
|
+
[![Gem Version](https://badge.fury.io/rb/hyper-store.svg)](https://badge.fury.io/rb/hyper-store)
|
26
|
+
|
27
|
+
<p align="center">
|
28
|
+
<img src="http://ruby-hyperloop.io/images/HyperStores.png" width="100" alt="Hyperstores">
|
29
|
+
</p>
|
30
|
+
|
31
|
+
</div>
|
32
|
+
|
33
|
+
## Hyper-Store GEM is part of Hyperloop GEMS family
|
34
|
+
|
35
|
+
Build interactive Web applications quickly. Hyperloop encourages rapid development with clean, pragmatic design. With developer productivity as our highest goal, Hyperloop takes care of much of the hassle of Web development, so you can focus on innovation and delivering end-user value.
|
36
|
+
|
37
|
+
One language. One model. One set of tests. The same business logic and domain models running on the clients and the server. Hyperloop is fully integrated with Rails and also gives you unfettered access to the complete universe of JavaScript libraries (including React) from within your Ruby code. Hyperloop lets you build beautiful interactive user interfaces in Ruby.
|
38
|
+
|
39
|
+
Everything has a place in our architecture. Components deliver interactive user experiences, Operations encapsulate business logic, Models magically synchronize data between clients and servers, Policies govern authorization and Stores hold local state.
|
40
|
+
|
41
|
+
**Stores** are where the state of your Application lives. Anything but a completely static web page will have dynamic states that change because of user inputs, the passage of time, or other external events.
|
42
|
+
|
43
|
+
**Stores are Ruby classes that keep the dynamic parts of the state in special state variables**
|
44
|
+
|
45
|
+
## Getting Started
|
46
|
+
|
47
|
+
1. Update your Gemfile:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
#Gemfile
|
51
|
+
|
52
|
+
gem 'hyperloop'
|
53
|
+
```
|
54
|
+
|
55
|
+
2. At the command prompt, update your bundle :
|
56
|
+
|
57
|
+
$ bundle update
|
58
|
+
|
59
|
+
3. Run the hyperloop install generator:
|
60
|
+
|
61
|
+
$ rails g hyperloop:install
|
62
|
+
|
63
|
+
4. Follow the guidelines to start developing your application. You may find
|
64
|
+
the following resources handy:
|
65
|
+
* [Getting Started with Hyperloop](http://ruby-hyperloop.io/start/components/)
|
66
|
+
* [Hyperloop Guides](http://ruby-hyperloop.io/docs/architecture)
|
67
|
+
* [Hyperloop Tutorial](http://ruby-hyperloop.io/tutorials)
|
68
|
+
|
69
|
+
## Community
|
70
|
+
|
71
|
+
#### Getting Help
|
72
|
+
Please **do not post** usage questions to GitHub Issues. For these types of questions use our [Gitter chatroom](https://gitter.im/ruby-hyperloop/chat) or [StackOverflow](http://stackoverflow.com/questions/tagged/hyperloop).
|
73
|
+
|
74
|
+
#### Submitting Bugs and Enhancements
|
75
|
+
[GitHub Issues](https://github.com/ruby-hyperloop/hyperloop/issues) is for suggesting enhancements and reporting bugs. Before submiting a bug make sure you do the following:
|
76
|
+
* Check out our [contributing guide](https://github.com/ruby-hyperloop/hyperloop/blob/master/CONTRIBUTING.md) for info on our release cycle.
|
77
|
+
|
78
|
+
## License
|
79
|
+
|
80
|
+
Hyperloop is released under the [MIT License](http://www.opensource.org/licenses/MIT).
|
data/Rakefile
CHANGED
data/dciy.toml
ADDED
data/hyper-store.gemspec
CHANGED
@@ -1,43 +1,41 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
lib = File.expand_path('../lib', __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require '
|
4
|
+
require 'hyper-store/version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = 'hyper-store'
|
8
|
-
spec.version =
|
8
|
+
spec.version = HyperStore::VERSION
|
9
9
|
spec.authors = ['Mitch VanDuyn', 'Adam Creekroad', 'Jan Biedermann']
|
10
10
|
spec.email = ['mitch@catprint.com', 'jan@kursator.com']
|
11
11
|
spec.summary = 'Flux Stores and more for Hyperloop'
|
12
|
-
spec.homepage = 'https://
|
12
|
+
spec.homepage = 'https://ruby-hyperloop.org'
|
13
13
|
spec.license = 'MIT'
|
14
|
+
# spec.metadata = {
|
15
|
+
# "homepage_uri" => 'http://ruby-hyperloop.org',
|
16
|
+
# "source_code_uri" => 'https://github.com/ruby-hyperloop/hyper-component'
|
17
|
+
# }
|
18
|
+
|
14
19
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(gemfiles|spec)/}) }
|
15
20
|
spec.bindir = 'exe'
|
16
21
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
17
22
|
spec.require_paths = ['lib']
|
18
23
|
|
19
|
-
spec.add_dependency '
|
20
|
-
spec.add_dependency '
|
21
|
-
|
24
|
+
spec.add_dependency 'hyperloop-config', HyperStore::VERSION
|
25
|
+
spec.add_dependency 'opal', '>= 0.11.0', '< 0.12.0'
|
22
26
|
spec.add_development_dependency 'bundler'
|
23
|
-
spec.add_development_dependency '
|
24
|
-
spec.add_development_dependency 'hyper-
|
25
|
-
spec.add_development_dependency 'hyper-spec', Hyperstack::Legacy::Store::VERSION
|
27
|
+
spec.add_development_dependency 'hyper-react', HyperStore::VERSION
|
28
|
+
spec.add_development_dependency 'hyper-spec', HyperStore::VERSION
|
26
29
|
spec.add_development_dependency 'listen'
|
27
|
-
|
30
|
+
spec.add_development_dependency 'mini_racer', '~> 0.1.15'
|
28
31
|
spec.add_development_dependency 'opal-browser', '~> 0.2.0'
|
29
|
-
spec.add_development_dependency 'opal-rails', '
|
30
|
-
spec.add_development_dependency 'pry-
|
31
|
-
spec.add_development_dependency '
|
32
|
-
spec.add_development_dependency 'puma'
|
33
|
-
spec.add_development_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 7.0'
|
32
|
+
spec.add_development_dependency 'opal-rails', '~> 0.9.4'
|
33
|
+
spec.add_development_dependency 'pry-byebug'
|
34
|
+
spec.add_development_dependency 'rails', '>= 4.0.0'
|
34
35
|
spec.add_development_dependency 'rake'
|
35
36
|
spec.add_development_dependency 'react-rails', '>= 2.4.0', '< 2.5.0'
|
36
37
|
spec.add_development_dependency 'rspec', '~> 3.7.0'
|
37
|
-
spec.add_development_dependency 'rspec-rails'
|
38
38
|
spec.add_development_dependency 'rspec-steps', '~> 2.1.1'
|
39
|
-
spec.add_development_dependency 'rubocop'
|
40
|
-
spec.add_development_dependency 'sqlite3'
|
41
|
-
spec.add_development_dependency 'timecop', '~> 0.8.1'
|
42
|
-
|
39
|
+
spec.add_development_dependency 'rubocop', '~> 0.51.0'
|
40
|
+
spec.add_development_dependency 'sqlite3'
|
43
41
|
end
|
data/lib/hyper-store.rb
CHANGED
@@ -1,27 +1,24 @@
|
|
1
1
|
require 'set'
|
2
|
-
require '
|
3
|
-
|
4
|
-
Hyperstack.import 'hyper-store'
|
2
|
+
require 'hyperloop-config'
|
3
|
+
Hyperloop.import 'hyper-store'
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
# allows us to easily turn off BasicObject for debug
|
10
|
-
class BaseStoreClass < BasicObject
|
11
|
-
end
|
12
|
-
end
|
5
|
+
|
6
|
+
module HyperStore # allows us to easily turn off BasicObject for debug
|
7
|
+
class BaseStoreClass < BasicObject
|
13
8
|
end
|
14
9
|
end
|
15
10
|
|
16
|
-
require '
|
17
|
-
require '
|
18
|
-
require '
|
19
|
-
require '
|
20
|
-
require '
|
21
|
-
require '
|
22
|
-
require '
|
23
|
-
require '
|
24
|
-
require '
|
11
|
+
require 'hyper-store/class_methods'
|
12
|
+
require 'hyper-store/dispatch_receiver'
|
13
|
+
require 'hyper-store/instance_methods'
|
14
|
+
require 'hyper-store/mutator_wrapper'
|
15
|
+
require 'hyper-store/state_wrapper/argument_validator'
|
16
|
+
require 'hyper-store/state_wrapper'
|
17
|
+
require 'hyper-store/version'
|
18
|
+
require 'hyperloop/store'
|
19
|
+
require 'hyperloop/application/boot'
|
20
|
+
require 'hyperloop/store/mixin'
|
21
|
+
require 'react/state'
|
25
22
|
|
26
23
|
if RUBY_ENGINE != 'opal'
|
27
24
|
require 'opal'
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module HyperStore
|
2
|
+
module ClassMethods
|
3
|
+
attr_accessor :__shared_states, :__class_states, :__instance_states
|
4
|
+
|
5
|
+
def state(*args, &block)
|
6
|
+
# If we're passing in any arguments then we are calling the macro to define a state
|
7
|
+
if args.count > 0
|
8
|
+
singleton_class.__state_wrapper.class_state_wrapper
|
9
|
+
.define_state_methods(self, *args, &block)
|
10
|
+
# Otherwise we are just accessing it
|
11
|
+
else
|
12
|
+
@state ||= singleton_class.__state_wrapper.class_state_wrapper.new(self)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def mutate
|
17
|
+
@mutate ||= singleton_class.__state_wrapper.class_mutator_wrapper.new(self)
|
18
|
+
end
|
19
|
+
|
20
|
+
def __shared_states
|
21
|
+
@__shared_states ||= []
|
22
|
+
end
|
23
|
+
|
24
|
+
def __class_states
|
25
|
+
@__class_states ||= []
|
26
|
+
end
|
27
|
+
|
28
|
+
def __instance_states
|
29
|
+
@__instance_states ||= []
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module HyperStore
|
2
|
+
module DispatchReceiver
|
3
|
+
class InvalidOperationError < StandardError; end
|
4
|
+
|
5
|
+
attr_accessor :params
|
6
|
+
|
7
|
+
def receives(*args, &block)
|
8
|
+
# Format the callback to be Proc or Nil
|
9
|
+
callback = format_callback(args)
|
10
|
+
|
11
|
+
if args.empty?
|
12
|
+
message = 'At least one operation must be passed in to the \'receives\' macro'
|
13
|
+
raise InvalidOperationError, message
|
14
|
+
end
|
15
|
+
|
16
|
+
# Loop through receivers and call callback and block on dispatch
|
17
|
+
args.each do |operation|
|
18
|
+
operation.on_dispatch do |params|
|
19
|
+
@params = params
|
20
|
+
|
21
|
+
callback.call if callback
|
22
|
+
yield params if block
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def format_callback(args)
|
30
|
+
if args.last.is_a?(Symbol)
|
31
|
+
method_name = args.pop
|
32
|
+
-> { send(:"#{method_name}") }
|
33
|
+
elsif args.last.is_a?(Proc)
|
34
|
+
args.pop
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module HyperStore
|
2
|
+
module InstanceMethods
|
3
|
+
def init_store
|
4
|
+
self.class.__instance_states.each do |instance_state|
|
5
|
+
# If the scope is shared then we initialize at the class level
|
6
|
+
next if instance_state[1][:scope] == :shared
|
7
|
+
|
8
|
+
# TODO: Figure out exactly how we're going to handle passing in procs and blocks together
|
9
|
+
# But for now...just do the proc first then the block
|
10
|
+
|
11
|
+
# First initialize value from initializer Proc
|
12
|
+
proc_value = initializer_value(instance_state[1][:initializer])
|
13
|
+
mutate.__send__(:"#{instance_state[0]}", proc_value)
|
14
|
+
|
15
|
+
# Then call the block if a block is passed
|
16
|
+
next unless instance_state[1][:block]
|
17
|
+
|
18
|
+
block_value = instance_eval(&instance_state[1][:block])
|
19
|
+
mutate.__send__(:"#{instance_state[0]}", block_value)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
def state
|
25
|
+
@state ||= self.class.singleton_class.__state_wrapper.instance_state_wrapper.new(self)
|
26
|
+
end
|
27
|
+
|
28
|
+
def mutate
|
29
|
+
@mutate ||= self.class.singleton_class.__state_wrapper.instance_mutator_wrapper.new(self)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def initializer_value(initializer)
|
35
|
+
# We gotta check the arity because a Proc passed in directly from initializer has no args,
|
36
|
+
# but if we created one then we might have wanted self
|
37
|
+
initializer.arity > 0 ? initializer.call(self) : initializer.call
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module HyperStore
|
2
|
+
class MutatorWrapper < BaseStoreClass # < BasicObject
|
3
|
+
|
4
|
+
class << self
|
5
|
+
def add_method(klass, method_name, opts = {})
|
6
|
+
define_method(:"#{method_name}") do |*args|
|
7
|
+
from = opts[:scope] == :shared ? klass.state.__from__ : __from__
|
8
|
+
current_value = React::State.get_state(from, method_name.to_s)
|
9
|
+
|
10
|
+
if args.count > 0
|
11
|
+
React::State.set_state(from, method_name.to_s, args[0])
|
12
|
+
current_value
|
13
|
+
else
|
14
|
+
React::State.set_state(from, method_name.to_s, current_value)
|
15
|
+
React::Observable.new(current_value) do |update|
|
16
|
+
React::State.set_state(from, method_name.to_s, update)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
initialize_values(klass, method_name, opts) if initialize_values?(opts)
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize_values?(opts)
|
25
|
+
[:class, :shared].include?(opts[:scope]) && (opts[:initializer] || opts[:block])
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize_values(klass, name, opts)
|
29
|
+
initializer = initializer_proc(opts[:initializer], klass, name) if opts[:initializer]
|
30
|
+
|
31
|
+
if initializer && opts[:block]
|
32
|
+
klass.receives(Hyperloop::Application::Boot, initializer) do
|
33
|
+
klass.mutate.__send__(:"#{name}", opts[:block].call)
|
34
|
+
end
|
35
|
+
elsif initializer
|
36
|
+
klass.receives(Hyperloop::Application::Boot, initializer)
|
37
|
+
elsif opts[:block]
|
38
|
+
klass.receives(Hyperloop::Application::Boot) do
|
39
|
+
klass.mutate.__send__(:"#{name}", opts[:block].call)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def initializer_proc(initializer, klass, name)
|
47
|
+
# We gotta check the arity because a Proc passed in directly from initializer has no args,
|
48
|
+
# but if we created one then we might have wanted the class
|
49
|
+
if initializer.arity > 0
|
50
|
+
-> { klass.mutate.__send__(:"#{name}", initializer.call(klass)) }
|
51
|
+
else
|
52
|
+
-> { klass.mutate.__send__(:"#{name}", initializer.call) }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
attr_accessor :__from__
|
58
|
+
|
59
|
+
def self.new(from)
|
60
|
+
instance = allocate
|
61
|
+
instance.__from__ = from
|
62
|
+
instance
|
63
|
+
end
|
64
|
+
|
65
|
+
# Any method_missing call will create a state and accessor with that name
|
66
|
+
def method_missing(name, *args, &block) # rubocop:disable Style/MethodMissing
|
67
|
+
(class << self; self end).add_method(nil, name)
|
68
|
+
__send__(name, *args, &block)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module HyperStore
|
2
|
+
class StateWrapper < BaseStoreClass
|
3
|
+
extend ArgumentValidator
|
4
|
+
|
5
|
+
class << self
|
6
|
+
attr_reader :instance_state_wrapper, :class_state_wrapper,
|
7
|
+
:instance_mutator_wrapper, :class_mutator_wrapper,
|
8
|
+
:wrappers
|
9
|
+
|
10
|
+
def inherited(subclass)
|
11
|
+
subclass.add_class_instance_vars(subclass) if self == StateWrapper
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_class_instance_vars(subclass)
|
15
|
+
@shared_state_wrapper = subclass
|
16
|
+
@instance_state_wrapper = Class.new(@shared_state_wrapper)
|
17
|
+
@class_state_wrapper = Class.new(@shared_state_wrapper)
|
18
|
+
|
19
|
+
@shared_mutator_wrapper = Class.new(MutatorWrapper)
|
20
|
+
@instance_mutator_wrapper = Class.new(@shared_mutator_wrapper)
|
21
|
+
@class_mutator_wrapper = Class.new(@shared_mutator_wrapper)
|
22
|
+
|
23
|
+
@wrappers = [@instance_state_wrapper, @instance_mutator_wrapper,
|
24
|
+
@class_state_wrapper, @class_mutator_wrapper]
|
25
|
+
end
|
26
|
+
|
27
|
+
def define_state_methods(klass, *args, &block)
|
28
|
+
return self if args.empty?
|
29
|
+
|
30
|
+
name, opts = validate_args!(klass, *args, &block)
|
31
|
+
|
32
|
+
add_readers(klass, name, opts)
|
33
|
+
klass.singleton_class.state.add_error_methods(name, opts)
|
34
|
+
klass.singleton_class.state.add_methods(klass, name, opts)
|
35
|
+
klass.singleton_class.state.remove_methods(name, opts)
|
36
|
+
klass.send(:"__#{opts[:scope]}_states") << [name, opts]
|
37
|
+
end
|
38
|
+
|
39
|
+
def add_readers(klass, name, opts)
|
40
|
+
return unless opts[:reader]
|
41
|
+
|
42
|
+
if [:instance, :shared].include?(opts[:scope])
|
43
|
+
klass.class_eval do
|
44
|
+
define_method(:"#{opts[:reader]}") { state.__send__(:"#{name}") }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
if [:class, :shared].include?(opts[:scope])
|
49
|
+
klass.define_singleton_method(:"#{opts[:reader]}") { state.__send__(:"#{name}") }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def add_error_methods(name, opts)
|
54
|
+
return if opts[:scope] == :shared
|
55
|
+
|
56
|
+
[@shared_state_wrapper, @shared_mutator_wrapper].each do |klass|
|
57
|
+
klass.define_singleton_method(:"#{name}") do
|
58
|
+
'nope!'
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def add_methods(klass, name, opts)
|
64
|
+
instance_variable_get("@#{opts[:scope]}_state_wrapper").add_method(klass, name, opts)
|
65
|
+
instance_variable_get("@#{opts[:scope]}_mutator_wrapper").add_method(klass, name, opts)
|
66
|
+
end
|
67
|
+
|
68
|
+
def add_method(klass, method_name, opts = {})
|
69
|
+
define_method(:"#{method_name}") do
|
70
|
+
from = opts[:scope] == :shared ? klass.state.__from__ : @__from__
|
71
|
+
React::State.get_state(from, method_name.to_s)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def remove_methods(name, opts)
|
76
|
+
return unless opts[:scope] == :shared
|
77
|
+
|
78
|
+
wrappers.each do |wrapper|
|
79
|
+
wrapper.send(:remove_method, :"#{name}") if wrapper.respond_to?(:"#{name}")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def default_scope(klass)
|
84
|
+
if self == klass.singleton_class.__state_wrapper.class_state_wrapper
|
85
|
+
:instance
|
86
|
+
else
|
87
|
+
:class
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
attr_accessor :__from__
|
93
|
+
|
94
|
+
def self.new(from)
|
95
|
+
instance = allocate
|
96
|
+
instance.__from__ = from
|
97
|
+
instance
|
98
|
+
end
|
99
|
+
|
100
|
+
# Any method_missing call will create a state and accessor with that name
|
101
|
+
def method_missing(name, *args, &block) # rubocop:disable Style/MethodMissing
|
102
|
+
$method_missing = [name, *args]
|
103
|
+
(class << self; self end).add_method(nil, name) #(class << self; self end).superclass.add_method(nil, name)
|
104
|
+
__send__(name, *args, &block)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|