hyper-mesh 0.5.3 → 0.5.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/Gemfile +7 -2
- data/README.md +112 -87
- data/Rakefile +6 -1
- data/docs/action_cable_quickstart.md +20 -16
- data/docs/activerecord_api.md +23 -17
- data/docs/authorization-policies.md +45 -35
- data/docs/client_side_scoping.md +5 -5
- data/docs/configuration_details.md +6 -46
- data/docs/pusher_faker_quickstart.md +7 -68
- data/docs/pusher_quickstart.md +7 -68
- data/docs/simple_poller_quickstart.md +6 -67
- data/docs/todo-example.md +2 -2
- data/docs/word_game.md +3 -1
- data/docs/words-example.md +2 -3
- data/examples/action-cable/Gemfile +2 -1
- data/examples/action-cable/Gemfile.lock +73 -54
- data/examples/action-cable/config/initializers/{hyper_mesh.rb → hyperloop.rb} +1 -1
- data/examples/action-cable/config/routes.rb +1 -1
- data/hyper-mesh.gemspec +10 -4
- data/lib/active_record_base.rb +3 -3
- data/{examples/action-cable-production-mode/log/.keep → lib/acts_as_string.rb} +0 -0
- data/lib/hyper-mesh.rb +11 -19
- data/lib/hyper_mesh/version.rb +3 -0
- data/lib/hypermesh/version.rb +1 -1
- data/lib/reactive_record/active_record/class_methods.rb +10 -3
- data/lib/reactive_record/active_record/instance_methods.rb +8 -0
- data/lib/reactive_record/active_record/public_columns_hash.rb +8 -2
- data/lib/reactive_record/active_record/reactive_record/collection.rb +0 -1
- data/lib/reactive_record/active_record/reactive_record/dummy_value.rb +2 -1
- data/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb +55 -63
- data/lib/reactive_record/active_record/reactive_record/operations.rb +51 -0
- data/lib/reactive_record/active_record/reactive_record/reactive_set_relationship_helpers.rb +3 -3
- data/lib/reactive_record/active_record/reactive_record/while_loading.rb +93 -84
- data/lib/reactive_record/broadcast.rb +183 -0
- data/lib/reactive_record/permissions.rb +2 -2
- data/reactive_record_test_app/Gemfile +6 -2
- data/reactive_record_test_app/Gemfile.lock +120 -60
- data/reactive_record_test_app/app/assets/javascripts/application.rb +3 -5
- data/reactive_record_test_app/app/assets/javascripts/bigdecimal.rb +1 -0
- data/reactive_record_test_app/app/assets/javascripts/reactive_record_config.js +2 -2
- data/reactive_record_test_app/app/controllers/application_controller.rb +3 -3
- data/reactive_record_test_app/app/controllers/home_controller.rb +1 -1
- data/reactive_record_test_app/app/models/models.rb.erb +6 -0
- data/reactive_record_test_app/config/application.rb +2 -0
- data/reactive_record_test_app/config/environments/development.rb +1 -1
- data/reactive_record_test_app/config/routes.rb +1 -2
- data/reactive_record_test_app/db/seeds.rb +6 -0
- data/reactive_record_test_app/script/rails +0 -0
- data/reactive_record_test_app/spec-opal/active-record/rendering_spec.rb +11 -2
- data/reactive_record_test_app/spec-opal/active-record/save_spec.rb +3 -4
- data/reactive_record_test_app/spec-opal/spec_helper.js.rb +1 -1
- data/reactive_record_test_app/spec-opal/test_spec.rb +7 -0
- data/reactive_record_test_app/spec_dont_run/README.md +7 -0
- data/reactive_record_test_app/{spec-opal/active-record → spec_dont_run/active_record_broken}/permissions_spec.rb +0 -0
- data/reactive_record_test_app/{spec-opal/active-record → spec_dont_run/active_record_broken}/prerendering_spec.rb +1 -0
- data/spec/{synchromesh/aaa-unit_tests/connection_spec.rb → batch1/aaa-unit_tests/connection_movedspec.rb} +0 -0
- data/spec/{synchromesh → batch1}/aaa-unit_tests/dummy_value_spec.rb +2 -2
- data/spec/{synchromesh → batch1}/column_types/column_type_spec.rb +2 -2
- data/spec/{synchromesh → batch1}/crud_access_regulation/broadcast_controls_access_spec.rb +1 -1
- data/spec/{synchromesh → batch1}/crud_access_regulation/model_policies_spec.rb +6 -6
- data/spec/batch1/misc/access_like_hash_spec.rb +43 -0
- data/spec/batch1/misc/while_loading_spec.rb +196 -0
- data/spec/{synchromesh → batch1}/policies/regulate_all_broadcasts_spec.rb +12 -12
- data/spec/{synchromesh → batch1}/policies/regulate_broadcast_spec.rb +25 -25
- data/spec/{synchromesh/integration → batch2}/authorization_spec.rb +8 -7
- data/spec/{synchromesh/integration → batch2}/default_scope_spec.rb +2 -2
- data/spec/{synchromesh/integration → batch2}/has_many_through_spec.rb +2 -2
- data/spec/{synchromesh/integration → batch2}/relationships_spec.rb +3 -3
- data/spec/{reactive_record → batch3}/auto_load_itself_spec.rb +1 -1
- data/spec/{reactive_record → batch3}/edge_cases_spec.rb +1 -1
- data/spec/{reactive_record → batch3}/finder_method_spec.rb +1 -1
- data/spec/{reactive_record → batch3}/many_to_many_spec.rb +2 -2
- data/spec/{reactive_record → batch3}/pry_rescue_xspec.rb +0 -0
- data/{examples/action-cable-production-mode/public/assets/application-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.css → spec/batch3/readme.txt} +0 -0
- data/spec/{reactive_record → batch3}/revert_spec.rb +2 -2
- data/spec/{reactive_record → batch3}/save_while_loading_spec.rb +1 -1
- data/spec/{reactive_record → batch3}/update_associations_spec.rb +2 -2
- data/spec/{reactive_record → batch3}/update_scopes_spec.rb +2 -2
- data/spec/{synchromesh/integration → batch4}/saving_during_commit_spec.rb +2 -2
- data/spec/{synchromesh/integration → batch4}/scope_spec.rb +30 -2
- data/spec/{synchromesh/examples → batch4}/scoped_todos_spec.rb +3 -3
- data/spec/{synchromesh/integration → batch4}/synchromesh_spec.rb +2 -2
- data/spec/{synchromesh/examples → examples}/dictionary.rb +2 -2
- data/spec/{synchromesh/examples → examples}/dictionary_with_client_scopes.rb +2 -2
- data/spec/{synchromesh/examples → examples}/random_examples.rb +1 -1
- data/spec/{reactive_record/play.rb → play_ground.rb} +0 -0
- data/spec/{reactive_record/factory.rb → reactive_record_factory.rb} +0 -0
- data/spec/spec_helper.rb +3 -2
- data/spec/test_app/Gemfile +8 -3
- data/spec/test_app/Gemfile.lock +114 -64
- data/spec/test_app/app/views/components.rb +1 -2
- data/spec/test_app/config/application.rb +2 -0
- data/spec/test_app/config/routes.rb +1 -1
- data/spec/{synchromesh/integration/test_components.rb → test_components.rb} +0 -0
- metadata +144 -137
- data/app/controllers/reactive_record/application_controller.rb +0 -4
- data/app/controllers/reactive_record/reactive_record_controller.rb +0 -49
- data/config/routes.rb +0 -7
- data/examples/action-cable-production-mode/public/assets/application-90043e04e9e784054fd08159fa7aafe5e23d3ffb31584b1bea1e47043c9cfb5a.js +0 -50
- data/examples/action-cable-production-mode/public/assets/application-90043e04e9e784054fd08159fa7aafe5e23d3ffb31584b1bea1e47043c9cfb5a.js.gz +0 -0
- data/examples/action-cable-production-mode/public/assets/application-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.css.gz +0 -0
- data/examples/action-cable-production-mode/tmp/.keep +0 -0
- data/examples/action-cable/log/.keep +0 -0
- data/examples/action-cable/tmp/.keep +0 -0
- data/examples/pusher-fake/log/.keep +0 -0
- data/examples/pusher-fake/tmp/.keep +0 -0
- data/examples/pusher/log/.keep +0 -0
- data/examples/pusher/tmp/.keep +0 -0
- data/examples/simple-poller/log/.keep +0 -0
- data/examples/simple-poller/tmp/.keep +0 -0
- data/examples/word-game/log/.keep +0 -0
- data/examples/word-game/tmp/.keep +0 -0
- data/examples/words/log/.keep +0 -0
- data/examples/words/tmp/.keep +0 -0
- data/lib/reactive_record/version.rb +0 -3
- data/lib/sources/hyper-mesh/pusher.js +0 -98
- data/lib/synchromesh/action_cable.rb +0 -39
- data/lib/synchromesh/client_drivers.rb +0 -357
- data/lib/synchromesh/configuration.rb +0 -40
- data/lib/synchromesh/connection.rb +0 -170
- data/lib/synchromesh/policy.rb +0 -504
- data/lib/synchromesh/synchromesh.rb +0 -159
- data/lib/synchromesh/synchromesh_controller.rb +0 -162
- data/reactive_record_test_app/README.rdoc +0 -261
- data/reactive_record_test_app/app/assets/javascripts/components/another_component.rb +0 -24
- data/reactive_record_test_app/app/assets/javascripts/components/empty_component.rb +0 -6
- data/reactive_record_test_app/app/assets/javascripts/components/todo_item_component.js.rb +0 -16
- data/reactive_record_test_app/app/assets/javascripts/components/todos_component.js.rb +0 -42
- data/reactive_record_test_app/app/assets/javascripts/components/todos_main_component.rb +0 -49
- data/reactive_record_test_app/app/assets/javascripts/react_js_test_only.js +0 -21618
- data/reactive_record_test_app/app/assets/javascripts/spec/reactive_record_xspec.js.rb +0 -42
- data/reactive_record_test_app/app/controllers/test_controller.rb +0 -7
- data/reactive_record_test_app/app/mailers/.gitkeep +0 -0
- data/reactive_record_test_app/app/models/models.rb +0 -1
- data/reactive_record_test_app/app/policies/application_policy.rb +0 -5
- data/reactive_record_test_app/app/views/components.rb +0 -4
- data/reactive_record_test_app/app/views/components/test.rb +0 -18
- data/reactive_record_test_app/app/views/home/index.html.erb +0 -1
- data/reactive_record_test_app/app/views/layouts/application.html.erb +0 -17
- data/reactive_record_test_app/config/environments/production.rb +0 -70
- data/reactive_record_test_app/config/environments/test.rb +0 -41
- data/spec/synchromesh/integration/transports_spec.rb +0 -308
- data/spec/synchromesh/policies/auto_connect_spec.rb +0 -60
- data/spec/synchromesh/policies/auto_loader_spec.rb +0 -34
- data/spec/synchromesh/policies/policy_methods_spec.rb +0 -85
- data/spec/synchromesh/policies/regulate_class_connection_spec.rb +0 -50
- data/spec/synchromesh/policies/regulate_instance_connection_spec.rb +0 -66
- data/spec/test_app/log/.keep +0 -0
data/docs/activerecord_api.md
CHANGED
@@ -1,46 +1,48 @@
|
|
1
1
|
## ActiveRecord API
|
2
2
|
|
3
|
-
|
3
|
+
Hyperloop uses a subset of the standard ActiveRecord API to give your Isomorphic Components, Operations and Stores access to your server side Models. As much as possible Hyperloop follows the syntax and semantics of ActiveRecord.
|
4
4
|
|
5
5
|
### Interfacing to React
|
6
6
|
|
7
|
-
|
7
|
+
Hyperloop integrates with React (through Components) to deliver your Model data to the client without you having to create extra APIs or specialized controllers. The key idea of React is that when state (or params) change, the portions of the display effected by this data will be updated.
|
8
8
|
|
9
|
-
|
9
|
+
Hyperloop automatically creates React state objects that will be updated as server side data is loaded or changes. When these states change the associated parts of the display will be updated.
|
10
10
|
|
11
|
-
A brief overview of how this works will help you understand the how
|
11
|
+
A brief overview of how this works will help you understand the how Hypeloop gets the job done.
|
12
12
|
|
13
13
|
#### Rendering Cycle
|
14
14
|
|
15
15
|
On the UI you will be reading models in order to display data.
|
16
16
|
|
17
|
-
If during the rendering of the display the
|
17
|
+
If during the rendering of the display the Model data is not yet loaded, placeholder values (the default values from the `columns_hash`) will be returned by Hyperloop.
|
18
18
|
|
19
|
-
|
19
|
+
Hyperloop then keeps track of where these placeholders (or `DummyValue`s) are displayed, and when they do get loaded, those parts of the display will re-render.
|
20
20
|
|
21
21
|
If later the data changes (either due to local user actions, or receiving push updates) then again any parts of the display that were dependent on the current values will be re-rendered.
|
22
22
|
|
23
|
-
You normally do not have to be aware of this. Just access your
|
23
|
+
You normally do not have to be aware of this. Just access your Models using the normal scopes and finders, then compute values and display attributes as you would on the server. Initially the display will show the placeholder values and then will be replaced with the real values.
|
24
24
|
|
25
25
|
#### Prerendering
|
26
26
|
|
27
|
-
During server-side pre-rendering,
|
27
|
+
During server-side pre-rendering, Hyperloop has direct access to the server so on initial page load all the values will be loaded and present.
|
28
28
|
|
29
29
|
#### Lazy Loading
|
30
30
|
|
31
|
-
|
31
|
+
Hyperloop lazy loads values, and does not load any thing until an explicit displayable value is requested. For example `Todo.all` will have no action, but `Todo.all.pluck[:title]` will return an array of titles.
|
32
32
|
|
33
33
|
At the end of the rendering cycle the set of all values requested will be merged into a tree structure and sent to the server, returning the minimum amount of data needed.
|
34
34
|
|
35
35
|
#### Load Cycle Methods
|
36
36
|
|
37
|
+
TODO check link below
|
38
|
+
|
37
39
|
There are a number of methods that allow you to interact with this load cycle when needed. These are documented [below](#other-methods-for-interacting-with-the-load-and-render-cycle).
|
38
40
|
|
39
41
|
### Class Methods
|
40
42
|
|
41
43
|
#### New and Create
|
42
44
|
|
43
|
-
`new`: Takes a hash of attributes and initializes a new unsaved record. The values of any attributes not specified in the hash will be taken from the
|
45
|
+
`new`: Takes a hash of attributes and initializes a new unsaved record. The values of any attributes not specified in the hash will be taken from the Models default values specified in the `columns_hash`.
|
44
46
|
|
45
47
|
If `new` is passed a native javascript object it will be treated as a hash and converted accordingly.
|
46
48
|
|
@@ -48,7 +50,9 @@ If `new` is passed a native javascript object it will be treated as a hash and c
|
|
48
50
|
|
49
51
|
#### Scoping and Finding
|
50
52
|
|
51
|
-
`scope` and `default_scope`:
|
53
|
+
`scope` and `default_scope`: Hyperloop adds four new options to these methods: `joins`, `client`, `select` and `server`. The `joins` option provides information on how the scope will be joined with other models. The `client` and `select` options allow scoping to be done on the client side to offload this from the server, and the `server` option is there just for symmetry with the other options. See the [Client Side Scoping](/docs/client_side_scoping.md) page for more details.
|
54
|
+
|
55
|
+
TODO check link above
|
52
56
|
|
53
57
|
```ruby
|
54
58
|
# the active scope proc is executed on the server
|
@@ -74,7 +78,7 @@ scope :completed,
|
|
74
78
|
Word.all.each { |word| LI { word.text }}
|
75
79
|
```
|
76
80
|
|
77
|
-
BTW: to save typing you can skip the `all`: Models will respond like enumerators
|
81
|
+
BTW: to save typing you can skip the `all`: Models will respond like enumerators.
|
78
82
|
|
79
83
|
`find`: takes an id and delivers the corresponding record.
|
80
84
|
|
@@ -94,7 +98,7 @@ Word.offset(500).limit(20) # get words 500-519
|
|
94
98
|
|
95
99
|
#### Relationships and Aggregations
|
96
100
|
|
97
|
-
`belongs_to, has_many, has_one`: These all work as on the server. However it is important that you fully specify both sides of the relationship
|
101
|
+
`belongs_to, has_many, has_one`: These all work as on the server. **However it is important that you fully specify both sides of the relationship.**
|
98
102
|
|
99
103
|
```ruby
|
100
104
|
class Todo < ActiveRecord::Base
|
@@ -233,12 +237,14 @@ After the destroy completes the record's `destroyed?` method will return true.
|
|
233
237
|
|
234
238
|
All Ruby objects will respond to these methods. If you want to put up a "Please Wait" message, spinner, etc, you can use the `loaded?` or `loading?` method to determine if the object represents a real loaded value or not. Any value for which `loaded?` returns `false` (or `loading?` returns `true`) will eventually load and cause a re-render
|
235
239
|
|
236
|
-
|
240
|
+
TODO check below (was HyperMesh.load)
|
241
|
+
|
242
|
+
#### The `HyperModel.load` Method
|
237
243
|
|
238
|
-
Sometimes it is necessary to insure values are loaded outside of the rendering cycle. For this you can use the `
|
244
|
+
Sometimes it is necessary to insure values are loaded outside of the rendering cycle. For this you can use the `HyperModel.load` method:
|
239
245
|
|
240
246
|
```ruby
|
241
|
-
|
247
|
+
HyperModel.load do
|
242
248
|
x = my_model.some_attribute
|
243
249
|
OtherModel.find(x+12).other_attribute
|
244
250
|
# code in here can be arbitrarily complex and load
|
@@ -266,4 +272,4 @@ before_mount do
|
|
266
272
|
end
|
267
273
|
```
|
268
274
|
|
269
|
-
Think hard about how you are using this, as
|
275
|
+
Think hard about how you are using this, as Hyperloop already acts as flux store, and is managing state for you. It may be you are just creating a redundant store!
|
@@ -1,6 +1,8 @@
|
|
1
|
-
###
|
1
|
+
### Hyperloop Authorization Policies
|
2
2
|
|
3
|
-
|
3
|
+
TODO: Move this doc to a Policy Section
|
4
|
+
|
5
|
+
Access to your Isomorphic Models is controlled by *Policies* that describe how the current *acting_user* and *channels* may access your Models.
|
4
6
|
|
5
7
|
Each browser session has an *acting_user* (which may be nil) and you will define `create`, `update`, and `destroy` policies giving (or denying) the `acting_user` the ability to do these operations.
|
6
8
|
|
@@ -91,23 +93,23 @@ class ApplicationController < ActionController::Base
|
|
91
93
|
end
|
92
94
|
```
|
93
95
|
|
94
|
-
Note that `acting_user` is also used by
|
96
|
+
Note that `acting_user` is also used by ReactiveRecord's permission system.
|
95
97
|
|
96
|
-
Our entire set of policies is defined in 29 lines of code of which 8 actually execute the policies. Our existing classes form the foundation, and we simply add
|
98
|
+
Our entire set of policies is defined in 29 lines of code of which 8 actually execute the policies. Our existing classes form the foundation, and we simply add Hyperloop specific policy directives. Pretty sweet huh?
|
97
99
|
|
98
100
|
### Details
|
99
101
|
|
100
|
-
|
102
|
+
Hyperloop uses *Policies* to *regulate* what *connections* are opened between clients and the server and what data is distributed over those connections.
|
101
103
|
|
102
104
|
Connections are made on *channels* of data flowing between the server and a number of clients. Each channel is associated with either a class or an instance of a class. Typically the channel class represents an entity (or is associated with an entity) that can be authenticated like a `User`, an `AdminUser`, or a `Team` of users. A channel associated with the class itself broadcasts data that is received by any member of that class. A channel associated with an instance is for data that is available only to that specific instance.
|
103
105
|
|
104
|
-
As
|
106
|
+
As Models on the server change (i.e. created, updated, or destroyed) the changes are broadcast over open channels. What specific attributes are sent (if any) is determined by broadcast policies.
|
105
107
|
|
106
|
-
Broadcast policies can be associated with
|
108
|
+
Broadcast policies can be associated with Models. As the Model changes the broadcast policy will regulate what attributes of the changed model will be sent over which channels.
|
107
109
|
|
108
|
-
Broadcast policies can also be associated with a channel and will regulate *all* model changes over specific channels. In other words this is just a convenient way to associate a common policy with *all*
|
110
|
+
Broadcast policies can also be associated with a channel and will regulate *all* model changes over specific channels. In other words this is just a convenient way to associate a common policy with *all* Models.
|
109
111
|
|
110
|
-
Note that
|
112
|
+
Note that Models that are associated with channels can also broadcast their changes on the same or different channels.
|
111
113
|
|
112
114
|
#### Defining Policies and Policy Classes
|
113
115
|
|
@@ -130,13 +132,15 @@ class ApplicationPolicy
|
|
130
132
|
end
|
131
133
|
```
|
132
134
|
|
133
|
-
Note that by default policy classes go in the `app/policies` directory.
|
135
|
+
Note that by default policy classes go in the `app/policies` directory. Hyperloop will require all the files in this directory.
|
136
|
+
|
137
|
+
TODO check the name of the module below
|
134
138
|
|
135
|
-
If you wish, you can also add policies directly in your
|
139
|
+
If you wish, you can also add policies directly in your Models by including the `Hyperloop::PolicyMethods` module in your model. You can then use the `regulate_class_connection`, `regulate_instance_connections`, `regulate_all_broadcasts` and `regulate_broadcast` methods directly in the model.
|
136
140
|
|
137
141
|
```ruby
|
138
142
|
class User < ActiveRecord::Base
|
139
|
-
include
|
143
|
+
include Hyperloop::PolicyMethods
|
140
144
|
regulate_class_connection ...
|
141
145
|
regulate_instance_connections ...
|
142
146
|
regulate_all_broadcasts ...
|
@@ -161,12 +165,12 @@ end
|
|
161
165
|
|
162
166
|
#### Channels and the connection policies
|
163
167
|
|
164
|
-
Any ruby class that has a connection policy is a
|
168
|
+
Any ruby class that has a connection policy is a Hyperloop channel. The fully scoped name of the class becomes the root of the channel name.
|
165
169
|
|
166
170
|
The purpose of having channels is to restrict what gets broadcast when models change, therefore typically channels represent *connections* to
|
167
171
|
|
168
172
|
+ the application, or some function within the application
|
169
|
-
+ or some class which *authenticated* like a User or Administrator,
|
173
|
+
+ or some class which is *authenticated* like a User or Administrator,
|
170
174
|
+ instances of those classes,
|
171
175
|
+ or instances of related classes.
|
172
176
|
|
@@ -202,15 +206,15 @@ regulate_instance_connections { group }
|
|
202
206
|
regulate_instance_connections { teams }
|
203
207
|
```
|
204
208
|
|
205
|
-
#### Class Names, Instances and
|
209
|
+
#### Class Names, Instances and IDs
|
206
210
|
|
207
211
|
While establishing connections, classes are represented as their fully scoped name, and instances are represented as the class name plus the result of calling `id` on the instance.
|
208
212
|
|
209
|
-
Typically connections are made to ActiveRecord models, and if those are in the `app/models
|
213
|
+
Typically connections are made to ActiveRecord models, and if those are in the `app/hyperloop/models` folder everything will work fine.
|
210
214
|
|
211
215
|
#### Acting User
|
212
216
|
|
213
|
-
|
217
|
+
Hyperloop looks for an `acting_user` method typically defined in the ApplicationController and would normally pick up the current session user, and return an appropriate object.
|
214
218
|
|
215
219
|
```ruby
|
216
220
|
class ApplicationController < ActiveController::Base
|
@@ -244,7 +248,9 @@ Normally the client will automatically connect to the available channels when a
|
|
244
248
|
manually connect on the client in response to some user action like logging in, or the user deciding to
|
245
249
|
display a specific team status on their dashboard.
|
246
250
|
|
247
|
-
|
251
|
+
TODO check below Hyperloop.connect
|
252
|
+
|
253
|
+
To manually connect a client use the `Hyperloop.connect` method.
|
248
254
|
|
249
255
|
The `connect` method takes any number of arguments each of which is either a class, an object, a String or Array.
|
250
256
|
|
@@ -252,7 +258,7 @@ If the argument is a class then the connection will be made to the matching clas
|
|
252
258
|
|
253
259
|
```ruby
|
254
260
|
# connect the client to the AdminUser class channel
|
255
|
-
|
261
|
+
Hyperloop.connect(AdminUser)
|
256
262
|
# if the connection is successful the client will begin getting updates on the
|
257
263
|
# AdminUser class channel
|
258
264
|
```
|
@@ -261,7 +267,7 @@ If the argument is an object then a connection will be made to the matching obje
|
|
261
267
|
|
262
268
|
```ruby
|
263
269
|
# assume current_user is an instance of class User
|
264
|
-
|
270
|
+
Hyperloop.connect(current_user)
|
265
271
|
# current_user.id is used to establish which User instance to connect to on the
|
266
272
|
# server
|
267
273
|
```
|
@@ -269,20 +275,20 @@ HyperMesh.connect(current_user)
|
|
269
275
|
The argument can also be a string, which matches the name of a class on the server
|
270
276
|
|
271
277
|
```ruby
|
272
|
-
|
278
|
+
Hyperloop.connect('AdminUser')
|
273
279
|
# same as AdminUser class
|
274
280
|
```
|
275
281
|
|
276
282
|
or the argument can be an array with a string and the id:
|
277
283
|
|
278
284
|
```ruby
|
279
|
-
|
285
|
+
Hyperloop.connect(['User', current_user.id])
|
280
286
|
# same as saying current_user
|
281
287
|
```
|
282
288
|
|
283
289
|
You can make several connections at once as well:
|
284
290
|
```ruby
|
285
|
-
|
291
|
+
Hyperloop.connect(AdminUser, current_user)
|
286
292
|
```
|
287
293
|
|
288
294
|
Finally falsy values are ignored.
|
@@ -290,25 +296,25 @@ Finally falsy values are ignored.
|
|
290
296
|
You can also send `connect` directly to ActiveRecord models:
|
291
297
|
|
292
298
|
```ruby
|
293
|
-
AdminUser.connect! # same as
|
294
|
-
current_user.connect! # same as
|
299
|
+
AdminUser.connect! # same as Hyperloop.connect(AdminUser)
|
300
|
+
current_user.connect! # same as Hyperloop.connect(current_user)
|
295
301
|
```
|
296
302
|
|
297
303
|
#### Connection Sequence Summary
|
298
304
|
|
299
305
|
For class connections:
|
300
306
|
|
301
|
-
1. The client calls `
|
302
|
-
2.
|
303
|
-
3.
|
307
|
+
1. The client calls `Hyperloop.connect`.
|
308
|
+
2. Hyperloop sends the channel name to the server.
|
309
|
+
3. Hyperloop has its own controller which will determine the `acting_user`,
|
304
310
|
4. and call the channel's `regulate_class_connection` method.
|
305
|
-
5. If `regulate_class_connection` returns a truthy value then the
|
311
|
+
5. If `regulate_class_connection` returns a truthy value then the connection is made,
|
306
312
|
6. otherwise a 500 error is returned.
|
307
313
|
|
308
314
|
For instance connections:
|
309
315
|
|
310
316
|
1. The process is the same but the channel name and id are sent to the server.
|
311
|
-
2. The
|
317
|
+
2. The Hyperloop controller will do a `find` of the id passed to get the instance,
|
312
318
|
3. and if successful `regulate_instance_connections` is called,
|
313
319
|
4. which must return an either the same instance, or an enumerable with that instance as a member.
|
314
320
|
5. Otherwise a 500 error is returned.
|
@@ -317,13 +323,15 @@ Note that the same sequence is used for auto connections and manually invoked co
|
|
317
323
|
|
318
324
|
#### Disconnecting
|
319
325
|
|
320
|
-
|
326
|
+
TODO check Hyperloop.disconnect(
|
327
|
+
|
328
|
+
Calling `Hyperloop.disconnect(channel)` or `channel.disconnect!` will disconnect from the channel.
|
321
329
|
|
322
330
|
#### Broadcasting and Broadcast Policies
|
323
331
|
|
324
332
|
Broadcast policies can be defined for channels using the `regulate_all_broadcasts` method, and for individual objects (typically ActiveRecord models) using the `regulate_broadcast` method. A `regulate_all_broadcasts` policy is essentially a `regulate_broadcast` that will be run for every record that changes in the system.
|
325
333
|
|
326
|
-
After an ActiveRecord
|
334
|
+
After an ActiveRecord Model change is committed, all active class channels run their channel broadcast policies, and then the instance broadcast policy associated with the changing Model is run. So for any change there may be multiple channel broadcast policies involved, but only one (at most) regulate_broadcast.
|
327
335
|
|
328
336
|
The result is that each channel may get a filtered copy of the record which is broadcast on that channel.
|
329
337
|
|
@@ -417,7 +425,7 @@ Each change access policy executes a block in the context of the record that wil
|
|
417
425
|
|
418
426
|
If the block returns a truthy value access will be allowed, otherwise if the block returns a falsy value or raises an exception, access will be denied.
|
419
427
|
|
420
|
-
In the below examples we assume that your user model responds to `admin?` but this is not built into
|
428
|
+
In the below examples we assume that your user model responds to `admin?` but this is not built into Hyperloop.
|
421
429
|
|
422
430
|
```ruby
|
423
431
|
class TodoPolicy
|
@@ -464,7 +472,7 @@ Note that there is no `allow_read` method. Read access is granted if this brows
|
|
464
472
|
|
465
473
|
#### Method Summary and Name Space Conflicts
|
466
474
|
|
467
|
-
Policy classes (and the
|
475
|
+
Policy classes (and the Hyperloop::PolicyMethods module) define the following class methods:
|
468
476
|
|
469
477
|
+ `regulate_connection`
|
470
478
|
+ `regulate_all_broadcasts`
|
@@ -476,7 +484,9 @@ As well as the following instance methods:
|
|
476
484
|
+ `send_only`
|
477
485
|
+ `obj`
|
478
486
|
|
479
|
-
|
487
|
+
TODO check `synchromesh_internal_policy_object` new name
|
488
|
+
|
489
|
+
To avoid name space conflicts with your classes, Hyperloop policy classes (and the Hyperloop::PolicyMethods module) maintain class and instance `attr_accessor`s named `synchromesh_internal_policy_object`. The above methods call methods of the same name in the appropriate internal policy object.
|
480
490
|
|
481
491
|
You may thus freely redefine of the class and instance methods if you have name space conflicts
|
482
492
|
|
data/docs/client_side_scoping.md
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
## ActiveRecord Scope Enhancement
|
2
2
|
|
3
|
-
When the client receives notification that a record has changed
|
3
|
+
When the client receives notification that a record has changed Hyperloop finds the set of currently rendered scopes that might be effected, and requests them to be updated from the server.
|
4
4
|
|
5
|
-
On the server scopes are a useful way to structure code. **On the client** scopes are vital as they limit the amount of data loaded, viewed, and updated in the browser. Consider a factory floor management system that shows *job* state as work flows through the factory. There may be millions of jobs that a production floor browser is authorized to view, but at any time there are probably only 50 being shown. Using ActiveRecord scopes is the way
|
5
|
+
On the server scopes are a useful way to structure code. **On the client** scopes are vital as they limit the amount of data loaded, viewed, and updated in the browser. Consider a factory floor management system that shows *job* state as work flows through the factory. There may be millions of jobs that a production floor browser is authorized to view, but at any time there are probably only 50 being shown. Using ActiveRecord scopes is the way Hyperloop keeps the data requested by the browser limited to a reasonable amount.
|
6
6
|
|
7
|
-
To make scopes work efficiently on the client
|
7
|
+
To make scopes work efficiently on the client Hyperloop adds some features to the ActiveRecord `scope` and `default_scope` macros. Note you must use the `scope` macro (and not class methods) for things to work with Hyperloop.
|
8
8
|
|
9
9
|
The additional features are accessed via the `:joins`, `:client`, and `:select` options.
|
10
10
|
|
11
|
-
The `:joins` option tells the
|
11
|
+
The `:joins` option tells the Hyperloop client which models are joined with the scope. *You must add a `:joins` option if the scope has any data base join operations in it, otherwise if a joined model changes, Hyperloop will not know to update the scope.*
|
12
12
|
|
13
13
|
The `:client` and `:select` options provide the client a way to update scopes without having to contact the server. Unlike the `:joins` option this is an optimization and is not required for scopes to work.
|
14
14
|
|
@@ -34,7 +34,7 @@ class Todo < ActiveRecord::Base
|
|
34
34
|
# Now with_recent_comments will be re-evaluated whenever a Todo record, or a Comment
|
35
35
|
# joined with a Todo change.
|
36
36
|
|
37
|
-
# Normally whenever
|
37
|
+
# Normally whenever Hyperloop detects that a scope may be effected by a changed
|
38
38
|
# model, it will request the scope be re-evaluated on the server. To offload this
|
39
39
|
# computation to the client provide a client side scope method:
|
40
40
|
|
@@ -1,39 +1,20 @@
|
|
1
|
-
#
|
1
|
+
# Hyperloop Configuration
|
2
2
|
|
3
|
+
TODO will this be replaced by readme from hyper-configuration gem?
|
3
4
|
|
4
|
-
|
5
|
-
|
6
|
-
There are XXX parts to the HyperMesh configuration:
|
5
|
+
There are parts to the Hyperloop configuration:
|
7
6
|
|
8
7
|
1. Model classes
|
9
8
|
2. Policies
|
10
9
|
3. Push Transport
|
11
10
|
|
12
|
-
## Installation
|
13
|
-
|
14
|
-
If you do not already have hyper-react installed, then use the reactrb-rails-generator gem to setup hyper-react, reactive-record and associated gems.
|
15
|
-
|
16
|
-
Then add this line to your application's Gemfile:
|
17
|
-
|
18
|
-
```ruby
|
19
|
-
gem 'HyperMesh'
|
20
|
-
```
|
21
|
-
|
22
|
-
And then execute:
|
23
|
-
|
24
|
-
$ bundle install
|
25
|
-
|
26
|
-
Also you must `require 'hyper-tracemesh'` from your client side code. The easiest way is to
|
27
|
-
find the `require 'reactive-record'` line (typically in `components.rb`) and replace it with
|
28
|
-
`require 'HyperMesh'`.
|
29
|
-
|
30
11
|
## Configuration
|
31
12
|
|
32
13
|
Add an initializer like this:
|
33
14
|
|
34
15
|
```ruby
|
35
|
-
# for rails this would go in: config/initializers/
|
36
|
-
|
16
|
+
# for rails this would go in: config/initializers/Hyperloop.rb
|
17
|
+
Hyperloop.configuration do |config|
|
37
18
|
config.transport = :simple_poller # or :none, action_cable, :pusher - see below)
|
38
19
|
end
|
39
20
|
# for a minimal setup you will need to define at least one channel, which you can do
|
@@ -47,27 +28,6 @@ class ApplicationPolicy
|
|
47
28
|
end
|
48
29
|
```
|
49
30
|
|
50
|
-
|
51
|
-
|
52
|
-
1. **Add the gem**
|
53
|
-
add `gem 'hyper-mesh'`, and bundle install
|
54
|
-
6. **Add the models directory to asset path**
|
55
|
-
```ruby
|
56
|
-
# application.rb
|
57
|
-
config.assets.paths << ::Rails.root.join('app', 'models').to_s
|
58
|
-
```
|
59
|
-
|
60
|
-
2. **Require HyperMesh instead of HyperReact**
|
61
|
-
replace `require 'hyper-react'` with `require 'hyper-mesh'` in the components manifest (`app/views/components.rb`.)
|
62
|
-
3. **Require your models on the client_side_scoping**
|
63
|
-
add `require 'models'` to the bottom of the components manifest
|
64
|
-
4. add a models manifest in the models directory:
|
65
|
-
```ruby
|
66
|
-
# app/models/models.rb
|
67
|
-
require_tree './public'
|
68
|
-
```
|
69
|
-
5. create a `public` directory in your models directory and move any models that you want access to on the client into this directory. Access to these models will be protected by *Policies* you will be creating later.
|
70
|
-
|
71
|
-
A minimal HyperMesh configuration consists of a simple initializer file, and at least one *Policy* class that will *authorize* who gets to see what.
|
31
|
+
A minimal Hyperloop configuration consists of a simple initializer file, and at least one *Policy* class that will *authorize* who gets to see what.
|
72
32
|
|
73
33
|
The initializer file specifies what transport will be used. Currently you can use [Pusher](http://pusher.com), ActionCable (if using Rails 5), Pusher-Fake (for development) or a Simple Poller for testing etc.
|
@@ -7,7 +7,7 @@ The [Pusher-Fake](https://github.com/tristandunn/pusher-fake) gem will provide a
|
|
7
7
|
- add `gem 'pusher'` to your Gemfile.
|
8
8
|
- add `gem 'pusher-fake'` to the development and test sections of your Gemfile.
|
9
9
|
|
10
|
-
If you have not already installed the `hyper-
|
10
|
+
If you have not already installed the `hyper-component` and `hyper-model` gems, then do so now using the [hyper-rails](https://github.com/ruby-hyperloop/hyper-rails) gem.
|
11
11
|
|
12
12
|
- add `gem 'hyper-rails'` to your gem file (in the development section)
|
13
13
|
- run `bundle install`
|
@@ -19,16 +19,16 @@ If you have not already installed the `hyper-react` and `hyper-mesh` gems, then
|
|
19
19
|
```ruby
|
20
20
|
# app/assets/javascript/application.js
|
21
21
|
...
|
22
|
-
//= require 'hyper-
|
22
|
+
//= require 'hyper-model/pusher'
|
23
23
|
//= require_tree .
|
24
24
|
Opal.load('components');
|
25
25
|
```
|
26
26
|
|
27
27
|
#### 3 Set the transport
|
28
28
|
|
29
|
-
Once you have
|
29
|
+
Once you have Hyperloop, and pusher installed then add this initializer:
|
30
30
|
```ruby
|
31
|
-
# typically app/config/initializers/
|
31
|
+
# typically app/config/initializers/Hyperloop.rb
|
32
32
|
# or you can do a similar setup in your tests (see this gem's specs)
|
33
33
|
require 'pusher'
|
34
34
|
require 'pusher-fake'
|
@@ -40,9 +40,9 @@ Pusher.secret = "MY_TEST_SECRET"
|
|
40
40
|
# The next line actually starts the pusher-fake server (see the Pusher-Fake readme for details.)
|
41
41
|
require 'pusher-fake/support/base' # if using pusher with rspec change this to pusher-fake/support/rspec
|
42
42
|
# now copy over the credentials, and merge with PusherFake's config details
|
43
|
-
|
43
|
+
Hyperloop.configuration do |config|
|
44
44
|
config.transport = :pusher
|
45
|
-
config.channel_prefix = "
|
45
|
+
config.channel_prefix = "Hyperloop"
|
46
46
|
config.opts = {
|
47
47
|
app_id: Pusher.app_id,
|
48
48
|
key: Pusher.key,
|
@@ -53,65 +53,4 @@ end
|
|
53
53
|
|
54
54
|
#### 4 Try It Out
|
55
55
|
|
56
|
-
|
57
|
-
|
58
|
-
`bundle exec rails generate model Word text:string`
|
59
|
-
|
60
|
-
`bundle exec rake db:migrate`
|
61
|
-
|
62
|
-
Move `app/models/word.rb` to `app/models/public/word.rb`
|
63
|
-
|
64
|
-
**Leave** `app/models/model.rb` where it is. This is your models client side manifest file.
|
65
|
-
|
66
|
-
Whatever model(s) you will plan to access on the client need to moved to the `app/models/public` directory. This allows reactive-record to build a client side proxy for the models. Models not moved will be completely invisible on the client side.
|
67
|
-
|
68
|
-
**Important** in rails 5 there is also a base `ApplicationRecord` class, that all other models are built from. This class must be moved to the public directory as well.
|
69
|
-
|
70
|
-
If you don't already have a component to play with, here is a simple one (make sure you added the Word model):
|
71
|
-
|
72
|
-
```ruby
|
73
|
-
# app/views/components/app.rb
|
74
|
-
class App < React::Component::Base
|
75
|
-
|
76
|
-
def add_new_word
|
77
|
-
# for fun we will use setgetgo.com to get random words!
|
78
|
-
HTTP.get("http://randomword.setgetgo.com/get.php", dataType: :jsonp) do |response|
|
79
|
-
Word.new(text: response.json[:Word]).save
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
render(DIV) do
|
84
|
-
SPAN { "Count of Words: #{Word.count}" }
|
85
|
-
BUTTON { "add another" }.on(:click) { add_new_word }
|
86
|
-
UL do
|
87
|
-
Word.each { |word| LI { word.text } }
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
91
|
-
```
|
92
|
-
|
93
|
-
Add a controller:
|
94
|
-
|
95
|
-
```ruby
|
96
|
-
#app/controllers/test_controller.rb
|
97
|
-
class TestController < ApplicationController
|
98
|
-
def app
|
99
|
-
render_component
|
100
|
-
end
|
101
|
-
end
|
102
|
-
```
|
103
|
-
|
104
|
-
Add the `test` route to your routes file:
|
105
|
-
|
106
|
-
```ruby
|
107
|
-
#app/config/routes.rb
|
108
|
-
|
109
|
-
get 'test', to: 'test#app'
|
110
|
-
|
111
|
-
```
|
112
|
-
|
113
|
-
Fire up rails with `bundle exec rails s` and open your app in a couple of browsers. As data changes you should see them all updating together.
|
114
|
-
|
115
|
-
You can also fire up a rails console, and then for example do a `Word.new(text: "Hello").save` and again see any browsers updating.
|
116
|
-
|
117
|
-
If you want to go into more details with the example check out [words-example](/docs/words-example.md)
|
56
|
+
TODO include try_it_out partial
|