hyper-operation 0.99.6 → 1.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,4 @@
1
- module Hyperloop
1
+ module Hyperstack
2
2
 
3
3
  class InternalClassPolicy
4
4
 
@@ -105,7 +105,7 @@ module Hyperloop
105
105
  def get_ar_model(str)
106
106
  if str.is_a?(Class)
107
107
  unless str <= ActiveRecord::Base
108
- Hyperloop::InternalPolicy.raise_operation_access_violation(:non_ar_class, "#{str} is not a subclass of ActiveRecord::Base")
108
+ Hyperstack::InternalPolicy.raise_operation_access_violation(:non_ar_class, "#{str} is not a subclass of ActiveRecord::Base")
109
109
  end
110
110
  str
111
111
  else
@@ -114,12 +114,12 @@ module Hyperloop
114
114
  # def self.ar_base_descendants_map_cache
115
115
  # @ar_base_descendants_map_cache ||= ActiveRecord::Base.descendants.map(&:name)
116
116
  # end
117
- # if Rails.env.production? && !Hyperloop::InternalClassPolicy.ar_base_descendants_map_cache.include?(str)
117
+ # if Rails.env.production? && !Hyperstack::InternalClassPolicy.ar_base_descendants_map_cache.include?(str)
118
118
  if Rails.application.config.eager_load && !ActiveRecord::Base.descendants.map(&:name).include?(str)
119
119
  # AR::Base.descendants is eager loaded in production -> this guard works.
120
120
  # In development it may be empty or partially filled -> this guard may fail.
121
121
  # Thus guarded here only in production.
122
- Hyperloop::InternalPolicy.raise_operation_access_violation(:non_ar_class, "#{str} is either not defined or is not a subclass of ActiveRecord::Base")
122
+ Hyperstack::InternalPolicy.raise_operation_access_violation(:non_ar_class, "#{str} is either not defined or is not a subclass of ActiveRecord::Base")
123
123
  end
124
124
  Object.const_get(str)
125
125
  end
@@ -202,7 +202,7 @@ module Hyperloop
202
202
  raise "Could not determine the class when regulating. This is probably caused by doing something like &:send_all"
203
203
  end
204
204
  wrapped_policy = policy_klass.new(nil, nil)
205
- wrapped_policy.hyperloop_internal_policy_object = policy
205
+ wrapped_policy.hyperstack_internal_policy_object = policy
206
206
  wrapped_policy
207
207
  end
208
208
 
@@ -353,7 +353,7 @@ module Hyperloop
353
353
  def self.channels(session, acting_user)
354
354
  channels = ClassConnectionRegulation.connections_for(acting_user, true) +
355
355
  InstanceConnectionRegulation.connections_for(acting_user, true)
356
- channels << "Hyperloop::Session-#{session.split('-').last}" if Hyperloop.connect_session && session
356
+ channels << "Hyperstack::Session-#{session.split('-').last}" if Hyperstack.connect_session && session
357
357
  channels.uniq.each { |channel| Connection.open(channel, session) }
358
358
  end
359
359
  end
@@ -379,16 +379,16 @@ module Hyperloop
379
379
  end
380
380
 
381
381
  def self.raise_operation_access_violation(message, details)
382
- Hyperloop.on_error(Hyperloop::AccessViolation, message, details)
383
- raise Hyperloop::AccessViolation
382
+ Hyperstack.on_error(Hyperstack::AccessViolation, message, details)
383
+ raise Hyperstack::AccessViolation
384
384
  end
385
385
 
386
386
  def self.regulate_connection(acting_user, channel_string)
387
387
  channel = channel_string.split("-")
388
388
  if channel.length > 1
389
389
  id = channel[1..-1].join("-")
390
- unless Hyperloop::InternalClassPolicy.regulated_klasses.include?(channel[0])
391
- Hyperloop::InternalPolicy.raise_operation_access_violation(:not_a_channel, "#{channel[0]} is not regulated channel class")
390
+ unless Hyperstack::InternalClassPolicy.regulated_klasses.include?(channel[0])
391
+ Hyperstack::InternalPolicy.raise_operation_access_violation(:not_a_channel, "#{channel[0]} is not regulated channel class")
392
392
  end
393
393
  object = Object.const_get(channel[0]).find(id)
394
394
  InstanceConnectionRegulation.connect(object, acting_user)
@@ -523,12 +523,12 @@ module Hyperloop
523
523
  end
524
524
 
525
525
  module ClassPolicyMethods
526
- def hyperloop_internal_policy_object
527
- @hyperloop_internal_policy_object ||= InternalClassPolicy.new(name || self)
526
+ def hyperstack_internal_policy_object
527
+ @hyperstack_internal_policy_object ||= InternalClassPolicy.new(name || self)
528
528
  end
529
529
  InternalClassPolicy::EXPOSED_METHODS.each do |policy_method|
530
530
  define_method policy_method do |*klasses, &block|
531
- hyperloop_internal_policy_object.send policy_method, *klasses, &block
531
+ hyperstack_internal_policy_object.send policy_method, *klasses, &block
532
532
  end unless respond_to? policy_method
533
533
  end
534
534
  end
@@ -539,10 +539,10 @@ module Hyperloop
539
539
  extend ClassPolicyMethods
540
540
  end
541
541
  end
542
- attr_accessor :hyperloop_internal_policy_object
542
+ attr_accessor :hyperstack_internal_policy_object
543
543
  InternalPolicy::EXPOSED_METHODS.each do |method|
544
544
  define_method method do |*args, &block|
545
- hyperloop_internal_policy_object.send method, *args, &block
545
+ hyperstack_internal_policy_object.send method, *args, &block
546
546
  end unless respond_to? method
547
547
  end
548
548
  define_method :initialize do |*args|
@@ -559,34 +559,34 @@ module Hyperloop
559
559
  end
560
560
 
561
561
  class Module
562
- alias pre_hyperloop_const_set const_set
562
+ alias pre_hyperstack_const_set const_set
563
563
 
564
564
  def const_set(name, value)
565
- pre_hyperloop_const_set(name, value).tap do
566
- Hyperloop::PolicyAutoLoader.load(name, value)
565
+ pre_hyperstack_const_set(name, value).tap do
566
+ Hyperstack::PolicyAutoLoader.load(name, value)
567
567
  end
568
568
  end
569
569
  end
570
570
 
571
571
  class Class
572
572
 
573
- alias pre_hyperloop_inherited inherited
573
+ alias pre_hyperstack_inherited inherited
574
574
 
575
575
  def inherited(child_class)
576
- pre_hyperloop_inherited(child_class).tap do
577
- Hyperloop::PolicyAutoLoader.load(child_class.name, child_class)
576
+ pre_hyperstack_inherited(child_class).tap do
577
+ Hyperstack::PolicyAutoLoader.load(child_class.name, child_class)
578
578
  end
579
579
  end
580
580
 
581
- Hyperloop::ClassPolicyMethods.instance_methods.each do |method|
581
+ Hyperstack::ClassPolicyMethods.instance_methods.each do |method|
582
582
  define_method method do |*args, &block|
583
583
  if name.end_with?("Policy".freeze)
584
- @hyperloop_internal_policy_object = Hyperloop::InternalClassPolicy.new(name.sub(/Policy$/,""))
585
- include Hyperloop::PolicyMethods
584
+ @hyperstack_internal_policy_object = Hyperstack::InternalClassPolicy.new(name.sub(/Policy$/,""))
585
+ include Hyperstack::PolicyMethods
586
586
  send method, *args, &block
587
587
  else
588
588
  class << self
589
- Hyperloop::ClassPolicyMethods.instance_methods.each do |method|
589
+ Hyperstack::ClassPolicyMethods.instance_methods.each do |method|
590
590
  undef_method method
591
591
  end
592
592
  end
@@ -1,5 +1,5 @@
1
- module Hyperloop
1
+ module Hyperstack
2
2
  class Operation
3
- VERSION = '0.99.6'
3
+ VERSION = '1.0.alpha1'
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hyper-operation
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.99.6
4
+ version: 1.0.alpha1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mitch VanDuyn
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2019-01-30 00:00:00.000000000 Z
12
+ date: 2018-11-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
@@ -31,14 +31,14 @@ dependencies:
31
31
  requirements:
32
32
  - - '='
33
33
  - !ruby/object:Gem::Version
34
- version: 0.99.6
34
+ version: 1.0.alpha1
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
39
  - - '='
40
40
  - !ruby/object:Gem::Version
41
- version: 0.99.6
41
+ version: 1.0.alpha1
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: mutations
44
44
  requirement: !ruby/object:Gem::Requirement
@@ -115,14 +115,14 @@ dependencies:
115
115
  requirements:
116
116
  - - '='
117
117
  - !ruby/object:Gem::Version
118
- version: 0.99.6
118
+ version: 1.0.alpha1
119
119
  type: :development
120
120
  prerelease: false
121
121
  version_requirements: !ruby/object:Gem::Requirement
122
122
  requirements:
123
123
  - - '='
124
124
  - !ruby/object:Gem::Version
125
- version: 0.99.6
125
+ version: 1.0.alpha1
126
126
  - !ruby/object:Gem::Dependency
127
127
  name: mysql2
128
128
  requirement: !ruby/object:Gem::Requirement
@@ -369,12 +369,8 @@ extra_rdoc_files: []
369
369
  files:
370
370
  - ".gitignore"
371
371
  - ".travis.yml"
372
- - DOCS-POLICIES.md
373
- - DOCS.md
374
372
  - Gemfile
375
373
  - Gemfile.lock
376
- - LICENSE.txt
377
- - README.md
378
374
  - Rakefile
379
375
  - hyper-operation.gemspec
380
376
  - lib/hyper-operation.rb
@@ -400,13 +396,13 @@ files:
400
396
  - lib/hyper-operation/transport/active_record.rb
401
397
  - lib/hyper-operation/transport/client_drivers.rb
402
398
  - lib/hyper-operation/transport/connection.rb
403
- - lib/hyper-operation/transport/hyperloop.rb
404
- - lib/hyper-operation/transport/hyperloop_controller.rb
399
+ - lib/hyper-operation/transport/hyperstack.rb
400
+ - lib/hyper-operation/transport/hyperstack_controller.rb
405
401
  - lib/hyper-operation/transport/pluck.rb
406
402
  - lib/hyper-operation/transport/policy.rb
407
403
  - lib/hyper-operation/version.rb
408
- - lib/sources/hyperloop/pusher.js
409
- homepage: http://ruby-hyperloop.org
404
+ - lib/sources/hyperstack/pusher.js
405
+ homepage: http://ruby-hyperstack.org
410
406
  licenses:
411
407
  - MIT
412
408
  metadata: {}
@@ -421,12 +417,13 @@ required_ruby_version: !ruby/object:Gem::Requirement
421
417
  version: '0'
422
418
  required_rubygems_version: !ruby/object:Gem::Requirement
423
419
  requirements:
424
- - - ">="
420
+ - - ">"
425
421
  - !ruby/object:Gem::Version
426
- version: '0'
422
+ version: 1.3.1
427
423
  requirements: []
428
- rubygems_version: 3.0.2
424
+ rubyforge_project:
425
+ rubygems_version: 2.7.8
429
426
  signing_key:
430
427
  specification_version: 4
431
- summary: HyperOperations are the swiss army knife of the Hyperloop
428
+ summary: HyperOperations are the swiss army knife of the Hyperstack
432
429
  test_files: []
@@ -1,582 +0,0 @@
1
- # Hyperloop Policies
2
-
3
- ## Authorization
4
-
5
- Access to your Isomorphic Models is controlled by *Policies* that describe how the current *acting_user* and *channels* may access your Models.
6
-
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.
8
-
9
- Read and *broadcast* access is defined based on *channels* which are connected based again on the current `acting_user`. Read access is initiated when a specific browser tries to read a record attribute, and broadcasts are initiated whenever a model changes.
10
-
11
- An application can have several channels and each channel and each active record model can have different policies to determine which attributes are sent when a record changes.
12
-
13
- For example a Todo application might have an *instance* of a channel for each currently logged in user; an instance of a channel for each team if that team has one or more logged in users; and a general `AdminUser` channel shared by all administrators that are logged in.
14
-
15
- Lets say a specific `Todo` changes, which is part of team id 123's Todo list, and users 7 and 8 who are members of that team are currently logged in as well as two of the `AdminUsers`.
16
-
17
- When the `Todo` changes we want all the attributes of the `Todo` broadcast on team 123's channel, as well on the `AdminUser`'s channel. Now lets say User 7 sends User 8 a private message, adding a new record to the `Message` model. This update should only be sent to user 7 and user 8's private channels, as well as to the AdminUser channel.
18
-
19
- We can define all these policies by creating the following classes:
20
-
21
- ```ruby
22
- class UserPolicy # defines policies for the User class
23
- # The regulate_instance_connections method enables instances of the User
24
- # class to be treated as a channel.
25
-
26
- # The policy is defined by a block that is executed in the context of the
27
- # current acting_user.
28
-
29
- # For our User instance connection the policy is that there must be a
30
- # logged-in user, and the connection is made to that user:
31
- regulate_instance_connections { self }
32
- # If there is no logged in user self will be nil, and no connection will be
33
- # made.
34
- end
35
-
36
- class TeamPolicy # defines policies for the Team class
37
- # Users can only connect to Teams that they belong to
38
- regulate_instance_connections { teams }
39
- end
40
-
41
- class AdminUserPolicy
42
- # All AdminUsers share the same connection so we setup a class wide
43
- # connection available to any users who are admins.
44
- regulate_class_connection { admin? }
45
-
46
- # The AdminUser channel will receive all attributes
47
- # of all records, unless the attribute is named :password
48
- regulate_all_broadcasts do |policy|
49
- policy.send_all_but(:password)
50
- end
51
- end
52
-
53
- class TodoPolicy
54
- # Policies can be established for models that are not channels as well.
55
-
56
- # The regulate_broadcast method will describe what attributes to send
57
- # when a Todo model changes.
58
-
59
- # The blocks of broadcast policies run in the context of the changed model
60
- # so we have access to all the models methods. In this case Todo
61
- # belongs to a Team through the 'team' relationship.
62
- regulate_broadcast do |policy|
63
- # send all Todo attributes to the todo's team channel
64
- policy.send_all.to(team)
65
- end
66
- end
67
-
68
- class MessagePolicy
69
- # Broadcast policies can be arbitrarily complex. In this case we
70
- # want to broadcast the entire message to the sender and the
71
- # recipient's instance channels.
72
- # In addition if the message is not private, then we want to send to all
73
- # the team instance channels that are shared between the sender and
74
- # recipient's teams.
75
- regulate_broadcast do |policy|
76
- policy.send_all.to(sender, recipient)
77
- policy.send_all.to(sender.teams.merge(recipient.teams)) unless private?
78
- end
79
- end
80
- ```
81
-
82
- Before we begin using these channels and policies we need to first define the Reactive-Record `acting_user` method in our ApplicationController:
83
-
84
- ```ruby
85
- class ApplicationController < ActionController::Base
86
- def acting_user
87
- # The acting_user method should return nil, or some object that corresponds to a
88
- # logged in user. Specifics will depend on your application and whatever other
89
- # authentication mechanisms you are using.
90
- @acting_user ||= session[:current_user_id] && User.find_by_id(session[:current_user_id])
91
- end
92
- end
93
- end
94
- ```
95
-
96
- Note that `acting_user` is also used by ReactiveRecord's permission system.
97
-
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?
99
-
100
- ### Details
101
-
102
- Hyperloop uses *Policies* to *regulate* what *connections* are opened between clients and the server and what data is distributed over those connections.
103
-
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.
105
-
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.
107
-
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.
109
-
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.
111
-
112
- Note that Models that are associated with channels can also broadcast their changes on the same or different channels.
113
-
114
- #### Defining Policies and Policy Classes
115
-
116
- The best way to define policies is to use a *Policy Class*. A policy class has the same class name as the class it is regulating, with `Policy` added to the end. Policy classes are compatible with `Pundit`, and you can add regular pundit policies as well.
117
-
118
- Policies are defined using four methods:
119
- + `regulate_class_connection` controls connections to the class channels,
120
- + `regulate_instance_connections` controls connections to instance channels,
121
- + `regulate_broadcast` controls what data will be sent when a model or object changes and,
122
- + `regulate_all_broadcasts` controls what data will be sent of some channels when any model changes.
123
-
124
- In addition `always_allow_connection` is short hand for `regulate_class_connection { true }`
125
-
126
- A policy class can be defined for which there is no regulated class. This is useful for application wide connections, which are typically open even if no one is logged in:
127
-
128
- ```ruby
129
- #app/policies/application.rb
130
- class ApplicationPolicy
131
- regulate_class_connection { true }
132
- end
133
- ```
134
-
135
- Note that by default policy classes go in the `app/policies` directory. Hyperloop will require all the files in this directory.
136
-
137
- 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.
138
-
139
- ```ruby
140
- class User < ActiveRecord::Base
141
- include Hyperloop::PolicyMethods
142
- regulate_class_connection ...
143
- regulate_instance_connections ...
144
- regulate_all_broadcasts ...
145
- regulate_broadcast ...
146
- end
147
- ```
148
-
149
- Normally the policy methods are regulating the class with the prefix as the policy, but you can override this by providing specific class names to the policy method. This allows you to group several different class policies together, and to reuse policies:
150
-
151
- ```ruby
152
- class ApplicationPolicy
153
- regulate_connection { ... } # Application is assumed
154
- regulate_class_connection(User) { ... }
155
- # regulate_class_connection, regulate_instance_connections and
156
- # regulate_all_broadcasts can take a list of channels.
157
- regulate_all_broadcasts(User, Application)
158
- # regulate_broadcast takes a list of object classes which
159
- # may also be channels.
160
- regulate_broadcast(Todo, Message, User) { ... }
161
- end
162
- ```
163
-
164
- #### Channels and connection policies
165
-
166
- 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.
167
-
168
- The purpose of having channels is to restrict what gets broadcast when models change, therefore typically channels represent *connections* to
169
-
170
- + the application, or some function within the application
171
- + or some class which is *authenticated* like a User or Administrator,
172
- + instances of those classes,
173
- + or instances of related classes.
174
-
175
- So a channel that is connected to the User class would get information readable by any logged-in user, while a channel that is connected to a specific User instance would get information readable by that specific user.
176
-
177
- The `regulate_class_connection` takes a block that will execute in the context of the current acting_user (which may be nil), and if the block returns any truthy value, the connection will be made.
178
-
179
- The `regulate_instance_connections` likewise takes a block that is executed in the context of the current acting_user. The block may do one of following:
180
-
181
- + raise an error meaning the connection cannot be made
182
- + return a falsy value also meaning the connection cannot be made
183
- + return a single object meaning the connection can be made to that object
184
- + return a enumerable of objects meaning the connection can made to any member of the enumerable
185
-
186
- Note that the object (or objects) returned are expected to be of the same class as the regulated policy.
187
-
188
- ```ruby
189
- # Create a class connection only if the acting_user is non-nil (i.e. logged in:)
190
- regulate_class_connection { self }
191
- # Always open the connection:
192
- regulate_class_connection { true }
193
- # Which can be shortened to:
194
- always_allow_connection
195
- # Create a class level connection if the acting_user is an admin:
196
- regulate_class_connection { admin? }
197
- # Create an instance connection for the current user:
198
- regulate_instance_connections { self }
199
- # Create an instance connection for the current user if the user is an admin:
200
- regulate_instance_connections { self if admin? }
201
- # create an instance_connection to the users' group
202
- regulate_instance_connections { group }
203
- # create an instance connection for any team the user belongs to
204
- regulate_instance_connections { teams }
205
- ```
206
-
207
- #### Class Names Instances and IDs
208
-
209
- 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.
210
-
211
- Typically connections are made to ActiveRecord models, and if those are in the `app/hyperloop/models` folder everything will work fine.
212
-
213
- ## Acting User
214
-
215
- 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.
216
-
217
- ```ruby
218
- class ApplicationController < ActiveController::Base
219
- def acting_user
220
- @acting_user ||= session[:current_user_id] && User.find_by_id(session[:current_user_id])
221
- end
222
- end
223
- end
224
- ```
225
-
226
- #### Automatic Connection
227
-
228
- Connections to channels available to the current `acting_user` are automatically made on the initial page load. This behavior can be turned off with the `auto_connect` option.
229
-
230
- ```ruby
231
- class TeamPolicy
232
- # Allow current users to establish connections to any teams they are
233
- # members of, but disable_auto_connect
234
- regulate_instance_connections(auto_connect: false) { teams }
235
- end
236
- ```
237
-
238
- Its important to consider turning off automatic connections for cases like the above where the user is likely to be a member of many teams. Typically the client application will want to dynamically determine which specific teams to connect to given the current state of the application.
239
-
240
- ### Manually Connecting to Channels
241
-
242
- Normally the client will automatically connect to the available channels when a page loads, but you can also
243
- manually connect on the client in response to some user action like logging in, or the user deciding to
244
- display a specific team status on their dashboard.
245
-
246
- To manually connect a client use the `Hyperloop.connect` method.
247
-
248
- The `connect` method takes any number of arguments each of which is either a class, an object, a String or Array.
249
-
250
- If the argument is a class then the connection will be made to the matching class channel on the server.
251
-
252
- ```ruby
253
- # connect the client to the AdminUser class channel
254
- Hyperloop.connect(AdminUser)
255
- # if the connection is successful the client will begin getting updates on the
256
- # AdminUser class channel
257
- ```
258
-
259
- If the argument is an object then a connection will be made to the matching object on the server.
260
-
261
- ```ruby
262
- # assume current_user is an instance of class User
263
- Hyperloop.connect(current_user)
264
- # current_user.id is used to establish which User instance to connect to on the
265
- # server
266
- ```
267
-
268
- The argument can also be a string, which matches the name of a class on the server
269
-
270
- ```ruby
271
- Hyperloop.connect('AdminUser')
272
- # same as AdminUser class
273
- ```
274
-
275
- or the argument can be an array with a string and the id:
276
-
277
- ```ruby
278
- Hyperloop.connect(['User', current_user.id])
279
- # same as saying current_user
280
- ```
281
-
282
- You can make several connections at once as well:
283
- ```ruby
284
- Hyperloop.connect(AdminUser, current_user)
285
- ```
286
-
287
- Finally falsy values are ignored.
288
-
289
- You can also send `connect` directly to ActiveRecord models:
290
-
291
- ```ruby
292
- AdminUser.connect! # same as Hyperloop.connect(AdminUser)
293
- current_user.connect! # same as Hyperloop.connect(current_user)
294
- ```
295
-
296
- #### Connection Sequence Summary
297
-
298
- For class connections:
299
-
300
- 1. The client calls `Hyperloop.connect`.
301
- 2. Hyperloop sends the channel name to the server.
302
- 3. Hyperloop has its own controller which will determine the `acting_user`,
303
- 4. and call the channel's `regulate_class_connection` method.
304
- 5. If `regulate_class_connection` returns a truthy value then the connection is made,
305
- 6. otherwise a 500 error is returned.
306
-
307
- For instance connections:
308
-
309
- 1. The process is the same but the channel name and id are sent to the server.
310
- 2. The Hyperloop controller will do a `find` of the id passed to get the instance,
311
- 3. and if successful `regulate_instance_connections` is called,
312
- 4. which must return an either the same instance, or an enumerable with that instance as a member.
313
- 5. Otherwise a 500 error is returned.
314
-
315
- Note that the same sequence is used for auto connections and manually invoked connections.
316
-
317
- #### Disconnecting
318
-
319
- Calling `Hyperloop.disconnect(channel)` or `channel.disconnect!` will disconnect from the channel.
320
-
321
- ## Broadcasting and Broadcast Policies
322
-
323
- 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.
324
-
325
- 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.
326
-
327
- The result is that each channel may get a filtered copy of the record which is broadcast on that channel.
328
-
329
- The purpose of the policies then is to determine which channel sees what. Each broadcast policy receives the instance of the policy which responds to the following methods
330
-
331
- + `send_all`: send all the attributes of the record.
332
- + `send_only`: send only the listed attributes of the record.
333
- + `send_all_but`: send all the attributes except the ones listed.
334
-
335
- The result of the `send...` method is then directed to the set of channels using the `to` method:
336
-
337
- ```ruby
338
- policy.send_all_but(:password).to(AdminUser)
339
- ```
340
-
341
- Within channel broadcast policies the channel is assumed to be the channel in question:
342
-
343
- ```ruby
344
- class AdminUserPolicy
345
- regulate_all_broadcasts do |policy|
346
- policy.send_all_but(:password) #.to(AdminUser) is not needed.
347
- end
348
- end
349
- ```
350
-
351
- The `to` method can take any number of arguments:
352
-
353
- + a class naming a channel,
354
- + an object that is instance channel,
355
- + an ActiveRecord collection,
356
- + any falsy value which will be ignored,
357
- + or an array that will be flattened and merged with the other arguments.
358
-
359
- The broadcast policy executes in the context of the model that has just changed, so the policy can use all the methods of that model, especially relationships. For example:
360
-
361
- ```ruby
362
- class Message < ActiveRecord::Base
363
- belongs_to :sender, class: "User"
364
- belongs_to :recipient, class: "User"
365
- end
366
-
367
- class MessagePolicy
368
- regulate_broadcast do |policy|
369
- # send all attributes to both the sender, and recipient User instance channels
370
- policy.send_all.to(sender, recipient)
371
- # send all attributes to intersection
372
- policy.send_all.to(sender.teams.merge(recipient.teams)) unless private?
373
- end
374
- end
375
- ```
376
-
377
- It is possible that the same channel may be sent a record from different policies, in this case the minimum set of attributes will be sent regardless of the order of the send operations. For example:
378
-
379
- ```ruby
380
- policy.send_all_but(:password).to(MyChannel)
381
- # ... later
382
- policy.send_all.to(MyChannel)
383
- # MyChannel gets everything but the password
384
- ```
385
-
386
- or even
387
-
388
- ```ruby
389
- policy.send_only(:foo, :bar).to(MyChannel)
390
- policy.send_only(:baz).to(MyChannel)
391
- # MyChannel gets nothing
392
- ```
393
-
394
- Keep in mind that the broadcast policies are sent a copy of the policy object so you can use helper methods in your policies. Also you can add policy specific methods to your models using `class_eval` thus keeping policy logic out of your models.
395
-
396
- So we could for example we can rewrite the above MessagePolicy like this:
397
-
398
- ```ruby
399
- class MessagePolicy
400
- Message.class_eval do
401
- scope :teams_for_policy, -> () { sender.teams.merge(recipient.teams) }
402
- end
403
- def teams # the obj method returns the instance being regulated
404
- [obj.sender, obj.recipient, !obj.private? && obj.teams_for_policy]
405
- end
406
- regulate_broadcast { |policy| policy.send_all.to(policy.teams) }
407
- end
408
- ```
409
-
410
- ## Regulating Scopes
411
-
412
- Consider the following expression (evaluated on the client)
413
-
414
- ```ruby
415
- Order.for_vip_customers.count
416
- ```
417
-
418
- Even though the policy system will prevent us from looking into the actual attributes of any record, a malicioius hacker can find private information about our data if the above expression is not secured. Moreover a DOS attack could be formed by repeatedly attempting to perform a variant of `Order.all`.
419
-
420
- To prevent this scopes and relationships can also be regulated. A scope and relationship regulation is a proc that will return either a truthy or falsy value or calls the `denied!` method. The proc is evaluated in the context of the relationship object, and the `acting_user` method is available for the proc's use in making decisions.
421
-
422
- All the scopes in a chain are evaluated together, and permission is granted or denied as follows:
423
-
424
- + If *any* of the regulations in a chain of scopes calls `denied!` then the remote request is aborted;
425
- + If *any* of the regulations in a chain of scopes returns a truthy value then access is granted to the entire chain;
426
- + If *none* of the regulations in a chain of scopes returns a truthy value then the request is aborted.
427
-
428
- Example:
429
-
430
- ```ruby
431
- class Order < ApplicationRecord
432
- regulate_scope(:for_vip_customers) { denied! unless acting_user.admin? }
433
- regulate_scope(:active) { acting_user.admin? }
434
- end
435
-
436
- class User < ApplicationRecord
437
- regulate_relationship(:orders) { self == acting_user }
438
- end
439
-
440
- # in component code
441
-
442
- user.orders.count # valid if user is the acting user because the orders regulation returned true
443
- # but will raise error if acting_user is not == user
444
- user.orders.active.count # valid if user is the acting user or if current user is an administrator
445
- user.orders.for_vip_customers # fails unless acting user is an admin
446
- ```
447
-
448
- By default all relationships and scopes (including `all` and `unscoped` have a regulation that returns nil, so unless you explicitly provide a regulation that returns true, the client can not access any scopes.
449
-
450
- There are some short hand ways to define regulations as well:
451
-
452
- #### Constant Regulations
453
-
454
- If the regulation always does the same thing you can specify what to do without the block:
455
-
456
- ```ruby
457
- regulate_scope my_scope: :always_allow # any truthy value works
458
- regulate_scope my_scope: :denied! # :deny or :denied work as well
459
- regulate_relationship many_of_those: :denied! # works the same on relationships
460
- ```
461
-
462
- Always denying a regulation effectively makes it inaccessible except on the server.
463
-
464
- Likewise be careful of always returning true for a scope, as this means that a hacker only needs
465
- to include this scope in the chain to gain access to the chain. So just make sure that scopes that return
466
- true, narrow the scope down to something you would not mind anybody seeing.
467
-
468
- For development you can easily access everything (except regulations that explicitly invoke denied!) simply by doing this:
469
-
470
- ```ruby
471
- class ApplicationRecord < ActiveRecord::Base
472
- regulate_scope all: :always_allow if Rails.env.development?
473
- regulate_scope unscoped: :always_allow if Rails.env.development?
474
- end
475
- ```
476
-
477
- #### Regulations directly on scopes and has_many relationships
478
-
479
- You can also directly add the regulation where you declare the scope or relationship using the `regulate:` option.
480
-
481
- ```ruby
482
- # here is a handy scope to add to ApplicationRecord that you can attach to
483
- # any scope chain to give admin's full access
484
- scope :admin, ->() {}, regulate: -> () { acting_user.admin? || denied! }
485
-
486
- # customers can always see their orders, otherwise we return nil meaning "don't know yet"
487
- has_many :orders, regulate: -> () { acting_user == self }
488
- ```
489
-
490
- ## Regulating server_method and finder_method methods
491
-
492
- The server or finder method proc will be executed in the context of the appropriate object (a record for server_method, and a relationship collection for finder_method.) Attached to this object will be the current `acting_user` method, and a `denied!` method.
493
-
494
- You can use these methods to restrict access to server and finder methods.
495
-
496
- ```ruby
497
- server_method :unit_cost do
498
- denied! unless acting_user.admin? # only admin's can see this
499
- # continue on calculating the unit cost
500
- end
501
- ```
502
-
503
- ## Browser Initiated Change policies
504
-
505
- To allow code in the browser to create, update or destroy a model, there must be a change access policy defined for that operation.
506
-
507
- Each change access policy executes a block in the context of the record that will be accessed. The current value of `acting_user` is also defined for the life of the block.
508
-
509
- 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.
510
-
511
- In the below examples we assume that your user model responds to `admin?` but this is not built into Hyperloop.
512
-
513
- ```ruby
514
- class TodoPolicy
515
- # allow creation to any logged in user
516
- allow_create { acting_user }
517
- # only allow the owner, author any any admin to update a todo
518
- allow_update { acting_user == owner || acting_user == author || acting_user.admin? }
519
- # don't allow Todo's to be destroyed
520
- # this is the default behavior so its not actually needed
521
- allow_destroy { false }
522
- end
523
- ```
524
-
525
- There are several variants of the access policy method:
526
-
527
- ```ruby
528
- class ConfigDataPolicy
529
- allow_change(on: [:create, :update, :destroy]) { acting_user.admin? }
530
- # which can be shortened to:
531
- allow_change { acting_user.admin? }
532
- end
533
- ```
534
-
535
- ```ruby
536
- class ApplicationPolicy
537
- # do any thing to all models unless we are in production! Be careful!
538
- allow_change(to: :all) { true } unless Rails.env.production?
539
- # and always allow admins to destroy models globally:
540
- allow_change(to: :all, on: :destroy) { acting_user.admin? }
541
- # which is the same as saying:
542
- allow_destroy(to: :all) { acting_user.admin? }
543
- # you can create model specific policies in the Application Policy as well.
544
- # Here we allow the author of a message to destroy the message within 5
545
- # minutes of creation.
546
- allow_destroy(to: Message) do
547
- return true if acting_user == author && created_at > 5.minutes.ago
548
- return true if acting_user.admin?
549
- end
550
- end
551
- ```
552
-
553
- Note that there is no `allow_read` method. Read access is granted if this browser would have the attribute broadcast to it.
554
-
555
- ## Method Summary and Name Space Conflicts
556
-
557
- Policy classes (and the Hyperloop::PolicyMethods module) define the following class methods:
558
-
559
- + `regulate_connection`
560
- + `regulate_all_broadcasts`
561
- + `regulate_broadcast`
562
-
563
- As well as the following instance methods:
564
- + `send_all`
565
- + `send_all_but`
566
- + `send_only`
567
- + `obj`
568
-
569
- 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.
570
-
571
- You may thus freely redefine of the class and instance methods if you have name space conflicts
572
-
573
- ```ruby
574
- class ProductionCenterPolicy < MyPolicyClass
575
- # MyPolicyClass already defines our version of obj
576
- # so we will call it 'this'
577
- def this
578
- synchromesh_internal_policy_object.obj
579
- end
580
- ...
581
- end
582
- ```