hyper-model 0.6.0 → 0.99.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|