hyper-mesh 1.0.0.lap23 → 1.0.0.lap24
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/DOCS.md +1 -0
- data/Rakefile +2 -2
- data/hyper-mesh.gemspec +1 -2
- data/lib/active_record_base.rb +249 -106
- data/lib/hyper-mesh.rb +3 -3
- data/lib/hypermesh/version.rb +1 -1
- data/lib/reactive_record/active_record/base.rb +3 -1
- data/lib/reactive_record/active_record/class_methods.rb +88 -13
- data/lib/reactive_record/active_record/instance_methods.rb +15 -1
- data/lib/reactive_record/active_record/reactive_record/base.rb +2 -2
- data/lib/reactive_record/active_record/reactive_record/collection.rb +17 -5
- data/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb +19 -12
- data/lib/reactive_record/active_record/reactive_record/while_loading.rb +3 -2
- data/lib/reactive_record/server_data_cache.rb +20 -13
- metadata +23 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f71958166fcd1ea5b7b054c460a431f45ebae9389098d3046e805a38225235d6
|
4
|
+
data.tar.gz: 9bcf2cb4090bc56920db979d5e016fdcfa450497c375b8487efa8e94e73da837
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 59e3c865aa085a2a16ee573d8dcd147d0dc96d91e04fa4799e29e3dc80cce5aba5801e837686a612c2ac72b15f42a21767c8250695552a882c24fd1bdd34fb41
|
7
|
+
data.tar.gz: aaa9fb686b480a3ad322e835fa7f8a95a13e37f4a508a374c94aa75585f12f03eb5053f4d33c819269a09f8864f27d67ed639f7146992e96f2c89555307e6d9a
|
data/DOCS.md
CHANGED
@@ -617,6 +617,7 @@ Pusher.app_id = "MY_TEST_ID" # you use the real or fake values
|
|
617
617
|
Pusher.key = "MY_TEST_KEY"
|
618
618
|
Pusher.secret = "MY_TEST_SECRET"
|
619
619
|
# The next line actually starts the pusher-fake server (see the Pusher-Fake readme for details.)
|
620
|
+
# it is important this require be AFTER the above settings, as it will use these
|
620
621
|
require 'pusher-fake/support/base' # if using pusher with rspec change this to pusher-fake/support/rspec
|
621
622
|
# now copy over the credentials, and merge with PusherFake's config details
|
622
623
|
Hyperloop.configuration do |config|
|
data/Rakefile
CHANGED
@@ -4,7 +4,7 @@ require "rspec/core/rake_task"
|
|
4
4
|
|
5
5
|
|
6
6
|
task :spec do
|
7
|
-
(1..
|
7
|
+
(1..6).each { |batch| Rake::Task["spec:batch#{batch}"].invoke }
|
8
8
|
end
|
9
9
|
|
10
10
|
namespace :spec do
|
@@ -12,7 +12,7 @@ namespace :spec do
|
|
12
12
|
sh %{bundle update}
|
13
13
|
sh %{cd spec/test_app; bundle update; bundle exec rails db:setup} # may need ;bundle exec rails db:setup as well
|
14
14
|
end
|
15
|
-
(1..
|
15
|
+
(1..6).each do |batch|
|
16
16
|
RSpec::Core::RakeTask.new(:"batch#{batch}") do |t|
|
17
17
|
t.pattern = "spec/batch#{batch}/**/*_spec.rb"
|
18
18
|
end
|
data/hyper-mesh.gemspec
CHANGED
@@ -25,8 +25,6 @@ Gem::Specification.new do |spec|
|
|
25
25
|
spec.test_files = `git ls-files -- {spec}/*`.split("\n")
|
26
26
|
spec.require_paths = ['lib']
|
27
27
|
|
28
|
-
spec.post_install_message = "\033[0;31;1mhyper-mesh #{Hypermesh::VERSION} is in development and has known security issues! Not recommended for production use!\033[0;30;21m"
|
29
|
-
|
30
28
|
spec.add_dependency 'activerecord', '>= 4.0.0'
|
31
29
|
spec.add_dependency 'hyper-component', Hypermesh::VERSION
|
32
30
|
spec.add_dependency 'hyper-operation', Hypermesh::VERSION
|
@@ -65,4 +63,5 @@ Gem::Specification.new do |spec|
|
|
65
63
|
spec.add_development_dependency 'timecop', '~> 0.8.1'
|
66
64
|
spec.add_development_dependency 'unparser'
|
67
65
|
spec.add_development_dependency 'pry'
|
66
|
+
spec.add_development_dependency 'pry-rescue'
|
68
67
|
end
|
data/lib/active_record_base.rb
CHANGED
@@ -1,131 +1,293 @@
|
|
1
|
+
# Monkey patches to ActiveRecord for scoping, security, and to synchronize models
|
1
2
|
module ActiveRecord
|
2
|
-
#
|
3
|
-
#
|
4
|
-
#
|
3
|
+
# hyperloop adds new features to scopes to allow for computing scopes on client side
|
4
|
+
# and for hinting at what joins are involved in a scope. _synchromesh_scope_args_check
|
5
|
+
# processes these arguments, and the will always leave the true server side scoping
|
6
|
+
# proc in the `:server` opts. This method is common to client and server.
|
5
7
|
class Base
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
8
|
+
def self._synchromesh_scope_args_check(args)
|
9
|
+
opts = if args.count == 2 && args[1].is_a?(Hash)
|
10
|
+
args[1].merge(server: args[0])
|
11
|
+
elsif args[0].is_a? Hash
|
12
|
+
args[0]
|
13
|
+
else
|
14
|
+
{ server: args[0] }
|
15
|
+
end
|
16
|
+
return opts if opts && opts[:server].respond_to?(:call)
|
17
|
+
raise 'must provide either a proc as the first arg or by the '\
|
18
|
+
'`:server` option to scope and default_scope methods'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
if RUBY_ENGINE != 'opal'
|
22
|
+
# __synchromesh_permission_granted indicates if permission has been given to return a scope
|
23
|
+
# The acting_user attribute is set to the current acting_user so regulation methods can check it
|
24
|
+
# The __secure_collection_check method is called at the end of a scope chain and will fail if
|
25
|
+
# no scope in the chain has positively granted access.
|
26
|
+
|
27
|
+
# allows us to easily handle scopes and finder_methods which return arrays of items
|
28
|
+
# (instead of ActiveRecord::Relations - see below)
|
29
|
+
class ReactiveRecordPsuedoRelationArray < Array
|
30
|
+
attr_accessor :__synchromesh_permission_granted
|
31
|
+
attr_accessor :acting_user
|
32
|
+
def __secure_collection_check(_acting_user)
|
33
|
+
self
|
20
34
|
end
|
35
|
+
end
|
21
36
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
37
|
+
# add the __synchromesh_permission_granted, acting_user and __secure_collection_check
|
38
|
+
# methods to Relation
|
39
|
+
class Relation
|
40
|
+
attr_accessor :__synchromesh_permission_granted
|
41
|
+
attr_accessor :acting_user
|
42
|
+
def __secure_collection_check(acting_user)
|
43
|
+
return self if __synchromesh_permission_granted
|
44
|
+
return self if __secure_remote_access_to_unscoped(acting_user).__synchromesh_permission_granted
|
45
|
+
denied!
|
26
46
|
end
|
47
|
+
end
|
48
|
+
# Monkey patches and extensions to base
|
49
|
+
class Base
|
50
|
+
class << self
|
51
|
+
# every method call that is legal from the client has a wrapper method prefixed with
|
52
|
+
# __secure_remote_access_to_
|
53
|
+
|
54
|
+
# The wrapper method may simply return the normal result or may act to secure the data.
|
55
|
+
# The simpliest case is for the method to call `denied!` which will raise a Hyperloop
|
56
|
+
# access protection fault.
|
57
|
+
|
58
|
+
def denied!
|
59
|
+
Hyperloop::InternalPolicy.raise_operation_access_violation
|
60
|
+
end
|
27
61
|
|
28
|
-
|
29
|
-
|
30
|
-
end
|
31
|
-
|
32
|
-
if RUBY_ENGINE != 'opal'
|
33
|
-
|
34
|
-
alias pre_synchromesh_default_scope default_scope
|
62
|
+
# Here we set up the base `all` and `unscoped` methods. See below for more on how
|
63
|
+
# access protection works on relationships.
|
35
64
|
|
36
|
-
def
|
37
|
-
|
38
|
-
pre_synchromesh_scope(name, opts[:server], &block)
|
65
|
+
def __secure_remote_access_to_all(_acting_user)
|
66
|
+
all
|
39
67
|
end
|
40
68
|
|
41
|
-
def
|
42
|
-
|
43
|
-
pre_synchromesh_default_scope(opts[:server], &block)
|
69
|
+
def __secure_remote_access_to_unscoped(_acting_user, *args)
|
70
|
+
unscoped(*args)
|
44
71
|
end
|
45
72
|
|
46
|
-
|
47
|
-
|
48
|
-
|
73
|
+
# finder_method and server_method provide secure RPCs against AR relations and records.
|
74
|
+
# The block is called in context with the object, and acting_user is set to the
|
75
|
+
# current acting user. The block may interogate acting_user to insure security as needed.
|
76
|
+
|
77
|
+
# For finder_method we have to preapply `all` so that we always have a relationship
|
49
78
|
|
50
79
|
def finder_method(name, &block)
|
51
|
-
singleton_class.send(:define_method, "
|
52
|
-
|
80
|
+
singleton_class.send(:define_method, :"__secure_remote_access_to__#{name}") do |acting_user, *args|
|
81
|
+
this = respond_to?(:acting_user) ? self : all
|
82
|
+
begin
|
83
|
+
old = this.acting_user
|
84
|
+
this.acting_user = acting_user
|
85
|
+
# returns a PsuedoRelationArray which will respond to the
|
86
|
+
# __secure_collection_check method
|
87
|
+
ReactiveRecordPsuedoRelationArray.new([this.instance_exec(*args, &block)])
|
88
|
+
ensure
|
89
|
+
this.acting_user = old
|
90
|
+
end
|
53
91
|
end
|
54
92
|
singleton_class.send(:define_method, name) do |*args|
|
55
|
-
|
93
|
+
all.instance_exec(*args, &block)
|
56
94
|
end
|
57
95
|
end
|
58
96
|
|
59
|
-
|
97
|
+
def server_method(name, _opts = {}, &block)
|
98
|
+
# callable from the server internally
|
99
|
+
define_method(name, &block)
|
100
|
+
# callable remotely from the client
|
101
|
+
define_method("__secure_remote_access_to_#{name}") do |acting_user, *args|
|
102
|
+
begin
|
103
|
+
old = self.acting_user
|
104
|
+
self.acting_user = acting_user
|
105
|
+
send(name, *args)
|
106
|
+
ensure
|
107
|
+
self.acting_user = old
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
60
111
|
|
61
|
-
|
112
|
+
# relationships (and scopes) are regulated using a tri-state system. Each
|
113
|
+
# remote access method will return the relationship as normal but will also set
|
114
|
+
# the value of __secure_remote_access_granted using the application defined regulation.
|
115
|
+
# Each regulation can explicitly allow the scope to be chained by returning a truthy
|
116
|
+
# value from the regulation. Or each regulation can explicitly deny the scope to
|
117
|
+
# be chained by called `denied!`. Otherwise each regulation can return a falsy
|
118
|
+
# value meaning the scope can be changed, but unless some other scope (before or
|
119
|
+
# after) in the chain explicitly allows the scope, the entire chain will fail.
|
120
|
+
|
121
|
+
# In otherwords within a chain of relationships and scopes, at least one Regulation
|
122
|
+
# must be return a truthy value otherwise the whole chain fails. Likewise if any
|
123
|
+
# regulation called `deined!` the whole chain fails.
|
124
|
+
|
125
|
+
# If no regulation is defined, the regulation is inherited from the superclass, and if
|
126
|
+
# no regulation is defined anywhere in the class heirarchy then the regulation will
|
127
|
+
# return a falsy value.
|
128
|
+
|
129
|
+
# regulations on scopes are inheritable. That is if a superclass defines a regulation
|
130
|
+
# for a scope, subclasses will inherit the regulation (but can override)
|
131
|
+
|
132
|
+
# helper method to sort out the options on the regulate_scope, regulate_relationship macros.
|
133
|
+
|
134
|
+
# We allow three forms:
|
135
|
+
# regulate_xxx name &block : the block is the regulation
|
136
|
+
# regulate_xxx name: const : const can be denied!, deny, denied, or any other truthy or
|
137
|
+
# falsy value
|
138
|
+
# regulate_xxx name: proc : the proc is the regulation
|
139
|
+
|
140
|
+
def __synchromesh_parse_regulator_params(name, block)
|
141
|
+
if name.is_a? Hash
|
142
|
+
name, block = name.first
|
143
|
+
if %i[denied! deny denied].include? block
|
144
|
+
block = ->(*_args) { denied! }
|
145
|
+
elsif !block.is_a? Proc
|
146
|
+
value = block
|
147
|
+
block = ->(*_args) { value }
|
148
|
+
end
|
149
|
+
end
|
150
|
+
[name, block || ->(*_args) { true }]
|
151
|
+
end
|
62
152
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
153
|
+
# helper method for providing a regulation in line with a scope or relationship
|
154
|
+
# this is done using the `regulate` key on the opts.
|
155
|
+
# if no regulate key is provided and there is no regulation already defined for
|
156
|
+
# this name, then we create one that returns nil (don't care)
|
157
|
+
# once we have things figured out, we yield to the provided proc which is either
|
158
|
+
# regulate_scope or regulate_relationship
|
159
|
+
|
160
|
+
def __synchromesh_regulate_from_macro(opts, name, already_defined)
|
161
|
+
if opts.key?(:regulate)
|
162
|
+
yield name => opts[:regulate]
|
163
|
+
elsif !already_defined
|
164
|
+
yield name => ->(*_args) {}
|
68
165
|
end
|
69
|
-
pre_synchromesh_method_missing(name, *args, &block)
|
70
166
|
end
|
71
167
|
|
72
|
-
|
73
|
-
|
168
|
+
# helper method to set the value of __synchromesh_permission_granted on the relationship
|
169
|
+
# Get the current value of __synchromesh_permission_granted, set acting_user on the
|
170
|
+
# object, and or in the result of running the block in context of the obj
|
171
|
+
|
172
|
+
def __set_synchromesh_permission_granted(r, obj, acting_user, args = [], &block)
|
173
|
+
r.__synchromesh_permission_granted = try(:__synchromesh_permission_granted)
|
174
|
+
old = acting_user
|
175
|
+
obj.acting_user = acting_user
|
176
|
+
r.__synchromesh_permission_granted ||= obj.instance_exec(*args, &block)
|
177
|
+
r
|
178
|
+
ensure
|
179
|
+
obj.acting_user = old
|
74
180
|
end
|
75
181
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
182
|
+
# regulate scope has to deal with the special case that the scope returns an
|
183
|
+
# an array instead of a relationship. In this case we wrap the array and go on
|
184
|
+
|
185
|
+
def regulate_scope(name, &block)
|
186
|
+
name, block = __synchromesh_parse_regulator_params(name, block)
|
187
|
+
singleton_class.send(:define_method, :"__secure_remote_access_to_#{name}") do |acting_user, *args|
|
188
|
+
r = send(name, *args)
|
189
|
+
r = ReactiveRecordPsuedoRelationArray.new(r) if r.is_a? Array
|
190
|
+
__set_synchromesh_permission_granted(r, r, acting_user, args, &block)
|
85
191
|
end
|
86
192
|
end
|
87
193
|
|
194
|
+
# regulate_default_scope
|
195
|
+
|
196
|
+
def regulate_default_scope(&block)
|
197
|
+
regulate_scope(:all, &block)
|
198
|
+
end
|
199
|
+
|
200
|
+
# monkey patch scope and default_scope macros to process hyperloop special opts,
|
201
|
+
# and add regulations if present
|
202
|
+
|
203
|
+
alias pre_synchromesh_scope scope
|
204
|
+
|
205
|
+
def scope(name, *args, &block)
|
206
|
+
__synchromesh_regulate_from_macro(
|
207
|
+
(opts = _synchromesh_scope_args_check(args)),
|
208
|
+
name,
|
209
|
+
respond_to?(:"__secure_remote_access_to_#{name}"),
|
210
|
+
&method(:regulate_scope)
|
211
|
+
)
|
212
|
+
pre_synchromesh_scope(name, opts[:server], &block)
|
213
|
+
end
|
214
|
+
|
215
|
+
alias pre_synchromesh_default_scope default_scope
|
216
|
+
|
88
217
|
def default_scope(*args, &block)
|
89
|
-
|
90
|
-
|
91
|
-
|
218
|
+
__synchromesh_regulate_from_macro(
|
219
|
+
(opts = _synchromesh_scope_args_check([*block, *args])),
|
220
|
+
:all,
|
221
|
+
respond_to?(:__secure_remote_access_to_all),
|
222
|
+
&method(:regulate_scope)
|
223
|
+
)
|
224
|
+
pre_synchromesh_default_scope(opts[:server], &block)
|
92
225
|
end
|
93
226
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
227
|
+
# add regulate_relationship method and monkey patch monkey patch has_many macro
|
228
|
+
# to add regulations if present
|
229
|
+
|
230
|
+
def regulate_relationship(name, &block)
|
231
|
+
name, block = __synchromesh_parse_regulator_params(name, block)
|
232
|
+
define_method(:"__secure_remote_access_to_#{name}") do |acting_user, *args|
|
233
|
+
self.class.__set_synchromesh_permission_granted(
|
234
|
+
send(name, *args), self, acting_user, &block
|
235
|
+
)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
alias pre_syncromesh_has_many has_many
|
240
|
+
|
241
|
+
def has_many(name, *args, &block)
|
242
|
+
__synchromesh_regulate_from_macro(
|
243
|
+
opts = args.extract_options!,
|
244
|
+
name,
|
245
|
+
method_defined?(:"__secure_remote_access_to_#{name}"),
|
246
|
+
&method(:regulate_relationship)
|
247
|
+
)
|
248
|
+
pre_syncromesh_has_many name, *args, opts.except(:regulate), &block
|
104
249
|
end
|
105
250
|
|
106
|
-
|
107
|
-
|
251
|
+
# add secure access for find, find_by, and belongs_to and has_one relations.
|
252
|
+
# No explicit security checks are needed here, as the data returned by these objects
|
253
|
+
# will be further processedand checked before returning. I.e. it is not possible to
|
254
|
+
# simply return `find(1)` but if you try returning `find(1).name` the permission system
|
255
|
+
# will check to see if the name attribute can be legally sent to the current acting user.
|
256
|
+
|
257
|
+
def __secure_remote_access_to_find(_acting_user, *args)
|
258
|
+
find(*args)
|
108
259
|
end
|
109
260
|
|
110
|
-
def
|
111
|
-
|
112
|
-
ReactiveRecord::Collection
|
113
|
-
.new(self, nil, nil, self, 'unscoped')
|
114
|
-
.extend(ReactiveRecord::UnscopedCollection)
|
261
|
+
def __secure_remote_access_to_find_by(_acting_user, *args)
|
262
|
+
find_by(*args)
|
115
263
|
end
|
116
264
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
265
|
+
%i[belongs_to has_one].each do |macro|
|
266
|
+
alias_method :"pre_syncromesh_#{macro}", macro
|
267
|
+
define_method(macro) do |name, scope = nil, opts = {}, &block|
|
268
|
+
define_method(:"__secure_remote_access_to_#{name}") do |_acting_user, *args|
|
269
|
+
send(name, *args)
|
122
270
|
end
|
271
|
+
send(:"pre_syncromesh_#{macro}", name, scope, opts, &block)
|
123
272
|
end
|
124
273
|
end
|
125
274
|
end
|
126
|
-
end
|
127
275
|
|
128
|
-
|
276
|
+
def denied!
|
277
|
+
Hyperloop::InternalPolicy.raise_operation_access_violation
|
278
|
+
end
|
279
|
+
|
280
|
+
# call do_not_synchronize to block synchronization of a model
|
281
|
+
|
282
|
+
def self.do_not_synchronize
|
283
|
+
@do_not_synchronize = true
|
284
|
+
end
|
285
|
+
|
286
|
+
# used by the broadcast mechanism to determine if this model is to be synchronized
|
287
|
+
|
288
|
+
def self.do_not_synchronize?
|
289
|
+
@do_not_synchronize
|
290
|
+
end
|
129
291
|
|
130
292
|
def do_not_synchronize?
|
131
293
|
self.class.do_not_synchronize?
|
@@ -136,7 +298,7 @@ module ActiveRecord
|
|
136
298
|
after_commit :synchromesh_after_destroy, on: [:destroy]
|
137
299
|
|
138
300
|
def synchromesh_after_create
|
139
|
-
return if do_not_synchronize?
|
301
|
+
return if do_not_synchronize?
|
140
302
|
ReactiveRecord::Broadcast.after_commit :create, self
|
141
303
|
end
|
142
304
|
|
@@ -149,27 +311,8 @@ module ActiveRecord
|
|
149
311
|
return if do_not_synchronize?
|
150
312
|
ReactiveRecord::Broadcast.after_commit :destroy, self
|
151
313
|
end
|
152
|
-
else
|
153
|
-
|
154
|
-
scope :limit, ->() {}
|
155
|
-
scope :offset, ->() {}
|
156
|
-
|
157
|
-
def update_attribute(attr, value, &block)
|
158
|
-
send("#{attr}=", value)
|
159
|
-
save(validate: false, &block)
|
160
|
-
end
|
161
|
-
|
162
|
-
def update(attrs = {}, &block)
|
163
|
-
attrs.each { |attr, value| send("#{attr}=", value) }
|
164
|
-
save(&block)
|
165
|
-
end
|
166
|
-
|
167
|
-
def <=>(other)
|
168
|
-
id.to_i <=> other.id.to_i
|
169
|
-
end
|
170
314
|
end
|
171
315
|
end
|
172
316
|
|
173
317
|
InternalMetadata.do_not_synchronize if defined? InternalMetadata
|
174
|
-
|
175
318
|
end
|
data/lib/hyper-mesh.rb
CHANGED
@@ -22,12 +22,12 @@ if RUBY_ENGINE == 'opal'
|
|
22
22
|
require "reactive_record/active_record/reactive_record/collection"
|
23
23
|
require "reactive_record/active_record/reactive_record/scoped_collection"
|
24
24
|
require "reactive_record/active_record/reactive_record/unscoped_collection"
|
25
|
+
require "reactive_record/interval"
|
26
|
+
require_relative 'active_record_base'
|
27
|
+
require_relative 'reactive_record/scope_description'
|
25
28
|
require "reactive_record/active_record/class_methods"
|
26
29
|
require "reactive_record/active_record/instance_methods"
|
27
30
|
require "reactive_record/active_record/base"
|
28
|
-
require "reactive_record/interval"
|
29
|
-
require_relative 'reactive_record/scope_description'
|
30
|
-
require_relative 'active_record_base'
|
31
31
|
require_relative 'hypermesh/version'
|
32
32
|
require_relative 'opal/parse_patch'
|
33
33
|
require_relative 'opal/set_patches'
|
data/lib/hypermesh/version.rb
CHANGED
@@ -60,6 +60,7 @@ module ActiveRecord
|
|
60
60
|
# ignore any of these methods if they get called on the client. This list should be trimmed down to include only
|
61
61
|
# methods to be called as "macros" such as :after_create, etc...
|
62
62
|
SERVER_METHODS = [
|
63
|
+
:regulate_relationship, :regulate_scope,
|
63
64
|
:attribute_type_decorations, :defined_enums, :_validators, :timestamped_migrations, :lock_optimistically, :lock_optimistically=,
|
64
65
|
:local_stored_attributes=, :lock_optimistically?, :attribute_aliases?, :attribute_method_matchers?, :defined_enums?,
|
65
66
|
:has_many_without_reactive_record_add_changed_method, :has_many_with_reactive_record_add_changed_method,
|
@@ -78,7 +79,7 @@ module ActiveRecord
|
|
78
79
|
:_find_callbacks, :_find_callbacks?, :_find_callbacks=, :_touch_callbacks, :_touch_callbacks?, :_touch_callbacks=, :_save_callbacks,
|
79
80
|
:_save_callbacks?, :_save_callbacks=, :_create_callbacks, :_create_callbacks?, :_create_callbacks=, :_update_callbacks,
|
80
81
|
:_update_callbacks?, :_update_callbacks=, :_destroy_callbacks, :_destroy_callbacks?, :_destroy_callbacks=, :record_timestamps?,
|
81
|
-
:
|
82
|
+
:pre_synchromesh_scope, :pre_synchromesh_default_scope, :do_not_synchronize, :do_not_synchronize?,
|
82
83
|
:logger=, :maintain_test_schema, :maintain_test_schema=, :scope, :time_zone_aware_attributes, :time_zone_aware_attributes=,
|
83
84
|
:default_timezone, :default_timezone=, :_attr_readonly, :warn_on_records_fetched_greater_than, :configurations, :configurations=,
|
84
85
|
:_attr_readonly?, :table_name_prefix=, :table_name_suffix=, :schema_migrations_table_name=, :internal_metadata_table_name,
|
@@ -145,33 +146,107 @@ module ActiveRecord
|
|
145
146
|
def method_missing(name, *args, &block)
|
146
147
|
if args.count == 1 && name.start_with?("find_by_") && !block
|
147
148
|
find_by(name.sub(/^find_by_/, "") => args[0])
|
149
|
+
elsif [].respond_to?(name)
|
150
|
+
all.send(name, *args, &block)
|
151
|
+
elsif name.end_with?('!')
|
152
|
+
send(name.chop, *args, &block).send(:reload_from_db) rescue nil
|
148
153
|
elsif !SERVER_METHODS.include?(name)
|
149
154
|
raise "#{self.name}.#{name}(#{args}) (called class method missing)"
|
150
155
|
end
|
151
156
|
end
|
152
157
|
|
153
|
-
|
154
|
-
|
158
|
+
# client side AR
|
159
|
+
|
160
|
+
# Any method that can be applied to an array will be applied to the result
|
161
|
+
# of all instead.
|
162
|
+
# Any method ending with ! just means apply the method after forcing a reload
|
163
|
+
# from the DB.
|
164
|
+
|
165
|
+
# alias pre_synchromesh_method_missing method_missing
|
166
|
+
#
|
167
|
+
# def method_missing(name, *args, &block)
|
168
|
+
# return all.send(name, *args, &block) if [].respond_to?(name)
|
169
|
+
# if name.end_with?('!')
|
170
|
+
# return send(name.chop, *args, &block).send(:reload_from_db) rescue nil
|
171
|
+
# end
|
172
|
+
# pre_synchromesh_method_missing(name, *args, &block)
|
173
|
+
# end
|
174
|
+
|
175
|
+
def create(*args, &block)
|
176
|
+
new(*args).save(&block)
|
155
177
|
end
|
156
178
|
|
157
|
-
def scope(name,
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
singleton_class.send(:define_method, "#{name}=") do |collection|
|
163
|
-
ReactiveRecord::Base.class_scopes(self)[name] = collection
|
179
|
+
def scope(name, *args)
|
180
|
+
opts = _synchromesh_scope_args_check(args)
|
181
|
+
scope_description = ReactiveRecord::ScopeDescription.new(self, name, opts)
|
182
|
+
singleton_class.send(:define_method, name) do |*vargs|
|
183
|
+
all.build_child_scope(scope_description, *name, *vargs)
|
164
184
|
end
|
185
|
+
# singleton_class.send(:define_method, "#{name}=") do |_collection|
|
186
|
+
# raise 'NO LONGER IMPLEMENTED - DOESNT PLAY WELL WITH SYNCHROMESH'
|
187
|
+
# end
|
188
|
+
end
|
189
|
+
|
190
|
+
def default_scope(*args, &block)
|
191
|
+
opts = _synchromesh_scope_args_check([*block, *args])
|
192
|
+
@_default_scopes ||= []
|
193
|
+
@_default_scopes << opts
|
165
194
|
end
|
166
195
|
|
167
196
|
def all
|
168
|
-
ReactiveRecord::Base.
|
197
|
+
ReactiveRecord::Base.default_scope[self] ||=
|
198
|
+
begin
|
199
|
+
root = ReactiveRecord::Collection
|
200
|
+
.new(self, nil, nil, self, 'all')
|
201
|
+
.extend(ReactiveRecord::UnscopedCollection)
|
202
|
+
(@_default_scopes || [{ client: -> () { true } }]).inject(root) do |scope, opts|
|
203
|
+
scope.build_child_scope(ReactiveRecord::ScopeDescription.new(self, :all, opts))
|
204
|
+
end
|
205
|
+
end
|
169
206
|
end
|
170
207
|
|
171
|
-
def all=(
|
172
|
-
|
208
|
+
# def all=(_collection)
|
209
|
+
# raise "NO LONGER IMPLEMENTED DOESNT PLAY WELL WITH SYNCHROMESH"
|
210
|
+
# end
|
211
|
+
|
212
|
+
def unscoped
|
213
|
+
ReactiveRecord::Base.unscoped[self] ||=
|
214
|
+
ReactiveRecord::Collection
|
215
|
+
.new(self, nil, nil, self, 'unscoped')
|
216
|
+
.extend(ReactiveRecord::UnscopedCollection)
|
173
217
|
end
|
174
218
|
|
219
|
+
def finder_method(name)
|
220
|
+
ReactiveRecord::ScopeDescription.new(self, "_#{name}", {})
|
221
|
+
[name, "#{name}!"].each do |method|
|
222
|
+
singleton_class.send(:define_method, method) do |*vargs|
|
223
|
+
all.apply_scope("_#{method}", *vargs).first
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def abstract_class=(val)
|
229
|
+
@abstract_class = val
|
230
|
+
end
|
231
|
+
|
232
|
+
# def scope(name, body)
|
233
|
+
# singleton_class.send(:define_method, name) do | *args |
|
234
|
+
# args = (args.count == 0) ? name : [name, *args]
|
235
|
+
# ReactiveRecord::Base.class_scopes(self)[args] ||= ReactiveRecord::Collection.new(self, nil, nil, self, args)
|
236
|
+
# end
|
237
|
+
# singleton_class.send(:define_method, "#{name}=") do |collection|
|
238
|
+
# ReactiveRecord::Base.class_scopes(self)[name] = collection
|
239
|
+
# end
|
240
|
+
# end
|
241
|
+
|
242
|
+
# def all
|
243
|
+
# ReactiveRecord::Base.class_scopes(self)[:all] ||= ReactiveRecord::Collection.new(self, nil, nil, self, "all")
|
244
|
+
# end
|
245
|
+
#
|
246
|
+
# def all=(collection)
|
247
|
+
# ReactiveRecord::Base.class_scopes(self)[:all] = collection
|
248
|
+
# end
|
249
|
+
|
175
250
|
[:belongs_to, :has_many, :has_one].each do |macro|
|
176
251
|
define_method(macro) do |*args| # is this a bug in opal? saying name, scope=nil, opts={} does not work!
|
177
252
|
name = args.first
|
@@ -116,7 +116,7 @@ module ActiveRecord
|
|
116
116
|
# for rails auto generated methods for booleans, remove '?' to get the attribute
|
117
117
|
name = name.chop if !is_server_method && is_attribute && name.end_with?('?')
|
118
118
|
@backing_record.reactive_get!(name, force_update)
|
119
|
-
elsif !block
|
119
|
+
elsif !block
|
120
120
|
# for rails auto generated methods for booleans, remove '?' to get the attribute
|
121
121
|
name = name.chop if !is_server_method && is_attribute && name.end_with?('?')
|
122
122
|
@backing_record.reactive_get!([[name]+args], force_update)
|
@@ -167,6 +167,20 @@ module ActiveRecord
|
|
167
167
|
@backing_record.errors
|
168
168
|
end
|
169
169
|
|
170
|
+
def update_attribute(attr, value, &block)
|
171
|
+
send("#{attr}=", value)
|
172
|
+
save(validate: false, &block)
|
173
|
+
end
|
174
|
+
|
175
|
+
def update(attrs = {}, &block)
|
176
|
+
attrs.each { |attr, value| send("#{attr}=", value) }
|
177
|
+
save(&block)
|
178
|
+
end
|
179
|
+
|
180
|
+
def <=>(other)
|
181
|
+
id.to_i <=> other.id.to_i
|
182
|
+
end
|
183
|
+
|
170
184
|
end
|
171
185
|
|
172
186
|
end
|
@@ -87,7 +87,7 @@ module ReactiveRecord
|
|
87
87
|
record = @records[model].detect { |record| record.id == id}
|
88
88
|
end
|
89
89
|
# if we don't have a record then create one
|
90
|
-
(record = new(model)).vector = [model, [
|
90
|
+
(record = new(model)).vector = [model, [:find_by, attribute => value]] unless record
|
91
91
|
# and set the value
|
92
92
|
record.sync_attribute(attribute, value)
|
93
93
|
# and set the primary if we have one
|
@@ -263,7 +263,7 @@ module ReactiveRecord
|
|
263
263
|
# nil must not have any children.
|
264
264
|
def initialize_collections
|
265
265
|
if (!vector || vector.empty?) && id && id != ''
|
266
|
-
@vector = [@model, [
|
266
|
+
@vector = [@model, [:find_by, @model.primary_key => id]]
|
267
267
|
end
|
268
268
|
@model.reflect_on_all_associations.each do |assoc|
|
269
269
|
if assoc.collection? && attributes[assoc.attribute].nil?
|
@@ -252,7 +252,7 @@ module ReactiveRecord
|
|
252
252
|
elsif filter?
|
253
253
|
@collection = filter_records(@parent.collection)
|
254
254
|
end
|
255
|
-
elsif @parent.count.zero?
|
255
|
+
elsif @parent._count_internal(false).zero? # just changed this from count.zero?
|
256
256
|
@count = 0
|
257
257
|
end
|
258
258
|
end
|
@@ -289,18 +289,26 @@ module ReactiveRecord
|
|
289
289
|
@count = val
|
290
290
|
end
|
291
291
|
|
292
|
-
|
292
|
+
|
293
|
+
|
294
|
+
def _count_internal(load_from_client)
|
295
|
+
# when count is called on a leaf, count_internal is called for each
|
296
|
+
# ancestor. Only the outermost count has load_from_client == true
|
293
297
|
observed
|
294
298
|
if @collection
|
295
299
|
@collection.count
|
296
300
|
elsif @count ||= ReactiveRecord::Base.fetch_from_db([*@vector, "*count"])
|
297
301
|
@count
|
298
302
|
else
|
299
|
-
ReactiveRecord::Base.load_from_db(nil, *@vector, "*count")
|
303
|
+
ReactiveRecord::Base.load_from_db(nil, *@vector, "*count") if load_from_client
|
300
304
|
@count = 1
|
301
305
|
end
|
302
306
|
end
|
303
307
|
|
308
|
+
def count
|
309
|
+
_count_internal(true)
|
310
|
+
end
|
311
|
+
|
304
312
|
alias_method :length, :count
|
305
313
|
|
306
314
|
# WHY IS THIS NEEDED? Perhaps it was just for debug
|
@@ -473,15 +481,19 @@ module ReactiveRecord
|
|
473
481
|
@dummy_collection.loading?
|
474
482
|
end
|
475
483
|
|
476
|
-
def empty?
|
484
|
+
def empty?
|
485
|
+
# should be handled by method missing below, but opal-rspec does not deal well
|
486
|
+
# with method missing, so to test...
|
477
487
|
all.empty?
|
478
488
|
end
|
479
489
|
|
480
490
|
def method_missing(method, *args, &block)
|
481
491
|
if [].respond_to? method
|
482
492
|
all.send(method, *args, &block)
|
483
|
-
elsif ScopeDescription.find(@target_klass, method)
|
493
|
+
elsif ScopeDescription.find(@target_klass, method)
|
484
494
|
apply_scope(method, *args)
|
495
|
+
elsif args.count == 1 && method.start_with?('find_by_')
|
496
|
+
apply_scope(:find_by, method.sub(/^find_by_/, '') => args.first)
|
485
497
|
elsif @target_klass.respond_to?(method) && ScopeDescription.find(@target_klass, "_#{method}")
|
486
498
|
apply_scope("_#{method}", *args).first
|
487
499
|
else
|
@@ -133,6 +133,7 @@ module ReactiveRecord
|
|
133
133
|
end
|
134
134
|
|
135
135
|
def self.schedule_fetch
|
136
|
+
React::State.set_state(WhileLoading, :quiet, false) # moved from while loading module see loading! method
|
136
137
|
@fetch_scheduled ||= after(0) do
|
137
138
|
if @pending_fetches.count > 0 # during testing we might reset the context while there are pending fetches otherwise this would never normally happen
|
138
139
|
last_fetch_at = @last_fetch_at
|
@@ -143,23 +144,29 @@ module ReactiveRecord
|
|
143
144
|
start_time = `Date.now()`
|
144
145
|
Operations::Fetch.run(models: models, associations: associations, pending_fetches: pending_fetches)
|
145
146
|
.then do |response|
|
146
|
-
fetch_time = `Date.now()`
|
147
|
-
log(" Fetched in: #{`(fetch_time - start_time)/ 1000`}s")
|
148
147
|
begin
|
149
|
-
|
150
|
-
|
151
|
-
|
148
|
+
fetch_time = `Date.now()`
|
149
|
+
log(" Fetched in: #{`(fetch_time - start_time)/ 1000`}s")
|
150
|
+
begin
|
151
|
+
ReactiveRecord::Base.load_from_json(response)
|
152
|
+
rescue Exception => e
|
153
|
+
log("Unexpected exception raised while loading json from server: #{e}", :error)
|
154
|
+
end
|
155
|
+
log(" Processed in: #{`(Date.now() - fetch_time) / 1000`}s")
|
156
|
+
log([" Returned: %o", response.to_n])
|
157
|
+
ReactiveRecord.run_blocks_to_load last_fetch_at
|
158
|
+
ensure
|
159
|
+
ReactiveRecord::WhileLoading.loaded_at last_fetch_at
|
160
|
+
ReactiveRecord::WhileLoading.quiet! if @pending_fetches.empty?
|
152
161
|
end
|
153
|
-
log(" Processed in: #{`(Date.now() - fetch_time) / 1000`}s")
|
154
|
-
log([" Returned: %o", response.to_n])
|
155
|
-
ReactiveRecord.run_blocks_to_load last_fetch_at
|
156
|
-
ReactiveRecord::WhileLoading.loaded_at last_fetch_at
|
157
|
-
ReactiveRecord::WhileLoading.quiet! if @pending_fetches.empty?
|
158
162
|
end
|
159
163
|
.fail do |response|
|
160
164
|
log("Fetch failed", :error)
|
161
|
-
|
162
|
-
|
165
|
+
begin
|
166
|
+
ReactiveRecord.run_blocks_to_load(last_fetch_at, response)
|
167
|
+
ensure
|
168
|
+
ReactiveRecord::WhileLoading.quiet! if @pending_fetches.empty?
|
169
|
+
end
|
163
170
|
end
|
164
171
|
@pending_fetches = []
|
165
172
|
@pending_records = []
|
@@ -123,7 +123,8 @@ module ReactiveRecord
|
|
123
123
|
def loading!
|
124
124
|
React::RenderingContext.waiting_on_resources = true
|
125
125
|
React::State.get_state(self, :loaded_at)
|
126
|
-
|
126
|
+
# this was moved to where the fetch is actually pushed on to the fetch array in isomorphic base
|
127
|
+
# React::State.set_state(self, :quiet, false)
|
127
128
|
@is_loading = true
|
128
129
|
end
|
129
130
|
|
@@ -298,7 +299,7 @@ if RUBY_ENGINE == 'opal'
|
|
298
299
|
def reactive_record_link_set_while_loading_container_class
|
299
300
|
node = dom_node
|
300
301
|
loading = (waiting_on_resources ? `true` : `false`)
|
301
|
-
%x{
|
302
|
+
%x{
|
302
303
|
if (typeof node === "undefined" || node === null) return;
|
303
304
|
var while_loading_container_id = node.getAttribute('data-reactive_record_while_loading_container_id');
|
304
305
|
if (#{!self.is_a?(ReactiveRecord::WhileLoading)} && while_loading_container_id !== null && while_loading_container_id !== "") {
|
@@ -203,29 +203,36 @@ module ReactiveRecord
|
|
203
203
|
def apply_method_to_cache(method)
|
204
204
|
@db_cache.inject(nil) do |representative, cache_item|
|
205
205
|
if cache_item.vector == vector
|
206
|
-
# TODO: Security - this is the wrong check in the wrong place...
|
207
|
-
if @value.class < ActiveRecord::Base and @value.attributes.has_key?(method) # TODO: second check is not needed, its built into check_permmissions, check should be does class respond to check_permissions...
|
208
|
-
@value.check_permission_with_acting_user(@acting_user, :view_permitted?, method)
|
209
|
-
end
|
210
206
|
if method == "*"
|
207
|
+
# apply_star does the security check if value is present
|
211
208
|
cache_item.apply_star || representative
|
212
209
|
elsif method == "*all"
|
213
|
-
|
210
|
+
# if we secure the collection then we assume its okay to read the ids
|
211
|
+
cache_item.build_new_cache_item(cache_item.value.__secure_collection_check(@acting_user).collect { |record| record.id }, method, method)
|
214
212
|
elsif method == "*count"
|
215
|
-
cache_item.build_new_cache_item(cache_item.value.count, method, method)
|
213
|
+
cache_item.build_new_cache_item(cache_item.value.__secure_collection_check(@acting_user).count, method, method)
|
216
214
|
elsif preloaded_value = @preloaded_records[cache_item.absolute_vector + [method]]
|
215
|
+
# no security check needed since we already evaluated this
|
217
216
|
cache_item.build_new_cache_item(preloaded_value, method, method)
|
218
217
|
elsif aggregation = cache_item.aggregation?(method)
|
218
|
+
# aggregations are not protected
|
219
219
|
cache_item.build_new_cache_item(aggregation.mapping.collect { |attribute, accessor| cache_item.value[attribute] }, method, method)
|
220
220
|
else
|
221
|
-
if !cache_item.value || cache_item.value.
|
221
|
+
if !cache_item.value || cache_item.value.is_a?(Array)
|
222
|
+
# seeing as we just returning representative, no check is needed (its already checked)
|
222
223
|
representative
|
223
224
|
else
|
224
|
-
# TODO: Security. Protect the send(*method). But its complicated.. method can be an attribute, scope, relationship or actual method.
|
225
|
-
# Each needs some protection logic.
|
226
225
|
begin
|
227
|
-
|
228
|
-
|
226
|
+
secured_method = "__secure_remote_access_to_#{[*method].first}"
|
227
|
+
if @value.class < ActiveRecord::Base and @value.attributes.has_key?(method) # TODO: second check is not needed, its built into check_permmissions, check should be does class respond to check_permissions...
|
228
|
+
@value.check_permission_with_acting_user(@acting_user, :view_permitted?, method)
|
229
|
+
cache_item.build_new_cache_item(cache_item.value.send(*method), method, method)
|
230
|
+
elsif cache_item.value.respond_to? secured_method
|
231
|
+
cache_item.build_new_cache_item(cache_item.value.send(secured_method, @acting_user, *([*method][1..-1])), method, method)
|
232
|
+
else
|
233
|
+
raise "method missing"
|
234
|
+
end
|
235
|
+
rescue Exception => e # this check may no longer be needed as we are quite explicit now on which methods we apply
|
229
236
|
# ReactiveRecord::Pry::rescued(e)
|
230
237
|
::Rails.logger.debug "\033[0;31;1mERROR: HyperModel exception caught when applying #{method} to db object #{cache_item.value}: #{e}\033[0;30;21m"
|
231
238
|
raise e, "HyperModel fetching records failed, exception caught when applying #{method} to db object #{cache_item.value}: #{e}", e.backtrace
|
@@ -248,9 +255,9 @@ module ReactiveRecord
|
|
248
255
|
end
|
249
256
|
end
|
250
257
|
|
251
|
-
# SECURITY - SAFE
|
258
|
+
# SECURITY - NOW SAFE
|
252
259
|
def apply_star
|
253
|
-
if @value && @value.length > 0
|
260
|
+
if @value && @value.__secure_collection_check(@acting_user) && @value.length > 0
|
254
261
|
i = -1
|
255
262
|
@value.inject(nil) do |representative, current_value|
|
256
263
|
i += 1
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hyper-mesh
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.
|
4
|
+
version: 1.0.0.lap24
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mitch VanDuyn
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2018-02-
|
12
|
+
date: 2018-02-28 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
@@ -31,28 +31,28 @@ dependencies:
|
|
31
31
|
requirements:
|
32
32
|
- - '='
|
33
33
|
- !ruby/object:Gem::Version
|
34
|
-
version: 1.0.0.
|
34
|
+
version: 1.0.0.lap24
|
35
35
|
type: :runtime
|
36
36
|
prerelease: false
|
37
37
|
version_requirements: !ruby/object:Gem::Requirement
|
38
38
|
requirements:
|
39
39
|
- - '='
|
40
40
|
- !ruby/object:Gem::Version
|
41
|
-
version: 1.0.0.
|
41
|
+
version: 1.0.0.lap24
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
43
|
name: hyper-operation
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
45
45
|
requirements:
|
46
46
|
- - '='
|
47
47
|
- !ruby/object:Gem::Version
|
48
|
-
version: 1.0.0.
|
48
|
+
version: 1.0.0.lap24
|
49
49
|
type: :runtime
|
50
50
|
prerelease: false
|
51
51
|
version_requirements: !ruby/object:Gem::Requirement
|
52
52
|
requirements:
|
53
53
|
- - '='
|
54
54
|
- !ruby/object:Gem::Version
|
55
|
-
version: 1.0.0.
|
55
|
+
version: 1.0.0.lap24
|
56
56
|
- !ruby/object:Gem::Dependency
|
57
57
|
name: bundler
|
58
58
|
requirement: !ruby/object:Gem::Requirement
|
@@ -129,14 +129,14 @@ dependencies:
|
|
129
129
|
requirements:
|
130
130
|
- - '='
|
131
131
|
- !ruby/object:Gem::Version
|
132
|
-
version: 1.0.0.
|
132
|
+
version: 1.0.0.lap24
|
133
133
|
type: :development
|
134
134
|
prerelease: false
|
135
135
|
version_requirements: !ruby/object:Gem::Requirement
|
136
136
|
requirements:
|
137
137
|
- - '='
|
138
138
|
- !ruby/object:Gem::Version
|
139
|
-
version: 1.0.0.
|
139
|
+
version: 1.0.0.lap24
|
140
140
|
- !ruby/object:Gem::Dependency
|
141
141
|
name: hyper-trace
|
142
142
|
requirement: !ruby/object:Gem::Requirement
|
@@ -549,6 +549,20 @@ dependencies:
|
|
549
549
|
- - ">="
|
550
550
|
- !ruby/object:Gem::Version
|
551
551
|
version: '0'
|
552
|
+
- !ruby/object:Gem::Dependency
|
553
|
+
name: pry-rescue
|
554
|
+
requirement: !ruby/object:Gem::Requirement
|
555
|
+
requirements:
|
556
|
+
- - ">="
|
557
|
+
- !ruby/object:Gem::Version
|
558
|
+
version: '0'
|
559
|
+
type: :development
|
560
|
+
prerelease: false
|
561
|
+
version_requirements: !ruby/object:Gem::Requirement
|
562
|
+
requirements:
|
563
|
+
- - ">="
|
564
|
+
- !ruby/object:Gem::Version
|
565
|
+
version: '0'
|
552
566
|
description: HyperMesh is the base for HyperModel. HyperModel gives your HyperComponents
|
553
567
|
CRUD access to your ActiveRecord models on the client, using the the standard ActiveRecord
|
554
568
|
API. HyperModel also implements push notifications (via a number of possible technologies)
|
@@ -622,8 +636,7 @@ homepage: http://ruby-hyperloop.org
|
|
622
636
|
licenses:
|
623
637
|
- MIT
|
624
638
|
metadata: {}
|
625
|
-
post_install_message:
|
626
|
-
known security issues! Not recommended for production use!\e[0;30;21m"
|
639
|
+
post_install_message:
|
627
640
|
rdoc_options: []
|
628
641
|
require_paths:
|
629
642
|
- lib
|