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.
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
@@ -1,6 +1,6 @@
1
- MIT License
1
+ The MIT License (MIT)
2
2
 
3
- Copyright (c) 2017 Ruby Hyperloop
3
+ Copyright (c) 2016 Mitch VanDuyn
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -9,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
9
  copies of the Software, and to permit persons to whom the Software is
10
10
  furnished to do so, subject to the following conditions:
11
11
 
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
14
 
15
15
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
16
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
17
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
18
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md CHANGED
@@ -1,2 +1,51 @@
1
- # hyper-model
2
- Isomorphic ActiveRecord wrapper for Hyperloop
1
+ <div class="githubhyperloopheader">
2
+
3
+ <p align="center">
4
+
5
+ <a href="http://ruby-hyperloop.org/" alt="Hyperloop" title="Hyperloop">
6
+ <img width="350px" src="http://ruby-hyperloop.org/images/hyperloop-github-logo.png">
7
+ </a>
8
+
9
+ </p>
10
+
11
+ <h2 align="center">The Complete Isomorphic Ruby Framework</h2>
12
+
13
+ <br>
14
+
15
+ <a href="http://ruby-hyperloop.org/" alt="Hyperloop" title="Hyperloop">
16
+ <img src="http://ruby-hyperloop.org/images/githubhyperloopbadge.png">
17
+ </a>
18
+
19
+ <a href="https://gitter.im/ruby-hyperloop/chat" alt="Gitter chat" title="Gitter chat">
20
+ <img src="http://ruby-hyperloop.org/images/githubgitterbadge.png">
21
+ </a>
22
+
23
+ [![Build Status](https://travis-ci.org/ruby-hyperloop/hyper-mesh.svg?branch=master)](https://travis-ci.org/ruby-hyperloop/hyper-mesh)
24
+ [![Codeship Status for ruby-hyperloop/hyper-store](https://app.codeship.com/projects/4454c560-d4ea-0134-7c96-362b4886dd22/status?branch=master)](https://app.codeship.com/projects/202301)
25
+ [![Gem Version](https://badge.fury.io/rb/hyper-mesh.svg)](https://badge.fury.io/rb/hyper-mesh)
26
+
27
+ <p align="center">
28
+ <img src="http://ruby-hyperloop.org/images/HyperModels.png" width="100" alt="Hyper-models">
29
+ </p>
30
+
31
+ </div>
32
+
33
+ ## Hyper-Mesh GEM is part of Hyperloop GEMS family
34
+
35
+ Hyper-Mesh GEM comes with the Hyperloop GEM.
36
+
37
+ But if you want to install it separately, please install the [Hyper-model GEM](https://github.com/ruby-hyperloop/hyper-model).
38
+
39
+ ## Community
40
+
41
+ #### Getting Help
42
+ Please **do not post** usage questions to GitHub Issues. For these types of questions use our [Gitter chatroom](https://gitter.im/ruby-hyperloop/chat) or [StackOverflow](http://stackoverflow.com/questions/tagged/hyperloop).
43
+
44
+ #### Submitting Bugs and Enhancements
45
+ [GitHub Issues](https://github.com/ruby-hyperloop/hyperloop/issues) is for suggesting enhancements and reporting bugs. Before submiting a bug make sure you do the following:
46
+ * Check out our [contributing guide](https://github.com/ruby-hyperloop/hyperloop/blob/master/CONTRIBUTING.md) for info on our release cycle.
47
+
48
+ ## License
49
+
50
+ Hyperloop is released under the [MIT License](http://www.opensource.org/licenses/MIT).
51
+
data/Rakefile CHANGED
@@ -1,2 +1,20 @@
1
1
  require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ task :spec do
5
+ (1..7).each { |batch| Rake::Task["spec:batch#{batch}"].invoke rescue nil }
6
+ end
7
+
8
+ namespace :spec do
9
+ task :prepare do
10
+ sh %(cd spec/test_app; bundle exec rails db:setup)
11
+ end
12
+ (1..7).each do |batch|
13
+ RSpec::Core::RakeTask.new(:"batch#{batch}") do |t|
14
+ t.fail_on_error = false unless batch == 7
15
+ t.pattern = "spec/batch#{batch}/**/*_spec.rb"
16
+ end
17
+ end
18
+ end
19
+
2
20
  task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "hyper-mesh"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,18 @@
1
+ development:
2
+ adapter: mysql2
3
+ host: localhost
4
+ encoding: utf8
5
+ pool: 10
6
+ username: <%= ENV['MYSQL_USER'] %>
7
+ password: <%= ENV['MYSQL_PASSWORD'] %>
8
+ database: development<%= ENV['TEST_ENV_NUMBER'] %>
9
+ socket: /var/run/mysqld/mysqld.sock
10
+ test:
11
+ adapter: mysql2
12
+ host: localhost
13
+ encoding: utf8
14
+ pool: 10
15
+ username: <%= ENV['MYSQL_USER'] %>
16
+ password: <%= ENV['MYSQL_PASSWORD'] %>
17
+ database: test<%= ENV['TEST_ENV_NUMBER'] %>
18
+ socket: /var/run/mysqld/mysqld.sock
@@ -1,43 +1,69 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'hyperloop/model/version'
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path('../lib/', __FILE__)
3
+ require 'hyper_model/version'
5
4
 
6
5
  Gem::Specification.new do |spec|
7
- spec.name = "hyper-model"
8
- spec.version = Hyperloop::Model::VERSION
9
- spec.authors = ["catmando"]
10
- spec.email = ["mitch@catprint.com"]
6
+ spec.name = 'hyper-model'
7
+ spec.version = HyperModel::VERSION
8
+ spec.authors = ['Mitch VanDuyn', 'Jan Biedermann']
9
+ spec.email = ['mitch@catprint.com', 'jan@kursator.com']
10
+ spec.summary = 'React based CRUD access and Synchronization of active record models across multiple clients'
11
+ spec.description = 'HyperModel gives your HyperComponents CRUD access to your '\
12
+ 'ActiveRecord models on the client, using the the standard ActiveRecord '\
13
+ 'API. HyperModel also implements push notifications (via a number of '\
14
+ 'possible technologies) so changes to records on the server are '\
15
+ 'dynamically updated on all authorised clients.'
16
+ spec.homepage = 'http://ruby-hyperloop.org'
17
+ spec.license = 'MIT'
18
+ # spec.metadata = {
19
+ # "homepage_uri" => 'http://ruby-hyperloop.org',
20
+ # "source_code_uri" => 'https://github.com/ruby-hyperloop/hyper-component'
21
+ # }
11
22
 
12
- spec.summary = %q{Isomorphic ActiveRecord wrapper for Hyperloop}
13
- spec.homepage = "http://ruby-hyperloop.io"
14
- spec.license = "MIT"
23
+ spec.files = `git ls-files`.split("\n").reject { |f| f.match(%r{^(examples|gemfiles|pkg|reactive_record_test_app|spec)/}) }
24
+ # spec.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
25
+ spec.test_files = `git ls-files -- {spec}/*`.split("\n")
26
+ spec.require_paths = ['lib']
15
27
 
16
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
- spec.bindir = "exe"
18
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
- spec.require_paths = ["lib"]
20
-
21
- spec.add_dependency 'hyper-mesh'
22
-
23
- spec.add_development_dependency 'hyper-spec'
28
+ spec.add_dependency 'activemodel'
29
+ spec.add_dependency 'activerecord', '>= 4.0.0'
30
+ spec.add_dependency 'hyper-component', HyperModel::VERSION
31
+ spec.add_dependency 'hyper-operation', HyperModel::VERSION
32
+ spec.add_development_dependency 'bundler'
33
+ spec.add_development_dependency 'capybara'
34
+ spec.add_development_dependency 'chromedriver-helper', '1.2.0'
35
+ spec.add_development_dependency 'libv8', '~> 6.3.0' # see https://github.com/discourse/mini_racer/issues/92
36
+ spec.add_development_dependency 'mini_racer', '~> 0.1.15'
37
+ spec.add_development_dependency 'selenium-webdriver'
24
38
  spec.add_development_dependency 'database_cleaner'
25
- spec.add_development_dependency 'listen'
26
- spec.add_development_dependency 'opal'
27
- spec.add_development_dependency 'opal-browser'
28
- spec.add_development_dependency 'opal-rails'
29
- spec.add_development_dependency 'pry-byebug'
30
- spec.add_development_dependency 'rails'
31
- spec.add_development_dependency 'react-rails', '< 1.10.0'
32
- spec.add_development_dependency 'rspec'
33
- spec.add_development_dependency 'rspec-steps'
34
- spec.add_development_dependency 'factory_girl_rails'
35
- spec.add_development_dependency 'sqlite3'
39
+ spec.add_development_dependency 'factory_bot_rails'
40
+ #spec.add_development_dependency 'hyper-spec', HyperModel::VERSION
41
+ spec.add_development_dependency 'mysql2'
42
+ spec.add_development_dependency 'opal-activesupport', '~> 0.3.1'
43
+ spec.add_development_dependency 'opal-browser', '~> 0.2.0'
44
+ spec.add_development_dependency 'opal-rails', '~> 0.9.4'
45
+ spec.add_development_dependency 'parser'
46
+ spec.add_development_dependency 'pry'
47
+ spec.add_development_dependency 'pry-rescue'
36
48
  spec.add_development_dependency 'puma'
37
-
38
- # Keep linter-rubocop happy
39
- spec.add_development_dependency 'rubocop'
40
-
41
- spec.add_development_dependency 'bundler', '~> 1.12'
42
- spec.add_development_dependency 'rake', '~> 10.0'
49
+ spec.add_development_dependency 'pusher'
50
+ spec.add_development_dependency 'pusher-fake'
51
+ spec.add_development_dependency 'rails', '>= 4.0.0'
52
+ spec.add_development_dependency 'rake'
53
+ spec.add_development_dependency 'react-rails', '>= 2.4.0', '< 2.5.0'
54
+ spec.add_development_dependency 'reactrb-rails-generator'
55
+ spec.add_development_dependency 'rspec-collection_matchers'
56
+ spec.add_development_dependency 'rspec-expectations'
57
+ spec.add_development_dependency 'rspec-its'
58
+ spec.add_development_dependency 'rspec-mocks'
59
+ spec.add_development_dependency 'rspec-rails'
60
+ spec.add_development_dependency 'rspec-steps', '~> 2.1.1'
61
+ spec.add_development_dependency 'rspec-wait'
62
+ spec.add_development_dependency 'rubocop', '~> 0.51.0'
63
+ spec.add_development_dependency 'shoulda'
64
+ spec.add_development_dependency 'shoulda-matchers'
65
+ spec.add_development_dependency 'spring-commands-rspec'
66
+ spec.add_development_dependency 'sqlite3'
67
+ spec.add_development_dependency 'timecop', '~> 0.8.1'
68
+ spec.add_development_dependency 'unparser'
43
69
  end
@@ -0,0 +1,16 @@
1
+ module ActiveModel
2
+ # minimal ActiveModel definitions so we can support I18n methods
3
+ class Name
4
+ attr_reader :name, :klass, :i18n_key
5
+
6
+ def initialize(klass)
7
+ @name = klass.name
8
+ @klass = klass
9
+ @i18n_key = :"#{@name.underscore}"
10
+ end
11
+
12
+ def to_s
13
+ @name
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,331 @@
1
+ # Monkey patches to ActiveRecord for scoping, security, and to synchronize models
2
+ module ActiveRecord
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.
7
+ class Base
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
34
+ end
35
+ end
36
+
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_all(self, acting_user).__synchromesh_permission_granted
45
+ return self if __secure_remote_access_to_unscoped(self, acting_user).__synchromesh_permission_granted
46
+ Hyperloop::InternalPolicy.raise_operation_access_violation(:scoped_permission_not_granted, "Last relation: #{self}, acting_user: #{acting_user}")
47
+ end
48
+ end
49
+ # Monkey patches and extensions to base
50
+ class Base
51
+ class << self
52
+ # every method call that is legal from the client has a wrapper method prefixed with
53
+ # __secure_remote_access_to_
54
+
55
+ # The wrapper method may simply return the normal result or may act to secure the data.
56
+ # The simpliest case is for the method to call `denied!` which will raise a Hyperloop
57
+ # access protection fault.
58
+
59
+ def denied!
60
+ Hyperloop::InternalPolicy.raise_operation_access_violation(:scoped_denied, "#{self} regulation denies scope access. Called from #{caller_locations(1)}")
61
+ end
62
+
63
+ # Here we set up the base `all` and `unscoped` methods. See below for more on how
64
+ # access protection works on relationships.
65
+
66
+ def __secure_remote_access_to_all(_self, _acting_user)
67
+ all
68
+ end
69
+
70
+ def __secure_remote_access_to_unscoped(_self, _acting_user, *args)
71
+ unscoped(*args)
72
+ end
73
+
74
+ # finder_method and server_method provide secure RPCs against AR relations and records.
75
+ # The block is called in context with the object, and acting_user is set to the
76
+ # current acting user. The block may interogate acting_user to insure security as needed.
77
+
78
+ # For finder_method we have to preapply `all` so that we always have a relationship
79
+
80
+ def finder_method(name, &block)
81
+ singleton_class.send(:define_method, :"__secure_remote_access_to__#{name}") do |this, acting_user, *args|
82
+ this = respond_to?(:acting_user) ? this : all
83
+ begin
84
+ old = this.acting_user
85
+ this.acting_user = acting_user
86
+ # returns a PsuedoRelationArray which will respond to the
87
+ # __secure_collection_check method
88
+ ReactiveRecordPsuedoRelationArray.new([this.instance_exec(*args, &block)])
89
+ ensure
90
+ this.acting_user = old
91
+ end
92
+ end
93
+ singleton_class.send(:define_method, name) do |*args|
94
+ all.instance_exec(*args, &block)
95
+ end
96
+ end
97
+
98
+ def server_method(name, _opts = {}, &block)
99
+ # callable from the server internally
100
+ define_method(name, &block)
101
+ # callable remotely from the client
102
+ define_method("__secure_remote_access_to_#{name}") do |_self, acting_user, *args|
103
+ begin
104
+ old = self.acting_user
105
+ self.acting_user = acting_user
106
+ send(name, *args)
107
+ ensure
108
+ self.acting_user = old
109
+ end
110
+ end
111
+ end
112
+
113
+ # relationships (and scopes) are regulated using a tri-state system. Each
114
+ # remote access method will return the relationship as normal but will also set
115
+ # the value of __secure_remote_access_granted using the application defined regulation.
116
+ # Each regulation can explicitly allow the scope to be chained by returning a truthy
117
+ # value from the regulation. Or each regulation can explicitly deny the scope to
118
+ # be chained by called `denied!`. Otherwise each regulation can return a falsy
119
+ # value meaning the scope can be changed, but unless some other scope (before or
120
+ # after) in the chain explicitly allows the scope, the entire chain will fail.
121
+
122
+ # In otherwords within a chain of relationships and scopes, at least one Regulation
123
+ # must be return a truthy value otherwise the whole chain fails. Likewise if any
124
+ # regulation called `deined!` the whole chain fails.
125
+
126
+ # If no regulation is defined, the regulation is inherited from the superclass, and if
127
+ # no regulation is defined anywhere in the class heirarchy then the regulation will
128
+ # return a falsy value.
129
+
130
+ # regulations on scopes are inheritable. That is if a superclass defines a regulation
131
+ # for a scope, subclasses will inherit the regulation (but can override)
132
+
133
+ # helper method to sort out the options on the regulate_scope, regulate_relationship macros.
134
+
135
+ # We allow three forms:
136
+ # regulate_xxx name &block : the block is the regulation
137
+ # regulate_xxx name: const : const can be denied!, deny, denied, or any other truthy or
138
+ # falsy value
139
+ # regulate_xxx name: proc : the proc is the regulation
140
+
141
+ def __synchromesh_parse_regulator_params(name, block)
142
+ if name.is_a? Hash
143
+ name, block = name.first
144
+ if %i[denied! deny denied].include? block
145
+ block = ->(*_args) { denied! }
146
+ elsif !block.is_a? Proc
147
+ value = block
148
+ block = ->(*_args) { value }
149
+ end
150
+ end
151
+ [name, block || ->(*_args) { true }]
152
+ end
153
+
154
+ # helper method for providing a regulation in line with a scope or relationship
155
+ # this is done using the `regulate` key on the opts.
156
+ # if no regulate key is provided and there is no regulation already defined for
157
+ # this name, then we create one that returns nil (don't care)
158
+ # once we have things figured out, we yield to the provided proc which is either
159
+ # regulate_scope or regulate_relationship
160
+
161
+ def __synchromesh_regulate_from_macro(opts, name, already_defined)
162
+ if opts.key?(:regulate)
163
+ yield name => opts[:regulate]
164
+ elsif !already_defined
165
+ yield name => ->(*_args) {}
166
+ end
167
+ end
168
+
169
+ # helper method to set the value of __synchromesh_permission_granted on the relationship
170
+ # Set acting_user on the object, then or in the result of running the block in context
171
+ # of the obj with the current value of __synchromesh_permission_granted
172
+
173
+ def __set_synchromesh_permission_granted(old_rel, new_rel, obj, acting_user, args = [], &block)
174
+ saved_acting_user = obj.acting_user
175
+ obj.acting_user = acting_user
176
+ new_rel.__synchromesh_permission_granted =
177
+ obj.instance_exec(*args, &block) || (old_rel && old_rel.try(:__synchromesh_permission_granted))
178
+ new_rel
179
+ ensure
180
+ obj.acting_user = saved_acting_user
181
+ end
182
+
183
+ # regulate scope has to deal with the special case that the scope returns an
184
+ # an array instead of a relationship. In this case we wrap the array and go on
185
+
186
+ def regulate_scope(name, &block)
187
+ name, block = __synchromesh_parse_regulator_params(name, block)
188
+ singleton_class.send(:define_method, :"__secure_remote_access_to_#{name}") do |this, acting_user, *args|
189
+ r = this.send(name, *args)
190
+ r = ReactiveRecordPsuedoRelationArray.new(r) if r.is_a? Array
191
+ __set_synchromesh_permission_granted(this, r, r, acting_user, args, &block)
192
+ end
193
+ end
194
+
195
+ # regulate_default_scope
196
+
197
+ def regulate_default_scope(*args, &block)
198
+ block = __synchromesh_parse_regulator_params({ all: args[0] }, block).last unless args.empty?
199
+ regulate_scope(:all, &block)
200
+ end
201
+
202
+ # monkey patch scope and default_scope macros to process hyperloop special opts,
203
+ # and add regulations if present
204
+
205
+ alias pre_synchromesh_scope scope
206
+
207
+ def scope(name, *args, &block)
208
+ __synchromesh_regulate_from_macro(
209
+ (opts = _synchromesh_scope_args_check(args)),
210
+ name,
211
+ respond_to?(:"__secure_remote_access_to_#{name}"),
212
+ &method(:regulate_scope)
213
+ )
214
+ pre_synchromesh_scope(name, opts[:server], &block)
215
+ end
216
+
217
+ alias pre_synchromesh_default_scope default_scope
218
+
219
+ def default_scope(*args, &block)
220
+ __synchromesh_regulate_from_macro(
221
+ (opts = _synchromesh_scope_args_check([*block, *args])),
222
+ :all,
223
+ respond_to?(:__secure_remote_access_to_all),
224
+ &method(:regulate_scope)
225
+ )
226
+ pre_synchromesh_default_scope(opts[:server], &block)
227
+ end
228
+
229
+ # add regulate_relationship method and monkey patch has_many macro
230
+ # to add regulations if present
231
+
232
+ def regulate_relationship(name, &block)
233
+ name, block = __synchromesh_parse_regulator_params(name, block)
234
+ define_method(:"__secure_remote_access_to_#{name}") do |this, acting_user, *args|
235
+ this.class.__set_synchromesh_permission_granted(
236
+ nil, this.send(name, *args), this, acting_user, &block
237
+ )
238
+ end
239
+ end
240
+
241
+ alias pre_syncromesh_has_many has_many
242
+
243
+ def has_many(name, *args, &block)
244
+ __synchromesh_regulate_from_macro(
245
+ opts = args.extract_options!,
246
+ name,
247
+ method_defined?(:"__secure_remote_access_to_#{name}"),
248
+ &method(:regulate_relationship)
249
+ )
250
+ pre_syncromesh_has_many name, *args, opts.except(:regulate), &block
251
+ end
252
+
253
+ # add secure access for find, find_by, and belongs_to and has_one relations.
254
+ # No explicit security checks are needed here, as the data returned by these objects
255
+ # will be further processedand checked before returning. I.e. it is not possible to
256
+ # simply return `find(1)` but if you try returning `find(1).name` the permission system
257
+ # will check to see if the name attribute can be legally sent to the current acting user.
258
+
259
+ def __secure_remote_access_to_find(_self, _acting_user, *args)
260
+ find(*args)
261
+ end
262
+
263
+ def __secure_remote_access_to_find_by(_self, _acting_user, *args)
264
+ find_by(*args)
265
+ end
266
+
267
+ %i[belongs_to has_one].each do |macro|
268
+ alias_method :"pre_syncromesh_#{macro}", macro
269
+ define_method(macro) do |name, *aargs, &block|
270
+ define_method(:"__secure_remote_access_to_#{name}") do |this, _acting_user, *args|
271
+ this.send(name, *args)
272
+ end
273
+ send(:"pre_syncromesh_#{macro}", name, *aargs, &block)
274
+ end
275
+ end
276
+ end
277
+
278
+ def denied!
279
+ Hyperloop::InternalPolicy.raise_operation_access_violation(:scoped_denied, "#{self.class} regulation denies scope access. Called from #{caller_locations(1)}")
280
+ end
281
+
282
+ # call do_not_synchronize to block synchronization of a model
283
+
284
+ def self.do_not_synchronize
285
+ @do_not_synchronize = true
286
+ end
287
+
288
+ # used by the broadcast mechanism to determine if this model is to be synchronized
289
+
290
+ def self.do_not_synchronize?
291
+ @do_not_synchronize
292
+ end
293
+
294
+ def do_not_synchronize?
295
+ self.class.do_not_synchronize?
296
+ end
297
+
298
+ after_commit :synchromesh_after_create, on: [:create]
299
+ after_commit :synchromesh_after_change, on: [:update]
300
+ after_commit :synchromesh_after_destroy, on: [:destroy]
301
+
302
+ def synchromesh_after_create
303
+ return if do_not_synchronize?
304
+ ReactiveRecord::Broadcast.after_commit :create, self
305
+ end
306
+
307
+ def synchromesh_after_change
308
+ return if do_not_synchronize? || previous_changes.empty?
309
+ ReactiveRecord::Broadcast.after_commit :change, self
310
+ end
311
+
312
+ def synchromesh_after_destroy
313
+ return if do_not_synchronize?
314
+ ReactiveRecord::Broadcast.after_commit :destroy, self
315
+ end
316
+
317
+ def __hyperloop_secure_attributes(acting_user)
318
+ accessible_attributes =
319
+ Hyperloop::InternalPolicy.accessible_attributes_for(self, acting_user)
320
+ attributes.select { |attr| accessible_attributes.include? attr.to_sym }
321
+ end
322
+
323
+ # regulate built in scopes so they are accesible from the client
324
+ %i[limit offset].each do |scope|
325
+ regulate_scope(scope) {}
326
+ end
327
+ end
328
+ end
329
+
330
+ InternalMetadata.do_not_synchronize if defined? InternalMetadata
331
+ end