hyper-store 0.2.3 → 0.99.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 7c7a734c084e864ca2c1d88aa5d89a68a4c03bed
4
- data.tar.gz: 3366e1e947296c941250067b4a090f92c47041ca
2
+ SHA256:
3
+ metadata.gz: 903ba5dc1213e9046457d0ea6c729d2d27ee4fb91506cdb0d475c3954a1ff0cc
4
+ data.tar.gz: f2351c8cd4fc69a738a68cc909c03bc669110cffc4cdac907624fdd8b5ff4c1e
5
5
  SHA512:
6
- metadata.gz: add208b28717ae9331d5a83b9255aa1d7713131723c1d585e5fb295f7f7c3855d89834298ca5af27c099d623e968b07124bf1e89019ff3893a3c9d69ba3f5c5b
7
- data.tar.gz: 573c48f25dd88f50c85c7aca0d8735a4369a0bc1b91db8a3eda15a1e12f069a8fbae0fe6d2049fbc2287c55e24ad2fc0279ded8af04c8712a12c94b2d533fb40
6
+ metadata.gz: a3c98cbc792abffe833851fd2254a964da17f20c7916a6624da413b6b4c93b17a6d13b7a275e3916dddbbd688f0aac502ddcc550eabcf3e67c5d44706575b992
7
+ data.tar.gz: 44cfae8d8d3ab138d253b2bec729ec089830b6218a456b769bf03b085ecdee3e8a26f7e74d17bd42b30e519bfbc3226dbd8a8bb5aee4d67e6f7c04d4b43c03bc
data/.gitignore CHANGED
@@ -11,6 +11,7 @@ capybara-*.html
11
11
  **.orig
12
12
  rerun.txt
13
13
  pickle-email-*.html
14
+ Gemfile.lock
14
15
 
15
16
  # TODO Comment out these rules if you are OK with secrets being uploaded to the repo
16
17
  config/initializers/secret_token.rb
@@ -44,3 +45,9 @@ bower.json
44
45
  # Ignore test_app tmp folders
45
46
  /test_app/tmp
46
47
  /test_app/log
48
+
49
+ # ignore gem
50
+ *.gem
51
+
52
+ # ignore IDE files
53
+ .idea
data/.travis.yml ADDED
@@ -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,312 @@
1
+ # Hyperloop Stores
2
+
3
+ Hyperloop **Stores** are implemented in the **HyperStore Gem**.
4
+
5
+ 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.
6
+
7
+ ```ruby
8
+ class UserStore < Hyperloop::Store
9
+ state :current, scope: :class, reader: true
10
+
11
+ def self.set_current! user
12
+ mutate.current user
13
+ end
14
+ end
15
+
16
+ # to access the store
17
+ UserStore.set_current! user
18
+ UserStore.current_user
19
+ ```
20
+
21
+ **Stores are Ruby classes that keep the dynamic parts of the state in special state variables**
22
+
23
+ + `Hyperloop::Store::Mixin` can be mixed in to any class to turn it into a Flux Store.
24
+ + You can also create Stores by subclassing `Hyperloop::Store`.
25
+ + Stores are built out of *reactive state variables*.
26
+ + Components that *read* a Store's state will **automatically** update when the state changes.
27
+ + All of your **shared** reactive state should be Stores - *The Store is the Truth*!
28
+ + Stores can *receive* **dispatches** from *Operations*
29
+
30
+ ## Reading and Mutating States
31
+
32
+ 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.
33
+
34
+ `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.
35
+
36
+ `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.
37
+
38
+ 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.
39
+
40
+ #### Initializing States
41
+
42
+ To assign a new value to a state use the `mutate` method and pass a parameter to the state:
43
+
44
+ ```ruby
45
+ mutate.items(Hash.new { |h, k| h[k] = 0 })
46
+ ```
47
+
48
+ #### Reading States
49
+
50
+ To read the current value of a state use the `state` method:
51
+
52
+ ```ruby
53
+ state.items # returns current value of items
54
+ ```
55
+
56
+ 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.
57
+
58
+ #### Mutating States
59
+
60
+ 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:
61
+
62
+ ```ruby
63
+ mutate.items[item] = value
64
+ ```
65
+
66
+ ### Instances and Classes
67
+
68
+ Stores are often singleton classes. In an application there is one 'cart' for example.
69
+
70
+ 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.
71
+
72
+ ```ruby
73
+ # Each UserStream provides a stream of unique user profiles.
74
+ # Each instance has a single HyperStore state variable called user
75
+ # user will contain a single hash representing the user profile.
76
+ class UserStream < Hyperloop::Store
77
+
78
+ # get another user
79
+
80
+ def get_another!
81
+ mutate.user UserStream._select_random_user
82
+ end
83
+
84
+ # extract various attributes from the user hash
85
+
86
+ def user_name
87
+ state.user[:login]
88
+ end
89
+
90
+ def user_url
91
+ state.user[:html_url]
92
+ end
93
+
94
+ def avatar
95
+ state.user[:avatar_url]
96
+ end
97
+
98
+ def initialize
99
+ get_another!
100
+ end
101
+
102
+ def self._select_random_user
103
+ # _select_random_user provides a stream of unique user profiles.
104
+ # It will either return a user profile hash, or a promise of one
105
+ # to come.
106
+
107
+ # The cache of users to choose from does not have to be an state
108
+ # variable, so we use plain instance variables.
109
+ return @users.delete_at(rand(@users.length)) unless @users.blank?
110
+ # execute the GetMoreUsers Operation to grab another batch of users
111
+ # if we are not already waiting on a promise
112
+ @promise = GetMoreUsers.then do |response|
113
+ @users = response.json
114
+ end if @promise.nil? || @promise.resolved?
115
+ # wait for the promise to resolve then try again
116
+ @promise.then { _select_random_user }
117
+ end
118
+ end
119
+
120
+ class GetMoreUsers < HyperOperation
121
+ def execute
122
+ HTTP.get("https://api.github.com/users?since=#{rand(500)}")
123
+ end
124
+ end
125
+ ```
126
+ 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.
127
+
128
+ ### States and Promises
129
+
130
+ 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.
131
+
132
+ ### Explicitly Declaring States
133
+
134
+ States like instance variables are created when they are first referenced.
135
+
136
+ As a convenience you may also explicitly declare states. This reduces code noise, and improves readability.
137
+
138
+ ```ruby
139
+ class Cart < Hyperloop::Store
140
+ state items: Hash.new { |h, k| h[k] = 0 }, scope: :class, reader: true
141
+ end
142
+ ```
143
+
144
+ This *declares* the `items` state as a class state variable, will initialize it with the hash on `Hyperloop::Boot`, and provides a reader method.
145
+ That is 6 lines of code for the price of 1, plus now the intention of `items` is clearly defined.
146
+
147
+ The `state` declaration has the following flavors, depending on how the state is to be initialized:
148
+
149
+ ```ruby
150
+ state :items, ... other options ... # items will be initialized to nil
151
+ state items: [1, 2, 3], ... other options ... # items will be initialized to the array [1, 2, 3]
152
+ state :items, ... other options ... do
153
+ ... compute initial value ...
154
+ ... context will be either the class an ...
155
+ ... instance depending on the scope ...
156
+ end
157
+ ```
158
+
159
+ Other options to the `state` declaration are:
160
+
161
+ + `scope:` either `:class`, `:instance`, `:shared`. Details below!
162
+ + `reader:` either `true`, or a symbol used to declare a reader (getter) method.
163
+ + `initializer:` either a Proc or a Symbol (indicating a method), to be used to initialize the state.
164
+
165
+ The value of the `scope` option determines where the state resides.
166
+
167
+ + A class state has one instance per class and is directly accessible in class methods, and indirectly in instances using `self.class.state`.
168
+ + An instance state has a different copy in each instance of the class, and is not accessible by class methods.
169
+ + A shared state is like a class state, but is also directly accessible in instances.
170
+
171
+ The default value for `scope:` depends on where the state is declared:
172
+
173
+ ```ruby
174
+ state :items # declares an instance state variable, each instance gets its own state
175
+ class << self
176
+ state :items # declares a class instance state variable
177
+ end
178
+ ```
179
+
180
+ In the above example there is one class instance state named `items` and an additional state variable also called
181
+ items for each instance.
182
+
183
+ The `shared` option just makes it easier to access a class state from instances.
184
+
185
+ ```ruby
186
+ class MyStore < Hyperloop::Store
187
+ state :shared_state, scope: :shared
188
+ state :class_state, scope: :class
189
+ state :instance_state # scope: :instance is default here
190
+
191
+ def instance_method
192
+ # shared state makes class states easy to access
193
+ state.shared_state
194
+ # without shared state class_state is still accessible
195
+ # with more typing
196
+ self.class.class_state
197
+ # each instance gets its own copy of instance states
198
+ state.instance_state
199
+ # attempt to access a declared state variable out of context
200
+ # results in an error!
201
+ state.class_state # exception!
202
+ end
203
+
204
+ def self.class_method
205
+ # this is the same state as was referenced in instance_method
206
+ state.shared_state
207
+ # and so is this
208
+ state.class_state
209
+ # and this will raise an exception
210
+ state.instance_state
211
+ end
212
+ ```
213
+
214
+ 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:
215
+
216
+ ```ruby
217
+ state :boot_counter, scope: :shared do
218
+ (state.boot_counter || 0)+1
219
+ end
220
+
221
+ # more practically perhaps:
222
+
223
+ state :my_state, scope: :shared do
224
+ state.my_state || [] # don't re-initialize me on reboots
225
+ end
226
+ ```
227
+
228
+ 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.
229
+
230
+ This initialization behavior will work in most cases but for more control simply leave off any initializer, and write your own.
231
+
232
+ **Note for class states there is a subtle difference between saying:**
233
+
234
+ ```ruby
235
+ state my_state: nil, scope: :shared # or :class
236
+ # and
237
+ state :my_state, scope: :shared # or :class
238
+
239
+ ```
240
+
241
+ In the first case `my_state` will be re-initialized to nil on every boot, in the second case it will not.
242
+
243
+ ## Receiving Operation Dispatches
244
+
245
+ Stores can receive Operation dispatches using the receive method.
246
+
247
+ Here is a simple shopping cart Store that receives Add, Remove and Empty Operations:
248
+
249
+ ```ruby
250
+ class Cart < Hyperloop::Store
251
+ # First we will define the two Operations.
252
+ # Because these are closely associated with the Cart
253
+ # we will name space them inside the cart.
254
+ class Add < HyperOperation
255
+ param :item
256
+ param :qty, type: Integer, min: 1
257
+ end
258
+ class Remove < HyperOperation
259
+ param :item
260
+ param :qty, type: Integer, nils: true, min: 1
261
+ end
262
+ class Empty < HyperOperation
263
+ end
264
+
265
+ # The cart's state is represented as a hash, items are the keys, qty is the value
266
+ # initialize the hash by receiving the system Hyperloop::Application::Boot or Empty dispatches
267
+
268
+ receives Hyperloop::Application::Boot, Empty do
269
+ mutate.items(Hash.new { |h, k| h[k] = 0 })
270
+ end
271
+
272
+ # The stores getter (or reader) method
273
+
274
+ def self.items
275
+ state.items
276
+ end
277
+
278
+ def self.empty?
279
+ state.items.empty?
280
+ end
281
+
282
+ receives Add do
283
+ # notice we use mutate.items since we are modifying the hash
284
+ mutate.items[params.item] += params.qty
285
+ end
286
+
287
+ receives Remove do
288
+ mutate.items[params.item] -= params.qty
289
+ # remove any items with zero qty from the cart
290
+ mutate.items.delete(params.item) if state.items[params.item] < 1
291
+ end
292
+ end
293
+ ```
294
+
295
+ This example demonstrates the two ingredients of a Store:
296
+
297
+ + Receiving Operation Dispatches and
298
+ + Reading, and Mutating *states*.
299
+
300
+ These are explained in detail below.
301
+
302
+ The `receive` method takes an list of Operations, and either a symbol (indicating a class method to call), a proc, or a block.
303
+
304
+ 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.
305
+
306
+ The *Flux* paradigm promotes only mutating state inside of receivers.
307
+
308
+ 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.
309
+
310
+ Note that it is reasonable to have several receivers for the same Operation. This allows subclassing, mixins, and separation of concerns.
311
+
312
+ 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  source 'https://rubygems.org'
2
-
3
- #gem 'hyper-react', path: '../hyper-react'
4
-
5
- # Specify your gem's dependencies in hyper-store.gemspec
2
+ #gem "opal-jquery", git: "https://github.com/opal/opal-jquery.git", branch: "master"
3
+ gem 'hyper-spec', path: '../hyper-spec'
4
+ gem 'hyperloop-config', path: '../hyperloop-config'
5
+ gem 'hyper-component', path: '../hyper-component'
6
6
  gemspec
data/README.md CHANGED
@@ -1,38 +1,70 @@
1
- [ ![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)
1
+ <h2 align="center">The Complete Isomorphic Ruby Framework</h2>
2
2
 
3
- ## Hyper-Store gem
3
+ <br>
4
4
 
5
- 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.
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.
6
32
 
7
33
  **Stores are Ruby classes that keep the dynamic parts of the state in special state variables**
8
34
 
9
- + `Hyperloop::Store::Mixin` can be mixed in to any class to turn it into a Flux Store.
10
- + You can also create Stores by subclassing `Hyperloop::Store`.
11
- + Stores are built out of *reactive state variables*.
12
- + Components that *read* a Store's state will **automatically** update when the state changes.
13
- + All of your **shared** reactive state should be Stores - *The Store is the Truth*!
14
- + Stores can *receive* **dispatches** from *Operations*
35
+ ## Getting Started
36
+
37
+ 1. Update your Gemfile:
38
+
39
+ ```ruby
40
+ #Gemfile
41
+
42
+ gem 'hyperloop'
43
+ ```
15
44
 
16
- ## Documentation and Help
45
+ 2. At the command prompt, update your bundle :
17
46
 
18
- + Please see the [ruby-hyperloop.io](http://ruby-hyperloop.io/) website for documentation.
19
- + Join the Hyperloop [gitter.io](https://gitter.im/ruby-hyperloop/chat) chat for help and support.
47
+ $ bundle update
20
48
 
21
- ## Basic Installation and Setup
49
+ 3. Run the hyperloop install generator:
22
50
 
23
- The easiest way to install is to use the `hyper-rails` gem.
51
+ $ rails g hyperloop:install
24
52
 
25
- 1. Add `gem 'hyper-rails'` to your Rails `Gemfile` development section.
26
- 2. Install the Gem: `bundle install`
27
- 3. Run the generator: `bundle exec rails g hyperloop:install --all`
28
- 4. Update the bundle: `bundle update`
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)
29
58
 
30
- Your Isomorphic Operations live in a `hyperloop/stores` folder.
59
+ ## Community
31
60
 
32
- ## Contributing
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).
33
63
 
34
- Bug reports and pull requests are welcome on GitHub at https://github.com/ruby-hyperloop/hyper-store. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Code of Conduct](https://github.com/ruby-hyperloop/hyper-store/blob/master/CODE_OF_CONDUCT.md) code of conduct.
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.
35
67
 
36
68
  ## License
37
69
 
38
- The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
70
+ Hyperloop is released under the [MIT License](http://www.opensource.org/licenses/MIT).