hyper-state 0.1

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: b29f86251c6d35f52bed68a7d459ab05426735f510ac0e351afaf209da21b375
4
+ data.tar.gz: f51b8622052a5544f8c8237a584c082bc592ad8702d0940e1267e3ca917c3433
5
+ SHA512:
6
+ metadata.gz: 6846b82c975c44f773ab6841dfde7a933e59be3fc28ddb96133490e7fafcff8987ca8671c98021462c23d73962d1a32a7bd2a0b1816cc4a443c8d4c7424e202c
7
+ data.tar.gz: 5b52aa1e8c186363471c7fc850892cbe0a13eb3bfbcf725f1a5bf30d05f5ad561f163a61a461ecbb540c970ab14e24bad2d385ead0b3c0a4c48a09559a95a711
@@ -0,0 +1,53 @@
1
+ *.rbc
2
+ capybara-*.html
3
+ .rspec
4
+ /log
5
+ /tmp
6
+ /db/*.sqlite3
7
+ /db/*.sqlite3-journal
8
+ /public/system
9
+ /coverage/
10
+ /spec/tmp
11
+ **.orig
12
+ rerun.txt
13
+ pickle-email-*.html
14
+ Gemfile.lock
15
+
16
+ # TODO Comment out these rules if you are OK with secrets being uploaded to the repo
17
+ config/initializers/secret_token.rb
18
+ config/secrets.yml
19
+
20
+ # dotenv
21
+ # TODO Comment out this rule if environment variables can be committed
22
+ .env
23
+
24
+ ## Environment normalization:
25
+ /.bundle
26
+ /vendor/bundle
27
+
28
+ # these should all be checked in to normalize the environment:
29
+ # Gemfile.lock, .ruby-version, .ruby-gemset
30
+
31
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
32
+ .rvmrc
33
+
34
+ # if using bower-rails ignore default bower_components path bower.json files
35
+ /vendor/assets/bower_components
36
+ *.bowerrc
37
+ bower.json
38
+
39
+ # Ignore pow environment settings
40
+ .powenv
41
+
42
+ # Ignore Byebug command history file.
43
+ .byebug_history
44
+
45
+ # Ignore test_app tmp folders
46
+ /test_app/tmp
47
+ /test_app/log
48
+
49
+ # ignore gem
50
+ *.gem
51
+
52
+ # ignore IDE files
53
+ .idea
@@ -0,0 +1,20 @@
1
+ language: ruby
2
+ rvm:
3
+ - ruby
4
+ env:
5
+ - HYPER_DEV_GEM_SOURCE="https://gems.ruby-hyperloop.org" TZ=Europe/Berlin
6
+ before_install:
7
+ - sudo apt-get install -y fonts-liberation
8
+ - wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
9
+ - sudo dpkg -i google-chrome*.deb
10
+ - gem install bundler
11
+ before_script:
12
+ - cd spec/test_app
13
+ - bundle update
14
+ - cd ../../
15
+ - chromedriver-update
16
+ - ls -lR ~/.chromedriver-helper/
17
+ script:
18
+ - bundle exec rake spec
19
+ gemfile:
20
+ - gemfiles/opal_0_11_react-rails_2_4.gemfile
data/DOCS.md ADDED
@@ -0,0 +1,325 @@
1
+ # Hyperloop Stores
2
+
3
+
4
+
5
+
6
+
7
+ PLEASE DO NOT EDIT THIS FILE - TO BE DELETED
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+ Hyperloop **Stores** are implemented in the **HyperStore Gem**.
17
+
18
+ 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.
19
+
20
+ ```ruby
21
+ class UserStore < Hyperloop::Store
22
+ state :current, scope: :class, reader: true
23
+
24
+ def self.set_current! user
25
+ mutate.current user
26
+ end
27
+ end
28
+
29
+ # to access the store
30
+ UserStore.set_current! user
31
+ UserStore.current_user
32
+ ```
33
+
34
+ **Stores are Ruby classes that keep the dynamic parts of the state in special state variables**
35
+
36
+ + `Hyperloop::Store::Mixin` can be mixed in to any class to turn it into a Flux Store.
37
+ + You can also create Stores by subclassing `Hyperloop::Store`.
38
+ + Stores are built out of *reactive state variables*.
39
+ + Components that *read* a Store's state will **automatically** update when the state changes.
40
+ + All of your **shared** reactive state should be Stores - *The Store is the Truth*!
41
+ + Stores can *receive* **dispatches** from *Operations*
42
+
43
+ ## Reading and Mutating States
44
+
45
+ A Store will have one or more *Reactive State Variables* or *State* for short. States are read using the `state` method, and are changed using the `mutate` method.
46
+
47
+ `state.items` reads the current value of the state named `items`. Hyperloop tracks all reads of state, and mutating those states will trigger a re-render of any Components depending on the current value.
48
+
49
+ `mutate.items` returns the current value of the state named `items`, but also tells Hyperloop that the value is changing, and that any Components depending on the current value will have to be re-rendered.
50
+
51
+ The one thing you must remember to do is use `mutate` if you intend to update the internal value of a state. For example if the state contains a hash, and you are updating the Hash's internal value you would use `mutate` otherwise the change will go unrecorded.
52
+
53
+ #### Initializing States
54
+
55
+ To assign a new value to a state use the `mutate` method and pass a parameter to the state:
56
+
57
+ ```ruby
58
+ mutate.items(Hash.new { |h, k| h[k] = 0 })
59
+ ```
60
+
61
+ #### Reading States
62
+
63
+ To read the current value of a state use the `state` method:
64
+
65
+ ```ruby
66
+ state.items # returns current value of items
67
+ ```
68
+
69
+ Typically a store will have quite a few reader (aka getter) methods that hide the details of the state, allowing the Store's implementation to change, without effecting the interface.
70
+
71
+ #### Mutating States
72
+
73
+ Often states hold data structures like arrays, hashes, sets, or other Ruby classes, which may be *mutated*. For example when you push a new value onto an array you will mutate it. The *value* of the array does not change, but its *contents* does. If you are accessing a state with the intent to change its content then use the `mutate` method:
74
+
75
+ ```ruby
76
+ mutate.items[item] = value
77
+ ```
78
+
79
+ ### Instances and Classes
80
+
81
+ Stores are often singleton classes. In an application there is one 'cart' for example.
82
+
83
+ However sometimes you will want to create a class where each instance is a Store. This is straight forward because if a state is read or mutated in an instance method, then you will be referring to that instance's copy of the state.
84
+
85
+ ```ruby
86
+ # Each UserStream provides a stream of unique user profiles.
87
+ # Each instance has a single HyperStore state variable called user
88
+ # user will contain a single hash representing the user profile.
89
+ class UserStream < Hyperloop::Store
90
+
91
+ # get another user
92
+
93
+ def get_another!
94
+ mutate.user UserStream._select_random_user
95
+ end
96
+
97
+ # extract various attributes from the user hash
98
+
99
+ def user_name
100
+ state.user[:login]
101
+ end
102
+
103
+ def user_url
104
+ state.user[:html_url]
105
+ end
106
+
107
+ def avatar
108
+ state.user[:avatar_url]
109
+ end
110
+
111
+ def initialize
112
+ get_another!
113
+ end
114
+
115
+ def self._select_random_user
116
+ # _select_random_user provides a stream of unique user profiles.
117
+ # It will either return a user profile hash, or a promise of one
118
+ # to come.
119
+
120
+ # The cache of users to choose from does not have to be an state
121
+ # variable, so we use plain instance variables.
122
+ return @users.delete_at(rand(@users.length)) unless @users.blank?
123
+ # execute the GetMoreUsers Operation to grab another batch of users
124
+ # if we are not already waiting on a promise
125
+ @promise = GetMoreUsers.then do |response|
126
+ @users = response.json
127
+ end if @promise.nil? || @promise.resolved?
128
+ # wait for the promise to resolve then try again
129
+ @promise.then { _select_random_user }
130
+ end
131
+ end
132
+
133
+ class GetMoreUsers < HyperOperation
134
+ def execute
135
+ HTTP.get("https://api.github.com/users?since=#{rand(500)}")
136
+ end
137
+ end
138
+ ```
139
+ Stores that have multiple instances will typically have instance methods that directly mutate the store. We recommend you end these methods with an exclamation (!) to make it clear you are exposing a mutator.
140
+
141
+ ### States and Promises
142
+
143
+ The above example is greatly simplified because if a promise is assigned to a state it will not mutate *until the promise resolves*. Combining this with instance Stores gives a powerful way to encapsulate system behavior.
144
+
145
+ ### Explicitly Declaring States
146
+
147
+ States like instance variables are created when they are first referenced.
148
+
149
+ As a convenience you may also explicitly declare states. This reduces code noise, and improves readability.
150
+
151
+ ```ruby
152
+ class Cart < Hyperloop::Store
153
+ state items: Hash.new { |h, k| h[k] = 0 }, scope: :class, reader: true
154
+ end
155
+ ```
156
+
157
+ This *declares* the `items` state as a class state variable, will initialize it with the hash on `Hyperloop::Boot`, and provides a reader method.
158
+ That is 6 lines of code for the price of 1, plus now the intention of `items` is clearly defined.
159
+
160
+ The `state` declaration has the following flavors, depending on how the state is to be initialized:
161
+
162
+ ```ruby
163
+ state :items, ... other options ... # items will be initialized to nil
164
+ state items: [1, 2, 3], ... other options ... # items will be initialized to the array [1, 2, 3]
165
+ state :items, ... other options ... do
166
+ ... compute initial value ...
167
+ ... context will be either the class an ...
168
+ ... instance depending on the scope ...
169
+ end
170
+ ```
171
+
172
+ Other options to the `state` declaration are:
173
+
174
+ + `scope:` either `:class`, `:instance`, `:shared`. Details below!
175
+ + `reader:` either `true`, or a symbol used to declare a reader (getter) method.
176
+ + `initializer:` either a Proc or a Symbol (indicating a method), to be used to initialize the state.
177
+
178
+ The value of the `scope` option determines where the state resides.
179
+
180
+ + A class state has one instance per class and is directly accessible in class methods, and indirectly in instances using `self.class.state`.
181
+ + An instance state has a different copy in each instance of the class, and is not accessible by class methods.
182
+ + A shared state is like a class state, but is also directly accessible in instances.
183
+
184
+ The default value for `scope:` depends on where the state is declared:
185
+
186
+ ```ruby
187
+ state :items # declares an instance state variable, each instance gets its own state
188
+ class << self
189
+ state :items # declares a class instance state variable
190
+ end
191
+ ```
192
+
193
+ In the above example there is one class instance state named `items` and an additional state variable also called
194
+ items for each instance.
195
+
196
+ The `shared` option just makes it easier to access a class state from instances.
197
+
198
+ ```ruby
199
+ class MyStore < Hyperloop::Store
200
+ state :shared_state, scope: :shared
201
+ state :class_state, scope: :class
202
+ state :instance_state # scope: :instance is default here
203
+
204
+ def instance_method
205
+ # shared state makes class states easy to access
206
+ state.shared_state
207
+ # without shared state class_state is still accessible
208
+ # with more typing
209
+ self.class.class_state
210
+ # each instance gets its own copy of instance states
211
+ state.instance_state
212
+ # attempt to access a declared state variable out of context
213
+ # results in an error!
214
+ state.class_state # exception!
215
+ end
216
+
217
+ def self.class_method
218
+ # this is the same state as was referenced in instance_method
219
+ state.shared_state
220
+ # and so is this
221
+ state.class_state
222
+ # and this will raise an exception
223
+ state.instance_state
224
+ end
225
+ ```
226
+
227
+ Class state variables are initialized by an implicit `Hyperloop::Application::Boot` receiver. If an initial value is directly provided (not via a proc, method or block) then the value will be `dup`ed when the second and following Boot dispatches are received. The proc, method or block initializers will run in the context of the class, and the state variable will be available. For example:
228
+
229
+ ```ruby
230
+ state :boot_counter, scope: :shared do
231
+ (state.boot_counter || 0)+1
232
+ end
233
+
234
+ # more practically perhaps:
235
+
236
+ state :my_state, scope: :shared do
237
+ state.my_state || [] # don't re-initialize me on reboots
238
+ end
239
+ ```
240
+
241
+ Instance variables are initialized when instances of the Store are created. Each initialization will `dup` the initial value unless supplied by a proc, method or block.
242
+
243
+ This initialization behavior will work in most cases but for more control simply leave off any initializer, and write your own.
244
+
245
+ **Note for class states there is a subtle difference between saying:**
246
+
247
+ ```ruby
248
+ state my_state: nil, scope: :shared # or :class
249
+ # and
250
+ state :my_state, scope: :shared # or :class
251
+
252
+ ```
253
+
254
+ In the first case `my_state` will be re-initialized to nil on every boot, in the second case it will not.
255
+
256
+ ## Receiving Operation Dispatches
257
+
258
+ Stores can receive Operation dispatches using the receive method.
259
+
260
+ Here is a simple shopping cart Store that receives Add, Remove and Empty Operations:
261
+
262
+ ```ruby
263
+ class Cart < Hyperloop::Store
264
+ # First we will define the two Operations.
265
+ # Because these are closely associated with the Cart
266
+ # we will name space them inside the cart.
267
+ class Add < HyperOperation
268
+ param :item
269
+ param :qty, type: Integer, min: 1
270
+ end
271
+ class Remove < HyperOperation
272
+ param :item
273
+ param :qty, type: Integer, nils: true, min: 1
274
+ end
275
+ class Empty < HyperOperation
276
+ end
277
+
278
+ # The cart's state is represented as a hash, items are the keys, qty is the value
279
+ # initialize the hash by receiving the system Hyperloop::Application::Boot or Empty dispatches
280
+
281
+ receives Hyperloop::Application::Boot, Empty do
282
+ mutate.items(Hash.new { |h, k| h[k] = 0 })
283
+ end
284
+
285
+ # The stores getter (or reader) method
286
+
287
+ def self.items
288
+ state.items
289
+ end
290
+
291
+ def self.empty?
292
+ state.items.empty?
293
+ end
294
+
295
+ receives Add do
296
+ # notice we use mutate.items since we are modifying the hash
297
+ mutate.items[@item] += @qty
298
+ end
299
+
300
+ receives Remove do
301
+ mutate.items[@item] -= @qty
302
+ # remove any items with zero qty from the cart
303
+ mutate.items.delete(@item) if state.items[@item] < 1
304
+ end
305
+ end
306
+ ```
307
+
308
+ This example demonstrates the two ingredients of a Store:
309
+
310
+ + Receiving Operation Dispatches and
311
+ + Reading, and Mutating *states*.
312
+
313
+ These are explained in detail below.
314
+
315
+ The `receive` method takes an list of Operations, and either a symbol (indicating a class method to call), a proc, or a block.
316
+
317
+ When the dispatch is received the method, proc, or block will be run within the context of the Store's class (not an instance.) In addition the `params` method from the Operation will be available to access the Operations parameters.
318
+
319
+ The *Flux* paradigm promotes only mutating state inside of receivers.
320
+
321
+ Hyperloop is less opinionated. You may also add mutator methods to your class. Our recommendation is that you append an exclamation (!) to methods that mutate state.
322
+
323
+ Note that it is reasonable to have several receivers for the same Operation. This allows subclassing, mixins, and separation of concerns.
324
+
325
+ Note also that the Ruby scoping rules make it very reasonable to define the Operations to be received by a Store inside the Store's scope. This does not change the semantics of either the Store or the Operation, but simply keeps the name space organized.
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+ gem 'hyper-spec', path: '../hyper-spec'
3
+ gem 'hyperstack-config', path: '../hyperstack-config'
4
+ gem 'hyper-component', path: '../hyper-component'
5
+ gemspec
@@ -0,0 +1,70 @@
1
+ <h2 align="center">The Complete Isomorphic Ruby Framework</h2>
2
+
3
+ <br>
4
+
5
+ <a href="http://ruby-hyperloop.io/" alt="Hyperloop" title="Hyperloop">
6
+ <img src="http://ruby-hyperloop.io/images/githubhyperloopbadge.png">
7
+ </a>
8
+
9
+ <a href="https://gitter.im/ruby-hyperloop/chat" alt="Gitter chat" title="Gitter chat">
10
+ <img src="http://ruby-hyperloop.io/images/githubgitterbadge.png">
11
+ </a>
12
+
13
+ [![Build Status](https://travis-ci.org/ruby-hyperloop/hyper-store.svg?branch=master)](https://travis-ci.org/ruby-hyperloop/hyper-store)
14
+ [![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)
15
+ [![Gem Version](https://badge.fury.io/rb/hyper-store.svg)](https://badge.fury.io/rb/hyper-store)
16
+
17
+ <p align="center">
18
+ <img src="http://ruby-hyperloop.io/images/HyperStores.png" width="100" alt="Hyperstores">
19
+ </p>
20
+
21
+ </div>
22
+
23
+ ## Hyper-Store GEM is part of Hyperloop GEMS family
24
+
25
+ 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.
26
+
27
+ 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.
28
+
29
+ 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.
30
+
31
+ **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.
32
+
33
+ **Stores are Ruby classes that keep the dynamic parts of the state in special state variables**
34
+
35
+ ## Getting Started
36
+
37
+ 1. Update your Gemfile:
38
+
39
+ ```ruby
40
+ #Gemfile
41
+
42
+ gem 'hyperloop'
43
+ ```
44
+
45
+ 2. At the command prompt, update your bundle :
46
+
47
+ $ bundle update
48
+
49
+ 3. Run the hyperloop install generator:
50
+
51
+ $ rails g hyperloop:install
52
+
53
+ 4. Follow the guidelines to start developing your application. You may find
54
+ the following resources handy:
55
+ * [Getting Started with Hyperloop](http://ruby-hyperloop.io/start/components/)
56
+ * [Hyperloop Guides](http://ruby-hyperloop.io/docs/architecture)
57
+ * [Hyperloop Tutorial](http://ruby-hyperloop.io/tutorials)
58
+
59
+ ## Community
60
+
61
+ #### Getting Help
62
+ 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).
63
+
64
+ #### Submitting Bugs and Enhancements
65
+ [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:
66
+ * Check out our [contributing guide](https://github.com/ruby-hyperloop/hyperloop/blob/master/CONTRIBUTING.md) for info on our release cycle.
67
+
68
+ ## License
69
+
70
+ Hyperloop is released under the [MIT License](http://www.opensource.org/licenses/MIT).