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.
Files changed (140) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +35 -41
  3. data/.rspec +2 -0
  4. data/.travis.yml +33 -0
  5. data/CHANGELOG.md +34 -0
  6. data/DOCS.md +735 -0
  7. data/Gemfile +7 -0
  8. data/Gemfile.lock +298 -224
  9. data/{LICENSE → LICENSE.txt} +6 -6
  10. data/README.md +51 -2
  11. data/Rakefile +18 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +7 -0
  14. data/codeship.database.yml +18 -0
  15. data/hyper-model.gemspec +62 -36
  16. data/lib/active_model_client_stubs.rb +16 -0
  17. data/lib/active_record_base.rb +331 -0
  18. data/{examples/chat-app/app/assets/images/.keep → lib/acts_as_string.rb} +0 -0
  19. data/lib/enumerable/pluck.rb +6 -0
  20. data/lib/hyper-model.rb +59 -8
  21. data/lib/hyper_model/version.rb +3 -0
  22. data/lib/hyper_react/input_tags.rb +47 -0
  23. data/lib/hyperloop/model/load.rb +1 -1
  24. data/lib/kernel/itself.rb +5 -0
  25. data/lib/object/tap.rb +7 -0
  26. data/lib/opal/equality_patches.rb +15 -0
  27. data/lib/opal/parse_patch.rb +14 -0
  28. data/lib/opal/set_patches.rb +8 -0
  29. data/lib/reactive_record/active_record/aggregations.rb +69 -0
  30. data/lib/reactive_record/active_record/associations.rb +118 -0
  31. data/lib/reactive_record/active_record/base.rb +10 -0
  32. data/lib/reactive_record/active_record/class_methods.rb +406 -0
  33. data/lib/reactive_record/active_record/error.rb +31 -0
  34. data/lib/reactive_record/active_record/errors.rb +374 -0
  35. data/lib/reactive_record/active_record/instance_methods.rb +187 -0
  36. data/lib/reactive_record/active_record/public_columns_hash.rb +44 -0
  37. data/lib/reactive_record/active_record/reactive_record/backing_record_inspector.rb +36 -0
  38. data/lib/reactive_record/active_record/reactive_record/base.rb +416 -0
  39. data/lib/reactive_record/active_record/reactive_record/collection.rb +558 -0
  40. data/lib/reactive_record/active_record/reactive_record/column_types.rb +75 -0
  41. data/lib/reactive_record/active_record/reactive_record/dummy_value.rb +236 -0
  42. data/lib/reactive_record/active_record/reactive_record/getters.rb +133 -0
  43. data/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb +576 -0
  44. data/lib/reactive_record/active_record/reactive_record/lookup_tables.rb +54 -0
  45. data/lib/reactive_record/active_record/reactive_record/operations.rb +107 -0
  46. data/lib/reactive_record/active_record/reactive_record/scoped_collection.rb +62 -0
  47. data/lib/reactive_record/active_record/reactive_record/setters.rb +194 -0
  48. data/lib/reactive_record/active_record/reactive_record/unscoped_collection.rb +16 -0
  49. data/lib/reactive_record/active_record/reactive_record/while_loading.rb +343 -0
  50. data/lib/reactive_record/active_record_error.rb +4 -0
  51. data/lib/reactive_record/broadcast.rb +223 -0
  52. data/lib/reactive_record/engine.rb +11 -0
  53. data/lib/reactive_record/interval.rb +190 -0
  54. data/lib/reactive_record/permissions.rb +117 -0
  55. data/lib/reactive_record/pry.rb +13 -0
  56. data/lib/reactive_record/reactive_scope.rb +18 -0
  57. data/lib/reactive_record/scope_description.rb +121 -0
  58. data/lib/reactive_record/serializers.rb +7 -0
  59. data/lib/reactive_record/server_data_cache.rb +478 -0
  60. data/path_release_steps.md +9 -0
  61. metadata +399 -109
  62. data/CODE_OF_CONDUCT.md +0 -49
  63. data/examples/chat-app/.gitignore +0 -21
  64. data/examples/chat-app/Gemfile +0 -62
  65. data/examples/chat-app/Gemfile.lock +0 -309
  66. data/examples/chat-app/README.md +0 -3
  67. data/examples/chat-app/Rakefile +0 -6
  68. data/examples/chat-app/app/assets/config/manifest.js +0 -3
  69. data/examples/chat-app/app/assets/javascripts/application.js +0 -3
  70. data/examples/chat-app/app/assets/stylesheets/application.scss +0 -33
  71. data/examples/chat-app/app/controllers/application_controller.rb +0 -3
  72. data/examples/chat-app/app/controllers/home_controller.rb +0 -5
  73. data/examples/chat-app/app/hyperloop/components/app.rb +0 -12
  74. data/examples/chat-app/app/hyperloop/components/formatted_div.rb +0 -15
  75. data/examples/chat-app/app/hyperloop/components/input_box.rb +0 -26
  76. data/examples/chat-app/app/hyperloop/components/message.rb +0 -29
  77. data/examples/chat-app/app/hyperloop/components/messages.rb +0 -8
  78. data/examples/chat-app/app/hyperloop/components/nav.rb +0 -30
  79. data/examples/chat-app/app/hyperloop/models/application_record.rb +0 -3
  80. data/examples/chat-app/app/hyperloop/models/message.rb +0 -6
  81. data/examples/chat-app/app/hyperloop/operations/operations.rb +0 -13
  82. data/examples/chat-app/app/hyperloop/stores/message_store.rb +0 -17
  83. data/examples/chat-app/app/policies/application_policy.rb +0 -9
  84. data/examples/chat-app/app/views/layouts/application.html.erb +0 -12
  85. data/examples/chat-app/bin/bundle +0 -3
  86. data/examples/chat-app/bin/rails +0 -9
  87. data/examples/chat-app/bin/rake +0 -9
  88. data/examples/chat-app/bin/setup +0 -34
  89. data/examples/chat-app/bin/spring +0 -17
  90. data/examples/chat-app/bin/update +0 -29
  91. data/examples/chat-app/config.ru +0 -5
  92. data/examples/chat-app/config/application.rb +0 -12
  93. data/examples/chat-app/config/boot.rb +0 -3
  94. data/examples/chat-app/config/cable.yml +0 -9
  95. data/examples/chat-app/config/database.yml +0 -25
  96. data/examples/chat-app/config/environment.rb +0 -5
  97. data/examples/chat-app/config/environments/development.rb +0 -56
  98. data/examples/chat-app/config/environments/production.rb +0 -86
  99. data/examples/chat-app/config/environments/test.rb +0 -42
  100. data/examples/chat-app/config/initializers/application_controller_renderer.rb +0 -6
  101. data/examples/chat-app/config/initializers/assets.rb +0 -11
  102. data/examples/chat-app/config/initializers/backtrace_silencers.rb +0 -7
  103. data/examples/chat-app/config/initializers/cookies_serializer.rb +0 -5
  104. data/examples/chat-app/config/initializers/filter_parameter_logging.rb +0 -4
  105. data/examples/chat-app/config/initializers/hyperloop.rb +0 -6
  106. data/examples/chat-app/config/initializers/inflections.rb +0 -16
  107. data/examples/chat-app/config/initializers/mime_types.rb +0 -4
  108. data/examples/chat-app/config/initializers/new_framework_defaults.rb +0 -24
  109. data/examples/chat-app/config/initializers/session_store.rb +0 -3
  110. data/examples/chat-app/config/initializers/wrap_parameters.rb +0 -14
  111. data/examples/chat-app/config/locales/en.yml +0 -23
  112. data/examples/chat-app/config/puma.rb +0 -47
  113. data/examples/chat-app/config/routes.rb +0 -5
  114. data/examples/chat-app/config/secrets.yml +0 -22
  115. data/examples/chat-app/config/spring.rb +0 -6
  116. data/examples/chat-app/db/migrate/20170319194429_create_message.rb +0 -9
  117. data/examples/chat-app/db/schema.rb +0 -48
  118. data/examples/chat-app/db/seeds.rb +0 -7
  119. data/examples/chat-app/lib/assets/.keep +0 -0
  120. data/examples/chat-app/lib/tasks/.keep +0 -0
  121. data/examples/chat-app/log/.keep +0 -0
  122. data/examples/chat-app/public/404.html +0 -67
  123. data/examples/chat-app/public/422.html +0 -67
  124. data/examples/chat-app/public/500.html +0 -66
  125. data/examples/chat-app/public/apple-touch-icon-precomposed.png +0 -0
  126. data/examples/chat-app/public/apple-touch-icon.png +0 -0
  127. data/examples/chat-app/public/favicon.ico +0 -0
  128. data/examples/chat-app/public/robots.txt +0 -5
  129. data/examples/chat-app/test/controllers/.keep +0 -0
  130. data/examples/chat-app/test/fixtures/.keep +0 -0
  131. data/examples/chat-app/test/fixtures/files/.keep +0 -0
  132. data/examples/chat-app/test/helpers/.keep +0 -0
  133. data/examples/chat-app/test/integration/.keep +0 -0
  134. data/examples/chat-app/test/mailers/.keep +0 -0
  135. data/examples/chat-app/test/models/.keep +0 -0
  136. data/examples/chat-app/test/test_helper.rb +0 -10
  137. data/examples/chat-app/tmp/.keep +0 -0
  138. data/examples/chat-app/vendor/assets/javascripts/.keep +0 -0
  139. data/examples/chat-app/vendor/assets/stylesheets/.keep +0 -0
  140. data/lib/hyperloop/model/version.rb +0 -5
@@ -0,0 +1,4 @@
1
+ module ActiveRecord
2
+ class ActiveRecordError < StandardError
3
+ end
4
+ end
@@ -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