hyper-mesh 1.0.0.lap27 → 1.0.0.lap28
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +6 -2
- data/Gemfile +0 -1
- data/Rakefile +2 -2
- data/hyper-mesh.gemspec +1 -1
- data/lib/active_record_base.rb +39 -27
- data/lib/hyper-mesh.rb +6 -1
- data/lib/hypermesh/version.rb +1 -1
- data/lib/object/tap.rb +7 -0
- data/lib/reactive_record/active_record/associations.rb +14 -3
- data/lib/reactive_record/active_record/base.rb +1 -2
- data/lib/reactive_record/active_record/class_methods.rb +120 -67
- data/lib/reactive_record/active_record/error.rb +17 -12
- data/lib/reactive_record/active_record/errors.rb +374 -0
- data/lib/reactive_record/active_record/instance_methods.rb +58 -67
- data/lib/reactive_record/active_record/reactive_record/backing_record_inspector.rb +1 -4
- data/lib/reactive_record/active_record/reactive_record/base.rb +129 -234
- data/lib/reactive_record/active_record/reactive_record/collection.rb +51 -18
- data/lib/reactive_record/active_record/reactive_record/column_types.rb +5 -3
- data/lib/reactive_record/active_record/reactive_record/dummy_value.rb +6 -4
- data/lib/reactive_record/active_record/reactive_record/getters.rb +133 -0
- data/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb +99 -87
- data/lib/reactive_record/active_record/reactive_record/lookup_tables.rb +54 -0
- data/lib/reactive_record/active_record/reactive_record/operations.rb +2 -1
- data/lib/reactive_record/active_record/reactive_record/scoped_collection.rb +1 -1
- data/lib/reactive_record/active_record/reactive_record/setters.rb +194 -0
- data/lib/reactive_record/active_record/reactive_record/while_loading.rb +4 -5
- data/lib/reactive_record/active_record_error.rb +4 -0
- data/lib/reactive_record/broadcast.rb +55 -18
- data/lib/reactive_record/permissions.rb +5 -4
- data/lib/reactive_record/scope_description.rb +14 -6
- data/lib/reactive_record/server_data_cache.rb +119 -70
- metadata +16 -13
- data/lib/reactive_record/active_record/reactive_record/reactive_set_relationship_helpers.rb +0 -189
@@ -0,0 +1,54 @@
|
|
1
|
+
module ReactiveRecord
|
2
|
+
module LookupTables
|
3
|
+
def initialize_lookup_tables
|
4
|
+
@records = Hash.new { |hash, key| hash[key] = [] }
|
5
|
+
@records_by_id = `{}`
|
6
|
+
@records_by_vector = `{}`
|
7
|
+
@records_by_object_id = `{}`
|
8
|
+
@class_scopes = Hash.new { |hash, key| hash[key] = {} }
|
9
|
+
@waiting_for_save = Hash.new { |hash, key| hash[key] = [] }
|
10
|
+
end
|
11
|
+
|
12
|
+
def class_scopes(model)
|
13
|
+
@class_scopes[model.base_class]
|
14
|
+
end
|
15
|
+
|
16
|
+
def waiting_for_save(model)
|
17
|
+
@waiting_for_save[model]
|
18
|
+
end
|
19
|
+
|
20
|
+
def wait_for_save(model, &block)
|
21
|
+
@waiting_for_save[model] << block
|
22
|
+
end
|
23
|
+
|
24
|
+
def clear_waiting_for_save(model)
|
25
|
+
@waiting_for_save[model] = []
|
26
|
+
end
|
27
|
+
|
28
|
+
def lookup_by_object_id(object_id)
|
29
|
+
`#{@records_by_object_id}[#{object_id}]`.ar_instance
|
30
|
+
end
|
31
|
+
|
32
|
+
def set_object_id_lookup(record)
|
33
|
+
`#{@records_by_object_id}[#{record.object_id}] = #{record}`
|
34
|
+
end
|
35
|
+
|
36
|
+
def lookup_by_id(*args) # model and id
|
37
|
+
`#{@records_by_id}[#{args}]` || nil
|
38
|
+
end
|
39
|
+
|
40
|
+
def set_id_lookup(record)
|
41
|
+
`#{@records_by_id}[#{[record.model, record.id]}] = #{record}`
|
42
|
+
end
|
43
|
+
|
44
|
+
def lookup_by_vector(vector)
|
45
|
+
`#{@records_by_vector}[#{vector}]` || nil
|
46
|
+
end
|
47
|
+
|
48
|
+
def set_vector_lookup(record, vector)
|
49
|
+
record.vector = vector
|
50
|
+
`delete #{@records_by_vector}[#{record.vector}]`
|
51
|
+
`#{@records_by_vector}[#{vector}] = record`
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -74,6 +74,7 @@ module ReactiveRecord
|
|
74
74
|
param :acting_user, nils: true
|
75
75
|
param models: []
|
76
76
|
param associations: []
|
77
|
+
param :save, type: :boolean
|
77
78
|
param :validate, type: :boolean
|
78
79
|
|
79
80
|
step do
|
@@ -82,7 +83,7 @@ module ReactiveRecord
|
|
82
83
|
params.associations.map(&:with_indifferent_access),
|
83
84
|
params.acting_user,
|
84
85
|
params.validate,
|
85
|
-
|
86
|
+
params.save
|
86
87
|
)
|
87
88
|
end
|
88
89
|
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
module ReactiveRecord
|
2
|
+
module Setters
|
3
|
+
def set_attr_value(attr, raw_value)
|
4
|
+
set_common(attr, raw_value) { |value| update_simple_attribute(attr, value) }
|
5
|
+
end
|
6
|
+
|
7
|
+
def set_ar_aggregate(aggr, raw_value)
|
8
|
+
set_common(aggr.attribute, raw_value) do |value, attr|
|
9
|
+
@attributes[attr] ||= aggr.klass.new if new?
|
10
|
+
abr = @attributes[attr].backing_record
|
11
|
+
abr.virgin = false
|
12
|
+
map = value.attributes if value
|
13
|
+
aggr.mapped_attributes.each do |mapped_attr|
|
14
|
+
abr.update_aggregate_attribute mapped_attr, map && map[mapped_attr]
|
15
|
+
end
|
16
|
+
return @attributes[attr]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def set_non_ar_aggregate(aggregation, raw_value)
|
21
|
+
set_common(aggregation.attribute, raw_value) do |value, attr|
|
22
|
+
if data_loading?
|
23
|
+
@synced_attributes[attr] = aggregation.deserialize(aggregation.serialize(value))
|
24
|
+
else
|
25
|
+
changed = !@synced_attributes.key?(attr) || @synced_attributes[attr] != value
|
26
|
+
end
|
27
|
+
set_attribute_change_status_and_notify attr, changed, value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def set_has_many(assoc, raw_value)
|
32
|
+
set_common(assoc.attribute, raw_value) do |value, attr|
|
33
|
+
# create a new collection to hold value, shove it in, and return the new collection
|
34
|
+
# the replace method will take care of updating the inverse belongs_to links as
|
35
|
+
# the collection is overwritten
|
36
|
+
collection = Collection.new(assoc.klass, @ar_instance, assoc)
|
37
|
+
collection.replace(value || [])
|
38
|
+
@synced_attributes[attr] = value if data_loading?
|
39
|
+
set_attribute_change_status_and_notify attr, value != @synced_attributes[attr], collection
|
40
|
+
return collection
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def set_belongs_to(assoc, raw_value)
|
45
|
+
set_common(assoc.attribute, raw_value) do |value, attr|
|
46
|
+
if assoc.inverse.collection?
|
47
|
+
update_has_many_through_associations assoc, value
|
48
|
+
update_inverse_collections assoc, value
|
49
|
+
else
|
50
|
+
update_inverse_attribute assoc, value
|
51
|
+
end
|
52
|
+
# itself will just reactively read the value (a model instance) by doing a .id
|
53
|
+
update_belongs_to attr, value.itself
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def sync_has_many(attr)
|
58
|
+
set_change_status_and_notify_only attr, @attributes[attr] != @synced_attributes[attr]
|
59
|
+
end
|
60
|
+
|
61
|
+
def update_simple_attribute(attr, value)
|
62
|
+
if data_loading?
|
63
|
+
@synced_attributes[attr] = value
|
64
|
+
else
|
65
|
+
changed = !@synced_attributes.key?(attr) || @synced_attributes[attr] != value
|
66
|
+
end
|
67
|
+
set_attribute_change_status_and_notify attr, changed, value
|
68
|
+
end
|
69
|
+
|
70
|
+
alias update_belongs_to update_simple_attribute
|
71
|
+
alias update_aggregate_attribute update_simple_attribute
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def set_common(attr, value)
|
76
|
+
value = convert(attr, value)
|
77
|
+
@virgin = false unless data_loading?
|
78
|
+
if !@destroyed && (
|
79
|
+
!@attributes.key?(attr) ||
|
80
|
+
@attributes[attr].is_a?(Base::DummyValue) ||
|
81
|
+
@attributes[attr] != value)
|
82
|
+
yield value, attr
|
83
|
+
end
|
84
|
+
value
|
85
|
+
end
|
86
|
+
|
87
|
+
def set_attribute_change_status_and_notify(attr, changed, new_value)
|
88
|
+
if @virgin
|
89
|
+
@attributes[attr] = new_value
|
90
|
+
else
|
91
|
+
change_status_and_notify_helper(attr, changed) do |had_key, current_value|
|
92
|
+
@attributes[attr] = new_value
|
93
|
+
if !data_loading? ||
|
94
|
+
(on_opal_client? && had_key && current_value.loaded? && current_value != new_value)
|
95
|
+
React::State.set_state(self, attr, new_value, data_loading?)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def set_change_status_and_notify_only(attr, changed)
|
102
|
+
return if @virgin
|
103
|
+
change_status_and_notify_helper(attr, changed) do
|
104
|
+
React::State.set_state(self, attr, nil) unless data_loading?
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def change_status_and_notify_helper(attr, changed)
|
109
|
+
empty_before = changed_attributes.empty?
|
110
|
+
# TODO: confirm this works:
|
111
|
+
# || data_loading? added so that model.new can be wrapped in a ReactiveRecord.load_data
|
112
|
+
if !changed || data_loading?
|
113
|
+
changed_attributes.delete(attr)
|
114
|
+
elsif !changed_attributes.include?(attr)
|
115
|
+
changed_attributes << attr
|
116
|
+
end
|
117
|
+
yield @attributes.key?(attr), @attributes[attr]
|
118
|
+
return unless empty_before != changed_attributes.empty?
|
119
|
+
if on_opal_client? && !data_loading?
|
120
|
+
React::State.set_state(self, '!CHANGED!', !changed_attributes.empty?, true)
|
121
|
+
end
|
122
|
+
return unless aggregate_owner
|
123
|
+
aggregate_owner.set_change_status_and_notify_only(
|
124
|
+
attr, !@attributes[attr].backing_record.changed_attributes.empty?
|
125
|
+
)
|
126
|
+
end
|
127
|
+
|
128
|
+
def update_inverse_attribute(association, value)
|
129
|
+
# when updating the inverse attribute of a belongs_to that is itself a belongs_to
|
130
|
+
# (i.e. 1-1 relationship) we clear the existing inverse value and then
|
131
|
+
# write the current record to the new value
|
132
|
+
current_value = @attributes[association.attribute]
|
133
|
+
inverse_attr = association.inverse.attribute
|
134
|
+
current_value.attributes[inverse_attr] = nil unless current_value.nil?
|
135
|
+
return if value.nil?
|
136
|
+
value.attributes[inverse_attr] = @ar_instance
|
137
|
+
return if data_loading?
|
138
|
+
React::State.set_state(value.backing_record, inverse_attr, @ar_instance)
|
139
|
+
end
|
140
|
+
|
141
|
+
def update_inverse_collections(association, value)
|
142
|
+
# when updating an inverse attribute of a belongs_to that is a has_many (i.e. a collection)
|
143
|
+
# we need to first remove the current associated value (if non-nil), then add the new
|
144
|
+
# value to the collection. If the inverse collection is not yet initialized we do it here.
|
145
|
+
current_value = @attributes[association.attribute]
|
146
|
+
inverse_attr = association.inverse.attribute
|
147
|
+
if value.nil?
|
148
|
+
current_value.attributes[inverse_attr].delete(@ar_instance) unless current_value.nil?
|
149
|
+
else
|
150
|
+
value.backing_record.push_onto_collection(@model, association.inverse, @ar_instance)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def push_onto_collection(model, association, ar_instance)
|
155
|
+
@attributes[association.attribute] ||= Collection.new(model, @ar_instance, association)
|
156
|
+
@attributes[association.attribute] << ar_instance
|
157
|
+
end
|
158
|
+
|
159
|
+
def update_has_many_through_associations(association, value)
|
160
|
+
association.through_associations.each { |ta| update_through_association(ta, value) }
|
161
|
+
association.source_associations.each { |sa| update_source_association(sa, value) }
|
162
|
+
end
|
163
|
+
|
164
|
+
def update_through_association(ta, new_belongs_to_value)
|
165
|
+
# appointment.doctor = doctor_new_value (i.e. through association is changing)
|
166
|
+
# means appointment.doctor_new_value.patients << appointment.patient
|
167
|
+
# and we have to appointment.doctor_current_value.patients.delete(appointment.patient)
|
168
|
+
source_value = @attributes[ta.source]
|
169
|
+
current_belongs_to_value = @attributes[ta.inverse.attribute]
|
170
|
+
return unless source_value
|
171
|
+
unless current_belongs_to_value.nil? || current_belongs_to_value.attributes[ta.attribute].nil?
|
172
|
+
current_belongs_to_value.attributes[ta.attribute].delete(source_value)
|
173
|
+
end
|
174
|
+
return unless new_belongs_to_value
|
175
|
+
new_belongs_to_value.attributes[ta.attribute] ||= Collection.new(ta.klass, new_belongs_to_value, ta)
|
176
|
+
new_belongs_to_value.attributes[ta.attribute] << source_value
|
177
|
+
end
|
178
|
+
|
179
|
+
def update_source_association(sa, new_source_value)
|
180
|
+
# appointment.patient = patient_value (i.e. source is changing)
|
181
|
+
# means appointment.doctor.patients.delete(appointment.patient)
|
182
|
+
# means appointment.doctor.patients << patient_value
|
183
|
+
belongs_to_value = @attributes[sa.inverse.attribute]
|
184
|
+
current_source_value = @attributes[sa.source]
|
185
|
+
return unless belongs_to_value
|
186
|
+
unless belongs_to_value.attributes[sa.attribute].nil? || current_source_value.nil?
|
187
|
+
belongs_to_value.attributes[sa.attribute].delete(current_source_value)
|
188
|
+
end
|
189
|
+
return unless new_source_value
|
190
|
+
belongs_to_value.attributes[sa.attribute] ||= Collection.new(sa.klass, belongs_to_value, sa)
|
191
|
+
belongs_to_value.attributes[sa.attribute] << new_source_value
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
@@ -17,7 +17,7 @@ module ReactiveRecord
|
|
17
17
|
result = block.call.itself
|
18
18
|
if @loads_pending
|
19
19
|
@blocks_to_load ||= []
|
20
|
-
@blocks_to_load << [Base.
|
20
|
+
@blocks_to_load << [Base.current_fetch_id, promise, block]
|
21
21
|
else
|
22
22
|
promise.resolve result
|
23
23
|
end
|
@@ -36,7 +36,7 @@ module ReactiveRecord
|
|
36
36
|
if Base.pending_fetches.count > 0
|
37
37
|
true
|
38
38
|
else # this happens when for example loading foo.x results in somebody looking at foo.y while foo.y is still being loaded
|
39
|
-
ReactiveRecord::WhileLoading.loaded_at Base.
|
39
|
+
ReactiveRecord::WhileLoading.loaded_at Base.current_fetch_id
|
40
40
|
ReactiveRecord::WhileLoading.quiet!
|
41
41
|
false
|
42
42
|
end
|
@@ -53,8 +53,8 @@ module ReactiveRecord
|
|
53
53
|
@load_stack << @loads_pending
|
54
54
|
@loads_pending = nil
|
55
55
|
result = block.call(failure)
|
56
|
-
if check_loads_pending
|
57
|
-
@blocks_to_load << [Base.
|
56
|
+
if check_loads_pending && !failure
|
57
|
+
@blocks_to_load << [Base.current_fetch_id, promise, block]
|
58
58
|
else
|
59
59
|
promise.resolve result
|
60
60
|
end
|
@@ -297,7 +297,6 @@ if RUBY_ENGINE == 'opal'
|
|
297
297
|
def reactive_record_link_set_while_loading_container_class
|
298
298
|
node = dom_node
|
299
299
|
loading = (waiting_on_resources ? `true` : `false`)
|
300
|
-
#puts "******* reactive_record_link_set_while_loading_container_class #{self} #{node} #{loading}"
|
301
300
|
%x{
|
302
301
|
if (typeof node === "undefined" || node === null) return;
|
303
302
|
var while_loading_container_id = node.getAttribute('data-reactive_record_while_loading_container_id');
|
@@ -23,7 +23,7 @@ module ReactiveRecord
|
|
23
23
|
operation: operation,
|
24
24
|
salt: salt,
|
25
25
|
authorization: authorization
|
26
|
-
)
|
26
|
+
).tap { |p| raise p.error if p.rejected? }
|
27
27
|
end unless RUBY_ENGINE == 'opal'
|
28
28
|
|
29
29
|
class SendPacket < Hyperloop::ServerOp
|
@@ -54,9 +54,7 @@ module ReactiveRecord
|
|
54
54
|
if params.operation == :destroy
|
55
55
|
ReactiveRecord::Collection.sync_scopes broadcast
|
56
56
|
else
|
57
|
-
ReactiveRecord::
|
58
|
-
ReactiveRecord::Collection.sync_scopes broadcast
|
59
|
-
end
|
57
|
+
ReactiveRecord::Collection.sync_scopes broadcast.process_previous_changes
|
60
58
|
end
|
61
59
|
end
|
62
60
|
end
|
@@ -137,47 +135,86 @@ module ReactiveRecord
|
|
137
135
|
@klass = record.class.name
|
138
136
|
@record = data
|
139
137
|
record.backing_record.destroyed = false
|
140
|
-
@record
|
138
|
+
@record[:id] = record.id if record.id
|
141
139
|
record.backing_record.destroyed = @destroyed
|
142
140
|
@backing_record = record.backing_record
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
141
|
+
@previous_changes = record.changes
|
142
|
+
# attributes = record.attributes
|
143
|
+
# data.each do |k, v|
|
144
|
+
# next if klass.reflect_on_association(k) || attributes[k] == v
|
145
|
+
# @previous_changes[k] = [attributes[k], v]
|
146
|
+
# end
|
148
147
|
self
|
149
148
|
end
|
150
149
|
|
151
150
|
def receive(params)
|
152
151
|
@destroyed = params.operation == :destroy
|
153
|
-
@is_new = params.operation == :create
|
154
152
|
@channels ||= Hyperloop::IncomingBroadcast.open_channels.intersection params.channels
|
155
|
-
#raise 'synchromesh security violation' unless @channels.include? params.channels
|
156
153
|
@received << params.channel
|
157
154
|
@klass ||= params.klass
|
158
155
|
@record.merge! params.record
|
159
156
|
@previous_changes.merge! params.previous_changes
|
160
|
-
|
161
|
-
|
157
|
+
ReactiveRecord::Base.when_not_saving(klass) do
|
158
|
+
@backing_record = ReactiveRecord::Base.exists?(klass, params.record[:id])
|
159
|
+
@is_new = params.operation == :create && !@backing_record
|
160
|
+
yield complete! if @channels == @received
|
161
|
+
end
|
162
162
|
end
|
163
163
|
|
164
164
|
def complete!
|
165
165
|
self.class.in_transit.delete @id
|
166
166
|
end
|
167
167
|
|
168
|
+
def value_changed?(attr, value)
|
169
|
+
attrs = @backing_record.synced_attributes
|
170
|
+
return true if attr == @backing_record.primary_key
|
171
|
+
return attrs[attr] != @backing_record.convert(attr, value) if attrs.key?(attr)
|
172
|
+
|
173
|
+
assoc = klass.reflect_on_association_by_foreign_key attr
|
174
|
+
|
175
|
+
return value unless assoc
|
176
|
+
child = attrs[assoc.attribute]
|
177
|
+
return value != child.id if child
|
178
|
+
value
|
179
|
+
end
|
180
|
+
|
181
|
+
def integrity_check
|
182
|
+
@previous_changes.each do |attr, value|
|
183
|
+
next if @record.key?(attr) && @record[attr] == value.last
|
184
|
+
React::IsomorphicHelpers.log "Broadcast contained change to #{attr} -> #{value.last} "\
|
185
|
+
"without corresponding value in attributes (#{@record}).\n",
|
186
|
+
:error
|
187
|
+
raise "Broadcast Integrity Error"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def process_previous_changes
|
192
|
+
return self unless @backing_record
|
193
|
+
integrity_check
|
194
|
+
return self if destroyed?
|
195
|
+
@record.dup.each do |attr, value|
|
196
|
+
next if value_changed?(attr, value)
|
197
|
+
@record.delete(attr)
|
198
|
+
@previous_changes.delete(attr)
|
199
|
+
end
|
200
|
+
self
|
201
|
+
end
|
202
|
+
|
168
203
|
def merge_current_values(br)
|
169
204
|
current_values = Hash[*@previous_changes.collect do |attr, values|
|
170
205
|
value = attr == :id ? record[:id] : values.first
|
171
206
|
if br.attributes.key?(attr) &&
|
172
207
|
br.attributes[attr] != br.convert(attr, value) &&
|
173
208
|
br.attributes[attr] != br.convert(attr, values.last)
|
174
|
-
|
175
|
-
"local value: #{br.attributes[attr]} remote value: #{br.convert(attr, value)}->#{br.convert(attr, values.last)}"
|
209
|
+
React::IsomorphicHelpers.log "warning #{attr} has changed locally - will force a reload.\n"\
|
210
|
+
"local value: #{br.attributes[attr]} remote value: #{br.convert(attr, value)}->#{br.convert(attr, values.last)}",
|
211
|
+
:warning
|
176
212
|
return nil
|
177
213
|
end
|
178
214
|
[attr, value]
|
179
|
-
end.compact.flatten]
|
180
|
-
|
215
|
+
end.compact.flatten]
|
216
|
+
# TODO: verify - it used to be current_values.merge(br.attributes)
|
217
|
+
klass._react_param_conversion(br.attributes.merge(current_values))
|
181
218
|
end
|
182
219
|
end
|
183
220
|
end
|
@@ -88,10 +88,11 @@ class ActiveRecord::Base
|
|
88
88
|
alias belongs_to_without_reactive_record_add_is_method belongs_to
|
89
89
|
|
90
90
|
def belongs_to(attr_name, scope = nil, options = {})
|
91
|
-
|
92
|
-
|
91
|
+
belongs_to_without_reactive_record_add_is_method(attr_name, scope, options).tap do
|
92
|
+
define_method "#{attr_name}_is?".to_sym do |model|
|
93
|
+
self.class.reflections[attr_name].foreign_key == model.id
|
94
|
+
end
|
93
95
|
end
|
94
|
-
belongs_to_without_reactive_record_add_is_method(attr_name, scope, options)
|
95
96
|
end
|
96
97
|
end
|
97
98
|
|
@@ -103,7 +104,7 @@ class ActiveRecord::Base
|
|
103
104
|
self.acting_user = old
|
104
105
|
self
|
105
106
|
else
|
106
|
-
raise
|
107
|
+
raise Hyperloop::AccessViolation, "for #{permission}(#{args})"
|
107
108
|
end
|
108
109
|
end
|
109
110
|
|