├── .gitignore ├── .rspec ├── .rubocop.yml ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── DOCS.md ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── codeship.database.yml ├── dciy.toml ├── gemfiles └── hyper-mesh.gemfile ├── hyper-mesh.gemspec ├── lib ├── active_record_base.rb ├── acts_as_string.rb ├── enumerable │ └── pluck.rb ├── hyper-mesh.rb ├── hyper_react │ └── input_tags.rb ├── hypermesh │ └── version.rb ├── kernel │ └── itself.rb ├── object │ └── tap.rb ├── opal │ ├── equality_patches.rb │ ├── parse_patch.rb │ └── set_patches.rb └── reactive_record │ ├── active_record │ ├── aggregations.rb │ ├── associations.rb │ ├── base.rb │ ├── class_methods.rb │ ├── error.rb │ ├── errors.rb │ ├── instance_methods.rb │ ├── public_columns_hash.rb │ └── reactive_record │ │ ├── backing_record_inspector.rb │ │ ├── base.rb │ │ ├── collection.rb │ │ ├── column_types.rb │ │ ├── dummy_value.rb │ │ ├── getters.rb │ │ ├── isomorphic_base.rb │ │ ├── lookup_tables.rb │ │ ├── operations.rb │ │ ├── scoped_collection.rb │ │ ├── setters.rb │ │ ├── unscoped_collection.rb │ │ └── while_loading.rb │ ├── active_record_error.rb │ ├── broadcast.rb │ ├── engine.rb │ ├── interval.rb │ ├── permissions.rb │ ├── pry.rb │ ├── reactive_scope.rb │ ├── scope_description.rb │ ├── serializers.rb │ └── server_data_cache.rb ├── logo.jpg ├── path_release_steps.md ├── remote.md ├── spec ├── batch1 │ ├── column_types │ │ └── column_type_spec.rb │ ├── crud_access_regulation │ │ ├── broadcast_controls_access_spec.rb │ │ └── model_policies_spec.rb │ ├── misc │ │ ├── access_like_hash_spec.rb │ │ ├── errors_spec.rb │ │ ├── validate_spec.rb │ │ └── while_loading_spec.rb │ └── policies │ │ ├── regulate_all_broadcasts_spec.rb │ │ ├── regulate_broadcast_spec.rb │ │ └── send_access_xspec.rb ├── batch2 │ ├── alias_attribute_spec.rb │ ├── default_scope_spec.rb │ ├── enum_xspec.rb │ ├── has_many_through_spec.rb │ ├── non_ar_aggregations_tbdspec.rb │ └── relationships_spec.rb ├── batch3 │ ├── auto_load_itself_spec.rb │ ├── edge_cases_spec.rb │ ├── finder_method_spec.rb │ ├── many_to_many_spec.rb │ ├── pry_rescue_xspec.rb │ └── revert_spec.rb ├── batch4 │ ├── default_value_spec.rb │ ├── scope_spec.rb │ ├── scoped_todos_spec.rb │ ├── synchromesh_spec.rb │ └── zzz_saving_during_commit_spec.rb ├── batch5 │ ├── authorization_spec.rb │ ├── get_model_spec.rb │ ├── load_from_json_xspec.rb │ ├── save_while_loading_spec.rb │ └── zzz_must_be_last_relationship_permissions_spec.rb ├── batch6 │ ├── aaa_update_scopes_spec.rb │ ├── inspect_spec.rb │ ├── on_fetch_error_spec.rb │ ├── server_method_spec.rb │ ├── speed_improvement.rb │ └── update_associations_spec.rb ├── batch7 │ ├── aaa-unit_tests │ │ ├── aggregation_experiments.rb │ │ ├── aggregations_spec.rb │ │ ├── ar_basics_tbdspec.rb │ │ ├── association_reflection_tbdspec.rb │ │ └── dummy_value_spec.rb │ └── sti_spec.rb ├── bin │ └── firebug-2.0.13-fx.xpi ├── examples │ ├── dictionary.rb │ ├── dictionary_with_client_scopes.rb │ └── random_examples.rb ├── factories │ ├── child_model.rb │ ├── comment.rb │ ├── test_models.rb │ ├── todo.rb │ └── user.rb ├── failing_tests.txt ├── play_ground.rb ├── reactive_record_factory.rb ├── spec_helper.rb ├── support │ └── component_helpers.rb ├── test_app │ ├── Gemfile │ ├── Rakefile │ ├── app │ │ ├── assets │ │ │ ├── javascripts │ │ │ │ ├── application.js │ │ │ │ └── server_rendering.js │ │ │ └── stylesheets │ │ │ │ └── application.css │ │ ├── controllers │ │ │ ├── application_controller.rb │ │ │ └── test_controller.rb │ │ ├── models │ │ │ ├── _react_public_models.rb │ │ │ └── public │ │ │ │ ├── address.rb │ │ │ │ ├── application_record.rb │ │ │ │ ├── child_model.rb │ │ │ │ ├── comment.rb │ │ │ │ ├── default_test.rb │ │ │ │ ├── test_model.rb │ │ │ │ ├── todo.rb │ │ │ │ ├── todo_item.rb │ │ │ │ ├── type_test.rb │ │ │ │ └── user.rb │ │ ├── other_classes │ │ │ └── unloaded_class.rb │ │ ├── policies │ │ │ ├── auto_loader_test_classa_policy.rb │ │ │ ├── auto_loader_test_classb_policy.rb │ │ │ ├── auto_loader_test_classc_policy.rb │ │ │ └── auto_loader_test_classd_policy.rb │ │ └── views │ │ │ ├── components.rb │ │ │ ├── components │ │ │ └── show.rb │ │ │ └── layouts │ │ │ └── application.html.erb │ ├── bin │ │ ├── bundle │ │ ├── rails │ │ ├── rake │ │ └── setup │ ├── config.ru │ ├── config │ │ ├── application.rb │ │ ├── boot.rb │ │ ├── cable.yml │ │ ├── database.yml │ │ ├── environment.rb │ │ ├── environments │ │ │ ├── development.rb │ │ │ ├── production.rb │ │ │ └── test.rb │ │ ├── initializers │ │ │ ├── assets.rb │ │ │ ├── backtrace_silencers.rb │ │ │ ├── cookies_serializer.rb │ │ │ ├── filter_parameter_logging.rb │ │ │ ├── inflections.rb │ │ │ ├── mime_types.rb │ │ │ ├── session_store.rb │ │ │ ├── synchromesh.rb │ │ │ └── wrap_parameters.rb │ │ ├── locales │ │ │ └── en.yml │ │ ├── routes.rb │ │ └── secrets.yml │ ├── db │ │ ├── development.sqlite3 │ │ ├── migrate │ │ │ └── 20160731182106_create_test_models.rb │ │ ├── schema.rb │ │ └── seeds.rb │ ├── lib │ │ └── assets │ │ │ └── .keep │ └── public │ │ ├── 404.html │ │ ├── 422.html │ │ ├── 500.html │ │ └── favicon.ico ├── test_components.rb └── vendor │ └── es5-shim.min.js ├── terminal.md └── work-in-progress-drinking.png /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /.byebug_history 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | tmp/ 10 | spec/test_app/tmp/ 11 | spec/test_app/db/test.sqlite3 12 | spec/test_app/log/test.log 13 | spec/test_app/log/development.log 14 | spec/test_app/Gemfile.lock 15 | /synchromesh-simple-poller-store 16 | /synchromesh-pusher-channel-store 17 | /examples/action-cable/rails_cache_dir/ 18 | rails_cache_dir/ 19 | react_prerendering_src.js 20 | 21 | .bundle/ 22 | log/*.log 23 | pkg/ 24 | reactive_record_test_app/db/*.sqlite3 25 | reactive_record_test_app/log/ 26 | reactive_record_test_app/tmp/ 27 | reactive_record_test_app/.sass-cache 28 | .DS_Store 29 | public/assets/* 30 | 31 | # ignore gems 32 | *.gem 33 | 34 | # ingore Idea 35 | .idea 36 | .vscode 37 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | 2 | Metrics/LineLength: 3 | Max: 100 4 | 5 | Style/MutableConstant: 6 | Enabled: false 7 | 8 | # Lint/LiteralInCondition: 9 | # Enabled: false 10 | 11 | Style/CommandLiteral: 12 | EnforcedStyle: mixed 13 | 14 | AllCops: 15 | TargetRubyVersion: 2.4.1 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | language: ruby 3 | cache: bundler 4 | rvm: 5 | - 2.4.4 6 | - 2.5.1 7 | - ruby-head 8 | services: 9 | - mysql 10 | env: 11 | - DRIVER=google-chrome TZ=Europe/Berlin 12 | before_install: 13 | - if [[ "$DRIVER" == "google-chrome" ]]; then wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -; fi 14 | - if [[ "$DRIVER" == "google-chrome" ]]; then echo "deb http://dl.google.com/linux/chrome/deb/ stable main" | sudo tee /etc/apt/sources.list.d/google-chrome.list; fi 15 | - if [[ "$DRIVER" == "google-chrome" ]]; then sudo apt-get update -qq && sudo apt-get install -qq -y google-chrome-stable; fi 16 | - sudo apt-get install -qq -y fonts-liberation 17 | - gem install bundler 18 | before_script: 19 | - cd spec/test_app 20 | - bundle install --jobs=3 --retry=3 21 | - bundle exec rails db:setup 22 | - cd ../../ 23 | - if [[ "$DRIVER" == "google-chrome" ]]; then chromedriver-update; fi 24 | - if [[ "$DRIVER" == "google-chrome" ]]; then ls -lR ~/.chromedriver-helper/; fi 25 | - if [[ "$DRIVER" == "google-chrome" ]]; then chromedriver --version; fi 26 | - if [[ "$DRIVER" == "google-chrome" ]]; then google-chrome --version; fi 27 | - if [[ "$DRIVER" == "google-chrome" ]]; then which chromedriver; fi 28 | - if [[ "$DRIVER" == "google-chrome" ]]; then which google-chrome; fi 29 | script: bundle exec rspec 30 | gemfile: 31 | - gemfiles/hyper-mesh.gemfile 32 | matrix: 33 | fast_finish: true 34 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file starting with v0.8.4. 4 | This project *tries* to adhere to [Semantic Versioning](http://semver.org/), even before v1.0. 5 | 6 | Changes are grouped as follows: 7 | - **Added** for new features. 8 | - **Changed** for changes in existing functionality. 9 | - **Deprecated** for once-stable features to be removed in upcoming releases. 10 | - **Removed** for deprecated features removed in this release. 11 | - **Fixed** for any bug fixes. 12 | - **Security** to invite users to upgrade in case of vulnerabilities. 13 | 14 | 20 | 21 | ## [0.5.3] - 2017-01-03 22 | 23 | 24 | ### Added 25 | 26 | - Fixed problem with synchronizing multiple requests to same record within one render cycle (#20) 27 | - Add *finder* methods. I.e. server side methods that return a single record using a custom query. (#12) 28 | - Allow `save` even if records are loading (#10) 29 | - `ReactiveRecord.Load` will automatically apply `.itself` to the final value of the load block (#9) 30 | 31 | 32 | ### Fixed 33 | 34 | - Can't create AR records from within Rake Tasks. (#20) 35 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 4 | 5 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion. 6 | 7 | Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. 8 | 9 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. 10 | 11 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. 12 | 13 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/) 14 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gem "opal-jquery", git: "https://github.com/opal/opal-jquery.git", branch: "master" 3 | gem 'hyperloop-config', path: '../hyperloop-config' 4 | gem 'hyper-operation', path: '../hyper-operation' 5 | gemspec 6 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Mitch VanDuyn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |

4 | 5 | 6 | 7 | 8 | 9 |

10 | 11 |

The Complete Isomorphic Ruby Framework

12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 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 |

28 | Hyper-models 29 |

30 | 31 |
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 | 52 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rspec/core/rake_task" 3 | 4 | 5 | 6 | task :spec do 7 | (1..7).each { |batch| Rake::Task["spec:batch#{batch}"].invoke rescue nil } 8 | end 9 | 10 | namespace :spec do 11 | task :prepare do 12 | sh %{bundle update} 13 | sh %{cd spec/test_app; bundle update; bundle exec rails db:setup} # may need ;bundle exec rails db:setup as well 14 | end 15 | (1..7).each do |batch| 16 | RSpec::Core::RakeTask.new(:"batch#{batch}") do |t| 17 | t.pattern = "spec/batch#{batch}/**/*_spec.rb" 18 | end 19 | end 20 | end 21 | 22 | task :default => :spec 23 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 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 15 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 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 8 | -------------------------------------------------------------------------------- /codeship.database.yml: -------------------------------------------------------------------------------- 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 19 | -------------------------------------------------------------------------------- /dciy.toml: -------------------------------------------------------------------------------- 1 | [dciy.commands] 2 | prepare = ["rake spec:prepare"] 3 | cibuild = ["bundle exec rake"] 4 | -------------------------------------------------------------------------------- /gemfiles/hyper-mesh.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | gem "opal-jquery", git: "https://github.com/opal/opal-jquery.git", branch: "master" 5 | gemspec :path => "../" 6 | -------------------------------------------------------------------------------- /hyper-mesh.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path('../lib/', __FILE__) 3 | require 'hypermesh/version' 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = 'hyper-mesh' 7 | spec.version = Hypermesh::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 = 'HyperMesh is the base for HyperModel. 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 | # } 22 | 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'] 27 | 28 | spec.add_dependency 'activerecord', '>= 4.0.0' 29 | spec.add_dependency 'hyper-component', Hypermesh::VERSION 30 | spec.add_dependency 'hyper-operation', Hypermesh::VERSION 31 | spec.add_development_dependency 'bundler' 32 | spec.add_development_dependency 'capybara' 33 | spec.add_development_dependency 'chromedriver-helper' 34 | spec.add_development_dependency 'database_cleaner' 35 | spec.add_development_dependency 'factory_bot_rails' 36 | spec.add_development_dependency 'hyper-spec', Hypermesh::VERSION 37 | spec.add_development_dependency 'hyper-trace' 38 | spec.add_development_dependency 'mysql2' 39 | spec.add_development_dependency 'opal-activesupport', '~> 0.3.1' 40 | spec.add_development_dependency 'opal-browser', '~> 0.2.0' 41 | spec.add_development_dependency 'opal-rails', '~> 0.9.4' 42 | spec.add_development_dependency 'parser' 43 | spec.add_development_dependency 'puma' 44 | spec.add_development_dependency 'pusher' 45 | spec.add_development_dependency 'pusher-fake' 46 | spec.add_development_dependency 'rails', '>= 4.0.0' 47 | spec.add_development_dependency 'rake' 48 | spec.add_development_dependency 'react-rails', '>= 2.4.0', '< 2.5.0' 49 | spec.add_development_dependency 'reactrb-rails-generator' 50 | spec.add_development_dependency 'rspec-collection_matchers' 51 | spec.add_development_dependency 'rspec-expectations' 52 | spec.add_development_dependency 'rspec-its' 53 | spec.add_development_dependency 'rspec-mocks' 54 | spec.add_development_dependency 'rspec-rails' 55 | spec.add_development_dependency 'rspec-steps', '~> 2.1.1' 56 | spec.add_development_dependency 'rspec-wait' 57 | spec.add_development_dependency 'rubocop', '~> 0.51.0' 58 | spec.add_development_dependency 'shoulda' 59 | spec.add_development_dependency 'shoulda-matchers' 60 | spec.add_development_dependency 'spring-commands-rspec' 61 | spec.add_development_dependency 'sqlite3' 62 | spec.add_development_dependency 'mini_racer', '~> 0.1.15' 63 | # https://github.com/discourse/mini_racer/issues/92 64 | spec.add_development_dependency 'libv8', '~> 6.3.0' 65 | spec.add_development_dependency 'timecop', '~> 0.8.1' 66 | spec.add_development_dependency 'unparser' 67 | spec.add_development_dependency 'pry' 68 | spec.add_development_dependency 'pry-rescue' 69 | end 70 | -------------------------------------------------------------------------------- /lib/acts_as_string.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruby-hyperloop/hyper-mesh/d29f00b16efbdd25419f07c7590505de4d3ba42a/lib/acts_as_string.rb -------------------------------------------------------------------------------- /lib/enumerable/pluck.rb: -------------------------------------------------------------------------------- 1 | # Add pluck to enumerable... its already done for us in rails 5+ 2 | module Enumerable 3 | def pluck(key) 4 | map { |element| element[key] } 5 | end 6 | end unless Enumerable.method_defined? :pluck 7 | -------------------------------------------------------------------------------- /lib/hyper-mesh.rb: -------------------------------------------------------------------------------- 1 | require 'set' 2 | require 'hyperloop-config' 3 | require 'hyper-component' 4 | if RUBY_ENGINE == 'opal' 5 | require 'hyper-operation' 6 | require 'active_support' 7 | require 'time' 8 | require 'date' 9 | require 'kernel/itself' unless Object.instance_methods.include?(:itself) 10 | require 'object/tap' 11 | require "reactive_record/active_record_error" 12 | require "reactive_record/active_record/errors" 13 | require "reactive_record/active_record/error" 14 | require "reactive_record/server_data_cache" 15 | require "reactive_record/active_record/reactive_record/while_loading" 16 | require "reactive_record/active_record/reactive_record/operations" 17 | require 'reactive_record/broadcast' 18 | require "reactive_record/active_record/reactive_record/isomorphic_base" 19 | require 'reactive_record/active_record/reactive_record/dummy_value' 20 | require 'reactive_record/active_record/reactive_record/column_types' 21 | require "reactive_record/active_record/aggregations" 22 | require "reactive_record/active_record/associations" 23 | require "reactive_record/active_record/reactive_record/backing_record_inspector" 24 | require "reactive_record/active_record/reactive_record/getters" 25 | require "reactive_record/active_record/reactive_record/setters" 26 | require "reactive_record/active_record/reactive_record/lookup_tables" 27 | require "reactive_record/active_record/reactive_record/base" 28 | require "reactive_record/active_record/reactive_record/collection" 29 | require "reactive_record/active_record/reactive_record/scoped_collection" 30 | require "reactive_record/active_record/reactive_record/unscoped_collection" 31 | require "reactive_record/interval" 32 | require_relative 'active_record_base' 33 | require_relative 'reactive_record/scope_description' 34 | require "reactive_record/active_record/class_methods" 35 | require "reactive_record/active_record/instance_methods" 36 | require "reactive_record/active_record/base" 37 | require 'hyper_react/input_tags' 38 | require_relative 'hypermesh/version' 39 | require_relative 'opal/parse_patch' 40 | require_relative 'opal/set_patches' 41 | require_relative 'opal/equality_patches' 42 | React::IsomorphicHelpers.log( 43 | "The gem 'hyper-mesh' is deprecated. Use gem 'hyper-model' instead.", :warning 44 | ) unless defined? Hyperloop::Model 45 | else 46 | require 'opal' 47 | require 'hyper-operation' 48 | require "reactive_record/permissions" 49 | require "reactive_record/server_data_cache" 50 | require "reactive_record/active_record/reactive_record/operations" 51 | require 'reactive_record/broadcast' 52 | require "reactive_record/active_record/reactive_record/isomorphic_base" 53 | require "reactive_record/active_record/public_columns_hash" 54 | require "reactive_record/serializers" 55 | require "reactive_record/pry" 56 | require_relative 'active_record_base' 57 | require 'hypermesh/version' 58 | 59 | Opal.append_path File.expand_path('../sources/', __FILE__).untaint 60 | Opal.append_path File.expand_path('../', __FILE__).untaint 61 | Opal.append_path File.expand_path('../../vendor', __FILE__).untaint 62 | end 63 | require 'enumerable/pluck' 64 | -------------------------------------------------------------------------------- /lib/hyper_react/input_tags.rb: -------------------------------------------------------------------------------- 1 | # Special handling of input tags so they ignore defaultValue (and defaultChecked) values while loading. 2 | # This is accomplished by adding a react 'key prop' that tracks whether the default value is loading. 3 | # When the default value transitions from loading to loaded the key will be updated causing react to 4 | # remount the component with the new default value. 5 | # To handle cases where defaultValue (or defaultChecked) is an expression, a proc (or lambda) can be 6 | # provided for the default value. The proc will be called, and if it raises the waiting_on_resources 7 | # flag then we know that within that expression there is a value still being loaded, and the react 8 | # key will be set accordingly. 9 | 10 | module React 11 | module Component 12 | module Tags 13 | %i[INPUT SELECT TEXTAREA].each do |component| 14 | remove_method component 15 | send(:remove_const, component) 16 | tag = component.downcase 17 | klass = Class.new(Hyperloop::Component) do 18 | collect_other_params_as :opts 19 | render do 20 | opts = props.dup # should be opts = params.opts.dup but requires next release candiate of hyper-react 21 | default_value = opts[:defaultValue] || opts[:defaultChecked] 22 | if default_value.respond_to? :call 23 | begin 24 | saved_waiting_on_resources = React::RenderingContext.waiting_on_resources 25 | React::RenderingContext.waiting_on_resources = false 26 | default_value = default_value.call 27 | opts[:key] = React::RenderingContext.waiting_on_resources 28 | if opts[:defaultValue] 29 | opts[:defaultValue] = default_value 30 | else 31 | opts[:defaultChecked] = default_value 32 | end 33 | ensure 34 | React::RenderingContext.waiting_on_resources = !!saved_waiting_on_resources 35 | end 36 | else 37 | opts[:key] = !!default_value.loading? 38 | end 39 | opts[:value] = opts[:value].to_s if opts.key? :value # this may not be needed 40 | React::RenderingContext.render(tag, opts) { children.each(&:render) } 41 | end 42 | end 43 | const_set component, klass 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/hypermesh/version.rb: -------------------------------------------------------------------------------- 1 | module Hypermesh 2 | VERSION = '1.0.0.lap28' 3 | end 4 | -------------------------------------------------------------------------------- /lib/kernel/itself.rb: -------------------------------------------------------------------------------- 1 | module Kernel 2 | def itself 3 | self 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/object/tap.rb: -------------------------------------------------------------------------------- 1 | class Object 2 | def tap 3 | val = `self.$$is_boolean` ? self==true : self 4 | yield val 5 | val 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/opal/equality_patches.rb: -------------------------------------------------------------------------------- 1 | class Date 2 | alias broken_equals == 3 | def ==(other) 4 | return false unless other.is_a?(Date) 5 | broken_equals(other) 6 | end 7 | end 8 | 9 | class Time 10 | alias broken_equals == 11 | def ==(other) 12 | return false unless other.is_a?(Time) 13 | broken_equals(other) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/opal/parse_patch.rb: -------------------------------------------------------------------------------- 1 | begin 2 | JSON.parse("test") 3 | rescue Exception => e 4 | JSON.class_eval do 5 | class << self 6 | alias old_parse parse 7 | end 8 | def self.parse(*args, &block) 9 | old_parse(*args, &block) 10 | rescue Exception => e 11 | raise StandardError.new e.message 12 | end 13 | end unless e.is_a? StandardError 14 | end 15 | -------------------------------------------------------------------------------- /lib/opal/set_patches.rb: -------------------------------------------------------------------------------- 1 | class Set 2 | def &(enum) 3 | n = self.class.new 4 | enum.each { |o| n.add(o) if include?(o) } 5 | n 6 | end 7 | alias intersection & 8 | end unless Set.method_defined? :intersection 9 | -------------------------------------------------------------------------------- /lib/reactive_record/active_record/aggregations.rb: -------------------------------------------------------------------------------- 1 | module ActiveRecord 2 | 3 | class Base 4 | 5 | def self.reflect_on_all_aggregations 6 | base_class.instance_eval { @aggregations ||= [] } 7 | end 8 | 9 | def self.reflect_on_aggregation(attribute) 10 | reflect_on_all_aggregations.detect { |aggregation| aggregation.attribute == attribute } 11 | end 12 | 13 | end 14 | 15 | module Aggregations 16 | 17 | class AggregationReflection 18 | 19 | attr_reader :klass_name 20 | attr_reader :attribute 21 | attr_reader :mapped_attributes 22 | attr_reader :constructor 23 | 24 | def construct(args) 25 | 26 | end 27 | 28 | def initialize(owner_class, macro, name, options = {}) 29 | owner_class.reflect_on_all_aggregations << self 30 | @owner_class = owner_class 31 | @constructor = options[:constructor] || :new 32 | @klass_name = options[:class_name] || name.camelize 33 | @attribute = name 34 | if options[:mapping].respond_to? :collect 35 | @mapped_attributes = options[:mapping].collect(&:last) 36 | else 37 | ReactiveRecord::Base.log("improper aggregate definition #{@owner_class}, :#{name}, class_name: #{@klass_name} - missing mapping", :error) 38 | @mapped_attributes = [] 39 | end 40 | end 41 | 42 | def klass 43 | @klass ||= Object.const_get(@klass_name) 44 | end 45 | 46 | def serialize(object) 47 | if object.nil? 48 | object # return dummy value if that is what we got 49 | else 50 | @mapped_attributes.collect { |attr| object.send(attr) } 51 | end 52 | end 53 | 54 | def deserialize(array) 55 | if array.nil? 56 | array # return dummy value if that is what we got 57 | elsif @constructor.respond_to?(:call) 58 | @constructor.call(*array) 59 | else 60 | klass.send(@constructor, *array) 61 | end 62 | end 63 | 64 | end 65 | 66 | end 67 | 68 | 69 | end 70 | -------------------------------------------------------------------------------- /lib/reactive_record/active_record/associations.rb: -------------------------------------------------------------------------------- 1 | module ActiveRecord 2 | 3 | class Base 4 | 5 | def self.reflect_on_all_associations 6 | base_class.instance_eval { @associations ||= superclass.instance_eval { (@associations && @associations.dup) || [] } } 7 | end 8 | 9 | def self.reflect_on_association(attr) 10 | reflection_finder { |assoc| assoc.attribute == attr } 11 | end 12 | 13 | def self.reflect_on_association_by_foreign_key(key) 14 | reflection_finder { |assoc| assoc.association_foreign_key == key } 15 | end 16 | 17 | def self.reflection_finder(&block) 18 | found = reflect_on_all_associations.detect do |assoc| 19 | assoc.owner_class == self && yield(assoc) 20 | end 21 | if found 22 | found 23 | elsif superclass == Base 24 | nil 25 | else 26 | superclass.reflection_finder(&block) 27 | end 28 | end 29 | 30 | end 31 | 32 | module Associations 33 | 34 | class AssociationReflection 35 | 36 | attr_reader :association_foreign_key 37 | attr_reader :attribute 38 | attr_reader :macro 39 | attr_reader :owner_class 40 | attr_reader :source 41 | 42 | def initialize(owner_class, macro, name, options = {}) 43 | owner_class.reflect_on_all_associations << self 44 | @owner_class = owner_class 45 | @macro = macro 46 | @options = options 47 | @klass_name = options[:class_name] || (collection? && name.camelize.sub(/s$/, '')) || name.camelize 48 | @association_foreign_key = options[:foreign_key] || (macro == :belongs_to && "#{name}_id") || "#{@owner_class.name.underscore}_id" 49 | @source = options[:source] || @klass_name.underscore if options[:through] 50 | @attribute = name 51 | end 52 | 53 | def through_association 54 | return unless @options[:through] 55 | @through_association ||= @owner_class.reflect_on_all_associations.detect do |association| 56 | association.attribute == @options[:through] 57 | end 58 | raise "Through association #{@options[:through]} for "\ 59 | "#{@owner_class}.#{attribute} not found." unless @through_association 60 | @through_association 61 | end 62 | 63 | alias through_association? through_association 64 | 65 | def through_associations 66 | # find all associations that use the inverse association as the through association 67 | # that is find all associations that are using this association in a through relationship 68 | @through_associations ||= klass.reflect_on_all_associations.select do |assoc| 69 | assoc.through_association && assoc.inverse == self 70 | end 71 | end 72 | 73 | def source_associations 74 | # find all associations that use this association as the source 75 | # that is final all associations that are using this association as the source in a 76 | # through relationship 77 | @source_associations ||= owner_class.reflect_on_all_associations.collect do |sibling| 78 | sibling.klass.reflect_on_all_associations.select do |assoc| 79 | assoc.source == attribute 80 | end 81 | end.flatten 82 | end 83 | 84 | def inverse 85 | @inverse ||= 86 | through_association ? through_association.inverse : find_inverse 87 | end 88 | 89 | def inverse_of 90 | @inverse_of ||= inverse.attribute 91 | end 92 | 93 | def find_inverse 94 | klass.reflect_on_all_associations.each do |association| 95 | next if association.association_foreign_key != @association_foreign_key 96 | next if association.klass != @owner_class 97 | next if association.attribute == attribute 98 | return association if klass == association.owner_class 99 | end 100 | raise "Association #{@owner_class}.#{attribute} "\ 101 | "(foreign_key: #{@association_foreign_key}) "\ 102 | "has no inverse in #{@klass_name}" 103 | end 104 | 105 | def klass 106 | @klass ||= Object.const_get(@klass_name) 107 | end 108 | 109 | def collection? 110 | [:has_many].include? @macro 111 | end 112 | 113 | end 114 | 115 | end 116 | 117 | 118 | end 119 | -------------------------------------------------------------------------------- /lib/reactive_record/active_record/base.rb: -------------------------------------------------------------------------------- 1 | module ActiveRecord 2 | # client side ActiveRecord::Base proxy 3 | class Base 4 | include InstanceMethods 5 | extend ClassMethods 6 | 7 | scope :limit, ->() {} 8 | scope :offset, ->() {} 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/reactive_record/active_record/error.rb: -------------------------------------------------------------------------------- 1 | module ActiveModel 2 | class Error 3 | 4 | attr_reader :messages 5 | 6 | def initialize(initial_msgs = {}) 7 | @messages = Hash.new { |h, k| h[k] = [] } 8 | initial_msgs.each { |attr, msgs| @messages[attr] = msgs.uniq } 9 | end 10 | 11 | def [](attribute) 12 | messages[attribute] 13 | end 14 | 15 | def delete(attribute) 16 | messages.delete(attribute) 17 | end 18 | 19 | def empty? 20 | messages.empty? 21 | end 22 | 23 | def clear 24 | @messages.clear 25 | end 26 | 27 | def add(attribute, message = :invalid, _options = {}) 28 | @messages[attribute] << message unless @messages[attribute].include? message 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/reactive_record/active_record/instance_methods.rb: -------------------------------------------------------------------------------- 1 | module ActiveRecord 2 | module InstanceMethods 3 | def inspect 4 | "<#{model_name}:#{ReactiveRecord::Operations::Base::FORMAT % to_key} "\ 5 | "(#{ReactiveRecord::Operations::Base::FORMAT % object_id}) "\ 6 | "#{backing_record.inspection_details} >" 7 | end 8 | 9 | attr_reader :backing_record 10 | 11 | def attributes 12 | @backing_record.attributes 13 | end 14 | 15 | def changed_attributes 16 | backing_record.changed_attributes_and_values 17 | end 18 | 19 | def changes 20 | backing_record.changes 21 | end 22 | 23 | def initialize(hash = {}) 24 | if hash.is_a? ReactiveRecord::Base 25 | @backing_record = hash 26 | else 27 | # standard active_record new -> creates a new instance, primary key is ignored if present 28 | # we have to build the backing record first then initialize it so associations work correctly 29 | @backing_record = ReactiveRecord::Base.new(self.class, {}, self) 30 | if self.class.inheritance_column && !hash.key?(self.class.inheritance_column) 31 | hash[self.class.inheritance_column] = self.class.name 32 | end 33 | @backing_record.instance_eval do 34 | h = {} 35 | hash.each do |a, v| 36 | a = model._dealias_attribute(a) 37 | h[a] = convert(a, v).itself 38 | end 39 | self.class.load_data do 40 | h.each do |attribute, value| 41 | next if attribute == primary_key 42 | @ar_instance[attribute] = value 43 | changed_attributes << attribute 44 | end 45 | end 46 | end 47 | end 48 | end 49 | 50 | def primary_key 51 | self.class.primary_key 52 | end 53 | 54 | def id 55 | @backing_record.get_primary_key_value 56 | end 57 | 58 | def id=(value) 59 | @backing_record.id = value 60 | end 61 | 62 | def id? 63 | id.present? 64 | end 65 | 66 | def model_name 67 | # in reality should return ActiveModel::Name object, blah blah 68 | self.class.model_name 69 | end 70 | 71 | def revert 72 | @backing_record.revert 73 | end 74 | 75 | def changed? 76 | @backing_record.changed? 77 | end 78 | 79 | def dup 80 | self.class.new(self.attributes) 81 | end 82 | 83 | def ==(ar_instance) 84 | @backing_record == ar_instance.instance_eval { @backing_record } 85 | end 86 | 87 | def [](attr) 88 | send(attr) 89 | end 90 | 91 | def []=(attr, val) 92 | send("#{attr}=", val) 93 | end 94 | 95 | def itself 96 | # this is useful when you just want to get a handle on record instance 97 | # in the ReactiveRecord.load method 98 | id # force load of id... 99 | # if self.class.columns_hash.keys.include?(self.class.inheritance_column) && 100 | # (klass = self[self.class.inheritance_column]).loaded? 101 | # Object.const_get(klass).new(attributes) 102 | # else 103 | self 104 | # end 105 | end 106 | 107 | def load(*attributes, &block) 108 | first_time = true 109 | ReactiveRecord.load do 110 | results = attributes.collect { |attr| send("#{attr}#{'!' if first_time}") } 111 | results = yield(*results) if block 112 | first_time = false 113 | block.nil? && results.count == 1 ? results.first : results 114 | end 115 | end 116 | 117 | def save(opts = {}, &block) 118 | @backing_record.save_or_validate(true, opts.has_key?(:validate) ? opts[:validate] : true, opts[:force], &block) 119 | end 120 | 121 | def validate(opts = {}, &block) 122 | @backing_record.save_or_validate(false, true, opts[:force]).then do 123 | if block 124 | yield @backing_record.ar_instance 125 | else 126 | @backing_record.ar_instance 127 | end 128 | end 129 | end 130 | 131 | def valid? 132 | errors.reactive_empty? 133 | end 134 | 135 | def saving? 136 | @backing_record.saving? 137 | end 138 | 139 | def destroy(&block) 140 | @backing_record.destroy(&block) 141 | end 142 | 143 | def destroyed? 144 | @backing_record.destroyed 145 | end 146 | 147 | def new? 148 | @backing_record.new? 149 | end 150 | 151 | def errors 152 | React::State.get_state(@backing_record, @backing_record) 153 | @backing_record.errors 154 | end 155 | 156 | def to_key 157 | @backing_record.object_id 158 | end 159 | 160 | def update_attribute(attr, value, &block) 161 | send("#{attr}=", value) 162 | save(validate: false, &block) 163 | end 164 | 165 | def update(attrs = {}, &block) 166 | attrs.each { |attr, value| send("#{attr}=", value) } 167 | save(&block) 168 | end 169 | 170 | def <=>(other) 171 | id.to_i <=> other.id.to_i 172 | end 173 | 174 | def becomes(klass) 175 | klass._new_without_sti_type_cast(backing_record) 176 | end 177 | 178 | def becomes!(klass) 179 | self[self.class.inheritance_column] = klass.name 180 | becomes(klass) 181 | end 182 | 183 | def cast_to_current_sti_type 184 | @backing_record.set_ar_instance! 185 | end 186 | end 187 | end 188 | -------------------------------------------------------------------------------- /lib/reactive_record/active_record/public_columns_hash.rb: -------------------------------------------------------------------------------- 1 | module Hyperloop 2 | define_setting :public_model_directories, [File.join('app','hyperloop','models'), File.join('app','models','public')] 3 | end 4 | 5 | module ActiveRecord 6 | # adds method to get the HyperMesh public column types 7 | # this works because the public folder is currently required to be eager loaded. 8 | class Base 9 | def self.public_columns_hash 10 | return @public_columns_hash if @public_columns_hash && Rails.env.production? 11 | files = [] 12 | Hyperloop.public_model_directories.each do |dir| 13 | dir_length = Rails.root.join(dir).to_s.length + 1 14 | Dir.glob(Rails.root.join(dir, '**', '*.rb')).each do |file| 15 | require_dependency(file) # still the file is loaded to make sure for development and test env 16 | files << file[dir_length..-4] 17 | end 18 | end 19 | @public_columns_hash = {} 20 | # descendants only works for already loaded models! 21 | descendants.each do |model| 22 | if files.include?(model.name.underscore) && model.name.underscore != 'application_record' 23 | @public_columns_hash[model.name] = model.columns_hash rescue nil # why rescue? 24 | end 25 | # begin 26 | # @public_columns_hash[model.name] = model.columns_hash if model.table_name 27 | # rescue Exception => e 28 | # binding.pry 29 | # @public_columns_hash = nil 30 | # raise $!, "Could not read 'columns_hash' for #{model}: #{$!}", $!.backtrace 31 | # end if files.include?(model.name.underscore) && model.name.underscore != 'application_record' 32 | end 33 | @public_columns_hash 34 | end 35 | 36 | def self.public_columns_hash_as_json 37 | return @public_columns_hash_json if @public_columns_hash_json && Rails.env.production? 38 | pch = public_columns_hash 39 | return @public_columns_hash_json if @prev_public_columns_hash == pch 40 | @prev_public_columns_hash = pch 41 | @public_columns_hash_json = pch.to_json 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/reactive_record/active_record/reactive_record/backing_record_inspector.rb: -------------------------------------------------------------------------------- 1 | module ReactiveRecord 2 | # inspection_details is used by client side ActiveRecord::Base 3 | # runs down the possible states of a backing record and returns 4 | # the appropriate string. The order of execution is important! 5 | module BackingRecordInspector 6 | def inspection_details 7 | return error_details unless errors.empty? 8 | return new_details if new? 9 | return destroyed_details if destroyed 10 | return loading_details unless @attributes.key? primary_key 11 | return dirty_details unless changed_attributes.empty? 12 | "[loaded id: #{id}]" 13 | end 14 | 15 | def error_details 16 | id_str = "id: #{id} " unless new? 17 | "[errors #{id_str}#{errors.messages}]" 18 | end 19 | 20 | def new_details 21 | "[new #{attributes.select { |attr| column_type(attr) }}]" 22 | end 23 | 24 | def destroyed_details 25 | "[destroyed id: #{id}]" 26 | end 27 | 28 | def loading_details 29 | "[loading #{vector}]" 30 | end 31 | 32 | def dirty_details 33 | "[changed id: #{id} #{changes}]" 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/reactive_record/active_record/reactive_record/column_types.rb: -------------------------------------------------------------------------------- 1 | module ReactiveRecord 2 | # ActiveRecord column access and conversion helpers 3 | class Base 4 | def columns_hash 5 | model.columns_hash 6 | end 7 | 8 | def self.column_type(column_hash) 9 | column_hash && column_hash[:sql_type_metadata] && column_hash[:sql_type_metadata][:type] 10 | end 11 | 12 | def column_type(attr) 13 | Base.column_type(columns_hash[attr]) 14 | end 15 | 16 | def convert_datetime(val) 17 | if val.is_a?(Numeric) 18 | Time.at(val) 19 | elsif val.is_a?(Time) 20 | val 21 | else 22 | Time.parse(val) 23 | end 24 | end 25 | 26 | alias convert_time convert_datetime 27 | alias convert_timestamp convert_datetime 28 | 29 | def convert_date(val) 30 | if val.is_a?(Time) 31 | Date.parse(val.strftime('%d/%m/%Y')) 32 | elsif val.is_a?(Date) 33 | val 34 | else 35 | Date.parse(val) 36 | end 37 | end 38 | 39 | def convert_boolean(val) 40 | !['false', false, nil, 0].include?(val) 41 | end 42 | 43 | def convert_integer(val) 44 | Integer(`parseInt(#{val})`) 45 | end 46 | 47 | alias convert_bigint convert_integer 48 | 49 | def convert_float(val) 50 | Float(val) 51 | end 52 | 53 | alias convert_decimal convert_float 54 | 55 | def convert_text(val) 56 | val.to_s 57 | end 58 | 59 | alias convert_string convert_text 60 | 61 | def self.serialized? 62 | @serialized_attrs ||= Hash.new { |h, k| h[k] = Hash.new } 63 | end 64 | 65 | def convert(attr, val) 66 | column_type = column_type(attr) 67 | return val if self.class.serialized?[model][attr] || 68 | !column_type || val.loading? || 69 | (!val && column_type != :boolean) 70 | conversion_method = "convert_#{column_type}" 71 | return send(conversion_method, val) if respond_to? conversion_method 72 | val 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /lib/reactive_record/active_record/reactive_record/lookup_tables.rb: -------------------------------------------------------------------------------- 1 | module ReactiveRecord 2 | module LookupTables 3 | def initialize_lookup_tables 4 | @records = Hash.new { |hash, key| hash[key] = [] } 5 | @records_by_id = `{}` 6 | @records_by_vector = `{}` 7 | @records_by_object_id = `{}` 8 | @class_scopes = Hash.new { |hash, key| hash[key] = {} } 9 | @waiting_for_save = Hash.new { |hash, key| hash[key] = [] } 10 | end 11 | 12 | def class_scopes(model) 13 | @class_scopes[model.base_class] 14 | end 15 | 16 | def waiting_for_save(model) 17 | @waiting_for_save[model] 18 | end 19 | 20 | def wait_for_save(model, &block) 21 | @waiting_for_save[model] << block 22 | end 23 | 24 | def clear_waiting_for_save(model) 25 | @waiting_for_save[model] = [] 26 | end 27 | 28 | def lookup_by_object_id(object_id) 29 | `#{@records_by_object_id}[#{object_id}]`.ar_instance 30 | end 31 | 32 | def set_object_id_lookup(record) 33 | `#{@records_by_object_id}[#{record.object_id}] = #{record}` 34 | end 35 | 36 | def lookup_by_id(*args) # model and id 37 | `#{@records_by_id}[#{args}]` || nil 38 | end 39 | 40 | def set_id_lookup(record) 41 | `#{@records_by_id}[#{[record.model, record.id]}] = #{record}` 42 | end 43 | 44 | def lookup_by_vector(vector) 45 | `#{@records_by_vector}[#{vector}]` || nil 46 | end 47 | 48 | def set_vector_lookup(record, vector) 49 | record.vector = vector 50 | `delete #{@records_by_vector}[#{record.vector}]` 51 | `#{@records_by_vector}[#{vector}] = record` 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/reactive_record/active_record/reactive_record/operations.rb: -------------------------------------------------------------------------------- 1 | module ReactiveRecord 2 | # redefine if you want to process errors (i.e. logging, rollbar, etc) 3 | def self.on_fetch_error(e, params); end 4 | 5 | # associations: {parent_id: record.object_id, attribute: attribute, child_id: assoc_record.object_id} 6 | # models: {id: record.object_id, model: record.model.model_name, attributes: changed_attributes} 7 | 8 | module Operations 9 | # to make debug easier we convert all the object_id strings to be hex representation 10 | class Base < Hyperloop::ControllerOp 11 | param :acting_user, nils: true 12 | 13 | FORMAT = '0x%x' 14 | 15 | def self.serialize_params(hash) 16 | hash['associations'].each do |assoc| 17 | assoc['parent_id'] = FORMAT % assoc['parent_id'] 18 | assoc['child_id'] = FORMAT % assoc['child_id'] 19 | end if hash['associations'] 20 | hash['models'].each do |assoc| 21 | assoc['id'] = FORMAT % assoc[:id] 22 | end if hash['models'] 23 | hash 24 | end 25 | 26 | def self.deserialize_params(hash) 27 | hash['associations'].each do |assoc| 28 | assoc['parent_id'] = assoc['parent_id'].to_i(16) 29 | assoc['child_id'] = assoc['child_id'].to_i(16) 30 | end if hash['associations'] 31 | hash['models'].each do |assoc| 32 | assoc['id'] = assoc['id'].to_i(16) 33 | end if hash['models'] 34 | hash 35 | end 36 | 37 | def self.serialize_response(response) 38 | response[:saved_models].each do |saved_model| 39 | saved_model[0] = FORMAT % saved_model[0] 40 | end if response.is_a?(Hash) && response[:saved_models] 41 | response 42 | end 43 | 44 | def self.deserialize_response(response) 45 | response[:saved_models].each do |saved_model| 46 | saved_model[0] = saved_model[0].to_i(16) 47 | end if response.is_a?(Hash) && response[:saved_models] 48 | response 49 | end 50 | end 51 | # fetch queued up records from the server 52 | # subclass of ControllerOp so we can pass the controller 53 | # along to on_error 54 | class Fetch < Base 55 | param :acting_user, nils: true 56 | param models: [] 57 | param associations: [] 58 | param :pending_fetches 59 | step do 60 | ReactiveRecord::ServerDataCache[ 61 | params.models.map(&:with_indifferent_access), 62 | params.associations.map(&:with_indifferent_access), 63 | params.pending_fetches, 64 | params.acting_user 65 | ] 66 | end 67 | failed do |e| 68 | # AccessViolations are already sent to on_error 69 | Hyperloop.on_error(e, :fetch_error, params.to_h) unless e.is_a? Hyperloop::AccessViolation 70 | raise e 71 | end 72 | end 73 | 74 | class Save < Base 75 | param :acting_user, nils: true 76 | param models: [] 77 | param associations: [] 78 | param :save, type: :boolean 79 | param :validate, type: :boolean 80 | 81 | step do 82 | ReactiveRecord::Base.save_records( 83 | params.models.map(&:with_indifferent_access), 84 | params.associations.map(&:with_indifferent_access), 85 | params.acting_user, 86 | params.validate, 87 | params.save 88 | ) 89 | end 90 | end 91 | 92 | class Destroy < Base 93 | param :acting_user, nils: true 94 | param :model 95 | param :id 96 | param :vector 97 | step do 98 | ReactiveRecord::Base.destroy_record( 99 | params.model, 100 | params.id, 101 | params.vector, 102 | params.acting_user 103 | ) 104 | end 105 | end 106 | end 107 | end 108 | -------------------------------------------------------------------------------- /lib/reactive_record/active_record/reactive_record/scoped_collection.rb: -------------------------------------------------------------------------------- 1 | module ReactiveRecord 2 | # The base collection class works with relationships 3 | # method overrides for scoped collections 4 | module ScopedCollection 5 | [:filter?, :collector?, :joins_with?, :related_records_for].each do |method| 6 | define_method(method) { |*args| @scope_description.send method, *args } 7 | end 8 | 9 | def set_pre_sync_related_records(related_records, _record = nil) 10 | @pre_sync_related_records = nil 11 | ReactiveRecord::Base.catch_db_requests do 12 | @pre_sync_related_records = filter_records(related_records) 13 | live_scopes.each do |scope| 14 | scope.set_pre_sync_related_records(@pre_sync_related_records) 15 | end 16 | end if filter? 17 | end 18 | 19 | def sync_scopes(related_records, record, filtering = true) 20 | filtering = 21 | @pre_sync_related_records && filtering && 22 | ReactiveRecord::Base.catch_db_requests do 23 | related_records = update_collection(related_records) 24 | end 25 | reload_from_db if !filtering && joins_with?(record) 26 | live_scopes.each { |scope| scope.sync_scopes(related_records, record, filtering) } 27 | ensure 28 | @pre_sync_related_records = nil 29 | end 30 | 31 | def update_collection(related_records) 32 | if collector? 33 | update_collector_scope(related_records) 34 | else 35 | related_records = filter_records(related_records) 36 | update_filter_scope(@pre_sync_related_records, related_records) 37 | end 38 | end 39 | 40 | def update_collector_scope(related_records) 41 | current = Set.new([*@collection]) 42 | (related_records - @pre_sync_related_records).each { |r| current << r } 43 | (@pre_sync_related_records - related_records).each { |r| current.delete(r) } 44 | replace(filter_records(current)) 45 | Set.new([*@collection]) 46 | end 47 | 48 | def update_filter_scope(before, after) 49 | if (collection || !@count.nil?) && before != after 50 | if collection 51 | (after - before).each { |r| push r } 52 | (before - after).each { |r| delete r } 53 | else 54 | @count += (after - before).count 55 | @count -= (before - after).count 56 | notify_of_change self # TODO: remove self .... and retest 57 | end 58 | end 59 | after 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /lib/reactive_record/active_record/reactive_record/unscoped_collection.rb: -------------------------------------------------------------------------------- 1 | module ReactiveRecord 2 | # The base collection class works with relationships 3 | # method overrides for the unscoped collection 4 | module UnscopedCollection 5 | def set_pre_sync_related_records(related_records, _record = nil) 6 | @pre_sync_related_records = related_records 7 | live_scopes.each { |scope| scope.set_pre_sync_related_records(@pre_sync_related_records) } 8 | end 9 | 10 | def sync_scopes(related_records, record, filtering = true) 11 | live_scopes.each { |scope| scope.sync_scopes(related_records, record, filtering) } 12 | ensure 13 | @pre_sync_related_records = nil 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/reactive_record/active_record_error.rb: -------------------------------------------------------------------------------- 1 | module ActiveRecord 2 | class ActiveRecordError < StandardError 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /lib/reactive_record/engine.rb: -------------------------------------------------------------------------------- 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 12 | -------------------------------------------------------------------------------- /lib/reactive_record/interval.rb: -------------------------------------------------------------------------------- 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 -------------------------------------------------------------------------------- /lib/reactive_record/permissions.rb: -------------------------------------------------------------------------------- 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 118 | -------------------------------------------------------------------------------- /lib/reactive_record/pry.rb: -------------------------------------------------------------------------------- 1 | module ReactiveRecord 2 | 3 | module Pry 4 | 5 | def self.rescued(e) 6 | if defined?(PryRescue) && e.instance_variable_defined?(:@rescue_bindings) && !e.is_a?(Hyperloop::AccessViolation) 7 | ::Pry::rescued(e) 8 | end 9 | end 10 | 11 | end 12 | 13 | end 14 | -------------------------------------------------------------------------------- /lib/reactive_record/reactive_scope.rb: -------------------------------------------------------------------------------- 1 | # class ActiveRecord::Base 2 | # 3 | # def self.to_sync(scope_name, opts={}, &block) 4 | # watch_list = if opts[:watch] 5 | # [*opts.delete[:watch]] 6 | # else 7 | # [self] 8 | # end 9 | # if RUBY_ENGINE=='opal' 10 | # watch_list.each do |klass_to_watch| 11 | # ReactiveRecord::Base.sync_blocks[klass_to_watch][self][scope_name] << block 12 | # end 13 | # else 14 | # # this is where we put server side watchers in place to sync all clients! 15 | # end 16 | # end 17 | # 18 | # end 19 | -------------------------------------------------------------------------------- /lib/reactive_record/scope_description.rb: -------------------------------------------------------------------------------- 1 | module ReactiveRecord 2 | # Keeps track of the details (client side) of a scope. 3 | # The main point is to provide knowledge of what models 4 | # the scope is joined with, and the client side 5 | # filter proc 6 | class ScopeDescription 7 | def initialize(model, name, opts) 8 | sself = self 9 | @filter_proc = filter_proc(opts) 10 | @name = name 11 | model.singleton_class.send(:define_method, "_#{@name}_synchromesh_scope_description_") do 12 | sself 13 | end 14 | @model = model 15 | build_joins opts[:joins] 16 | end 17 | 18 | attr_reader :name 19 | 20 | def self.find(target_model, name) 21 | name = name.gsub(/!$/, '') 22 | target_model.send "_#{name}_synchromesh_scope_description_" 23 | rescue 24 | nil 25 | end 26 | 27 | def filter? 28 | @filter_proc.respond_to?(:call) 29 | end 30 | 31 | def collector? 32 | @is_collector 33 | end 34 | 35 | def joins_with?(record) 36 | @joins.detect do |klass, vector| 37 | # added klass < record.class to handle STI case... should check to see if this could ever 38 | # cause a problem. Probably not a problem. 39 | next unless vector.any? 40 | (klass == :all || record.class == klass || record.class < klass || klass < record.class) 41 | end 42 | end 43 | 44 | def get_joins(klass) 45 | joins = @joins[klass] if @joins.key? klass 46 | joins ||= @joins[klass.base_class] if @joins.key?(klass.base_class) 47 | joins || @joins[:all] 48 | end 49 | 50 | def related_records_for(record) 51 | ReactiveRecord::Base.catch_db_requests([]) do 52 | get_joins(record.class).collect do |vector| 53 | crawl(record, *vector) 54 | end.flatten.compact 55 | end 56 | end 57 | 58 | def filter_records(related_records, args) 59 | if collector? 60 | Set.new(related_records.to_a.instance_exec(*args, &@filter_proc)) 61 | else 62 | Set.new(related_records.select { |r| r.instance_exec(*args, &@filter_proc) }) 63 | end 64 | end 65 | 66 | # private methods 67 | 68 | def filter_proc(opts) 69 | return true unless opts.key?(:client) || opts.key?(:select) 70 | client_opt = opts[:client] || opts[:select] 71 | @is_collector = opts.key?(:select) 72 | return client_opt if !client_opt || client_opt.respond_to?(:call) 73 | raise 'Scope option :client or :select must be a proc, false, or nil' 74 | end 75 | 76 | def build_joins(joins_list) 77 | if !@filter_proc || joins_list == [] 78 | @joins = { all: [] } 79 | elsif joins_list.nil? 80 | klass = @model < ActiveRecord::Base ? @model.base_class : @model 81 | @joins = { klass => [[]], all: [] } 82 | elsif joins_list == :all 83 | @joins = { all: [[]] } 84 | else 85 | joins_list = [joins_list] unless joins_list.is_a? Array 86 | map_joins_path joins_list 87 | end 88 | end 89 | 90 | def map_joins_path(paths) 91 | @joins = Hash.new { |h, k| h[k] = Array.new }.merge(@model => [[]]) 92 | paths.each do |path| 93 | vector = [] 94 | path.split('.').inject(@model) do |model, attribute| 95 | association = model.reflect_on_association(attribute) 96 | raise build_error(path, model, attribute) unless association 97 | vector = [association.inverse_of, *vector] 98 | @joins[association.klass] << vector 99 | association.klass 100 | end 101 | end 102 | end 103 | 104 | def build_error(path, model, attribute) 105 | "Could not find joins association '#{model.name}.#{attribute}' "\ 106 | "for '#{path}' while processing scope #{@model.name}.#{@name}." 107 | end 108 | 109 | def crawl(item, method = nil, *vector) 110 | if !method && item.is_a?(Collection) 111 | item.all 112 | elsif !method 113 | item 114 | elsif item.respond_to? :collect 115 | item.collect { |record| crawl(record.send(method), *vector) } 116 | else 117 | crawl(item.send(method), *vector) 118 | end 119 | end 120 | end 121 | end 122 | -------------------------------------------------------------------------------- /lib/reactive_record/serializers.rb: -------------------------------------------------------------------------------- 1 | ActiveRecord::Base.send(:define_method, :react_serializer) do 2 | serializable_hash.merge(ReactiveRecord::Base.get_type_hash(self)) 3 | end 4 | 5 | ActiveRecord::Relation.send(:define_method, :react_serializer) do 6 | all.to_a.react_serializer 7 | end 8 | -------------------------------------------------------------------------------- /logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruby-hyperloop/hyper-mesh/d29f00b16efbdd25419f07c7590505de4d3ba42a/logo.jpg -------------------------------------------------------------------------------- /path_release_steps.md: -------------------------------------------------------------------------------- 1 | 2 | For example assuming you are releasing fix to 0.8.18 3 | 4 | 1. Checkout 0-8-stable 5 | 2. Update tests, fix the bug and commit the changes. 6 | 3. Build & Release to RubyGems (Remember the version in version.rb should already be 0.8.19) 7 | 4. Create a tag 'v0.8.19' pointing to that commit. 8 | 5. Bump the version in 0-8-stable to 0.8.20 so it will be ready for the next patch level release. 9 | 6. Commit the version bump, and do a `git push --tags` so the new tag goes up 10 | -------------------------------------------------------------------------------- /remote.md: -------------------------------------------------------------------------------- 1 | ```ruby 2 | class Model < ActiveRecord::Base 3 | 4 | def <=>(other) 5 | self.text.downcase <=> other.text.downcase 6 | end 7 | 8 | scope :sorted, -> { order('lower(text) ASC')}, client: -> { sort } 9 | 10 | scope :proper_nouns, -> { where('lower(text) <> text ')}, client: -> (r) { r.text.downcase != r.text } 11 | end 12 | ``` 13 | 14 | if the remote proc takes a param it is given each record to check (i.e. its wrapped in a select) 15 | 16 | if there is no param the proc is executed in context of a collection which it can return a modified version of. 17 | 18 | How: 19 | 20 | three kinds of scopes: 21 | 22 | Base collections (i.e. has_many, all, and unscoped) 23 | 24 | outer scopes (i.e. directly following a base collection Model.sorted === Model.all.sorted parent.children.proper_nouns ) 25 | 26 | inner scopes (i.e. other scopes that follow outer scopes) 27 | 28 | 29 | First we need to update the base collections based on the changed models. By definition everything we need to know to update the base scopes is available in the changed record. 30 | 31 | + has_many collections get updated by the load_from_json method 32 | + all, and unscoped get updated by a new method on the class: update_base_scopes (maybe not needed? ) 33 | 34 | Then we need to find all the outer scopes in the world. 35 | 36 | To do this whenever an scope is applied, if it is being applied to a base collection (or to a Model itself which is equivilent to applying it Model.all) then it is added to a list, along with its parent base scope. 37 | 38 | We can then iterate through this list. 39 | 40 | During the iteration we do this: 41 | 42 | ```ruby 43 | def update_collections(updated_record, base_collection, local_updates) 44 | if this scope is joined with the updated_record 45 | if local_updates && updated_record.class = self.class && my_client_proc 46 | local_updates << self 47 | else 48 | update_from_server 49 | client_procs = nil 50 | end 51 | end 52 | if children_collections 53 | children_collections.each { |child| child.update_collections(udpated_record, base_collection, client_procs)} 54 | elsif client_procs 55 | client_procs.inject(base_collection) do |collection, scope| 56 | dup collection into scope.all 57 | either call scope.proc with each record in a select 58 | or just call the proc in the context of scope 59 | end 60 | end 61 | end 62 | ``` 63 | 64 | how about initial load? 65 | 66 | 67 | 1) if there is client_proc then run it on first load... 68 | 69 | filter_most_out_on_server.filter_a_few_out_on_client <- good 70 | filter_a_few_out_on_client.filter_most_out_on_server <- blah 71 | 72 | fix later... 73 | or 74 | as each scope is applied... of course we go back through all parent scopes and clear a filter flag (proc?) 75 | 76 | 77 | then on a load as we add each scope in, we can do the initial filter right then... which is okay because 78 | we don't have that data yet!!! 79 | 80 | or don't even bother, just run the filter as things are replaced from the server... 81 | 82 | its static... can always know if something should be filtered 83 | 84 | 85 | arghhh... 86 | 87 | Model.filter1.filter2... 88 | 89 | its still true that to filter, all scopes in chain must be client filters. 90 | 91 | but what is the diff between 92 | 93 | Model.inc_filter1.coll_filter1.inc_filter2.coll_filter2 94 | 95 | vs. 96 | 97 | Model.coll_filter 98 | 99 | right because this implies: 100 | 101 | Model.all.coll_filter, which means we have to do Model.all.each 102 | 103 | but Model.all.inc_filter should just pass any new/changed entries to inc_filter, and then update the collection 104 | 105 | Model.inc_filter1.inc_filter2 106 | 107 | is okay because we should not have inc_filter1's collection all updated. 108 | 109 | but really? what if we are doing this: 110 | 111 | Model.inc_minor_filter.inc_major_filter.count 112 | 113 | We do not have to fetch all inc_minor_filter's collection ever. 114 | 115 | We do not have to 116 | -------------------------------------------------------------------------------- /spec/batch1/crud_access_regulation/broadcast_controls_access_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'test_components' 3 | 4 | describe "regulate access allowed" do 5 | 6 | context "basic tests" do 7 | before(:each) do 8 | # spec_helper resets the policy system after each test so we have to setup 9 | # before each test 10 | stub_const 'TestApplication', Class.new 11 | stub_const 'C2', Class.new 12 | stub_const 'TestApplicationPolicy', Class.new 13 | TestApplicationPolicy.class_eval do 14 | regulate_class_connection { self } 15 | regulate_class_connection(C2) { self } 16 | regulate_instance_connections(TestModel) { self if self.is_a? TestModel } 17 | regulate_all_broadcasts(C2) { |policy| policy.send_all_but(:created_at) } 18 | regulate_broadcast(TestModel) do |policy| 19 | policy.send_all.to(TestApplication) unless test_attribute == "bogus" 20 | policy.send_all.to(self) 21 | end 22 | end 23 | end 24 | 25 | it "will allow access if the broadcast policy allows access" do 26 | m = FactoryBot.create(:test_model, test_attribute: "hello") 27 | expect { m.check_permission_with_acting_user("user", :view_permitted?, :test_attribute) }. 28 | not_to raise_error 29 | expect { m.check_permission_with_acting_user("user", :view_permitted?, :created_at) }. 30 | not_to raise_error 31 | end 32 | 33 | it "will disallow access if acting_user is not allowed to connect" do 34 | m = FactoryBot.create(:test_model, test_attribute: "hello") 35 | expect { m.check_permission_with_acting_user(nil, :view_permitted?, :test_attribute) }. 36 | to raise_error(Hyperloop::AccessViolation) 37 | expect { m.check_permission_with_acting_user(nil, :view_permitted?, :created_at) }. 38 | to raise_error(Hyperloop::AccessViolation) 39 | end 40 | 41 | it "will disallow access to attributes not broadcast by the model" do 42 | m = FactoryBot.create(:test_model, test_attribute: "bogus") 43 | expect { m.check_permission_with_acting_user("user", :view_permitted?, :test_attribute) }. 44 | not_to raise_error 45 | expect { m.check_permission_with_acting_user("user", :view_permitted?, :created_at) }. 46 | to raise_error(Hyperloop::AccessViolation) 47 | end 48 | 49 | it "will allow access to attributes broadcast over an instance channel" do 50 | m = FactoryBot.create(:test_model, test_attribute: "bogus") 51 | expect { m.check_permission_with_acting_user(m, :view_permitted?, :test_attribute) }. 52 | not_to raise_error 53 | expect { m.check_permission_with_acting_user(m, :view_permitted?, :created_at) }. 54 | not_to raise_error 55 | end 56 | end 57 | 58 | it "will prevent access to specific attributes" do 59 | stub_const 'TestApplication', Class.new 60 | stub_const 'TestApplicationPolicy', Class.new 61 | TestApplicationPolicy.class_eval do 62 | always_allow_connection 63 | regulate_broadcast(TestModel) do |policy| 64 | policy.send_all_but(:created_at).to(TestApplication) 65 | end 66 | end 67 | m = FactoryBot.create(:test_model) 68 | expect { m.check_permission_with_acting_user(nil, :view_permitted?, :test_attribute) }. 69 | not_to raise_error 70 | expect { m.check_permission_with_acting_user(nil, :view_permitted?, :created_at) }. 71 | to raise_error(Hyperloop::AccessViolation) 72 | end 73 | 74 | it "will include :id as read attribute as long as any other attribute is readable" do 75 | stub_const 'TestApplication', Class.new 76 | stub_const 'TestApplicationPolicy', Class.new 77 | TestApplicationPolicy.class_eval do 78 | always_allow_connection 79 | regulate_all_broadcasts { |policy| policy.send_only(:test_attribute) } 80 | end 81 | m = FactoryBot.create(:test_model) 82 | expect { m.check_permission_with_acting_user(nil, :view_permitted?, :id) }. 83 | not_to raise_error 84 | end 85 | 86 | it "will not include :id as read attribute if no other attributes are readable" do 87 | stub_const 'TestApplication', Class.new 88 | stub_const 'TestApplicationPolicy', Class.new 89 | TestApplicationPolicy.class_eval do 90 | always_allow_connection 91 | end 92 | m = FactoryBot.create(:test_model) 93 | expect { m.check_permission_with_acting_user(nil, :view_permitted?, :id) }. 94 | to raise_error(Hyperloop::AccessViolation) 95 | end 96 | 97 | it "will ignore auto_connect: false " do 98 | stub_const 'TestApplication', Class.new 99 | stub_const 'TestApplicationPolicy', Class.new 100 | TestApplicationPolicy.class_eval do 101 | regulate_class_connection(auto_connect: false) { true } 102 | regulate_instance_connections(TestModel, auto_connect: false) { self } 103 | regulate_all_broadcasts { |policy| policy.send_only(:test_attribute) } 104 | regulate_broadcast(TestModel) { |policy| policy.send_only(:created_at).to(self) } 105 | end 106 | m = FactoryBot.create(:test_model) 107 | expect { m.check_permission_with_acting_user(m, :view_permitted?, :id) }. 108 | not_to raise_error 109 | expect { m.check_permission_with_acting_user(m, :view_permitted?, :test_attribute) }. 110 | not_to raise_error 111 | expect { m.check_permission_with_acting_user(m, :view_permitted?, :created_at) }. 112 | not_to raise_error 113 | end 114 | 115 | end 116 | -------------------------------------------------------------------------------- /spec/batch1/crud_access_regulation/model_policies_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'test_components' 3 | 4 | describe "regulate access allowed" do 5 | 6 | 7 | before(:each) do 8 | stub_const 'DummyModel', Class.new(ActiveRecord::Base) 9 | DummyModel.class_eval do 10 | self.table_name = 'test_models' 11 | end 12 | end 13 | 14 | after(:each) do 15 | class ActiveRecord::Base 16 | def view_permitted?(attribute) 17 | Hyperloop::InternalPolicy.accessible_attributes_for(self, acting_user).include? attribute.to_sym 18 | end 19 | [:create, :update, :destroy].each do |access| 20 | define_method("#{access}_permitted?".to_sym) { false } 21 | end 22 | end 23 | end 24 | 25 | Hyperloop::InternalClassPolicy::CHANGE_POLICIES.each do |policy| 26 | 27 | it "will define a basic allow_#{policy} policy" do 28 | stub_const 'DummyModelPolicy', Class.new 29 | DummyModelPolicy.class_eval do 30 | send("allow_#{policy}") { "called #{policy}" } 31 | end 32 | DummyModel.new.send("#{policy}_permitted?").should eq("called #{policy}") 33 | end 34 | 35 | it "will define allow_#{policy} policy with a class argument" do 36 | stub_const 'ApplicationPolicy', Class.new 37 | ApplicationPolicy.class_eval do 38 | send("allow_#{policy}", DummyModel) { "called #{policy}" } 39 | end 40 | DummyModel.new.send("#{policy}_permitted?").should eq("called #{policy}") 41 | end 42 | 43 | it "will define allow_#{policy} policy with the to: :all option" do 44 | stub_const 'ApplicationPolicy', Class.new 45 | ApplicationPolicy.class_eval do 46 | send("allow_#{policy}", to: :all) { "called #{policy} on #{self.class.name}" } 47 | end 48 | stub_const 'FooModel', Class.new(ActiveRecord::Base) 49 | FooModel.class_eval do 50 | self.table_name = 'test_models' 51 | end 52 | DummyModel.new.send("#{policy}_permitted?").should eq("called #{policy} on DummyModel") 53 | FooModel.new.send("#{policy}_permitted?").should eq("called #{policy} on FooModel") 54 | end 55 | 56 | end 57 | 58 | it "will define a basic allow_change policy" do 59 | stub_const 'DummyModelPolicy', Class.new 60 | DummyModelPolicy.class_eval do 61 | send("allow_change") { "called change" } 62 | end 63 | Hyperloop::InternalClassPolicy::CHANGE_POLICIES.each do |policy| 64 | DummyModel.new.send("#{policy}_permitted?").should eq("called change") 65 | end 66 | end 67 | 68 | it "will define allow_change policy with a class argument" do 69 | stub_const 'ApplicationPolicy', Class.new 70 | ApplicationPolicy.class_eval do 71 | send("allow_change", DummyModel) { "called change" } 72 | end 73 | Hyperloop::InternalClassPolicy::CHANGE_POLICIES.each do |policy| 74 | DummyModel.new.send("#{policy}_permitted?").should eq("called change") 75 | end 76 | end 77 | 78 | it "will define allow_change policy with the to: :all option" do 79 | stub_const 'ApplicationPolicy', Class.new 80 | ApplicationPolicy.class_eval do 81 | send("allow_change", to: :all) { "called change on #{self.class.name}" } 82 | end 83 | stub_const 'FooModel', Class.new(ActiveRecord::Base) 84 | FooModel.class_eval do 85 | self.table_name = 'test_models' 86 | end 87 | Hyperloop::InternalClassPolicy::CHANGE_POLICIES.each do |policy| 88 | DummyModel.new.send("#{policy}_permitted?").should eq("called change on DummyModel") 89 | FooModel.new.send("#{policy}_permitted?").should eq("called change on FooModel") 90 | end 91 | end 92 | 93 | it "will define allow_change policy with an :on option" do 94 | stub_const 'ApplicationPolicy', Class.new 95 | ApplicationPolicy.class_eval do 96 | send("allow_change", DummyModel, on: [:create, :update]) { "called change" } 97 | end 98 | [:create, :update].each do |policy| 99 | DummyModel.new.send("#{policy}_permitted?").should eq("called change") 100 | end 101 | DummyModel.new.send("destroy_permitted?").should be_falsy 102 | end 103 | 104 | it "will define a basic allow_read policy" do 105 | stub_const 'DummyModelPolicy', Class.new 106 | DummyModelPolicy.class_eval do 107 | send("allow_read") { |attr| "called read #{attr}" } 108 | end 109 | DummyModel.new.send("view_permitted?", :foo).should eq("called read foo") 110 | end 111 | 112 | it "will define allow_read policy with a class argument" do 113 | stub_const 'ApplicationPolicy', Class.new 114 | ApplicationPolicy.class_eval do 115 | send("allow_read", DummyModel) { |attr| "called read #{attr}" } 116 | end 117 | DummyModel.new.send("view_permitted?", :foo).should eq("called read foo") 118 | end 119 | 120 | it "will define allow_read policy with the to: :all option" do 121 | stub_const 'ApplicationPolicy', Class.new 122 | ApplicationPolicy.class_eval do 123 | send("allow_read", to: :all) { |attr| "called read #{attr} on #{self.class.name}" } 124 | end 125 | stub_const 'FooModel', Class.new(ActiveRecord::Base) 126 | FooModel.class_eval do 127 | self.table_name = 'test_models' 128 | end 129 | DummyModel.new.send("view_permitted?", :foo).should eq("called read foo on DummyModel") 130 | FooModel.new.send("view_permitted?", :foo).should eq("called read foo on FooModel") 131 | end 132 | 133 | end 134 | -------------------------------------------------------------------------------- /spec/batch1/misc/access_like_hash_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'test_components' 3 | require 'rspec-steps' 4 | 5 | 6 | RSpec::Steps.steps "access like a hash", js: true do 7 | 8 | before(:step) do 9 | # spec_helper resets the policy system after each test so we have to setup 10 | # before each test 11 | stub_const 'TestApplication', Class.new 12 | stub_const 'TestApplicationPolicy', Class.new 13 | TestApplicationPolicy.class_eval do 14 | always_allow_connection 15 | regulate_all_broadcasts { |policy| policy.send_all } 16 | allow_change(to: :all, on: [:create, :update, :destroy]) { true } 17 | end 18 | size_window(:small, :portrait) 19 | FactoryBot.create(:user, first_name: 'Lily', last_name: 'DaDog') 20 | end 21 | 22 | it "can access attributes using the [] operator" do 23 | expect_promise do 24 | HyperMesh.load do 25 | User.find_by_first_name('Lily') 26 | end.then do |lily| 27 | lily[:first_name] 28 | end 29 | end.to eq('Lily') 30 | end 31 | 32 | it "can update attributes using the []= operator" do 33 | expect_promise do 34 | HyperMesh.load do 35 | User.find_by_first_name('Lily') 36 | end.then do |lily| 37 | lily[:last_name] = 'DerDog' 38 | lily.save 39 | end 40 | end.to be_truthy 41 | expect(User.find_by_first_name('Lily')[:last_name]).to eq('DerDog') 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/batch1/misc/validate_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'test_components' 3 | require 'rspec-steps' 4 | 5 | 6 | RSpec::Steps.steps "validate and valid? methods", js: true do 7 | 8 | before(:step) do 9 | # spec_helper resets the policy system after each test so we have to setup 10 | # before each test 11 | stub_const 'TestApplication', Class.new 12 | stub_const 'TestApplicationPolicy', Class.new 13 | TestApplicationPolicy.class_eval do 14 | #always_allow_connection TURN OFF BROADCAST SO TESTS DON"T EFFECT EACH OTHER 15 | regulate_all_broadcasts { |policy| policy.send_all } 16 | allow_change(to: :all, on: [:create, :update, :destroy]) { true } 17 | end 18 | #size_window(:large, :landscape) 19 | User.validates :last_name, exclusion: { in: %w[f**k], message: 'no swear words allowed' } 20 | TestModel.validates_presence_of :child_models 21 | client_option raise_on_js_errors: :off 22 | end 23 | 24 | it "can validate the presence of an association" do 25 | expect_promise do 26 | @test_model = TestModel.new 27 | @test_model.validate.then { |test_model| test_model.errors.messages } 28 | end.not_to be_empty 29 | expect_promise do 30 | @test_model.child_models << ChildModel.new 31 | @test_model.validate.then { |test_model| test_model.errors.messages } 32 | end.to be_empty 33 | expect(TestModel.count).to be_zero 34 | expect(ChildModel.count).to be_zero 35 | end 36 | 37 | it "can validate only using the validate method" do 38 | expect_promise do 39 | User.new(last_name: 'f**k').validate.then do |new_user| 40 | new_user.errors.messages 41 | end 42 | end.to eq("last_name"=>["no swear words allowed"]) 43 | end 44 | 45 | it "the valid? method will return true if the model has no errors" do 46 | mount "Validator" do 47 | class Validator < Hyperloop::Component 48 | include React::IsomorphicHelpers 49 | class << self 50 | attr_reader :model 51 | end 52 | before_first_mount { @model = User.new } 53 | render(DIV) { "#{Validator.model}.valid? #{!!Validator.model.valid?}" } 54 | end 55 | end 56 | expect_promise do 57 | user = User.new(last_name: 'dog') 58 | user.save.then { user.valid? } 59 | end.to be_truthy 60 | end 61 | 62 | it "the valid? method will return false if the model has errors" do 63 | expect_promise do 64 | user = User.new(last_name: 'f**k') 65 | user.save.then { user.valid? } 66 | end.to be_falsy 67 | end 68 | 69 | it "the valid? method reacts to the model being saved" do 70 | evaluate_ruby do 71 | Validator.model.last_name = 'f**k' 72 | end 73 | expect(page).to have_content('.valid? true') 74 | evaluate_ruby do 75 | Validator.model.save 76 | end 77 | expect(page).to have_content('.valid? false') 78 | evaluate_ruby do 79 | Validator.model.update(last_name: 'nice doggy') 80 | end 81 | expect(page).to have_content('.valid? true') 82 | end 83 | 84 | it "the valid? method reacts to the model being validated" do 85 | evaluate_ruby do 86 | Validator.model.last_name = 'f**k' 87 | end 88 | evaluate_ruby do 89 | Validator.model.validate 90 | end 91 | expect(page).to have_content('.valid? false') 92 | evaluate_ruby do 93 | Validator.model.last_name = 'nice doggy' 94 | Validator.model.validate 95 | end 96 | expect(page).to have_content('.valid? true') 97 | end 98 | 99 | it "the valid? method reacts to the error object changing state" do 100 | expect(page).to have_content('.valid? true') 101 | evaluate_ruby do 102 | Validator.model.errors.add(:bite) 103 | end 104 | expect(page).to have_content('.valid? false') 105 | evaluate_ruby do 106 | Validator.model.errors.clear 107 | end 108 | expect(page).to have_content('.valid? true') 109 | end 110 | 111 | end 112 | -------------------------------------------------------------------------------- /spec/batch2/alias_attribute_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'rspec-steps' 3 | 4 | RSpec::Steps.steps 'alias_attribute', js: true do 5 | 6 | before(:each) do 7 | require 'pusher' 8 | require 'pusher-fake' 9 | Pusher.app_id = "MY_TEST_ID" 10 | Pusher.key = "MY_TEST_KEY" 11 | Pusher.secret = "MY_TEST_SECRET" 12 | require "pusher-fake/support/base" 13 | 14 | Hyperloop.configuration do |config| 15 | config.transport = :pusher 16 | config.channel_prefix = "synchromesh" 17 | config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) 18 | end 19 | 20 | end 21 | 22 | before(:step) do 23 | stub_const 'TestApplicationPolicy', Class.new 24 | TestApplicationPolicy.class_eval do 25 | always_allow_connection 26 | regulate_all_broadcasts { |policy| policy.send_all } 27 | allow_change(to: :all, on: [:create, :update, :destroy]) { true } 28 | end 29 | ApplicationController.acting_user = nil 30 | isomorphic do 31 | User.alias_attribute :surname, :last_name 32 | class SubUser < User 33 | end 34 | end 35 | on_client do 36 | # Aliases are implemented as method aliases. However these will not 37 | # work with class methods like create, so we also keep a hash of aliases 38 | # associated with the class. 39 | # In order to test alias inheritence we will just add this alias on the 40 | # client. Thus if during any access we DID not inherit the alias 41 | # it will remain as client_name, which will break the server side store 42 | User.alias_attribute :client_name, :last_name 43 | end 44 | end 45 | 46 | it "implements find_by" do 47 | @user = User.create(first_name: "Mitch", last_name: "VanDuyn") 48 | expect_promise do 49 | ReactiveRecord.load { User.find_by(first_name: "Mitch", surname: "VanDuyn").id } 50 | end.to eq(@user.id) 51 | end 52 | 53 | it "implements the finder" do 54 | @user = User.create(first_name: "M.", last_name: "Pantel") 55 | expect_promise do 56 | ReactiveRecord.load { User.find_by_surname('Pantel').id } 57 | end.to eq(@user.id) 58 | end 59 | 60 | it "works with find_by without fetching from the DB" do 61 | expect_evaluate_ruby do 62 | User.find_by(first_name: 'M.', last_name: 'Pantel').id 63 | end.to eq(@user.id) 64 | end 65 | 66 | it "implements the getter" do 67 | expect_promise do 68 | ReactiveRecord.load { User.find_by_first_name('M.').surname } 69 | end.to eq('Pantel') 70 | end 71 | 72 | it "implements the setter" do 73 | evaluate_promise do 74 | user = User.find_by_first_name('M.') 75 | user.surname = "Someoneelse" 76 | user.save 77 | end 78 | expect(@user.reload.surname).to eq('Someoneelse') 79 | end 80 | 81 | it "implements the _changed? method" do 82 | expect_evaluate_ruby do 83 | user = User.find_by_first_name('M.') 84 | user.last_name = "Pantel" 85 | user.surname_changed? 86 | end.to be_truthy 87 | end 88 | 89 | it "can inherit the aliases" do 90 | evaluate_promise do 91 | SubUser.create(client_name: 'Fred') 92 | end 93 | expect(SubUser.find_by_surname('Fred')).to be_truthy 94 | end 95 | 96 | end 97 | -------------------------------------------------------------------------------- /spec/batch2/default_scope_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'test_components' 3 | 4 | describe "default_scope" do 5 | 6 | context "client tests", js: true do 7 | 8 | before(:all) do 9 | require 'pusher' 10 | require 'pusher-fake' 11 | Pusher.app_id = "MY_TEST_ID" 12 | Pusher.key = "MY_TEST_KEY" 13 | Pusher.secret = "MY_TEST_SECRET" 14 | require "pusher-fake/support/base" 15 | 16 | Hyperloop.configuration do |config| 17 | config.transport = :pusher 18 | config.channel_prefix = "synchromesh" 19 | config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) 20 | end 21 | end 22 | 23 | before(:each) do 24 | # spec_helper resets the policy system after each test so we have to setup 25 | # before each test 26 | stub_const 'TestApplication', Class.new 27 | stub_const 'TestApplicationPolicy', Class.new 28 | TestApplicationPolicy.class_eval do 29 | always_allow_connection 30 | regulate_all_broadcasts { |policy| policy.send_all } 31 | allow_change(to: :all, on: [:create, :update, :destroy]) { true } 32 | end 33 | size_window(:small, :portrait) 34 | end 35 | 36 | after(:each) do 37 | TestModel.default_scopes = [] 38 | end 39 | 40 | it "a default scope can be added server side using either a block or proc" do 41 | isomorphic do 42 | TestModel.class_eval do 43 | default_scope -> { where(completed: true) } 44 | default_scope { where(test_attribute: 'foo') } 45 | end 46 | end 47 | mount "TestComponent2" do 48 | class TestComponent2 < React::Component::Base 49 | render(:div) do 50 | "#{TestModel.count} items".br 51 | "#{TestModel.unscoped.count} unscoped items" 52 | end 53 | end 54 | end 55 | wait_for_ajax 56 | page.should have_content("0 items") 57 | page.should have_content("0 unscoped items") 58 | m1 = FactoryBot.create(:test_model, completed: false, test_attribute: nil) 59 | wait_for_ajax 60 | page.should have_content("0 items") 61 | page.should have_content("1 unscoped items") 62 | m2 = FactoryBot.create(:test_model, completed: true, test_attribute: nil) 63 | wait_for_ajax 64 | page.should have_content("0 items") 65 | page.should have_content("2 unscoped items") 66 | m2.update(test_attribute: 'foo') 67 | wait_for_ajax 68 | page.should have_content("1 items") 69 | page.should have_content("2 unscoped items") 70 | m3 = FactoryBot.create(:test_model) 71 | wait_for_ajax 72 | page.should have_content("2 items") 73 | page.should have_content("3 unscoped items") 74 | m3.update_attribute(:completed, false) 75 | wait_for_ajax 76 | page.should have_content("1 items") 77 | page.should have_content("3 unscoped items") 78 | m2.destroy 79 | wait_for_ajax 80 | page.should have_content("0 items") 81 | page.should have_content("2 unscoped items") 82 | end 83 | 84 | it "a default scope can be added client side" do 85 | isomorphic do 86 | TestModel.class_eval do 87 | default_scope server: -> { where(completed: true) }, 88 | client: -> { completed } 89 | default_scope server: -> { where(test_attribute: 'foo') }, 90 | client: -> { test_attribute == 'foo' } 91 | end 92 | end 93 | mount "TestComponent2" do 94 | class TestComponent2 < React::Component::Base 95 | render(:div) do 96 | "#{TestModel.count} items".br 97 | "#{TestModel.unscoped.count} unscoped items" 98 | end 99 | end 100 | end 101 | wait_for_ajax 102 | starting_fetch_time = evaluate_ruby("ReactiveRecord::Base.current_fetch_id") 103 | page.should have_content("0 items") 104 | page.should have_content("0 unscoped items") 105 | m1 = FactoryBot.create(:test_model, completed: false, test_attribute: nil) 106 | wait_for_ajax 107 | page.should have_content("0 items") 108 | page.should have_content("1 unscoped items") 109 | m2 = FactoryBot.create(:test_model, completed: true, test_attribute: nil) 110 | wait_for_ajax 111 | page.should have_content("0 items") 112 | page.should have_content("2 unscoped items") 113 | m2.update(test_attribute: 'foo') 114 | wait_for_ajax 115 | page.should have_content("1 items") 116 | page.should have_content("2 unscoped items") 117 | m3 = FactoryBot.create(:test_model) 118 | wait_for_ajax 119 | page.should have_content("2 items") 120 | page.should have_content("3 unscoped items") 121 | m3.update_attribute(:completed, false) 122 | wait_for_ajax 123 | page.should have_content("1 items") 124 | page.should have_content("3 unscoped items") 125 | m2.destroy 126 | wait_for_ajax 127 | page.should have_content("0 items") 128 | page.should have_content("2 unscoped items") 129 | # there should be no client fetches should replace this with a double of 130 | # ServerDataCache[] which should not be called 131 | wait_for_ajax 132 | starting_fetch_time.should eq(evaluate_ruby("ReactiveRecord::Base.current_fetch_id")) 133 | end 134 | end 135 | end 136 | -------------------------------------------------------------------------------- /spec/batch2/enum_xspec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'rspec-steps' 3 | 4 | RSpec::Steps.steps 'Reading and Writing Enums', js: true do 5 | 6 | before(:each) do 7 | 8 | require 'pusher' 9 | require 'pusher-fake' 10 | Pusher.app_id = "MY_TEST_ID" 11 | Pusher.key = "MY_TEST_KEY" 12 | Pusher.secret = "MY_TEST_SECRET" 13 | require "pusher-fake/support/base" 14 | 15 | Hyperloop.configuration do |config| 16 | config.transport = :pusher 17 | config.channel_prefix = "synchromesh" 18 | config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) 19 | end 20 | end 21 | before(:step) do 22 | stub_const 'TestApplicationPolicy', Class.new 23 | TestApplicationPolicy.class_eval do 24 | always_allow_connection 25 | regulate_all_broadcasts { |policy| policy.send_all } 26 | end 27 | ApplicationController.acting_user = nil 28 | end 29 | 30 | it "can change the enum and read it back" do 31 | User.create(name: 'test user') 32 | evaluate_ruby do 33 | ReactiveRecord.load { User.find(1).itself }.then do |user| 34 | user.test_enum = :no 35 | user.save.then do 36 | React::IsomorphicHelpers.load_context 37 | ReactiveRecord.load do 38 | User.find(1).test_enum 39 | end 40 | end 41 | end 42 | end.to eq('no') 43 | end 44 | 45 | # async "can set it back" do 46 | # React::IsomorphicHelpers.load_context 47 | # set_acting_user "super-user" 48 | # user = User.find(1) 49 | # user.test_enum = :yes 50 | # user.save.then do 51 | # React::IsomorphicHelpers.load_context 52 | # ReactiveRecord.load do 53 | # User.find(1).test_enum 54 | # end.then do |test_enum| 55 | # async { expect(test_enum).to eq(:yes) } 56 | # end 57 | # end 58 | # end 59 | # 60 | # it "can change it back" do 61 | # user = User.find(1) 62 | # user.test_enum = :yes 63 | # user.save.then do |success| 64 | # expect(success).to be_truthy 65 | # end 66 | # end 67 | 68 | end 69 | -------------------------------------------------------------------------------- /spec/batch2/non_ar_aggregations_tbdspec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'rspec-steps' 3 | 4 | RSpec::Steps.steps 'using non-ar aggregations', js: true do 5 | 6 | before(:each) do 7 | require 'pusher' 8 | require 'pusher-fake' 9 | Pusher.app_id = "MY_TEST_ID" 10 | Pusher.key = "MY_TEST_KEY" 11 | Pusher.secret = "MY_TEST_SECRET" 12 | require "pusher-fake/support/base" 13 | 14 | Hyperloop.configuration do |config| 15 | config.transport = :pusher 16 | config.channel_prefix = "synchromesh" 17 | config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) 18 | end 19 | 20 | User.do_not_synchronize 21 | end 22 | 23 | before(:step) do 24 | stub_const 'TestApplicationPolicy', Class.new 25 | TestApplicationPolicy.class_eval do 26 | always_allow_connection 27 | regulate_all_broadcasts { |policy| policy.send_all } 28 | allow_change(to: :all, on: [:create, :update, :destroy]) { true } 29 | end 30 | ApplicationController.acting_user = nil 31 | end 32 | 33 | it "create an aggregation" do 34 | expect_evaluate_ruby do 35 | User.new(first_name: "Data", data: TestData.new("hello", 3)).data.big_string 36 | end.to eq("hellohellohello") 37 | end 38 | 39 | it "save it" do 40 | evaluate_promise do 41 | User.find_by_first_name("Data").save 42 | end 43 | expect(User.find_by_first_name("Data").data.big_string).to eq("hellohellohello") 44 | binding.pry 45 | end 46 | 47 | it "read it" do 48 | User.create(first_name: 'User2', data: TestData.new('goodby', 3)) 49 | expect_promise do 50 | ReactiveRecord.load { User.find_by_first_name("User2").data.big_string } 51 | end.to eq('goodbygoodbygoodby') 52 | end 53 | 54 | # and restored ... 55 | # 56 | # async "is time to change it, and force the save" do 57 | # user = User.find_by_first_name("Data") 58 | # user.data.string = "goodby" 59 | # user.save(force: true).then do 60 | # React::IsomorphicHelpers.load_context 61 | # ReactiveRecord.load do 62 | # User.find_by_first_name("Data").data 63 | # end.then do |data| 64 | # async { expect(data.big_string).to eq("goodbygoodbygoodby") } 65 | # end 66 | # end 67 | # end 68 | # 69 | # async "is time to change the value completely and save it (no force needed)" do 70 | # user = User.find_by_first_name("Data") 71 | # user.data = TestData.new("the end", 1) 72 | # user.save.then do 73 | # React::IsomorphicHelpers.load_context 74 | # ReactiveRecord.load do 75 | # User.find_by_first_name("Data").data 76 | # end.then do |data| 77 | # async { expect(data.big_string).to eq("the end") } 78 | # end 79 | # end 80 | # end 81 | # 82 | # async "is time to delete the value and see if returns nil after saving" do 83 | # user = User.find_by_first_name("Data") 84 | # user.data = nil 85 | # user.save.then do 86 | # React::IsomorphicHelpers.load_context 87 | # ReactiveRecord.load do 88 | # User.find_by_first_name("Data").data 89 | # end.then do |data| 90 | # async { expect(data).to be_nil } 91 | # end 92 | # end 93 | # end 94 | # 95 | # it "is time to delete our user" do 96 | # User.find_by_first_name("Data").destroy.then do 97 | # expect(User.find_by_first_name("Data")).to be_destroyed 98 | # end 99 | # end 100 | # 101 | # it "is time to see to make sure a nil aggregate that has never had a value returns nil" do 102 | # ReactiveRecord.load do 103 | # User.find_by_email("mitch@catprint.com").data 104 | # end.then do |data| 105 | # expect(data).to be_nil 106 | # end 107 | # end 108 | 109 | end 110 | -------------------------------------------------------------------------------- /spec/batch3/auto_load_itself_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'test_components' 3 | 4 | describe "HyperMesh.load", js: true do 5 | 6 | before(:each) do 7 | # spec_helper resets the policy system after each test so we have to setup 8 | # before each test 9 | stub_const 'TestApplication', Class.new 10 | stub_const 'TestApplicationPolicy', Class.new 11 | TestApplicationPolicy.class_eval do 12 | always_allow_connection 13 | regulate_all_broadcasts { |policy| policy.send_all } 14 | end 15 | size_window(:small, :portrait) 16 | end 17 | 18 | it "uses itself to force loading" do 19 | user = FactoryBot.create(:user, first_name: 'Ima') 20 | expect_promise do 21 | HyperMesh.load { User.find_by_first_name('Ima') }.then { |user| user.id } 22 | end.to eq(user.id) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/batch3/edge_cases_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'test_components' 3 | 4 | describe "reactive-record edge cases", js: true do 5 | 6 | before(:all) do 7 | # Hyperloop.configuration do |config| 8 | # config.transport = :simple_poller 9 | # # slow down the polling so wait_for_ajax works 10 | # config.opts = { seconds_between_poll: 2 } 11 | # end 12 | 13 | require 'pusher' 14 | require 'pusher-fake' 15 | Pusher.app_id = "MY_TEST_ID" 16 | Pusher.key = "MY_TEST_KEY" 17 | Pusher.secret = "MY_TEST_SECRET" 18 | require "pusher-fake/support/base" 19 | 20 | Hyperloop.configuration do |config| 21 | config.transport = :pusher 22 | config.channel_prefix = "synchromesh" 23 | config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) 24 | end 25 | 26 | end 27 | 28 | before(:each) do 29 | # spec_helper resets the policy system after each test so we have to setup 30 | # before each test 31 | stub_const 'TestApplication', Class.new 32 | stub_const 'TestApplicationPolicy', Class.new 33 | TestApplicationPolicy.class_eval do 34 | always_allow_connection 35 | regulate_all_broadcasts { |policy| policy.send_all } 36 | allow_change(to: :all, on: [:create, :update, :destroy]) { true } 37 | end 38 | size_window(:small, :portrait) 39 | end 40 | 41 | it "trims the association tree" do 42 | 5.times do |i| 43 | user = FactoryBot.create(:user, first_name: i) unless i == 3 44 | FactoryBot.create(:todo, title: "User #{i}'s todo", owner: user) 45 | end 46 | expect_promise do 47 | HyperMesh.load do 48 | Todo.all.collect do |todo| 49 | todo.owner && todo.owner.first_name 50 | end.compact 51 | end 52 | end.to contain_exactly('0', '1', '2', '4') 53 | end 54 | 55 | it "does not double count local saves" do 56 | expect_promise do 57 | HyperMesh.load do 58 | Todo.count 59 | end.then do |count| 60 | Todo.create(title: 'test todo') 61 | end.then do 62 | Todo.count 63 | end 64 | end.to eq(1) 65 | end 66 | 67 | it "fetches data during prerendering" do 68 | 5.times do |i| 69 | FactoryBot.create(:todo, title: "Todo #{i}") 70 | end 71 | # cause spec to fail if there are attempts to fetch data after prerendering 72 | hide_const 'ReactiveRecord::Operations::Fetch' 73 | mount "TestComponent77", {}, render_on: :both do 74 | class TestComponent77 < Hyperloop::Component 75 | render(UL) do 76 | Todo.each do |todo| 77 | LI { todo.title } 78 | end 79 | end 80 | end 81 | end 82 | Todo.all.each do |todo| 83 | page.should have_content(todo.title) 84 | end 85 | end 86 | 87 | it "prerenders a belongs to relationship" do 88 | user_item = User.create(name: 'Fred') 89 | todo_item = TodoItem.create(title: 'test-todo', user: user_item) 90 | mount "PrerenderTest", {}, render_on: :server_only do 91 | class PrerenderTest < Hyperloop::Component 92 | render(DIV) do 93 | TodoItem.first.user.name 94 | end 95 | end 96 | end 97 | page.should have_content("Fred") 98 | end 99 | 100 | it "the limit and offset predefined scopes work" do 101 | 5.times do |i| 102 | FactoryBot.create(:todo, title: "Todo #{i}") 103 | end 104 | mount "TestComponent77" do 105 | class TestComponent77 < Hyperloop::Component 106 | render(UL) do 107 | Todo.limit(2).offset(3).each do |todo| 108 | LI { todo.title } 109 | end 110 | end 111 | end 112 | end 113 | Todo.limit(2).offset(3).each do |todo| 114 | page.should have_content(todo.title) 115 | end 116 | end 117 | end 118 | -------------------------------------------------------------------------------- /spec/batch3/finder_method_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'test_components' 3 | require 'rspec-steps' 4 | 5 | 6 | RSpec::Steps.steps "finder_method", js: true do 7 | 8 | before(:step) do 9 | # spec_helper resets the policy system after each test so we have to setup 10 | # before each test 11 | stub_const 'TestApplication', Class.new 12 | stub_const 'TestApplicationPolicy', Class.new 13 | TestApplicationPolicy.class_eval do 14 | always_allow_connection 15 | regulate_all_broadcasts { |policy| policy.send_all } 16 | end 17 | isomorphic do 18 | Todo.class_eval do 19 | class << self 20 | attr_accessor :current_random_value 21 | end 22 | finder_method :random_item do |i| 23 | find(current_random_value + i.to_i) 24 | end 25 | scope :test_scope, -> { all } 26 | end 27 | end 28 | size_window(:small, :portrait) 29 | 5.times { FactoryBot.create(:todo) } 30 | end 31 | 32 | it "returns the correct value" do 33 | Todo.current_random_value = 1 34 | expect_promise do 35 | HyperMesh.load { Todo.random_item(2) }.then { |todo| todo.id } 36 | end.to eq(3) 37 | end 38 | 39 | it "returns the correct value on the server too" do 40 | Todo.current_random_value = 1 41 | expect(Todo.random_item(2).id).to eq(3) 42 | end 43 | 44 | it "will not reload the value unless forced" do 45 | Todo.current_random_value = 2 46 | expect_promise do 47 | HyperMesh.load { Todo.random_item(2) }.then { |todo| todo.id } 48 | end.to eq(3) 49 | end 50 | 51 | it "can be forced to reload the value" do 52 | expect_promise do 53 | current_value = Todo.random_item(2).id 54 | HyperMesh.load do 55 | new_value = Todo.random_item(2).id 56 | Todo.random_item!(2) if current_value == new_value 57 | new_value 58 | end 59 | end.to eq(4) 60 | end 61 | 62 | it "can apply to a nested scope" do 63 | expect_promise do 64 | HyperMesh.load { Todo.test_scope.random_item(2) }.then { |todo| todo.id } 65 | end.to eq(4) 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /spec/batch3/many_to_many_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'test_components' 3 | require 'reactive_record_factory' 4 | 5 | describe "many to many associations", js: true do 6 | 7 | before(:each) do 8 | seed_database 9 | end 10 | 11 | before(:each) do 12 | # spec_helper resets the policy system after each test so we have to setup 13 | # before each test 14 | stub_const 'TestApplication', Class.new 15 | stub_const 'TestApplicationPolicy', Class.new 16 | TestApplicationPolicy.class_eval do 17 | always_allow_connection 18 | regulate_all_broadcasts { |policy| policy.send_all } 19 | end 20 | size_window(:small, :portrait) 21 | end 22 | 23 | it "does not effect the base relationship count" do 24 | expect_promise do 25 | ReactiveRecord.load do 26 | TodoItem.find_by_title("a todo for mitch").comments.count 27 | end 28 | end.to be(1) 29 | end 30 | 31 | it "does not effect access to attributes in the base relationship" do 32 | expect_promise do 33 | ReactiveRecord.load do 34 | TodoItem.find_by_title("a todo for mitch").comments.count 35 | end.then do 36 | ReactiveRecord.load do 37 | TodoItem.find_by_title("a todo for mitch").comments.first.user.email 38 | end 39 | end 40 | end.to eq("adamg@catprint.com") 41 | end 42 | 43 | it "can be followed directly" do 44 | expect_promise do 45 | ReactiveRecord.load do 46 | TodoItem.find_by_title("a todo for mitch").commenters.first.email 47 | end 48 | end.to eq("adamg@catprint.com") 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /spec/batch3/pry_rescue_xspec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "ServerDataCache" do 4 | 5 | before(:all) do 6 | @current_pry_definition = Object.const_get("Pry") if defined? Pry 7 | @current_pry_rescue_definition = Object.const_get("PryRescue") if defined? PryRescue 8 | end 9 | 10 | after(:all) do 11 | Object.const_set("Pry", @current_pry_definition) if @current_pry_definition 12 | Object.const_set("PryRescue", @current_pry_definition) if @current_pry_rescue_definition 13 | end 14 | 15 | it "behaves normally if there is no pry rescue" do 16 | expect { ReactiveRecord::ServerDataCache[[],[], [["User", ["find", 1], "fake_attribute"]], nil] }.to raise_error(ActiveRecord::RecordNotFound) 17 | end 18 | 19 | context "will use pry rescue if it is defined" do 20 | 21 | before(:all) do 22 | pry = Class.new do 23 | def self.rescue 24 | yield 25 | end 26 | def self.rescued(e) 27 | @last_exception = e 28 | end 29 | def self.last_exception 30 | @last_exception 31 | end 32 | end 33 | Object.const_set("PryRescue", true) 34 | Object.const_set("Pry", pry) 35 | end 36 | 37 | it "and it will still raise an error" do 38 | expect { ReactiveRecord::ServerDataCache[[],[], [["User", ["find", 1], "fake_attribute"]], nil] }.to raise_error(ActiveRecord::RecordNotFound) 39 | end 40 | 41 | it "but it will call Pry.rescued first" do 42 | ReactiveRecord::ServerDataCache[[],[], ["User", ["new", 10852], "fake_attribute"], nil] rescue nil 43 | expect(Pry.last_exception).to be_a(Exception) 44 | end 45 | 46 | end 47 | 48 | end 49 | -------------------------------------------------------------------------------- /spec/batch3/revert_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'test_components' 3 | require 'reactive_record_factory' 4 | require 'rspec-steps' 5 | 6 | RSpec::Steps.steps "reverting records", js: true do 7 | 8 | before(:all) do 9 | seed_database 10 | end 11 | 12 | before(:step) do 13 | # spec_helper resets the policy system after each test so we have to setup 14 | # before each test 15 | stub_const 'TestApplication', Class.new 16 | stub_const 'TestApplicationPolicy', Class.new 17 | TestApplicationPolicy.class_eval do 18 | always_allow_connection 19 | regulate_all_broadcasts { |policy| policy.send_all } 20 | allow_change(to: :all, on: [:create, :update, :destroy]) { true } 21 | end 22 | size_window(:small, :portrait) 23 | end 24 | 25 | it "finds that the user Adam has not changed yet" do 26 | expect_promise do 27 | ReactiveRecord.load do 28 | User.find_by_first_name("Adam") 29 | end.then { |u| u.changed? } 30 | end.to be_falsy 31 | end 32 | 33 | it "creates a new todo which should be changed (because its new)" do 34 | expect_evaluate_ruby do 35 | TodoItem.new({title: "Adam is not getting this todo"}).changed? 36 | end.to be_truthy 37 | end 38 | 39 | it "adds the todo to adam's todos and expects adam to change" do 40 | expect_evaluate_ruby do 41 | adam = User.find_by_first_name("Adam") 42 | adam.todo_items << TodoItem.find_by_title("Adam is not getting this todo") 43 | adam.changed? 44 | end.to be_truthy 45 | end 46 | 47 | it "will show that the new todo is still changed" do 48 | expect_evaluate_ruby do 49 | TodoItem.find_by_title("Adam is not getting this todo").changed? 50 | end.to be_truthy 51 | end 52 | 53 | it "the todo now has an owner" do 54 | expect_evaluate_ruby do 55 | TodoItem.find_by_title("Adam is not getting this todo").user 56 | end.not_to be_nil 57 | end 58 | 59 | it "can be reverted and the todo will not be changed" do 60 | expect_evaluate_ruby do 61 | todo = TodoItem.find_by_title("Adam is not getting this todo") 62 | todo.revert 63 | todo.changed? 64 | end.not_to be_truthy 65 | end 66 | 67 | it "will not have changed adam" do 68 | expect_evaluate_ruby do 69 | User.find_by_first_name("Adam").changed? 70 | end.not_to be_truthy 71 | end 72 | 73 | it "is time to test going the other way, lets give adam a todo again" do 74 | expect_evaluate_ruby do 75 | new_todo = TodoItem.new({title: "Adam is still not getting this todo"}) 76 | adam = User.find_by_first_name("Adam") 77 | adam.todo_items << new_todo 78 | adam.changed? 79 | end.to be_truthy 80 | end 81 | 82 | it "can be reverted" do 83 | expect_evaluate_ruby do 84 | adam = User.find_by_first_name("Adam") 85 | adam.revert 86 | adam.changed? 87 | end.not_to be_truthy 88 | end 89 | 90 | it "finds the todo is still changed" do 91 | expect_evaluate_ruby do 92 | TodoItem.find_by_title("Adam is still not getting this todo").changed? 93 | end.to be_truthy 94 | end 95 | 96 | it "can change an attribute, revert, and make sure nothing else changes" do 97 | original_count = User.find_by_email("mitch@catprint.com").todo_items.count 98 | expect_promise do 99 | mitch = nil 100 | ReactiveRecord.load do 101 | mitch = User.find_by_email("mitch@catprint.com") 102 | mitch.last_name 103 | mitch.todo_items.count 104 | end.then do 105 | mitch.last_name = "xxxx" 106 | mitch.save 107 | end.then do 108 | mitch.revert 109 | mitch.todo_items.count 110 | end 111 | end.to eq(original_count) 112 | end 113 | end 114 | -------------------------------------------------------------------------------- /spec/batch4/synchromesh_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'test_components' 3 | 4 | describe "HyperMesh", js: true do 5 | 6 | before(:all) do 7 | require 'pusher' 8 | require 'pusher-fake' 9 | Pusher.app_id = "MY_TEST_ID" 10 | Pusher.key = "MY_TEST_KEY" 11 | Pusher.secret = "MY_TEST_SECRET" 12 | require "pusher-fake/support/base" 13 | 14 | Hyperloop.configuration do |config| 15 | config.transport = :pusher 16 | config.channel_prefix = "synchromesh" 17 | config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) 18 | end 19 | end 20 | 21 | before(:each) do 22 | # spec_helper resets the policy system after each test so we have to setup 23 | # before each test 24 | stub_const 'TestApplicationPolicy', Class.new 25 | TestApplicationPolicy.class_eval do 26 | always_allow_connection 27 | regulate_all_broadcasts { |policy| policy.send_all } 28 | end 29 | size_window(:small, :portrait) 30 | end 31 | 32 | it "will synchronize on an attribute update" do 33 | mount "TestComponent" 34 | FactoryBot.create(:test_model, test_attribute: "hello") 35 | page.should have_content("hello") 36 | TestModel.first.update_attribute(:test_attribute, 'goodby') 37 | page.should have_content("goodby") 38 | end 39 | 40 | describe "the .all method" do 41 | before(:each) do 42 | mount "TestComponent" 43 | 5.times { |i| FactoryBot.create(:test_model, test_attribute: "I am item #{i}") } 44 | page.should have_content("5 items") 45 | end 46 | 47 | it "will synchronize on create" do 48 | TestModel.new(test_attribute: "I'm new here!").save 49 | page.should have_content("6 items") 50 | end 51 | 52 | it "will synchronize on destroy" do 53 | TestModel.first.destroy 54 | page.should have_content("4 items") 55 | end 56 | end 57 | 58 | describe "scopes" do 59 | before(:each) do 60 | mount "TestComponent", scope: :active 61 | 5.times { |i| FactoryBot.create(:test_model, test_attribute: "I am item #{i}", completed: false) } 62 | page.should have_content("5 items") 63 | end 64 | 65 | it "will synchronize on create" do 66 | TestModel.new(test_attribute: "I'm new here!", completed: false).save 67 | page.should have_content("6 items") 68 | end 69 | 70 | it "will synchronize on destroy" do 71 | TestModel.first.destroy 72 | page.should have_content("4 items") 73 | end 74 | 75 | it "will syncronize on an update" do 76 | TestModel.first.update_attribute(:completed, true) 77 | page.should have_content("4 items") 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /spec/batch4/zzz_saving_during_commit_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'test_components' 3 | require 'rspec-steps' 4 | 5 | describe "saving during commit", js: true do 6 | 7 | before(:each) do 8 | require 'pusher' 9 | require 'pusher-fake' 10 | Pusher.app_id = "MY_TEST_ID" 11 | Pusher.key = "MY_TEST_KEY" 12 | Pusher.secret = "MY_TEST_SECRET" 13 | require "pusher-fake/support/base" 14 | 15 | Hyperloop.configuration do |config| 16 | config.transport = :pusher 17 | config.channel_prefix = "synchromesh" 18 | config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) 19 | end 20 | 21 | class ActiveRecord::Base 22 | class << self 23 | def public_columns_hash 24 | @public_columns_hash ||= {} 25 | end 26 | end 27 | end 28 | 29 | class CommitIssue < ActiveRecord::Base 30 | def self.build_tables 31 | connection.create_table :commit_issues, force: true do |t| 32 | t.string :name 33 | t.timestamps 34 | end 35 | end 36 | ActiveRecord::Base.public_columns_hash[name] = columns_hash 37 | end 38 | 39 | isomorphic do 40 | class CommitIssue < ActiveRecord::Base 41 | after_create :save_again 42 | def save_again 43 | save 44 | end 45 | end 46 | end 47 | 48 | CommitIssue.build_tables rescue nil 49 | 50 | stub_const 'ApplicationPolicy', Class.new 51 | ApplicationPolicy.class_eval do 52 | always_allow_connection 53 | regulate_all_broadcasts { |policy| policy.send_all } 54 | allow_change(to: :all, on: [:create, :update, :destroy]) { true } 55 | end 56 | size_window(:small, :portrait) 57 | end 58 | 59 | it "broadcast even if saving during after_save" do 60 | CommitIssue.create(name: 1) 61 | mount "CommitIssueTest" do 62 | class CommitIssueTest < React::Component::Base 63 | render do 64 | "all: [#{CommitIssue.all.pluck(:name)}]" 65 | end 66 | end 67 | end 68 | page.should have_content('all: [1]') 69 | CommitIssue.create(name: 2) 70 | page.should have_content('all: [1,2]') 71 | end 72 | 73 | end 74 | -------------------------------------------------------------------------------- /spec/batch5/get_model_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'test_components' 3 | 4 | describe "ReactiveRecord::ServerDataCache.get_model" do 5 | 6 | before(:each) do 7 | ActiveRecord::Base.public_columns_hash 8 | end 9 | 10 | it "will raise an access violation for an unloaded class" do 11 | expect { ReactiveRecord::ServerDataCache.get_model('UnloadedClass') }.to raise_exception 12 | end 13 | 14 | it "will not raise an access violation for an AR model in the Models folder" do 15 | expect(ReactiveRecord::ServerDataCache.get_model('Comment')).to eq Comment 16 | end 17 | 18 | it "will not raise an access violation if the class is already loaded" do 19 | expect(UnloadedClass).to eq ReactiveRecord::ServerDataCache.get_model('UnloadedClass') 20 | end 21 | 22 | end 23 | -------------------------------------------------------------------------------- /spec/batch5/load_from_json_xspec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'test_components' 3 | require 'reactive_record_factory' 4 | require 'rspec-steps' 5 | 6 | RSpec::Steps.steps 'Load From Json', js: true do 7 | 8 | before(:all) do 9 | Hyperloop.configuration do |config| 10 | config.transport = :crud_only 11 | end 12 | seed_database 13 | end 14 | 15 | before(:step) do 16 | # spec_helper resets the policy system after each test so we have to setup 17 | # before each test 18 | stub_const 'TestApplication', Class.new 19 | stub_const 'TestApplicationPolicy', Class.new 20 | TestApplicationPolicy.class_eval do 21 | always_allow_connection 22 | regulate_all_broadcasts { |policy| policy.send_all } 23 | allow_change(to: :all, on: [:create, :update, :destroy]) { true } 24 | end 25 | size_window(:small, :portrait) 26 | end 27 | 28 | it '*all key' do 29 | binding.pry 30 | evaluate_ruby do 31 | User.all.count 32 | end 33 | # ["User", "all", "*count"] -> {'User': {'unscoped': {'*count': [4]}}} 34 | evaluate_ruby do 35 | User.collect { |user| user.id } 36 | end 37 | # ["User", "all", "*all"], ["User", "all", "*0", "id"] -> 38 | # { 39 | # 'User': { 40 | # 'all': { 41 | # 1: {id: [1]}}, 42 | # 2: {id: [2]}}, 43 | # 3: {id: [3]}}, 44 | # 4: {id: [4]}} 45 | # '*all': [1, 2, 3, 4] 46 | # } 47 | # } 48 | # } 49 | evaluate_ruby('User.collect { |x| x.first_name }') 50 | # [["User", ["find_by", {"id":1}], "first_name"], ["User", ["find_by", {"id":2}], "first_name"], ["User", ["find_by", {"id":3}], "first_name"], ["User", ["find_by", {"id":4}], "first_name"]] 51 | # {"User":{"[\"find_by\",{\"id\":1}]":{"first_name":["Mitch"],"id":[1],"type":[null]},"[\"find_by\",{\"id\":2}]":{"first_name":["Todd"],"id":[2],"type":[null]},"[\"find_by\",{\"id\":3}]":{"first_name":["Adam"],"id":[3],"type":[null]},"[\"find_by\",{\"id\":4}]":{"first_name":["Test1"],"id":[4],"type":[null]}}}} 52 | 53 | # [["User", "all", "*all"], ["User", "all", "*0", "first_name"]] -> 54 | # { 55 | # 'User': { 56 | # 'all': { 57 | # 1: {first_name: ['Mitch']}}, 58 | # 2: {first_name: ['Todd']}}, 59 | # 3: {first_name: ['Adam']}}, 60 | # 4: {first_name: ['Test1']}} 61 | # '*all': [1, 2, 3, 4] 62 | # } 63 | # } 64 | # } 65 | evaluate_ruby('User.all[2].first_name') 66 | # works like User.all.each... 67 | # but User.all[2] will insert dummy records for items [0], [1] and [2] 68 | # but then we only look up `first_name` for [2], so only 69 | # the [2] (or *2) vector gets pushed to server 70 | # but strangely this results in the whole array being returned anyway 71 | # same as above. 72 | evaluate_ruby('User.all[1].first_name; User.all[3].first_name') 73 | # actually contains 2 requests, but works the same as above, *1, *3 74 | # resolve to just * on server. so *1, and *3 on client are just way to 75 | # identify the records (via vectors) so on return we correctly match up 76 | # and update record [1] and record[3] for example. Point being is that each 77 | # record has a unique vector... Not sure why that is important... 78 | 79 | end 80 | 81 | end 82 | 83 | # require 'spec_helper' 84 | # require 'test_components' 85 | # require 'reactive_record_factory' 86 | # require 'rspec-steps' 87 | # 88 | # RSpec::Steps.steps 'Load From Json', js: true do 89 | # 90 | # before(:all) do 91 | # seed_database 92 | # end 93 | # 94 | # before(:step) do 95 | # # spec_helper resets the policy system after each test so we have to setup 96 | # # before each test 97 | # stub_const 'TestApplication', Class.new 98 | # stub_const 'TestApplicationPolicy', Class.new 99 | # TestApplicationPolicy.class_eval do 100 | # always_allow_connection 101 | # regulate_all_broadcasts { |policy| policy.send_all } 102 | # allow_change(to: :all, on: [:create, :update, :destroy]) { true } 103 | # end 104 | # size_window(:small, :portrait) 105 | # end 106 | # 107 | # it '*all key' do 108 | # #mount "TestComponent2" 109 | # x = evaluate_ruby do 110 | # User.all 111 | # end 112 | # binding.pry 113 | # end 114 | # 115 | # end 116 | -------------------------------------------------------------------------------- /spec/batch5/save_while_loading_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'test_components' 3 | 4 | describe "save while loading", js: true do 5 | 6 | before(:each) do 7 | # spec_helper resets the policy system after each test so we have to setup 8 | # before each test 9 | stub_const 'TestApplication', Class.new 10 | stub_const 'TestApplicationPolicy', Class.new 11 | TestApplicationPolicy.class_eval do 12 | always_allow_connection 13 | regulate_all_broadcasts { |policy| policy.send_all } 14 | #allow_change(to: User, on: [:update]) { true } 15 | allow_change(to: :all, on: [:create, :update, :destroy]) { true } 16 | end 17 | size_window(:small, :portrait) 18 | end 19 | 20 | it "with new and create" do 21 | user = FactoryBot.create(:user, first_name: 'Ima') 22 | expect_promise do 23 | TodoItem.create(user: User.find_by_first_name('Ima')) 24 | end.to include('success' => true) 25 | expect(user.todo_items.to_a).to match_array([TodoItem.first]) 26 | end 27 | 28 | it "with push" do 29 | user = FactoryBot.create(:user, first_name: 'Ima') 30 | expect_promise do 31 | User.find(1).todo_items << TodoItem.new 32 | User.find(1).save 33 | end.to include('success' => true) 34 | expect(user.todo_items).to match_array([TodoItem.first]) 35 | end 36 | 37 | it "with assignment" do 38 | user = FactoryBot.create(:user, first_name: 'Ima') 39 | expect_promise do 40 | todo = TodoItem.new 41 | todo.user = User.find_by_first_name('Ima') 42 | todo.save 43 | end.to include('success' => true) 44 | expect(user.todo_items).to match_array([TodoItem.first]) 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /spec/batch6/aaa_update_scopes_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'test_components' 3 | require 'reactive_record_factory' 4 | require 'rspec-steps' 5 | 6 | RSpec::Steps.steps "updating scopes", js: true do 7 | 8 | before(:all) do 9 | seed_database 10 | end 11 | 12 | before(:step) do 13 | # spec_helper resets the policy system after each test so we have to setup 14 | # before each test 15 | stub_const 'TestApplication', Class.new 16 | stub_const 'TestApplicationPolicy', Class.new 17 | TestApplicationPolicy.class_eval do 18 | always_allow_connection 19 | regulate_all_broadcasts { |policy| policy.send_all } 20 | allow_change(to: :all, on: [:create, :update, :destroy]) { true } 21 | end 22 | size_window(:small, :portrait) 23 | end 24 | 25 | it "will update .all and rerender after saving a record" do 26 | mount "TestComponent" do 27 | class TestComponent < React::Component::Base 28 | def render 29 | #div do 30 | "TodoItem.count = #{TodoItem.all.count}".span 31 | #ul { TodoItem.each { |todo| li { todo.id.to_s } }} 32 | #end 33 | end 34 | end 35 | end 36 | starting_count = TodoItem.count 37 | expect(page).to have_content("TodoItem.count = #{starting_count}") 38 | evaluate_ruby { TodoItem.new(title: "play it again sam").save } 39 | expect(page).to have_content("TodoItem.count = #{starting_count+1}") 40 | end 41 | 42 | it "destroying records causes a rerender" do 43 | count = TodoItem.count 44 | while count > 0 45 | expect(page).to have_content("TodoItem.count = #{count}") 46 | evaluate_ruby do 47 | ReactiveRecord.load { TodoItem.last }.then { |todo| todo.destroy } 48 | end 49 | count -= 1 50 | end 51 | expect(page).to have_content("TodoItem.count = 0") 52 | end 53 | 54 | end 55 | -------------------------------------------------------------------------------- /spec/batch6/inspect_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'test_components' 3 | require 'reactive_record_factory' 4 | require 'rspec-steps' 5 | 6 | RSpec::Steps.steps 'ActiveRecord::Base.inspect displays', js: true do 7 | before(:all) do 8 | require 'pusher' 9 | require 'pusher-fake' 10 | Pusher.app_id = "MY_TEST_ID" 11 | Pusher.key = "MY_TEST_KEY" 12 | Pusher.secret = "MY_TEST_SECRET" 13 | require "pusher-fake/support/base" 14 | 15 | Hyperloop.configuration do |config| 16 | config.transport = :pusher 17 | config.channel_prefix = "synchromesh" 18 | config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) 19 | end 20 | TodoItem.do_not_synchronize 21 | end 22 | 23 | after(:all) do 24 | ['TodoItem'].each do |klass| 25 | Object.send(:remove_const, klass.to_sym) && load("#{klass.underscore}.rb") rescue nil 26 | end 27 | end 28 | 29 | before(:step) do 30 | stub_const 'ApplicationPolicy', Class.new 31 | ApplicationPolicy.class_eval do 32 | always_allow_connection 33 | regulate_all_broadcasts { |policy| policy.send_all } 34 | allow_change(to: :all, on: [:create, :update, :destroy]) { true } 35 | end 36 | size_window(:small, :portrait) 37 | client_option raise_on_js_errors: :off 38 | end 39 | 40 | it 'shows the backing record id and actual record id' do 41 | backing_record_id = evaluate_ruby( 42 | 'ReactiveRecord::Operations::Base::FORMAT % TodoItem.find(999).backing_record.object_id' 43 | ) 44 | record_id = evaluate_ruby( 45 | 'ReactiveRecord::Operations::Base::FORMAT % TodoItem.find(999).object_id' 46 | ) 47 | expect_evaluate_ruby('TodoItem.find_by_id(999).inspect') 48 | .to match(/\"test\"}\] >/ 55 | end 56 | 57 | it 'loading records with the vector' do 58 | expect_evaluate_ruby do 59 | TodoItem.find_by_title('test2').inspect 60 | end.to match(/\"test2\"}\] >/) 61 | end 62 | 63 | it 'loaded records with the primary key value' do 64 | TodoItem.create(title: 'test3') 65 | expect_promise do 66 | ReactiveRecord.load do 67 | TodoItem.find_by_title('test3').itself 68 | end.then do |loaded_item| 69 | loaded_item.inspect 70 | end 71 | end.to match // 72 | end 73 | 74 | it 'changed records with the new attributes' do 75 | expect_evaluate_ruby do 76 | TodoItem.find_by_title('test3').tap do |todo| 77 | todo.title = 'new title' 78 | todo.user = User.new 79 | end.inspect 80 | end.to match /\[\"test3\", \"new title\"\]}\] >/ 81 | end 82 | 83 | it 'destroyed records with the primary key value' do 84 | expect_promise do 85 | todo = TodoItem.find_by_title('test3') 86 | todo.destroy.then do 87 | todo.inspect 88 | end 89 | end.to match // 90 | end 91 | 92 | it 'new records with the errors after attempting to save' do 93 | TodoItem.validates :title, presence: true 94 | expect_promise do 95 | todo = TodoItem.new(description: 'this has no title') 96 | todo.save.then do 97 | todo.inspect 98 | end 99 | end.to match /\[\"can't be blank\"\]}\] >/ 100 | end 101 | 102 | it 'updated records with the errors after attempting to save' do 103 | expect_promise do 104 | todo = TodoItem.new(title: 'test4') 105 | todo.save.then do 106 | todo.title = nil 107 | todo.save 108 | end.then do 109 | todo.inspect 110 | end 111 | end.to match /\[\"can't be blank\"\]}\] >/ 112 | end 113 | 114 | it 'new records with the errors after attempting to save (deprecated error handler)' do 115 | 116 | evaluate_ruby do 117 | class ReactiveRecord::Base 118 | def errors 119 | @errors ||= ActiveModel::Error.new 120 | end 121 | end 122 | end 123 | 124 | TodoItem.validates :title, presence: true 125 | expect_promise do 126 | todo = TodoItem.new(description: 'this has no title') 127 | todo.save.then do 128 | todo.inspect 129 | end 130 | end.to match /\[\"can't be blank\"\]}\] >/ 131 | end 132 | 133 | it 'updated records with the errors after attempting to save (deprecated error handler)' do 134 | expect_promise do 135 | todo = TodoItem.new(title: 'test5') 136 | todo.save.then do 137 | todo.title = nil 138 | todo.save 139 | end.then do 140 | todo.inspect 141 | end 142 | end.to match /\[\"can't be blank\"\]}\] >/ 143 | end 144 | end 145 | -------------------------------------------------------------------------------- /spec/batch6/on_fetch_error_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'test_components' 3 | 4 | describe "Hyperloop.on_error (for fetches) ", js: true do 5 | 6 | before(:all) do 7 | require 'pusher' 8 | require 'pusher-fake' 9 | Pusher.app_id = "MY_TEST_ID" 10 | Pusher.key = "MY_TEST_KEY" 11 | Pusher.secret = "MY_TEST_SECRET" 12 | require "pusher-fake/support/base" 13 | 14 | Hyperloop.configuration do |config| 15 | config.transport = :pusher 16 | config.channel_prefix = "synchromesh" 17 | config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) 18 | end 19 | 20 | end 21 | 22 | before(:each) do 23 | # spec_helper resets the policy system after each test so we have to setup 24 | # before each test 25 | stub_const 'TestApplicationPolicy', Class.new 26 | TestApplicationPolicy.class_eval do 27 | regulate_class_connection { self } 28 | end 29 | ActiveRecord::Base.regulate_scope unscoped: nil 30 | ApplicationController.acting_user = User.new(first_name: 'fred') 31 | size_window(:large, :landscape) 32 | client_option raise_on_js_errors: :off 33 | end 34 | 35 | after(:each) do 36 | ['ApplicationRecord', 'TodoItem', 'Comment'].each do |klass| 37 | Object.send(:remove_const, klass.to_sym) && load("#{klass.underscore}.rb") rescue nil 38 | end 39 | ApplicationController.acting_user = nil 40 | end 41 | 42 | it 'call Hyperloop.on_error for access violations' do 43 | TodoItem.class_eval do 44 | TodoItem.regulate_relationship(:comments) { acting_user == user } 45 | end 46 | todo_item1 = TodoItem.create(user: ApplicationController.acting_user) 47 | todo_item2 = TodoItem.create(user: nil) 48 | Comment.create(todo_item: todo_item1) 49 | Comment.create(todo_item: todo_item1) 50 | # expect(Hyperloop).to receive(:on_error).once.with( 51 | # Hyperloop::AccessViolation, 52 | # :fetch_error, 53 | # 'acting_user' => ApplicationController.acting_user, 54 | # 'controller' => kind_of(ActionController::Base), 55 | # 'pending_fetches' => [['TodoItem', ['find_by', { 'id' => 2 }], 'comments', '*count']], 56 | # 'models' => [], 57 | # 'associations' => [] 58 | # ) 59 | expect(Hyperloop).to receive(:on_error).once.with( 60 | Hyperloop::AccessViolation, 61 | :scoped_permission_not_granted, 62 | anything 63 | ) 64 | expect_promise("ReactiveRecord.load { TodoItem.find(#{todo_item1.id}).comments.count }") 65 | .to eq(2) 66 | expect_promise("ReactiveRecord.load { TodoItem.find(#{todo_item2.id}).comments.count }") 67 | .not_to eq(0) 68 | end 69 | 70 | it 'call ReactiveRecord.on_fetch_error for errors raised by models' do 71 | TodoItem.class_eval do 72 | def title 73 | raise 'Bogus' 74 | end 75 | end 76 | TodoItem.create(user: nil) 77 | expect(Hyperloop).to receive(:on_error).once.with( 78 | Exception, 79 | :fetch_error, 80 | hash_including(:acting_user, :controller, :pending_fetches, :models, :associations) 81 | ) 82 | evaluate_ruby('TodoItem.find(1).title') 83 | wait_for_ajax 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /spec/batch6/server_method_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'rspec-steps' 3 | 4 | RSpec::Steps.steps 'server_method', js: true do 5 | 6 | before(:each) do 7 | require 'pusher' 8 | require 'pusher-fake' 9 | Pusher.app_id = "MY_TEST_ID" 10 | Pusher.key = "MY_TEST_KEY" 11 | Pusher.secret = "MY_TEST_SECRET" 12 | require "pusher-fake/support/base" 13 | 14 | Hyperloop.configuration do |config| 15 | config.transport = :pusher 16 | config.channel_prefix = "synchromesh" 17 | config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) 18 | end 19 | 20 | #User.do_not_synchronize 21 | end 22 | 23 | before(:step) do 24 | stub_const 'TestApplicationPolicy', Class.new 25 | TestApplicationPolicy.class_eval do 26 | always_allow_connection 27 | regulate_all_broadcasts { |policy| policy.send_all } 28 | allow_change(to: :all, on: [:create, :update, :destroy]) { true } 29 | end 30 | ApplicationController.acting_user = nil 31 | end 32 | 33 | it "can call a server method" do 34 | isomorphic do 35 | TodoItem.class_eval do 36 | class << self 37 | attr_writer :server_method_count 38 | 39 | def server_method_count 40 | @server_method_count ||= 0 41 | end 42 | end 43 | server_method(:test, default: 0) { TodoItem.server_method_count += 1 } 44 | end 45 | TestModel.server_method(:test) { child_models.count } 46 | end 47 | TodoItem.create 48 | mount 'ServerMethodTester' do 49 | class ServerMethodTester < Hyperloop::Component 50 | render(DIV) do 51 | "test = #{TodoItem.first.test}" 52 | end 53 | end 54 | end 55 | expect(page).to have_content('test = 1') 56 | end 57 | 58 | it "can update the server method" do 59 | evaluate_ruby("TodoItem.first.test!") 60 | expect(page).to have_content('test = 2') 61 | end 62 | 63 | it "when updating the server method it returns the current value while waiting for the promise" do 64 | expect_evaluate_ruby("TodoItem.first.test!").to eq(2) 65 | end 66 | 67 | it "returns the default value on the first call while waiting for the promise" do 68 | expect_evaluate_ruby("TodoItem.new.test").to eq(0) 69 | end 70 | 71 | it "works with the load method" do 72 | expect_promise do 73 | new_todo = TodoItem.new 74 | ReactiveRecord.load do 75 | new_todo.test 76 | end 77 | end.to eq(5) 78 | end 79 | 80 | it "the server method can access any unsaved associations" do 81 | expect_promise do 82 | test_model = TestModel.new 83 | ChildModel.new(test_model: test_model) 84 | ReactiveRecord.load do 85 | test_model.test 86 | end 87 | end.to eq(1) 88 | expect(TestModel.count).to be_zero 89 | expect(ChildModel.count).to be_zero 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /spec/batch6/speed_improvement.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'test_components' 3 | #require 'reactive_record_factory' 4 | #require 'rspec-steps' 5 | 6 | #RSpec::Steps.steps 'Load From Json', js: true do 7 | describe "speed tests", js: true do 8 | 9 | def build_records(users, todos_per_user, comments_per_user) 10 | User.destroy_all 11 | Comment.destroy_all 12 | TodoItem.destroy_all 13 | users.times do |u| 14 | user = User.create(name: "User#{u}") 15 | todos_per_user.times do |t| 16 | todo = TodoItem.create(title: "Todo #{u} - #{t}", user: user) 17 | comments_per_user.times do |c| 18 | Comment.create(comment: "Comment #{c} #{u} - #{t}", user: user, todo_item: todo) 19 | end 20 | end 21 | end 22 | end 23 | 24 | def measure(test, users, todos_per_user, comments_per_user) 25 | build_records(users, todos_per_user, comments_per_user) 26 | evaluate_promise("SpeedTester.load_all(#{test})") 27 | end 28 | 29 | before(:all) do 30 | Hyperloop.configuration do |config| 31 | config.transport = :crud_only 32 | end 33 | end 34 | 35 | before(:each) do 36 | stub_const 'TestApplication', Class.new 37 | stub_const 'TestApplicationPolicy', Class.new 38 | TestApplicationPolicy.class_eval do 39 | always_allow_connection 40 | regulate_all_broadcasts { |policy| policy.send_all } 41 | allow_change(to: :all, on: [:create, :update, :destroy]) { true } 42 | end 43 | on_client do 44 | class SpeedTester < Hyperloop::Component 45 | def self.load_all(id) 46 | React::IsomorphicHelpers.load_context 47 | start_time = Time.now 48 | timer_promise = Promise.new 49 | case id 50 | when 1 51 | ReactiveRecord.load do 52 | Comment.all.collect { |todo| todo.id } 53 | end.then do |ids| 54 | ReactiveRecord.load do 55 | ids.each do |id| 56 | Comment.find(id).todo_item.user.name 57 | end 58 | end 59 | end 60 | when 2 61 | ReactiveRecord.load do 62 | User.each do |user| 63 | user.name 64 | user.todo_items.each do |todo| 65 | todo.title 66 | todo.comments.each do |comment| 67 | comment.comment 68 | end 69 | end 70 | end 71 | end 72 | when 3 73 | ReactiveRecord.load do 74 | Comment.all.collect { |todo| todo.id } 75 | end.then do |ids| 76 | ReactiveRecord.load do 77 | ids.each do |id| 78 | Comment.find(id).comment 79 | end 80 | end 81 | end 82 | # puts gets clock to sync otherwise its slightly inaccurate 83 | end.then do 84 | after(0) { timer_promise.resolve(Time.now-start_time) } 85 | end 86 | timer_promise 87 | end 88 | 89 | after_mount do 90 | @start_time = Time.now 91 | end 92 | 93 | render(DIV) do 94 | DIV { "fetched in #{Time.now-@start_time} seconds"} if @start_time 95 | User.each do |user| 96 | LI do 97 | DIV do 98 | user.name.span 99 | UL do 100 | user.todo_items.each do |todo| 101 | LI do 102 | DIV do 103 | todo.title.span 104 | UL do 105 | todo.comments.each do |comment| 106 | LI { comment.comment } 107 | end 108 | end 109 | end 110 | end 111 | end 112 | end 113 | end 114 | end 115 | end 116 | end 117 | end 118 | end 119 | size_window(:large, :landscape) 120 | end 121 | 122 | it "can display 9 items" do 123 | binding.pry 124 | end 125 | end 126 | 127 | =begin 128 | results: 129 | with improvements: 130 | measure(1, 9, 9, 9) 131 | ********* Total Time 10.760204 *********************** 132 | process_vectors: 7.729447 (71)% 133 | building cache_items: 7.713941 (71)% 134 | save_records: 2.569092 (23)% 135 | as_json: 0.457144 (4)% 136 | active_record: 0.44621399999999695 (4)% 137 | apply_method lookup: 0.06301599999999652 (0)% 138 | root_lookup: 0.009902000000000168 (0)% 139 | public_columns_hash: 0.003181 (0)% 140 | ********* Other Time *********************** 141 | measure(1, 7, 7, 7) (1372 data points fetched) vs 107 seconds w/o fixes 142 | ********* Total Time 3.230448 *********************** 143 | process_vectors: 1.854479 (57)% 144 | building cache_items: 1.847171 (57)% 145 | save_records: 1.197451 (37)% 146 | active_record: 0.19789300000000268 (6)% 147 | as_json: 0.175386 (5)% 148 | apply_method lookup: 0.030857000000000908 (0)% 149 | root_lookup: 0.004622000000000015 (0)% 150 | public_columns_hash: 0.001442 (0)% 151 | ********* Other Time *********************** 152 | processed in 2.449s with getters fixed 153 | processed in 2.420s with setters fixed 154 | processed in 2.392s without React set or get state 155 | processed in 1.551s with hashing used instead of detects 156 | =end 157 | -------------------------------------------------------------------------------- /spec/batch7/aaa-unit_tests/aggregation_experiments.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'rspec-steps' 3 | 4 | describe "aggregation experiments" do 5 | it "even AR models (that are aggregates) are immutable" do 6 | user = User.new 7 | address = user.address 8 | address.save 9 | address.reload 10 | user.save 11 | user.reload 12 | expect(user.address).not_to eq(address) 13 | user.address = address 14 | user.save 15 | user.reload 16 | expect(user.address).to eq(address) 17 | end 18 | 19 | it "updating an aggregate does NOT change the container" do 20 | user = User.new 21 | expect { user.address.state = "philly" }.to raise_error 22 | address = user.address 23 | user.address.save 24 | expect(user).not_to be_changed 25 | user.address = address 26 | expect(user).to be_changed 27 | end 28 | 29 | end 30 | -------------------------------------------------------------------------------- /spec/batch7/aaa-unit_tests/aggregations_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'rspec-steps' 3 | 4 | RSpec::Steps.steps 'Aggregation Reflection', js: true do 5 | 6 | it "knows the aggregates class" do 7 | expect_evaluate_ruby do 8 | User.reflect_on_aggregation(:address).klass 9 | end.to eq('Address') 10 | end 11 | 12 | it "knows the aggregates attribute" do 13 | expect_evaluate_ruby do 14 | User.reflect_on_aggregation(:address).attribute 15 | end.to eq('address') 16 | end 17 | 18 | it "knows all the Aggregates" do 19 | expect_evaluate_ruby do 20 | User.reflect_on_all_aggregations.count 21 | end.to eq(3) 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/batch7/aaa-unit_tests/ar_basics_tbdspec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | #Opal::RSpec::Runner.autorun 3 | 4 | class BaseClass < ActiveRecord::Base 5 | end 6 | 7 | class SubClass < BaseClass 8 | end 9 | 10 | class Funky < ActiveRecord::Base 11 | self.primary_key = :funky_id 12 | self.inheritance_column = :funky_type 13 | end 14 | 15 | class BelongsTo < ActiveRecord::Base 16 | belongs_to :has_many 17 | belongs_to :has_one 18 | belongs_to :best_friend, class_name: "HasMany", foreign_key: :bf_id 19 | end 20 | 21 | class HasMany < ActiveRecord::Base 22 | has_many :belongs_to 23 | has_many :best_friends, class_name: "BelongsTo", foreign_key: :bf_id 24 | end 25 | 26 | class HasOne < ActiveRecord::Base 27 | has_one :belongs_to 28 | end 29 | 30 | class Scoped < ActiveRecord::Base 31 | scope :only_those_guys, -> () {} 32 | end 33 | 34 | describe "ActiveRecord" do 35 | 36 | before(:all) { React::IsomorphicHelpers.load_context } 37 | 38 | after(:each) { React::API.clear_component_class_cache } 39 | 40 | # uncomment if you are having trouble with tests failing. One non-async test must pass for things to work 41 | 42 | # describe "a passing dummy test" do 43 | # it "passes" do 44 | # expect(true).to be(true) 45 | # end 46 | # end 47 | 48 | describe "reactive_record active_record base methods" do 49 | 50 | it "will find the base class" do 51 | expect(SubClass.base_class).to eq(BaseClass) 52 | end 53 | 54 | it "knows the primary key" do 55 | expect(BaseClass.primary_key).to eq(:id) 56 | end 57 | 58 | it "can override the primary key" do 59 | expect(Funky.primary_key).to eq(:funky_id) 60 | end 61 | 62 | it "knows the inheritance column" do 63 | expect(BaseClass.inheritance_column).to eq(:type) 64 | end 65 | 66 | it "can override the inheritance column" do 67 | expect(Funky.inheritance_column).to eq(:funky_type) 68 | end 69 | 70 | it "knows the model name" do 71 | expect(BaseClass.model_name).to eq("BaseClass") 72 | end 73 | 74 | it "can find a record by id" do 75 | expect(BaseClass.find(12).id).to eq(12) 76 | end 77 | 78 | it "has a find_by_xxx method" do 79 | expect(BaseClass.find_by_xxx("beer").xxx).to eq("beer") 80 | end 81 | 82 | it "will correctly infer the model type from the inheritance column" do 83 | expect(BaseClass.find_by_type("SubClass").class).to eq(SubClass) 84 | expect(BaseClass.find_by_type(nil).class).to eq(BaseClass) 85 | end 86 | 87 | it "can have a has_many association" do 88 | expect(HasMany.reflect_on_association(:belongs_to).klass.reflect_on_association(:has_many).klass).to eq(HasMany) 89 | end 90 | 91 | it "can have a has_one association" do 92 | expect(HasOne.reflect_on_association(:belongs_to).klass.reflect_on_association(:has_one).klass).to eq(HasOne) 93 | end 94 | 95 | it "can override the class and foreign_key values when creating an association" do 96 | reflection = HasMany.reflect_on_association(:best_friends) 97 | expect(reflection.klass).to eq(BelongsTo) 98 | expect(reflection.association_foreign_key).to eq(:bf_id) 99 | end 100 | 101 | it "can have a scoping method" do 102 | expect(Scoped.only_those_guys.respond_to? :all).to be_truthy 103 | end 104 | 105 | it "can type check parameters" do 106 | expect(SubClass._react_param_conversion({attr1: 1, attr2: 2, type: "SubClass", id: 123}.to_n, :validate_only)).to be(true) 107 | end 108 | 109 | it "can type check parameters with native wrappers" do 110 | expect(SubClass._react_param_conversion(Native({attr1: 1, attr2: 2, type: "SubClass", id: 123}.to_n), :validate_only)).to be(true) 111 | end 112 | 113 | it "will fail type checking if type does not match" do 114 | expect(SubClass._react_param_conversion({attr1: 1, attr2: 2, type: nil, id: 123}.to_n, :validate_only)).to be_falsy 115 | end 116 | 117 | it "will convert a hash to an instance" do 118 | ar = SubClass._react_param_conversion({attr1: 1, attr2: 2, type: "SubClass", id: 123}.to_n) 119 | expect(ar.attr1).to eq(1) 120 | expect(ar.attr2).to eq(2) 121 | expect(ar.id).to eq(123) 122 | end 123 | 124 | end 125 | 126 | end 127 | -------------------------------------------------------------------------------- /spec/batch7/aaa-unit_tests/association_reflection_tbdspec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | #require 'user' 3 | #require 'todo_item' 4 | 5 | 6 | class Thing < ActiveRecord::Base 7 | belongs_to :bucket 8 | end 9 | 10 | class Bucket < ActiveRecord::Base 11 | has_many :things 12 | end 13 | 14 | class OtherThing < ActiveRecord::Base 15 | has_many :things, through: :thing_group 16 | end 17 | 18 | describe "ActiveRecord" do 19 | 20 | after(:each) { React::API.clear_component_class_cache } 21 | 22 | # uncomment if you are having trouble with tests failing. One non-async test must pass for things to work 23 | 24 | # describe "a passing dummy test" do 25 | # it "passes" do 26 | # expect(true).to be(true) 27 | # end 28 | # end 29 | 30 | 31 | describe "Association Reflection" do 32 | 33 | it "knows the foreign key of a belongs_to relationship" do 34 | expect(Thing.reflect_on_association(:bucket).association_foreign_key).to eq(:bucket_id) 35 | end 36 | 37 | it "knows the foreign key of a has_many relationship" do 38 | expect(Bucket.reflect_on_association(:things).association_foreign_key).to eq(:bucket_id) 39 | end 40 | 41 | it "knows the attribute name" do 42 | expect(Bucket.reflect_on_association(:things).attribute).to eq(:things) 43 | end 44 | 45 | it "knows the associated klass" do 46 | expect(Bucket.reflect_on_association(:things).klass).to eq(Thing) 47 | end 48 | 49 | it "knows the macro" do 50 | expect(Bucket.reflect_on_association(:things).macro).to eq(:has_many) 51 | end 52 | 53 | it "knows the inverse" do 54 | expect(Bucket.reflect_on_association(:things).inverse_of).to eq(:bucket) 55 | end 56 | 57 | it "knows if the association is a collection" do 58 | expect(Bucket.reflect_on_association(:things).collection?).to be_truthy 59 | end 60 | 61 | it "knows if the association is not a collection" do 62 | expect(Thing.reflect_on_association(:bucket).collection?).to be_falsy 63 | end 64 | 65 | it "knows the associated klass of a has_many_through relationship" do 66 | expect(OtherThing.reflect_on_association(:things).klass).to eq(Thing) 67 | end 68 | 69 | it "knows a has_many_through is a collection" do 70 | expect(OtherThing.reflect_on_association(:things).collection?).to be_truthy 71 | end 72 | 73 | end 74 | 75 | end 76 | -------------------------------------------------------------------------------- /spec/batch7/aaa-unit_tests/dummy_value_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'test_components' 3 | require 'reactive_record_factory' 4 | require 'rspec-steps' 5 | 6 | RSpec::Steps.steps 'DummyValue', js: true do 7 | before(:step) do 8 | # spec_helper resets the policy system after each test so we have to setup 9 | # before each test 10 | # stub_const 'TestApplication', Class.new 11 | # stub_const 'TestApplicationPolicy', Class.new 12 | # TestApplicationPolicy.class_eval do 13 | # always_allow_connection 14 | # regulate_all_broadcasts { |policy| policy.send_all } 15 | # allow_change(to: :all, on: [:create, :update, :destroy]) { true } 16 | # end 17 | size_window(:small, :portrait) 18 | end 19 | 20 | it 'works with string interpolation (defines a JS .toString method)' do 21 | expect_evaluate_ruby do 22 | column_hash = { default: 'foo', sql_type_metadata: { type: 'text' } } 23 | "value = #{ReactiveRecord::Base::DummyValue.new(column_hash)}" 24 | end.to eq('value = foo') 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/bin/firebug-2.0.13-fx.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruby-hyperloop/hyper-mesh/d29f00b16efbdd25419f07c7590505de4d3ba42a/spec/bin/firebug-2.0.13-fx.xpi -------------------------------------------------------------------------------- /spec/examples/random_examples.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'test_components' 3 | 4 | describe "random examples", js: true do 5 | 6 | it "can pass an array subclass as a param" do 7 | mount "Tester" do 8 | class SubArray < Array 9 | end 10 | 11 | class HelloWorld < React::Component::Base 12 | param :array, type: SubArray 13 | render do 14 | i = 10 15 | div { 16 | div { "params.array.is_a? #{params.array.class}" } 17 | params.array.each {|i| h1 {i.to_s}} 18 | } 19 | end 20 | end 21 | 22 | class Tester < React::Component::Base 23 | def render 24 | normal_array = [1, 2] 25 | sub_array = SubArray.new 26 | sub_array << 1; sub_array << 2 27 | DIV do 28 | # this works 29 | HelloWorld(array: normal_array) 30 | # this doesn't 31 | DIV { "out here a sub_array is a #{sub_array.class}" } 32 | HelloWorld(array: sub_array) 33 | end 34 | end 35 | end 36 | end 37 | pause 38 | end 39 | 40 | it "pass a native hash as a param" do 41 | mount "Tester" do 42 | 43 | class React::RenderingContext 44 | def self.remove_nodes_from_args(args) 45 | args[0].each do |key, value| 46 | begin 47 | value.as_node if value.is_a?(Element) 48 | rescue Exception 49 | end 50 | end if args[0] && args[0].is_a?(Hash) 51 | end 52 | end 53 | 54 | class HelloWorld < React::Component::Base 55 | param :hash 56 | render do 57 | debugger 58 | "hash[key] = #{`#{params.hash['key']}`}" 59 | end 60 | end 61 | 62 | class Tester < React::Component::Base 63 | def render 64 | HelloWorld(hash: `{key: 'the key'}`) 65 | end 66 | end 67 | end 68 | page.should have_content("hash[key] = 'the key'") 69 | pause 70 | end 71 | 72 | 73 | it "can destroy on the fly" do 74 | 75 | 5.times do |i| 76 | FactoryBot.create(:test_model, test_attribute: "I am model #{i}") 77 | end 78 | 79 | mount "RecordsComp" do 80 | class RecordsComp < React::Component::Base 81 | # you had state.credits as an expression... not sure that is what you wanted 82 | render(:div, class: "state.credits") do 83 | h2.title { 'Records' } 84 | #RecordFormComp() 85 | hr { nil } 86 | table.table.table_bordered do 87 | thead { tr { th { 'Date' } 88 | th { 'Title' } 89 | #th { 'Amount' } 90 | th { 'Actions' } } } 91 | tbody do 92 | TestModel.each do |record| 93 | RecordComp key: record[:id], record: record 94 | end 95 | end 96 | end 97 | end 98 | end 99 | 100 | class RecordComp < React::Component::Base 101 | param :key, type: String 102 | param :record, type: TestModel # type is optional here 103 | 104 | def handle_delete 105 | params.record.destroy do |result| 106 | alert 'unable to delete record' unless result 107 | end 108 | end 109 | 110 | def render 111 | tr do 112 | # currently you should access attributes using 113 | # dot notation only. Currently the [] operator 114 | # accesses record values directly without setting up 115 | # any reactive response. This will probably change 116 | # with release of Hypermesh 117 | td { params.record.created_at } 118 | td { params.record.test_attribute } 119 | #td { amount_format(params.record[:amount]) } 120 | td { a.btn.btn_danger { 'Delete' }.on(:click) { handle_delete } } 121 | end 122 | end 123 | 124 | def amount_format(amount) 125 | '$ ' + amount.to_s.reverse.gsub(/...(?!-)(?=.)/,'\&,').reverse 126 | end 127 | end 128 | end 129 | 130 | pause 131 | 132 | end 133 | end 134 | -------------------------------------------------------------------------------- /spec/factories/child_model.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | 3 | factory :child_model 4 | 5 | end 6 | -------------------------------------------------------------------------------- /spec/factories/comment.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | 3 | factory :comment 4 | 5 | end 6 | -------------------------------------------------------------------------------- /spec/factories/test_models.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | 3 | factory :test_model 4 | 5 | end 6 | -------------------------------------------------------------------------------- /spec/factories/todo.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | 3 | factory :todo 4 | 5 | end 6 | -------------------------------------------------------------------------------- /spec/factories/user.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | 3 | factory :user 4 | 5 | end 6 | -------------------------------------------------------------------------------- /spec/failing_tests.txt: -------------------------------------------------------------------------------- 1 | all while_loading specs 2 | -------------------------------------------------------------------------------- /spec/play_ground.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "playground", js: true do 4 | 5 | it "works!" do 6 | mount "SuperTest::Derp" do 7 | module SuperTest 8 | end 9 | 10 | SuperTest.const_set :Derp, Class.new(React::Component::Base) 11 | SuperTest::Derp.class_eval do 12 | inherited(self) 13 | def render 14 | p {"hi"} 15 | end 16 | end 17 | SuperTest::Derp.hypertrace instrument: :all 18 | end 19 | page.should have_content('hi') 20 | pause 21 | end 22 | 23 | it "works! as well" do 24 | 25 | mount "SuperTest::Derp" do 26 | module SuperTest 27 | end 28 | 29 | # YOU HAVE TO have the parens otherwise ruby rules say that 30 | # the block will be sent to const_set (where it is ignored) 31 | 32 | SuperTest.const_set(:Derp, Class.new(React::Component::Base) do 33 | inherited(self) 34 | def render 35 | p {"hi"} 36 | end 37 | end) 38 | SuperTest::Derp.hypertrace instrument: :all 39 | end 40 | page.should have_content('hi') 41 | pause 42 | end 43 | 44 | it "works!?" do 45 | 46 | mount "SuperTest::Derp" do 47 | module SuperTest 48 | end 49 | 50 | SuperTest.const_set(:Derp, Class.new do 51 | #include React::Component 52 | #inherited(self) 53 | def render 54 | p {"hi"} 55 | end 56 | end) 57 | SuperTest::Derp.hypertrace instrument: :all 58 | end 59 | pause 60 | page.should have_content('hi') 61 | pause 62 | end 63 | 64 | end 65 | -------------------------------------------------------------------------------- /spec/reactive_record_factory.rb: -------------------------------------------------------------------------------- 1 | def seed_database 2 | users = [ 3 | ["Mitch", "VanDuyn", "mitch@catprint.com"], 4 | ["Todd", "Russell", "todd@catprint.com"], 5 | ["Adam", "George", "adamg@catprint.com"], 6 | ["Test1", "Test1", "test1@catprint.com"] 7 | ] 8 | 9 | users.each do |first_name, last_name, email| 10 | User.create({ 11 | first_name: first_name, last_name: last_name, email: email, 12 | address_street: "4348 Culver Road", address_city: "Rochester", address_state: "NY", address_zip: "14617" 13 | } 14 | #without_protection: true 15 | ) 16 | end 17 | 18 | todo_items = [ 19 | { 20 | title: "a todo for mitch", 21 | description: "mitch has a big fat todo to do!", 22 | user: User.find_by_email("mitch@catprint.com"), 23 | comments: [{user: User.find_by_email("adamg@catprint.com"), comment: "get it done mitch"}] 24 | }, 25 | { 26 | title: "another todo for mitch", 27 | description: "mitch has too many todos", 28 | user: User.find_by_email("mitch@catprint.com") 29 | }, 30 | { 31 | title: "do it again Todd", 32 | description: "Todd please do that great thing you did again", 33 | user: User.find_by_email("todd@catprint.com") 34 | }, 35 | { 36 | title: "no user todo", 37 | description: "the description" 38 | }, 39 | { 40 | title: "test 1 todo 1", description: "test 1 todo 1", user: User.find_by_email("test1@catprint.com"), 41 | comments: [ 42 | {user: User.find_by_email("mitch@catprint.com"), comment: "test 1 todo 1 comment 1"}, 43 | {user: User.find_by_email("mitch@catprint.com"), comment: "test 1 todo 1 comment 2"} 44 | ] 45 | }, 46 | { 47 | title: "test 1 todo 2", description: "test 1 todo 2", user: User.find_by_email("test1@catprint.com"), 48 | comments: [ 49 | {user: User.find_by_email("mitch@catprint.com"), comment: "test 1 todo 2 comment 1"}, 50 | {user: User.find_by_email("mitch@catprint.com"), comment: "test 1 todo 2 comment 2"} 51 | ] 52 | } 53 | ] 54 | 55 | todo_items.each do |attributes| 56 | comments = attributes.delete(:comments) || [] 57 | todo = TodoItem.create(attributes) #, without_protection: true) 58 | comments.each do |attributes| 59 | Comment.create(attributes.merge(todo_item: todo)) 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /spec/test_app/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 4 | gem 'rails' 5 | # Use sqlite3 as the database for Active Record 6 | # gem 'sqlite3' 7 | gem 'mysql2' 8 | # Use SCSS for stylesheets 9 | # gem 'sass-rails' 10 | # Use Uglifier as compressor for JavaScript assets 11 | # gem 'uglifier' 12 | # Use CoffeeScript for .coffee assets and views 13 | # gem 'coffee-rails' 14 | # See https://github.com/rails/execjs#readme for more supported runtimes 15 | # gem 'mini_racer', platforms: :ruby 16 | 17 | # Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks 18 | gem 'turbolinks' 19 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 20 | gem 'jbuilder' 21 | 22 | # Use ActiveModel has_secure_password 23 | # gem 'bcrypt', '~> 3.1.7' 24 | 25 | # Use Unicorn as the app server 26 | # gem 'unicorn' 27 | 28 | # Use Capistrano for deployment 29 | # gem 'capistrano-rails', group: :development 30 | 31 | group :development, :test do 32 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console 33 | gem 'byebug' 34 | gem 'pry' 35 | gem 'hyper-trace' 36 | end 37 | 38 | group :development do 39 | # Access an IRB console on exception pages or by using <%= console %> in views 40 | gem 'web-console' 41 | # gem 'reactrb-rails-generator' 42 | gem 'puma' 43 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring 44 | gem 'spring' 45 | end 46 | 47 | # gem 'opal' 48 | gem "opal", '>= 0.11.0' 49 | gem "opal-jquery", git: "https://github.com/opal/opal-jquery.git", branch: "master" 50 | gem "opal-rails", '~> 0.9.4' 51 | gem "opal-activesupport", '~> 0.3.1' 52 | gem 'react-rails', '>= 2.4.0', '< 2.5.0' 53 | gem 'mini_racer', platforms: :ruby 54 | gem 'hyper-router' 55 | gem 'hyper-mesh', path: '../..' 56 | gem 'hyperloop-config' #, path: '../../../hyperloop-config' 57 | gem 'hyper-operation' #, path: '../../../hyper-operation' 58 | gem 'hyper-component'#, path: '../../../hyper-component' 59 | gem 'hyper-react' #, path: '../../../hyper-react' 60 | gem 'hyper-store'#, path: '../../../hyper-store' 61 | gem 'opal-browser' 62 | gem 'rspec-rails' 63 | -------------------------------------------------------------------------------- /spec/test_app/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /spec/test_app/app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | //= require 'react' 2 | //= require 'react_ujs' 3 | //= require 'components' 4 | //= require action_cable 5 | //= require 'hyperloop/pusher' 6 | Opal.load('components'); 7 | -------------------------------------------------------------------------------- /spec/test_app/app/assets/javascripts/server_rendering.js: -------------------------------------------------------------------------------- 1 | //= require 'react-server' 2 | //= require 'react_ujs' 3 | //= require 'components' 4 | Opal.load('components') -------------------------------------------------------------------------------- /spec/test_app/app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any styles 10 | * defined in the other CSS/SCSS files in this directory. It is generally better to create a new 11 | * file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | */ 16 | -------------------------------------------------------------------------------- /spec/test_app/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | # Prevent CSRF attacks by raising an exception. 3 | # For APIs, you may want to use :null_session instead. 4 | protect_from_forgery with: :exception 5 | 6 | class << self 7 | attr_accessor :acting_user 8 | end 9 | 10 | def acting_user 11 | ApplicationController.acting_user 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/test_app/app/controllers/test_controller.rb: -------------------------------------------------------------------------------- 1 | class TestController < ApplicationController 2 | def show 3 | render_component 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/test_app/app/models/_react_public_models.rb: -------------------------------------------------------------------------------- 1 | # app/models/_react_public_models.rb 2 | require_tree './public' if RUBY_ENGINE == 'opal' 3 | -------------------------------------------------------------------------------- /spec/test_app/app/models/public/address.rb: -------------------------------------------------------------------------------- 1 | class Address < ActiveRecord::Base 2 | 3 | MAPPED_FIELDS = %w(id street city state zip) 4 | 5 | def self.compose(*args) 6 | new.tap do |address| 7 | MAPPED_FIELDS.each_with_index do |field_name, i| 8 | address.send("#{field_name}=", args[i]) 9 | end 10 | end 11 | end 12 | 13 | end -------------------------------------------------------------------------------- /spec/test_app/app/models/public/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | self.abstract_class = true 3 | end 4 | -------------------------------------------------------------------------------- /spec/test_app/app/models/public/child_model.rb: -------------------------------------------------------------------------------- 1 | class ChildModel < ActiveRecord::Base 2 | belongs_to :test_model 3 | end 4 | -------------------------------------------------------------------------------- /spec/test_app/app/models/public/comment.rb: -------------------------------------------------------------------------------- 1 | class Comment < ActiveRecord::Base 2 | # see spec/examples folder for typically setup 3 | belongs_to :author, class_name: "User" 4 | belongs_to :todoz, class_name: "Todo", foreign_key: :todo_id 5 | end 6 | 7 | class Comment < ActiveRecord::Base 8 | 9 | def create_permitted? 10 | # for testing we allow anything if there is no acting_user 11 | # in the real world you would have something like this: 12 | # acting_user and (acting_user.admin? or user_is? acting_user) 13 | !acting_user or user_is? acting_user 14 | end 15 | 16 | def destroy_permitted? 17 | !acting_user or user_is? acting_user 18 | end 19 | 20 | belongs_to :user 21 | belongs_to :todo_item 22 | 23 | has_one :todo, -> {}, class_name: "TodoItem" # this is just so we can test scopes params and null belongs_to relations 24 | 25 | end 26 | -------------------------------------------------------------------------------- /spec/test_app/app/models/public/default_test.rb: -------------------------------------------------------------------------------- 1 | class DefaultTest < ActiveRecord::Base 2 | def self.build_tables 3 | connection.create_table :default_tests, force: true do |t| 4 | t.string :string, default: "I'm a string!" 5 | t.date :date, default: Date.today 6 | t.datetime :datetime, default: Time.now 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/test_app/app/models/public/test_model.rb: -------------------------------------------------------------------------------- 1 | class TestModel < ActiveRecord::Base 2 | has_many :child_models 3 | scope :completed, -> () { where(completed: true) } 4 | scope :active, -> () { where(completed: false) } 5 | end 6 | -------------------------------------------------------------------------------- /spec/test_app/app/models/public/todo.rb: -------------------------------------------------------------------------------- 1 | class Todo < ActiveRecord::Base 2 | # see spec/examples folder for typically setup 3 | belongs_to :owner, class_name: "User" 4 | belongs_to :created_by, class_name: "User" 5 | has_many :comments 6 | end 7 | -------------------------------------------------------------------------------- /spec/test_app/app/models/public/todo_item.rb: -------------------------------------------------------------------------------- 1 | class TodoItem < ApplicationRecord 2 | 3 | def view_permitted?(attribute) 4 | !acting_user or user_is? acting_user 5 | end 6 | 7 | def update_permitted? 8 | return true unless acting_user 9 | return only_changed? :comments unless user_is? acting_user 10 | true 11 | end 12 | 13 | belongs_to :user 14 | has_many :comments 15 | has_many :commenters, class_name: "User", through: :comments, source: :user 16 | belongs_to :comment # just so we can test an empty belongs_to relationship 17 | 18 | scope :find_string, ->(s) { where("title LIKE ? OR description LIKE ?", "%#{s}%", "%#{s}%") } 19 | 20 | scope :active, -> { where("title LIKE '%mitch%' OR description LIKE '%mitch%'")} 21 | # to_sync :active do |scope, record| 22 | # if record.title =~ /mitch/ || record.description =~ /mitch/ 23 | # scope << record 24 | # else 25 | # scope.delete(record) 26 | # end 27 | # end 28 | 29 | scope :important, -> { where("title LIKE '%another%' OR description LIKE '%another%'")} 30 | # to_sync(:important) {title =~ /another/ || description =~ /another/ } 31 | 32 | def virtual_user_first_name 33 | user.first_name 34 | end unless RUBY_ENGINE == 'opal' 35 | 36 | end 37 | -------------------------------------------------------------------------------- /spec/test_app/app/models/public/type_test.rb: -------------------------------------------------------------------------------- 1 | class TypeTest < ActiveRecord::Base 2 | def self.build_tables 3 | connection.create_table(:type_tests, force: true) do |t| 4 | t.binary(:binary) 5 | t.boolean(:boolean) 6 | t.date(:date) 7 | t.datetime(:datetime) 8 | t.decimal(:decimal, precision: 5, scale: 2) 9 | t.float(:float) 10 | t.integer(:integer) 11 | t.bigint(:bigint) 12 | t.string(:string) 13 | t.text(:text) 14 | t.time(:time) 15 | t.timestamp(:timestamp) 16 | end 17 | end 18 | end -------------------------------------------------------------------------------- /spec/test_app/app/models/public/user.rb: -------------------------------------------------------------------------------- 1 | class User < ActiveRecord::Base 2 | # see spec/examples folder for typically setup 3 | has_many :assigned_todos, class_name: "Todo", foreign_key: :owner_id 4 | has_many :authored_todos, class_name: "Todo", foreign_key: :created_by_id 5 | has_many :commentz, class_name: "Comment", foreign_key: :author_id 6 | belongs_to :manager, class_name: "User" 7 | has_many :employees, class_name: "User", foreign_key: :manager_id 8 | end 9 | 10 | 11 | class TestData 12 | 13 | def initialize(string, times) 14 | @string = string 15 | @times = times 16 | end 17 | 18 | attr_accessor :string 19 | attr_accessor :times 20 | 21 | def big_string 22 | puts "calling big_string #{string} * #{times}" 23 | string * times 24 | end 25 | 26 | end 27 | 28 | class User < ActiveRecord::Base 29 | 30 | def view_permitted?(attribute) 31 | return self == acting_user if acting_user 32 | super # we call super to test if its there (just for the spec) not really the right way to do it, see comments or todo_items 33 | end 34 | 35 | has_many :todo_items 36 | has_many :comments 37 | has_many :commented_on_items, class_name: "TodoItem", through: :comments, source: :todo_item 38 | 39 | composed_of :address, :class_name => 'Address', :constructor => :compose, :mapping => Address::MAPPED_FIELDS.map {|f| ["address_#{f}", f] } 40 | composed_of :address2, :class_name => 'Address', :constructor => :compose, :mapping => Address::MAPPED_FIELDS.map {|f| ["address2_#{f}", f] } 41 | 42 | composed_of :data, :class_name => 'TestData', :allow_nil => true, :mapping => [['data_string', 'string'], ['data_times', 'times']] 43 | 44 | enum test_enum: [:yes, :no] 45 | 46 | def name 47 | "#{first_name} #{last_name}" 48 | end 49 | 50 | # two examples of server side calculated attributes. The second takes a parameter. 51 | # the first does not rely on an id, so can be used before the record is saved. 52 | 53 | def detailed_name 54 | s = "#{first_name[0]}. #{last_name}" rescue "" 55 | s += " - #{email}" if email 56 | s += " (#{todo_items.size} todo#{'s' if todo_items.size > 1})" if todo_items.size > 0 57 | s 58 | end unless RUBY_ENGINE == 'opal' 59 | 60 | def expensive_math(n) 61 | n*n 62 | end unless RUBY_ENGINE == 'opal' 63 | 64 | # this is also used for remote calculation in the aggregate test 65 | 66 | def verify_zip 67 | if address.zip =~ /^\d{5}$/ 68 | address.zip 69 | end 70 | end unless RUBY_ENGINE == 'opal' 71 | 72 | end 73 | 74 | class User < ActiveRecord::Base 75 | 76 | def as_json(*args) 77 | {name: "bozo"} 78 | end 79 | 80 | validates :email, format: {with: /\@.+\./}, :allow_nil => true 81 | 82 | def name=(val) # this is here to test ability to save changes to this type of psuedo attribute 83 | val = val.to_s.split(" ") 84 | self.first_name = val[0] 85 | self.last_name = val[1] 86 | end 87 | 88 | end unless RUBY_ENGINE == 'opal' 89 | -------------------------------------------------------------------------------- /spec/test_app/app/other_classes/unloaded_class.rb: -------------------------------------------------------------------------------- 1 | class UnloadedClass 2 | def am_i_loaded? 3 | true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/test_app/app/policies/auto_loader_test_classa_policy.rb: -------------------------------------------------------------------------------- 1 | class AutoLoaderTestClassaPolicy 2 | always_allow_connection 3 | end 4 | -------------------------------------------------------------------------------- /spec/test_app/app/policies/auto_loader_test_classb_policy.rb: -------------------------------------------------------------------------------- 1 | class AutoLoaderTestClassbPolicy 2 | always_allow_connection 3 | end 4 | -------------------------------------------------------------------------------- /spec/test_app/app/policies/auto_loader_test_classc_policy.rb: -------------------------------------------------------------------------------- 1 | class AutoLoaderTestClassxPolicy 2 | always_allow_connection 3 | end 4 | -------------------------------------------------------------------------------- /spec/test_app/app/policies/auto_loader_test_classd_policy.rb: -------------------------------------------------------------------------------- 1 | class AutoLoaderTestClassyPolicy 2 | always_allow_connection 3 | end 4 | -------------------------------------------------------------------------------- /spec/test_app/app/views/components.rb: -------------------------------------------------------------------------------- 1 | require 'opal' 2 | require 'hyper-component' 3 | if React::IsomorphicHelpers.on_opal_client? 4 | require 'browser' 5 | require 'browser/delay' 6 | require 'browser/interval' 7 | #require 'hyperloop/pusher' 8 | end 9 | require 'hyper-mesh' 10 | require '_react_public_models' 11 | require_tree './components' 12 | 13 | 14 | # require 'opal' 15 | # require 'promise' 16 | # require 'hyper-react' 17 | # if React::IsomorphicHelpers.on_opal_client? 18 | # require 'browser' 19 | # require 'browser/delay' 20 | # end 21 | # require_tree './components' 22 | -------------------------------------------------------------------------------- /spec/test_app/app/views/components/show.rb: -------------------------------------------------------------------------------- 1 | class Show < React::Component::Base 2 | render do 3 | "hello" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/test_app/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | HyperMesh Test App 5 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> 6 | <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> 7 | <%= csrf_meta_tags %> 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /spec/test_app/bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /spec/test_app/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../../config/application', __FILE__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /spec/test_app/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative '../config/boot' 3 | require 'rake' 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /spec/test_app/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | 4 | # path to your application root. 5 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 6 | 7 | Dir.chdir APP_ROOT do 8 | # This script is a starting point to setup your application. 9 | # Add necessary setup steps to this file: 10 | 11 | puts "== Installing dependencies ==" 12 | system "gem install bundler --conservative" 13 | system "bundle check || bundle install" 14 | 15 | # puts "\n== Copying sample files ==" 16 | # unless File.exist?("config/database.yml") 17 | # system "cp config/database.yml.sample config/database.yml" 18 | # end 19 | 20 | puts "\n== Preparing database ==" 21 | system "bin/rake db:setup" 22 | 23 | puts "\n== Removing old logs and tempfiles ==" 24 | system "rm -f log/*" 25 | system "rm -rf tmp/cache" 26 | 27 | puts "\n== Restarting application server ==" 28 | system "touch tmp/restart.txt" 29 | end 30 | -------------------------------------------------------------------------------- /spec/test_app/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Rails.application 5 | -------------------------------------------------------------------------------- /spec/test_app/config/application.rb: -------------------------------------------------------------------------------- 1 | 2 | require 'rails/all' 3 | require File.expand_path('../boot', __FILE__) 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups(assets: %w(development test))) 8 | 9 | require 'opal-rails' 10 | require 'hyper-react' 11 | 12 | module TestApp 13 | class Application < Rails::Application 14 | config.action_cable.allowed_request_origins = [/http\:\/\/127\.0\.0\.1\:[0-9]*/] 15 | config.eager_load_paths += %W(#{config.root}/app/models/public) 16 | config.autoload_paths += %W(#{config.root}/app/models/public) 17 | config.assets.paths << ::Rails.root.join('app', 'models').to_s 18 | config.hyperloop.auto_config = false 19 | config.opal.method_missing = true 20 | config.opal.optimized_operators = true 21 | config.opal.arity_check = false 22 | config.opal.const_missing = true 23 | config.opal.dynamic_require_severity = :ignore 24 | config.opal.enable_specs = true 25 | config.opal.spec_location = 'spec-opal' 26 | 27 | config.assets.cache_store = :null_store 28 | config.hyperloop.auto_config = false 29 | 30 | config.react.server_renderer_options = { 31 | files: ['server_rendering.js'] 32 | } 33 | config.react.server_renderer_directories = ['/app/assets/javascripts'] 34 | 35 | # Settings in config/environments/* take precedence over those specified here. 36 | # Application configuration should go into files in config/initializers 37 | # -- all .rb files in that directory are automatically loaded. 38 | 39 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 40 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 41 | # config.time_zone = 'Central Time (US & Canada)' 42 | 43 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 44 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 45 | # config.i18n.default_locale = :de 46 | 47 | # Do not swallow errors in after_commit/after_rollback callbacks. 48 | #config.active_record.raise_in_transactional_callbacks = true 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /spec/test_app/config/boot.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | gemfile = File.expand_path("../../../../Gemfile", __FILE__) 3 | 4 | ENV['BUNDLE_GEMFILE'] = gemfile 5 | require 'bundler' 6 | Bundler.setup 7 | -------------------------------------------------------------------------------- /spec/test_app/config/cable.yml: -------------------------------------------------------------------------------- 1 | 2 | development: 3 | adapter: async 4 | 5 | test: 6 | adapter: async 7 | 8 | production: 9 | adapter: redis 10 | url: redis://10.10.3.153:6381 11 | -------------------------------------------------------------------------------- /spec/test_app/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | # 7 | default: &default 8 | adapter: mysql2 9 | encoding: utf8 10 | username: root 11 | 12 | development: 13 | <<: *default 14 | database: hyper_mesh_development_db 15 | 16 | test: 17 | <<: *default 18 | database: hyper_mesh_test_db 19 | 20 | production: 21 | <<: *default 22 | database: hyper_mesh_production_db 23 | 24 | # default: &default 25 | # adapter: sqlite3 26 | # pool: 5 27 | # timeout: 10000 28 | # 29 | # development: 30 | # <<: *default 31 | # database: db/development.sqlite3 32 | # 33 | # # Warning: The database defined as "test" will be erased and 34 | # # re-generated from your development database when you run "rake". 35 | # # Do not set this db to the same as development or production. 36 | # test: 37 | # <<: *default 38 | # database: db/test.sqlite3 39 | # 40 | # production: 41 | # <<: *default 42 | # database: db/production.sqlite3 43 | -------------------------------------------------------------------------------- /spec/test_app/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /spec/test_app/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports and disable caching. 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send. 17 | config.action_mailer.raise_delivery_errors = false 18 | 19 | # Print deprecation notices to the Rails logger. 20 | config.active_support.deprecation = :log 21 | 22 | # Raise an error on page load if there are pending migrations. 23 | config.active_record.migration_error = :page_load 24 | 25 | # Debug mode disables concatenation and preprocessing of assets. 26 | # This option may cause significant delays in view rendering with a large 27 | # number of complex assets. 28 | config.assets.debug = false 29 | 30 | # Asset digests allow you to set far-future HTTP expiration dates on all assets, 31 | # yet still be able to expire them through the digest params. 32 | config.assets.digest = true 33 | 34 | # Adds additional error checking when serving assets at runtime. 35 | # Checks for improperly declared sprockets dependencies. 36 | # Raises helpful error messages. 37 | config.assets.raise_runtime_errors = true 38 | 39 | # Raises error for missing translations 40 | # config.action_view.raise_on_missing_translations = true 41 | end 42 | -------------------------------------------------------------------------------- /spec/test_app/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application 18 | # Add `rack-cache` to your Gemfile before enabling this. 19 | # For large-scale production use, consider using a caching reverse proxy like 20 | # NGINX, varnish or squid. 21 | # config.action_dispatch.rack_cache = true 22 | 23 | # Disable serving static files from the `/public` folder by default since 24 | # Apache or NGINX already handles this. 25 | config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present? 26 | 27 | # Compress JavaScripts and CSS. 28 | config.assets.js_compressor = :uglifier 29 | # config.assets.css_compressor = :sass 30 | 31 | # Do not fallback to assets pipeline if a precompiled asset is missed. 32 | config.assets.compile = false 33 | 34 | # Asset digests allow you to set far-future HTTP expiration dates on all assets, 35 | # yet still be able to expire them through the digest params. 36 | config.assets.digest = true 37 | 38 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb 39 | 40 | # Specifies the header that your server uses for sending files. 41 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 42 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 43 | 44 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 45 | # config.force_ssl = true 46 | 47 | # Use the lowest log level to ensure availability of diagnostic information 48 | # when problems arise. 49 | config.log_level = :debug 50 | 51 | # Prepend all log lines with the following tags. 52 | # config.log_tags = [ :subdomain, :uuid ] 53 | 54 | # Use a different logger for distributed setups. 55 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 56 | 57 | # Use a different cache store in production. 58 | # config.cache_store = :mem_cache_store 59 | 60 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 61 | # config.action_controller.asset_host = 'http://assets.example.com' 62 | 63 | # Ignore bad email addresses and do not raise email delivery errors. 64 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 65 | # config.action_mailer.raise_delivery_errors = false 66 | 67 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 68 | # the I18n.default_locale when a translation cannot be found). 69 | config.i18n.fallbacks = true 70 | 71 | # Send deprecation notices to registered listeners. 72 | config.active_support.deprecation = :notify 73 | 74 | # Use default logging formatter so that PID and timestamp are not suppressed. 75 | config.log_formatter = ::Logger::Formatter.new 76 | 77 | # Do not dump schema after migrations. 78 | config.active_record.dump_schema_after_migration = false 79 | end 80 | -------------------------------------------------------------------------------- /spec/test_app/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = false 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure static file server for tests with Cache-Control for performance. 16 | config.public_file_server.enabled = true 17 | config.public_file_server.headers = { 'Cache-Control' => 'public, max-age=3600' } 18 | 19 | # Show full error reports and disable caching. 20 | config.consider_all_requests_local = true 21 | config.action_controller.perform_caching = false 22 | 23 | # Raise exceptions instead of rendering exception templates. 24 | config.action_dispatch.show_exceptions = false 25 | 26 | # Disable request forgery protection in test environment. 27 | config.action_controller.allow_forgery_protection = false 28 | 29 | # Tell Action Mailer not to deliver emails to the real world. 30 | # The :test delivery method accumulates sent emails in the 31 | # ActionMailer::Base.deliveries array. 32 | config.action_mailer.delivery_method = :test 33 | 34 | # Randomize the order test cases are executed. 35 | config.active_support.test_order = :random 36 | 37 | # Print deprecation notices to the stderr. 38 | config.active_support.deprecation = :stderr 39 | 40 | # Raises error for missing translations 41 | # config.action_view.raise_on_missing_translations = true 42 | end 43 | -------------------------------------------------------------------------------- /spec/test_app/config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.0' 5 | 6 | # Add additional assets to the asset load path 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Precompile additional assets. 10 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 11 | Rails.application.config.assets.precompile += %w[time_cop.js] 12 | -------------------------------------------------------------------------------- /spec/test_app/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /spec/test_app/config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.action_dispatch.cookies_serializer = :json 4 | -------------------------------------------------------------------------------- /spec/test_app/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /spec/test_app/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /spec/test_app/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /spec/test_app/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.session_store :cookie_store, key: '_test_app_session' 4 | -------------------------------------------------------------------------------- /spec/test_app/config/initializers/synchromesh.rb: -------------------------------------------------------------------------------- 1 | # require 'pusher' 2 | # Pusher.app_id = "MY_TEST_ID" 3 | # Pusher.key = "MY_TEST_KEY" 4 | # Pusher.secret = "MY_TEST_SECRET" 5 | # require 'pusher-fake' 6 | # 7 | # HyperMesh.configuration do |config| 8 | # config.transport = :pusher 9 | # config.channel_prefix = "synchromesh" 10 | # config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) 11 | # end 12 | class MiniRacer::Context 13 | alias original_eval eval 14 | def eval(str, options=nil) 15 | original_eval str, options 16 | rescue Exception => e 17 | File.write('react_prerendering_src.js', str) rescue nil 18 | raise e 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/test_app/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters) 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /spec/test_app/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more, please read the Rails Internationalization guide 20 | # available at http://guides.rubyonrails.org/i18n.html. 21 | 22 | en: 23 | hello: "Hello world" 24 | -------------------------------------------------------------------------------- /spec/test_app/config/routes.rb: -------------------------------------------------------------------------------- 1 | require 'hyper-mesh' 2 | 3 | Rails.application.routes.draw do 4 | mount Hyperloop::Engine => "/rr" 5 | # The priority is based upon order of creation: first created -> highest priority. 6 | # See how all your routes lay out with "rake routes". 7 | 8 | # You can have the root of your site routed with "root" 9 | # root 'welcome#index' 10 | 11 | get 'test' => 'test#show' 12 | 13 | # Example of regular route: 14 | # get 'products/:id' => 'catalog#view' 15 | 16 | # Example of named route that can be invoked with purchase_url(id: product.id) 17 | # get 'products/:id/purchase' => 'catalog#purchase', as: :purchase 18 | 19 | # Example resource route (maps HTTP verbs to controller actions automatically): 20 | # resources :products 21 | 22 | # Example resource route with options: 23 | # resources :products do 24 | # member do 25 | # get 'short' 26 | # post 'toggle' 27 | # end 28 | # 29 | # collection do 30 | # get 'sold' 31 | # end 32 | # end 33 | 34 | # Example resource route with sub-resources: 35 | # resources :products do 36 | # resources :comments, :sales 37 | # resource :seller 38 | # end 39 | 40 | # Example resource route with more complex sub-resources: 41 | # resources :products do 42 | # resources :comments 43 | # resources :sales do 44 | # get 'recent', on: :collection 45 | # end 46 | # end 47 | 48 | # Example resource route with concerns: 49 | # concern :toggleable do 50 | # post 'toggle' 51 | # end 52 | # resources :posts, concerns: :toggleable 53 | # resources :photos, concerns: :toggleable 54 | 55 | # Example resource route within a namespace: 56 | # namespace :admin do 57 | # # Directs /admin/products/* to Admin::ProductsController 58 | # # (app/controllers/admin/products_controller.rb) 59 | # resources :products 60 | # end 61 | end 62 | -------------------------------------------------------------------------------- /spec/test_app/config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | development: 14 | secret_key_base: ad4964ee73093a0573860466d1e714c8e7b1103d56bbae9e7c7b9843163e51aa9b85c2fef3979a29dbeb0d77a8ade834d77b04890042a45b8121068c2a0e8cba 15 | 16 | test: 17 | secret_key_base: b1cc8e3d36a79eed70d5aa66b05ff6aa8872a43adbeaa8c9f6d21a5bd1b65f231c56a4efadc73c975ccfc5bbd7f238c0db0fe4b5a116353b646cb6de369f8063 18 | 19 | # Do not keep production secrets in the repository, 20 | # instead read values from the environment. 21 | production: 22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 23 | -------------------------------------------------------------------------------- /spec/test_app/db/development.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruby-hyperloop/hyper-mesh/d29f00b16efbdd25419f07c7590505de4d3ba42a/spec/test_app/db/development.sqlite3 -------------------------------------------------------------------------------- /spec/test_app/db/migrate/20160731182106_create_test_models.rb: -------------------------------------------------------------------------------- 1 | class CreateTestModels < ActiveRecord::Migration 2 | def change 3 | create_table :test_models do |t| 4 | t.string :test_attribute 5 | t.boolean :completed 6 | t.timestamps null: false 7 | end 8 | 9 | create_table :child_models do |t| 10 | t.string :child_attribute 11 | t.belongs_to :test_model 12 | end 13 | 14 | create_table :users do |t| 15 | t.string :role 16 | t.references :manager 17 | t.string "first_name" 18 | t.string "last_name" 19 | t.string "email" 20 | t.datetime "created_at" 21 | t.datetime "updated_at" 22 | t.string "address_street" 23 | t.string "address_city" 24 | t.string "address_state" 25 | t.string "address_zip" 26 | t.integer "address_id" 27 | t.string "address2_street" 28 | t.string "address2_city" 29 | t.string "address2_state" 30 | t.string "address2_zip" 31 | t.string "data_string" 32 | t.integer "data_times" 33 | t.integer "test_enum" 34 | end 35 | 36 | create_table :todos do |t| 37 | t.string :title 38 | t.text :description 39 | t.timestamps null: false 40 | t.boolean :completed, default: false, null: false 41 | t.references :created_by 42 | t.references :owner 43 | end 44 | 45 | create_table :comments do |t| 46 | t.text :comment 47 | t.timestamps null: false 48 | t.belongs_to :todo 49 | t.references :author 50 | t.integer "user_id" 51 | t.integer "todo_item_id" 52 | t.datetime "created_at" 53 | t.datetime "updated_at" 54 | end 55 | 56 | create_table "addresses" do |t| 57 | t.string "street" 58 | t.string "city" 59 | t.string "state" 60 | t.string "zip" 61 | t.datetime "created_at" 62 | t.datetime "updated_at" 63 | end 64 | 65 | create_table "todo_items" do |t| 66 | t.string "title" 67 | t.text "description" 68 | t.boolean "complete" 69 | t.datetime "created_at" 70 | t.datetime "updated_at" 71 | t.integer "user_id" 72 | t.integer "comment_id" 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /spec/test_app/db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # Note that this schema.rb definition is the authoritative source for your 6 | # database schema. If you need to create the application database on another 7 | # system, you should be using db:schema:load, not running all the migrations 8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 9 | # you'll amass, the slower it'll run and the greater likelihood for issues). 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(version: 20160731182106) do 14 | 15 | create_table "addresses", force: :cascade do |t| 16 | t.string "street" 17 | t.string "city" 18 | t.string "state" 19 | t.string "zip" 20 | t.datetime "created_at" 21 | t.datetime "updated_at" 22 | end 23 | 24 | create_table "child_models", force: :cascade do |t| 25 | t.string "child_attribute" 26 | t.integer "test_model_id" 27 | end 28 | 29 | create_table "comments", force: :cascade do |t| 30 | t.text "comment" 31 | t.datetime "created_at" 32 | t.datetime "updated_at" 33 | t.integer "todo_id" 34 | t.integer "author_id" 35 | t.integer "user_id" 36 | t.integer "todo_item_id" 37 | end 38 | 39 | create_table "hyperloop_connections", force: :cascade do |t| 40 | t.string "channel" 41 | t.string "session" 42 | t.datetime "created_at" 43 | t.datetime "expires_at" 44 | t.datetime "refresh_at" 45 | end 46 | 47 | create_table "hyperloop_queued_messages", force: :cascade do |t| 48 | t.text "data" 49 | t.integer "connection_id" 50 | end 51 | 52 | create_table "test_models", force: :cascade do |t| 53 | t.string "test_attribute" 54 | t.boolean "completed" 55 | t.datetime "created_at", null: false 56 | t.datetime "updated_at", null: false 57 | end 58 | 59 | create_table "todo_items", force: :cascade do |t| 60 | t.string "title" 61 | t.text "description" 62 | t.boolean "complete" 63 | t.datetime "created_at" 64 | t.datetime "updated_at" 65 | t.integer "user_id" 66 | t.integer "comment_id" 67 | end 68 | 69 | create_table "todos", force: :cascade do |t| 70 | t.string "title" 71 | t.text "description" 72 | t.datetime "created_at", null: false 73 | t.datetime "updated_at", null: false 74 | t.boolean "completed", default: false, null: false 75 | t.integer "created_by_id" 76 | t.integer "owner_id" 77 | end 78 | 79 | create_table "users", force: :cascade do |t| 80 | t.string "role" 81 | t.integer "manager_id" 82 | t.string "first_name" 83 | t.string "last_name" 84 | t.string "email" 85 | t.datetime "created_at" 86 | t.datetime "updated_at" 87 | t.string "address_street" 88 | t.string "address_city" 89 | t.string "address_state" 90 | t.string "address_zip" 91 | t.integer "address_id" 92 | t.string "address2_street" 93 | t.string "address2_city" 94 | t.string "address2_state" 95 | t.string "address2_zip" 96 | t.string "data_string" 97 | t.integer "data_times" 98 | t.integer "test_enum" 99 | end 100 | 101 | end 102 | -------------------------------------------------------------------------------- /spec/test_app/db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) 7 | # Mayor.create(name: 'Emanuel', city: cities.first) 8 | -------------------------------------------------------------------------------- /spec/test_app/lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruby-hyperloop/hyper-mesh/d29f00b16efbdd25419f07c7590505de4d3ba42a/spec/test_app/lib/assets/.keep -------------------------------------------------------------------------------- /spec/test_app/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /spec/test_app/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /spec/test_app/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /spec/test_app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruby-hyperloop/hyper-mesh/d29f00b16efbdd25419f07c7590505de4d3ba42a/spec/test_app/public/favicon.ico -------------------------------------------------------------------------------- /spec/test_components.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.before(:all) do 3 | on_client do 4 | class TestComponent < React::Component::Base 5 | param scope: :all 6 | render(:div) do 7 | div { "#{TestModel.send(params.scope).count} items" } 8 | ul { TestModel.send(params.scope).each { |model| li { model.test_attribute }}} 9 | end 10 | end 11 | class TestComponent2 < React::Component::Base 12 | render do 13 | "hello" 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /terminal.md: -------------------------------------------------------------------------------- 1 | console helpfulness 2 | 3 | need to make things work with rails console, and async mode. 4 | 5 | Okay... 6 | 7 | detect where we are... am I running the server, or the console... if console then I do a post to server 8 | 9 | after-commit/change-or-destroy/model/id 10 | 11 | 12 | 13 | execute `rubycon` to bring up console in a new window 14 | 15 | uses 16 | 17 | 18 | 19 | the rubycon function will bring up a new browser window running the terminal emulator. 20 | 21 | the rubycon function will intercept all console.log/warn/error function calls and send them to the server, the server will push these out 22 | 23 | As each line is typed it is passed to the opal compiler 24 | 25 | once the compiler can parse the input it will be compiled to JS, and sent the server 26 | 27 | the server will then push using a dedicated debug channel out to the browser window. 28 | 29 | any result will be be sent back to the console. 30 | 31 | get new-console/clientid 32 | 33 | post client-to-console/clientid log-message 34 | post console-to-client/clientid js-code-string 35 | post console-to-server/clientid js-code-string (only accepts in dev mode) 36 | 37 | channel ClientToConsole-clientid log-message 38 | channel ConsoleToClient-clientid js-code-string 39 | 40 | 1) `rubycon()` will generate a GUID 41 | 2) then open a popup window with url syncromesh-root/new-console/GUID 42 | 3) then open channel connection ConsoleToClient-GUID 43 | 4) then patch window.log/warn/error 44 | 5) the `new-console` page will bring up a terminal window 45 | 6) then open channel connection ClientToConsole-GUID 46 | 47 | when either channel terminates, that will get resent to the other channel 48 | 7) the client window will disconnect the log/warn/error window, and disconnect the channel 49 | 8) the console window will close 50 | 51 | 1-4 + 7 are in a class RubyCon 52 | 5-6 + 8 use hyper-react-express in SAP 53 | 54 | 55 | 56 | 57 | nothing works whilst in debugger 58 | 59 | sooo... one place to hook in is render: if in debug mode when hitting render we render out a message, then wait for an event, then message the debugger with the component... debugger can poke around in the component, see what the state is, change state, whatever.. in otherwords we want to execute some opal code as if self were that component... fine! debugger can set state and finally leave. 60 | 61 | so render method is wrapped like this: 62 | 63 | if React::State(Debugger, :debugging_in_session) 64 | "DEBUGGING IN SESSSION" 65 | else 66 | ...normal code... 67 | -------------------------------------------------------------------------------- /work-in-progress-drinking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruby-hyperloop/hyper-mesh/d29f00b16efbdd25419f07c7590505de4d3ba42a/work-in-progress-drinking.png --------------------------------------------------------------------------------