hyper-model 0.6.0 → 0.99.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +35 -41
- data/.rspec +2 -0
- data/.travis.yml +33 -0
- data/CHANGELOG.md +34 -0
- data/DOCS.md +735 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +298 -224
- data/{LICENSE → LICENSE.txt} +6 -6
- data/README.md +51 -2
- data/Rakefile +18 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/codeship.database.yml +18 -0
- data/hyper-model.gemspec +62 -36
- data/lib/active_model_client_stubs.rb +16 -0
- data/lib/active_record_base.rb +331 -0
- data/{examples/chat-app/app/assets/images/.keep → lib/acts_as_string.rb} +0 -0
- data/lib/enumerable/pluck.rb +6 -0
- data/lib/hyper-model.rb +59 -8
- data/lib/hyper_model/version.rb +3 -0
- data/lib/hyper_react/input_tags.rb +47 -0
- data/lib/hyperloop/model/load.rb +1 -1
- data/lib/kernel/itself.rb +5 -0
- data/lib/object/tap.rb +7 -0
- data/lib/opal/equality_patches.rb +15 -0
- data/lib/opal/parse_patch.rb +14 -0
- data/lib/opal/set_patches.rb +8 -0
- data/lib/reactive_record/active_record/aggregations.rb +69 -0
- data/lib/reactive_record/active_record/associations.rb +118 -0
- data/lib/reactive_record/active_record/base.rb +10 -0
- data/lib/reactive_record/active_record/class_methods.rb +406 -0
- data/lib/reactive_record/active_record/error.rb +31 -0
- data/lib/reactive_record/active_record/errors.rb +374 -0
- data/lib/reactive_record/active_record/instance_methods.rb +187 -0
- data/lib/reactive_record/active_record/public_columns_hash.rb +44 -0
- data/lib/reactive_record/active_record/reactive_record/backing_record_inspector.rb +36 -0
- data/lib/reactive_record/active_record/reactive_record/base.rb +416 -0
- data/lib/reactive_record/active_record/reactive_record/collection.rb +558 -0
- data/lib/reactive_record/active_record/reactive_record/column_types.rb +75 -0
- data/lib/reactive_record/active_record/reactive_record/dummy_value.rb +236 -0
- data/lib/reactive_record/active_record/reactive_record/getters.rb +133 -0
- data/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb +576 -0
- data/lib/reactive_record/active_record/reactive_record/lookup_tables.rb +54 -0
- data/lib/reactive_record/active_record/reactive_record/operations.rb +107 -0
- data/lib/reactive_record/active_record/reactive_record/scoped_collection.rb +62 -0
- data/lib/reactive_record/active_record/reactive_record/setters.rb +194 -0
- data/lib/reactive_record/active_record/reactive_record/unscoped_collection.rb +16 -0
- data/lib/reactive_record/active_record/reactive_record/while_loading.rb +343 -0
- data/lib/reactive_record/active_record_error.rb +4 -0
- data/lib/reactive_record/broadcast.rb +223 -0
- data/lib/reactive_record/engine.rb +11 -0
- data/lib/reactive_record/interval.rb +190 -0
- data/lib/reactive_record/permissions.rb +117 -0
- data/lib/reactive_record/pry.rb +13 -0
- data/lib/reactive_record/reactive_scope.rb +18 -0
- data/lib/reactive_record/scope_description.rb +121 -0
- data/lib/reactive_record/serializers.rb +7 -0
- data/lib/reactive_record/server_data_cache.rb +478 -0
- data/path_release_steps.md +9 -0
- metadata +399 -109
- data/CODE_OF_CONDUCT.md +0 -49
- data/examples/chat-app/.gitignore +0 -21
- data/examples/chat-app/Gemfile +0 -62
- data/examples/chat-app/Gemfile.lock +0 -309
- data/examples/chat-app/README.md +0 -3
- data/examples/chat-app/Rakefile +0 -6
- data/examples/chat-app/app/assets/config/manifest.js +0 -3
- data/examples/chat-app/app/assets/javascripts/application.js +0 -3
- data/examples/chat-app/app/assets/stylesheets/application.scss +0 -33
- data/examples/chat-app/app/controllers/application_controller.rb +0 -3
- data/examples/chat-app/app/controllers/home_controller.rb +0 -5
- data/examples/chat-app/app/hyperloop/components/app.rb +0 -12
- data/examples/chat-app/app/hyperloop/components/formatted_div.rb +0 -15
- data/examples/chat-app/app/hyperloop/components/input_box.rb +0 -26
- data/examples/chat-app/app/hyperloop/components/message.rb +0 -29
- data/examples/chat-app/app/hyperloop/components/messages.rb +0 -8
- data/examples/chat-app/app/hyperloop/components/nav.rb +0 -30
- data/examples/chat-app/app/hyperloop/models/application_record.rb +0 -3
- data/examples/chat-app/app/hyperloop/models/message.rb +0 -6
- data/examples/chat-app/app/hyperloop/operations/operations.rb +0 -13
- data/examples/chat-app/app/hyperloop/stores/message_store.rb +0 -17
- data/examples/chat-app/app/policies/application_policy.rb +0 -9
- data/examples/chat-app/app/views/layouts/application.html.erb +0 -12
- data/examples/chat-app/bin/bundle +0 -3
- data/examples/chat-app/bin/rails +0 -9
- data/examples/chat-app/bin/rake +0 -9
- data/examples/chat-app/bin/setup +0 -34
- data/examples/chat-app/bin/spring +0 -17
- data/examples/chat-app/bin/update +0 -29
- data/examples/chat-app/config.ru +0 -5
- data/examples/chat-app/config/application.rb +0 -12
- data/examples/chat-app/config/boot.rb +0 -3
- data/examples/chat-app/config/cable.yml +0 -9
- data/examples/chat-app/config/database.yml +0 -25
- data/examples/chat-app/config/environment.rb +0 -5
- data/examples/chat-app/config/environments/development.rb +0 -56
- data/examples/chat-app/config/environments/production.rb +0 -86
- data/examples/chat-app/config/environments/test.rb +0 -42
- data/examples/chat-app/config/initializers/application_controller_renderer.rb +0 -6
- data/examples/chat-app/config/initializers/assets.rb +0 -11
- data/examples/chat-app/config/initializers/backtrace_silencers.rb +0 -7
- data/examples/chat-app/config/initializers/cookies_serializer.rb +0 -5
- data/examples/chat-app/config/initializers/filter_parameter_logging.rb +0 -4
- data/examples/chat-app/config/initializers/hyperloop.rb +0 -6
- data/examples/chat-app/config/initializers/inflections.rb +0 -16
- data/examples/chat-app/config/initializers/mime_types.rb +0 -4
- data/examples/chat-app/config/initializers/new_framework_defaults.rb +0 -24
- data/examples/chat-app/config/initializers/session_store.rb +0 -3
- data/examples/chat-app/config/initializers/wrap_parameters.rb +0 -14
- data/examples/chat-app/config/locales/en.yml +0 -23
- data/examples/chat-app/config/puma.rb +0 -47
- data/examples/chat-app/config/routes.rb +0 -5
- data/examples/chat-app/config/secrets.yml +0 -22
- data/examples/chat-app/config/spring.rb +0 -6
- data/examples/chat-app/db/migrate/20170319194429_create_message.rb +0 -9
- data/examples/chat-app/db/schema.rb +0 -48
- data/examples/chat-app/db/seeds.rb +0 -7
- data/examples/chat-app/lib/assets/.keep +0 -0
- data/examples/chat-app/lib/tasks/.keep +0 -0
- data/examples/chat-app/log/.keep +0 -0
- data/examples/chat-app/public/404.html +0 -67
- data/examples/chat-app/public/422.html +0 -67
- data/examples/chat-app/public/500.html +0 -66
- data/examples/chat-app/public/apple-touch-icon-precomposed.png +0 -0
- data/examples/chat-app/public/apple-touch-icon.png +0 -0
- data/examples/chat-app/public/favicon.ico +0 -0
- data/examples/chat-app/public/robots.txt +0 -5
- data/examples/chat-app/test/controllers/.keep +0 -0
- data/examples/chat-app/test/fixtures/.keep +0 -0
- data/examples/chat-app/test/fixtures/files/.keep +0 -0
- data/examples/chat-app/test/helpers/.keep +0 -0
- data/examples/chat-app/test/integration/.keep +0 -0
- data/examples/chat-app/test/mailers/.keep +0 -0
- data/examples/chat-app/test/models/.keep +0 -0
- data/examples/chat-app/test/test_helper.rb +0 -10
- data/examples/chat-app/tmp/.keep +0 -0
- data/examples/chat-app/vendor/assets/javascripts/.keep +0 -0
- data/examples/chat-app/vendor/assets/stylesheets/.keep +0 -0
- data/lib/hyperloop/model/version.rb +0 -5
@@ -0,0 +1,223 @@
|
|
1
|
+
module ReactiveRecord
|
2
|
+
class Broadcast
|
3
|
+
|
4
|
+
def self.after_commit(operation, model)
|
5
|
+
Hyperloop::InternalPolicy.regulate_broadcast(model) do |data|
|
6
|
+
if !Hyperloop.on_server? && Hyperloop::Connection.root_path
|
7
|
+
send_to_server(operation, data) rescue nil # server no longer running so ignore
|
8
|
+
else
|
9
|
+
SendPacket.run(data, operation: operation)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
rescue ActiveRecord::StatementInvalid => e
|
13
|
+
raise e unless e.message == "Could not find table 'hyperloop_connections'"
|
14
|
+
end unless RUBY_ENGINE == 'opal'
|
15
|
+
|
16
|
+
def self.send_to_server(operation, data)
|
17
|
+
salt = SecureRandom.hex
|
18
|
+
authorization = Hyperloop.authorization(salt, data[:channel], data[:broadcast_id])
|
19
|
+
raise 'no server running' unless Hyperloop::Connection.root_path
|
20
|
+
SendPacket.remote(
|
21
|
+
Hyperloop::Connection.root_path,
|
22
|
+
data,
|
23
|
+
operation: operation,
|
24
|
+
salt: salt,
|
25
|
+
authorization: authorization
|
26
|
+
).tap { |p| raise p.error if p.rejected? }
|
27
|
+
end unless RUBY_ENGINE == 'opal'
|
28
|
+
|
29
|
+
class SendPacket < Hyperloop::ServerOp
|
30
|
+
param authorization: nil, nils: true
|
31
|
+
param salt: nil
|
32
|
+
param :operation
|
33
|
+
param :broadcast_id
|
34
|
+
param :channel
|
35
|
+
param :channels
|
36
|
+
param :klass
|
37
|
+
param :record
|
38
|
+
param :operation
|
39
|
+
param :previous_changes
|
40
|
+
|
41
|
+
unless RUBY_ENGINE == 'opal'
|
42
|
+
validate do
|
43
|
+
params.authorization.nil? ||
|
44
|
+
Hyperloop.authorization(
|
45
|
+
params.salt, params.channel, params.broadcast_id
|
46
|
+
) == params.authorization
|
47
|
+
end
|
48
|
+
dispatch_to do
|
49
|
+
# No need to broadcast if the changes are filtered out by a policy
|
50
|
+
params.channel unless params.operation == :change && params.previous_changes.empty?
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
SendPacket.on_dispatch do |params|
|
56
|
+
in_transit[params.broadcast_id].receive(params) do |broadcast|
|
57
|
+
if params.operation == :destroy
|
58
|
+
ReactiveRecord::Collection.sync_scopes broadcast
|
59
|
+
else
|
60
|
+
ReactiveRecord::Collection.sync_scopes broadcast.process_previous_changes
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.to_self(record, data = {})
|
66
|
+
# simulate incoming packet after a local save
|
67
|
+
operation = if record.new?
|
68
|
+
:create
|
69
|
+
elsif record.destroyed?
|
70
|
+
:destroy
|
71
|
+
else
|
72
|
+
:change
|
73
|
+
end
|
74
|
+
dummy_broadcast = new.local(operation, record, data)
|
75
|
+
record.backing_record.sync! data unless operation == :destroy
|
76
|
+
ReactiveRecord::Collection.sync_scopes dummy_broadcast
|
77
|
+
end
|
78
|
+
|
79
|
+
def record_with_current_values
|
80
|
+
ReactiveRecord::Base.load_data do
|
81
|
+
backing_record = @backing_record || klass.find(record[:id]).backing_record
|
82
|
+
if destroyed?
|
83
|
+
backing_record.ar_instance
|
84
|
+
else
|
85
|
+
merge_current_values(backing_record)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def record_with_new_values
|
91
|
+
klass._react_param_conversion(record).tap do |ar_instance|
|
92
|
+
if destroyed?
|
93
|
+
ar_instance.backing_record.destroy_associations
|
94
|
+
elsif new?
|
95
|
+
ar_instance.backing_record.initialize_collections
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def new?
|
101
|
+
@is_new
|
102
|
+
end
|
103
|
+
|
104
|
+
def destroyed?
|
105
|
+
@destroyed
|
106
|
+
end
|
107
|
+
|
108
|
+
def klass
|
109
|
+
Object.const_get(@klass)
|
110
|
+
end
|
111
|
+
|
112
|
+
def to_s
|
113
|
+
"klass: #{klass} record: #{record} new?: #{new?} destroyed?: #{destroyed?}"
|
114
|
+
end
|
115
|
+
|
116
|
+
# private
|
117
|
+
|
118
|
+
attr_reader :record
|
119
|
+
|
120
|
+
def self.open_channels
|
121
|
+
@open_channels ||= Set.new
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.in_transit
|
125
|
+
@in_transit ||= Hash.new { |h, k| h[k] = new(k) }
|
126
|
+
end
|
127
|
+
|
128
|
+
def initialize(id)
|
129
|
+
@id = id
|
130
|
+
@received = Set.new
|
131
|
+
@record = {}
|
132
|
+
@previous_changes = {}
|
133
|
+
end
|
134
|
+
|
135
|
+
def local(operation, record, data)
|
136
|
+
@destroyed = operation == :destroy
|
137
|
+
@is_new = operation == :create
|
138
|
+
@klass = record.class.name
|
139
|
+
@record = data
|
140
|
+
record.backing_record.destroyed = false
|
141
|
+
@record[:id] = record.id if record.id
|
142
|
+
record.backing_record.destroyed = @destroyed
|
143
|
+
@backing_record = record.backing_record
|
144
|
+
@previous_changes = record.changes
|
145
|
+
# attributes = record.attributes
|
146
|
+
# data.each do |k, v|
|
147
|
+
# next if klass.reflect_on_association(k) || attributes[k] == v
|
148
|
+
# @previous_changes[k] = [attributes[k], v]
|
149
|
+
# end
|
150
|
+
self
|
151
|
+
end
|
152
|
+
|
153
|
+
def receive(params)
|
154
|
+
@destroyed = params.operation == :destroy
|
155
|
+
@channels ||= Hyperloop::IncomingBroadcast.open_channels.intersection params.channels
|
156
|
+
@received << params.channel
|
157
|
+
@klass ||= params.klass
|
158
|
+
@record.merge! params.record
|
159
|
+
@previous_changes.merge! params.previous_changes
|
160
|
+
ReactiveRecord::Base.when_not_saving(klass) do
|
161
|
+
@backing_record = ReactiveRecord::Base.exists?(klass, params.record[:id])
|
162
|
+
@is_new = params.operation == :create && !@backing_record
|
163
|
+
yield complete! if @channels == @received
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def complete!
|
168
|
+
self.class.in_transit.delete @id
|
169
|
+
end
|
170
|
+
|
171
|
+
def value_changed?(attr, value)
|
172
|
+
attrs = @backing_record.synced_attributes
|
173
|
+
return true if attr == @backing_record.primary_key
|
174
|
+
return attrs[attr] != @backing_record.convert(attr, value) if attrs.key?(attr)
|
175
|
+
|
176
|
+
assoc = klass.reflect_on_association_by_foreign_key attr
|
177
|
+
|
178
|
+
return value unless assoc
|
179
|
+
child = attrs[assoc.attribute]
|
180
|
+
return value != child.id if child
|
181
|
+
value
|
182
|
+
end
|
183
|
+
|
184
|
+
def integrity_check
|
185
|
+
@previous_changes.each do |attr, value|
|
186
|
+
next if @record.key?(attr) && @record[attr] == value.last
|
187
|
+
React::IsomorphicHelpers.log "Broadcast contained change to #{attr} -> #{value.last} "\
|
188
|
+
"without corresponding value in attributes (#{@record}).\n",
|
189
|
+
:error
|
190
|
+
raise "Broadcast Integrity Error"
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def process_previous_changes
|
195
|
+
return self unless @backing_record
|
196
|
+
integrity_check
|
197
|
+
return self if destroyed?
|
198
|
+
@record.dup.each do |attr, value|
|
199
|
+
next if value_changed?(attr, value)
|
200
|
+
@record.delete(attr)
|
201
|
+
@previous_changes.delete(attr)
|
202
|
+
end
|
203
|
+
self
|
204
|
+
end
|
205
|
+
|
206
|
+
def merge_current_values(br)
|
207
|
+
current_values = Hash[*@previous_changes.collect do |attr, values|
|
208
|
+
value = attr == :id ? record[:id] : values.first
|
209
|
+
if br.attributes.key?(attr) &&
|
210
|
+
br.attributes[attr] != br.convert(attr, value) &&
|
211
|
+
br.attributes[attr] != br.convert(attr, values.last)
|
212
|
+
React::IsomorphicHelpers.log "warning #{attr} has changed locally - will force a reload.\n"\
|
213
|
+
"local value: #{br.attributes[attr]} remote value: #{br.convert(attr, value)}->#{br.convert(attr, values.last)}",
|
214
|
+
:warning
|
215
|
+
return nil
|
216
|
+
end
|
217
|
+
[attr, value]
|
218
|
+
end.compact.flatten(1)]
|
219
|
+
# TODO: verify - it used to be current_values.merge(br.attributes)
|
220
|
+
klass._react_param_conversion(br.attributes.merge(current_values))
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module HyperMesh
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace ReactiveRecord
|
4
|
+
config.generators do |g|
|
5
|
+
g.test_framework :rspec, :fixture => false
|
6
|
+
g.fixture_replacement :factory_bot, :dir => 'spec/factories'
|
7
|
+
g.assets false
|
8
|
+
g.helper false
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
module Browser
|
2
|
+
|
3
|
+
# Allows you to create an interval that executes the function every given
|
4
|
+
# seconds.
|
5
|
+
#
|
6
|
+
# @see https://developer.mozilla.org/en-US/docs/Web/API/Window.setInterval
|
7
|
+
class Interval
|
8
|
+
# @!attribute [r] every
|
9
|
+
# @return [Float] the seconds every which the block is called
|
10
|
+
attr_reader :every
|
11
|
+
|
12
|
+
# Create and start an interval.
|
13
|
+
#
|
14
|
+
# @param window [Window] the window to start the interval on
|
15
|
+
# @param time [Float] seconds every which to call the block
|
16
|
+
def initialize(window, time, &block)
|
17
|
+
@window = Native.convert(window)
|
18
|
+
@every = time
|
19
|
+
@block = block
|
20
|
+
|
21
|
+
@aborted = false
|
22
|
+
end
|
23
|
+
|
24
|
+
# Check if the interval has been stopped.
|
25
|
+
def stopped?
|
26
|
+
@id.nil?
|
27
|
+
end
|
28
|
+
|
29
|
+
# Check if the interval has been aborted.
|
30
|
+
def aborted?
|
31
|
+
@aborted
|
32
|
+
end
|
33
|
+
|
34
|
+
# Abort the interval, it won't be possible to start it again.
|
35
|
+
def abort
|
36
|
+
`#@window.clearInterval(#@id)`
|
37
|
+
|
38
|
+
@aborted = true
|
39
|
+
@id = nil
|
40
|
+
end
|
41
|
+
|
42
|
+
# Stop the interval, it will be possible to start it again.
|
43
|
+
def stop
|
44
|
+
return if stopped?
|
45
|
+
|
46
|
+
`#@window.clearInterval(#@id)`
|
47
|
+
|
48
|
+
@stopped = true
|
49
|
+
@id = nil
|
50
|
+
end
|
51
|
+
|
52
|
+
# Start the interval if it has been stopped.
|
53
|
+
def start
|
54
|
+
raise "the interval has been aborted" if aborted?
|
55
|
+
return unless stopped?
|
56
|
+
|
57
|
+
@id = `#@window.setInterval(#@block, #@every * 1000)`
|
58
|
+
end
|
59
|
+
|
60
|
+
# Call the [Interval] block.
|
61
|
+
def call
|
62
|
+
@block.call
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class Window
|
67
|
+
# Execute the block every given seconds.
|
68
|
+
#
|
69
|
+
# @param time [Float] the seconds between every call
|
70
|
+
#
|
71
|
+
# @return [Interval] the object representing the interval
|
72
|
+
def every(time, &block)
|
73
|
+
Interval.new(@native, time, &block).tap(&:start)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Execute the block every given seconds, you have to call [#start] on it
|
77
|
+
# yourself.
|
78
|
+
#
|
79
|
+
# @param time [Float] the seconds between every call
|
80
|
+
#
|
81
|
+
# @return [Interval] the object representing the interval
|
82
|
+
def every!(time, &block)
|
83
|
+
Interval.new(@native, time, &block)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
module Kernel
|
90
|
+
# (see Browser::Window#every)
|
91
|
+
def every(time, &block)
|
92
|
+
$window.every(time, &block)
|
93
|
+
end
|
94
|
+
|
95
|
+
# (see Browser::Window#every!)
|
96
|
+
def every!(time, &block)
|
97
|
+
$window.every!(time, &block)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
class Proc
|
102
|
+
# (see Browser::Window#every)
|
103
|
+
def every(time)
|
104
|
+
$window.every(time, &self)
|
105
|
+
end
|
106
|
+
|
107
|
+
# (see Browser::Window#every!)
|
108
|
+
def every!(time)
|
109
|
+
$window.every!(time, &self)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
module Browser
|
114
|
+
|
115
|
+
# Allows you to delay the call to a function which gets called after the
|
116
|
+
# given time.
|
117
|
+
#
|
118
|
+
# @see https://developer.mozilla.org/en-US/docs/Web/API/Window.setTimeout
|
119
|
+
class Delay
|
120
|
+
# @!attribute [r] after
|
121
|
+
# @return [Float] the seconds after which the block is called
|
122
|
+
attr_reader :after
|
123
|
+
|
124
|
+
# Create and start a timeout.
|
125
|
+
#
|
126
|
+
# @param window [Window] the window to start the timeout on
|
127
|
+
# @param time [Float] seconds after which the block is called
|
128
|
+
def initialize(window, time, &block)
|
129
|
+
@window = Native.convert(window)
|
130
|
+
@after = time
|
131
|
+
@block = block
|
132
|
+
end
|
133
|
+
|
134
|
+
# Abort the timeout.
|
135
|
+
def abort
|
136
|
+
`#@window.clearTimeout(#@id)`
|
137
|
+
end
|
138
|
+
|
139
|
+
# Start the delay.
|
140
|
+
def start
|
141
|
+
@id = `#@window.setTimeout(#{@block.to_n}, #@after * 1000)`
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
class Window
|
146
|
+
# Execute a block after the given seconds.
|
147
|
+
#
|
148
|
+
# @param time [Float] the seconds after it gets called
|
149
|
+
#
|
150
|
+
# @return [Delay] the object representing the timeout
|
151
|
+
def after(time, &block)
|
152
|
+
Delay.new(@native, time, &block).tap(&:start)
|
153
|
+
end
|
154
|
+
|
155
|
+
# Execute a block after the given seconds, you have to call [#start] on it
|
156
|
+
# yourself.
|
157
|
+
#
|
158
|
+
# @param time [Float] the seconds after it gets called
|
159
|
+
#
|
160
|
+
# @return [Delay] the object representing the timeout
|
161
|
+
def after!(time, &block)
|
162
|
+
Delay.new(@native, time, &block)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
module Kernel
|
169
|
+
# (see Browser::Window#after)
|
170
|
+
def after(time, &block)
|
171
|
+
`setTimeout(#{block.to_n}, time * 1000)`
|
172
|
+
end
|
173
|
+
|
174
|
+
# (see Browser::Window#after!)
|
175
|
+
def after!(time, &block)
|
176
|
+
`setTimeout(#{block.to_n}, time * 1000)`
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
class Proc
|
181
|
+
# (see Browser::Window#after)
|
182
|
+
def after(time)
|
183
|
+
$window.after(time, &self)
|
184
|
+
end
|
185
|
+
|
186
|
+
# (see Browser::Window#after!)
|
187
|
+
def after!(time)
|
188
|
+
$window.after!(time, &self)
|
189
|
+
end
|
190
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module Hyperloop
|
2
|
+
class InternalPolicy
|
3
|
+
|
4
|
+
def self.accessible_attributes_for(model, acting_user)
|
5
|
+
user_channels = ClassConnectionRegulation.connections_for(acting_user, false) +
|
6
|
+
InstanceConnectionRegulation.connections_for(acting_user, false)
|
7
|
+
internal_policy = InternalPolicy.new(model, model.attribute_names, user_channels)
|
8
|
+
ChannelBroadcastRegulation.broadcast(internal_policy)
|
9
|
+
InstanceBroadcastRegulation.broadcast(model, internal_policy)
|
10
|
+
internal_policy.accessible_attributes_for
|
11
|
+
end
|
12
|
+
|
13
|
+
def accessible_attributes_for
|
14
|
+
accessible_attributes = Set.new
|
15
|
+
@channel_sets.each do |channel, attribute_set|
|
16
|
+
accessible_attributes.merge attribute_set
|
17
|
+
end
|
18
|
+
accessible_attributes << :id unless accessible_attributes.empty?
|
19
|
+
accessible_attributes
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class ActiveRecord::Base
|
25
|
+
|
26
|
+
attr_accessor :acting_user
|
27
|
+
|
28
|
+
def view_permitted?(attribute)
|
29
|
+
Hyperloop::InternalPolicy.accessible_attributes_for(self, acting_user).include? attribute.to_sym
|
30
|
+
end
|
31
|
+
|
32
|
+
def create_permitted?
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
36
|
+
def update_permitted?
|
37
|
+
false
|
38
|
+
end
|
39
|
+
|
40
|
+
def destroy_permitted?
|
41
|
+
false
|
42
|
+
end
|
43
|
+
|
44
|
+
def only_changed?(*attributes)
|
45
|
+
(self.attributes.keys + self.class.reactive_record_association_keys).each do |key|
|
46
|
+
return false if self.send("#{key}_changed?") and !attributes.include? key
|
47
|
+
end
|
48
|
+
true
|
49
|
+
end
|
50
|
+
|
51
|
+
def none_changed?(*attributes)
|
52
|
+
attributes.each do |key|
|
53
|
+
return false if self.send("#{key}_changed?")
|
54
|
+
end
|
55
|
+
true
|
56
|
+
end
|
57
|
+
|
58
|
+
def any_changed?(*attributes)
|
59
|
+
attributes.each do |key|
|
60
|
+
return true if self.send("#{key}_changed?")
|
61
|
+
end
|
62
|
+
false
|
63
|
+
end
|
64
|
+
|
65
|
+
def all_changed?(*attributes)
|
66
|
+
attributes.each do |key|
|
67
|
+
return false unless self.send("#{key}_changed?")
|
68
|
+
end
|
69
|
+
true
|
70
|
+
end
|
71
|
+
|
72
|
+
class << self
|
73
|
+
|
74
|
+
attr_reader :reactive_record_association_keys
|
75
|
+
|
76
|
+
[:has_many, :belongs_to, :composed_of].each do |macro|
|
77
|
+
define_method "#{macro}_with_reactive_record_add_changed_method".to_sym do |attr_name, *args, &block|
|
78
|
+
define_method "#{attr_name}_changed?".to_sym do
|
79
|
+
instance_variable_get "@reactive_record_#{attr_name}_changed".to_sym
|
80
|
+
end
|
81
|
+
(@reactive_record_association_keys ||= []) << attr_name
|
82
|
+
send "#{macro}_without_reactive_record_add_changed_method".to_sym, attr_name, *args, &block
|
83
|
+
end
|
84
|
+
alias_method "#{macro}_without_reactive_record_add_changed_method".to_sym, macro
|
85
|
+
alias_method macro, "#{macro}_with_reactive_record_add_changed_method".to_sym
|
86
|
+
end
|
87
|
+
|
88
|
+
alias belongs_to_without_reactive_record_add_is_method belongs_to
|
89
|
+
|
90
|
+
def belongs_to(attr_name, *args)
|
91
|
+
belongs_to_without_reactive_record_add_is_method(attr_name, *args).tap do
|
92
|
+
define_method "#{attr_name}_is?".to_sym do |model|
|
93
|
+
self.class.reflections[attr_name].foreign_key == model.id
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def check_permission_with_acting_user(user, permission, *args)
|
100
|
+
old = acting_user
|
101
|
+
self.acting_user = user
|
102
|
+
if self.send(permission, *args)
|
103
|
+
self.acting_user = old
|
104
|
+
self
|
105
|
+
else
|
106
|
+
Hyperloop::InternalPolicy.raise_operation_access_violation(:crud_access_violation, "for #{self} - #{permission}(#{args}) acting_user: #{user}")
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
class ActionController::Base
|
113
|
+
|
114
|
+
def acting_user
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|