├── .gitignore ├── .rspec ├── .rubocop.yml ├── .ruby-version ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Gemfile ├── Guardfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── app └── volt │ ├── assets │ ├── css │ │ └── notices.scss │ └── js │ │ ├── jquery-2.0.3.js │ │ ├── volt_js_polyfills.js │ │ └── volt_watch.js │ ├── config │ └── dependencies.rb │ ├── controllers │ └── notices_controller.rb │ ├── models │ ├── active_volt_instance.rb │ ├── user.rb │ └── volt_app_property.rb │ ├── tasks │ ├── live_query │ │ ├── live_query.rb │ │ ├── live_query_pool.rb │ │ └── query_tracker.rb │ ├── query_tasks.rb │ ├── store_tasks.rb │ └── user_tasks.rb │ └── views │ └── notices │ └── index.html ├── bin └── volt ├── docs ├── UPGRADE_GUIDE.md └── volt-logo.jpg ├── lib ├── volt.rb └── volt │ ├── assets │ └── test.rb │ ├── benchmark │ └── benchmark.rb │ ├── boot.rb │ ├── cli.rb │ ├── cli │ ├── asset_compile.rb │ ├── base_index_renderer.rb │ ├── bundle.rb │ ├── console.rb │ ├── destroy.rb │ ├── generate.rb │ ├── generators.rb │ ├── new_gem.rb │ └── runner.rb │ ├── config.rb │ ├── controllers │ ├── collection_helpers.rb │ ├── http_controller.rb │ ├── http_controller │ │ └── http_cookie_persistor.rb │ ├── login_as_helper.rb │ ├── model_controller.rb │ └── template_helpers.rb │ ├── data_stores │ ├── base_adaptor_client.rb │ ├── base_adaptor_server.rb │ └── data_store.rb │ ├── extra_core │ ├── array.rb │ ├── blank.rb │ ├── class.rb │ ├── extra_core.rb │ ├── hash.rb │ ├── inflections.rb │ ├── inflector.rb │ ├── inflector │ │ ├── inflections.rb │ │ └── methods.rb │ ├── logger.rb │ ├── object.rb │ ├── string.rb │ ├── stringify_keys.rb │ └── symbol.rb │ ├── helpers │ ├── time.rb │ └── time │ │ ├── calculations.rb │ │ ├── distance.rb │ │ ├── duration.rb │ │ ├── local_calculations.rb │ │ ├── local_volt_time.rb │ │ ├── numeric.rb │ │ └── volt_time.rb │ ├── models.rb │ ├── models │ ├── array_model.rb │ ├── associations.rb │ ├── buffer.rb │ ├── cursor.rb │ ├── errors.rb │ ├── field_helpers.rb │ ├── helpers │ │ ├── array_model.rb │ │ ├── base.rb │ │ ├── change_helpers.rb │ │ ├── dirty.rb │ │ ├── listener_tracker.rb │ │ └── model.rb │ ├── location.rb │ ├── model.rb │ ├── model_hash_behaviour.rb │ ├── model_wrapper.rb │ ├── permissions.rb │ ├── persistors │ │ ├── array_store.rb │ │ ├── base.rb │ │ ├── cookies.rb │ │ ├── flash.rb │ │ ├── local_store.rb │ │ ├── model_identity_map.rb │ │ ├── model_store.rb │ │ ├── page.rb │ │ ├── params.rb │ │ ├── query │ │ │ ├── normalizer.rb │ │ │ ├── query_listener.rb │ │ │ └── query_listener_pool.rb │ │ ├── store.rb │ │ ├── store_factory.rb │ │ └── store_state.rb │ ├── root_models │ │ ├── root_models.rb │ │ └── store_root.rb │ ├── state_manager.rb │ ├── url.rb │ ├── validations │ │ ├── errors.rb │ │ └── validations.rb │ └── validators │ │ ├── email_validator.rb │ │ ├── format_validator.rb │ │ ├── inclusion_validator.rb │ │ ├── length_validator.rb │ │ ├── numericality_validator.rb │ │ ├── phone_number_validator.rb │ │ ├── presence_validator.rb │ │ ├── type_validator.rb │ │ ├── unique_validator.rb │ │ └── user_validation.rb │ ├── page │ ├── bindings │ │ ├── attribute_binding.rb │ │ ├── base_binding.rb │ │ ├── bindings.rb │ │ ├── component_binding.rb │ │ ├── content_binding.rb │ │ ├── each_binding.rb │ │ ├── event_binding.rb │ │ ├── html_safe │ │ │ └── string_extension.rb │ │ ├── if_binding.rb │ │ ├── view_binding.rb │ │ ├── view_binding │ │ │ ├── controller_handler.rb │ │ │ ├── grouped_controllers.rb │ │ │ └── view_lookup_for_path.rb │ │ └── yield_binding.rb │ ├── channel.rb │ ├── channel_stub.rb │ ├── document.rb │ ├── document_events.rb │ ├── path_string_renderer.rb │ ├── string_template_renderer.rb │ ├── sub_context.rb │ ├── targets │ │ ├── attribute_section.rb │ │ ├── attribute_target.rb │ │ ├── base_section.rb │ │ ├── binding_document │ │ │ ├── base_node.rb │ │ │ ├── component_node.rb │ │ │ ├── html_node.rb │ │ │ └── tag_node.rb │ │ ├── dom_section.rb │ │ ├── dom_target.rb │ │ ├── dom_template.rb │ │ └── helpers │ │ │ └── comment_searchers.rb │ ├── tasks.rb │ ├── template_renderer.rb │ └── url_tracker.rb │ ├── reactive │ ├── class_eventable.rb │ ├── computation.rb │ ├── dependency.rb │ ├── eventable.rb │ ├── hash_dependency.rb │ ├── reactive_accessors.rb │ ├── reactive_array.rb │ └── reactive_hash.rb │ ├── router │ └── routes.rb │ ├── server.rb │ ├── server │ ├── banner.txt │ ├── component_templates.rb │ ├── forking_server.rb │ ├── forking_server │ │ └── boot_error.html.erb │ ├── html_parser │ │ ├── attribute_scope.rb │ │ ├── component_view_scope.rb │ │ ├── each_scope.rb │ │ ├── if_view_scope.rb │ │ ├── sandlebars_parser.rb │ │ ├── textarea_scope.rb │ │ ├── view_handler.rb │ │ ├── view_parser.rb │ │ └── view_scope.rb │ ├── message_bus │ │ ├── base_message_bus.rb │ │ ├── message_encoder.rb │ │ ├── peer_to_peer.rb │ │ ├── peer_to_peer │ │ │ ├── peer_connection.rb │ │ │ ├── peer_server.rb │ │ │ ├── server_tracker.rb │ │ │ └── socket_with_timeout.rb │ │ └── redis.rb │ ├── middleware │ │ ├── default_middleware_stack.rb │ │ └── middleware_stack.rb │ ├── rack │ │ ├── asset_files.rb │ │ ├── component_code.rb │ │ ├── component_paths.rb │ │ ├── http_content_types.rb │ │ ├── http_request.rb │ │ ├── http_resource.rb │ │ ├── http_response_header.rb │ │ ├── http_response_renderer.rb │ │ ├── index_files.rb │ │ ├── keep_alive.rb │ │ ├── opal_files.rb │ │ ├── quiet_common_logger.rb │ │ ├── source_map_server.rb │ │ └── sprockets_helpers_setup.rb │ ├── socket_connection_handler.rb │ ├── socket_connection_handler_stub.rb │ ├── template_handlers │ │ ├── preprocessors.rb │ │ ├── sprockets_component_handler.rb │ │ └── view_processor.rb │ └── websocket │ │ ├── rack_server_adaptor.rb │ │ └── websocket_handler.rb │ ├── spec │ ├── capybara.rb │ ├── sauce_labs.rb │ └── setup.rb │ ├── tasks │ ├── dispatcher.rb │ └── task.rb │ ├── utils │ ├── boolean_patch.rb │ ├── csso_patch.rb │ ├── data_transformer.rb │ ├── ejson.rb │ ├── event_counter.rb │ ├── generic_counting_pool.rb │ ├── generic_pool.rb │ ├── lifecycle_callbacks.rb │ ├── local_storage.rb │ ├── logging │ │ ├── task_argument_filterer.rb │ │ └── task_logger.rb │ ├── modes.rb │ ├── parsing.rb │ ├── promise_extensions.rb │ ├── read_write_lock.rb │ ├── recursive_exists.rb │ ├── tilt_patch.rb │ ├── time_opal_patch.rb │ ├── time_patch.rb │ ├── timers.rb │ └── volt_user_error.rb │ ├── version.rb │ └── volt │ ├── app.rb │ ├── client_setup │ └── browser.rb │ ├── core.rb │ ├── environment.rb │ ├── properties.rb │ ├── repos.rb │ ├── server_setup │ └── app.rb │ ├── templates.rb │ └── users.rb ├── spec ├── apps │ ├── file_loading │ │ └── app │ │ │ ├── bootstrap │ │ │ └── assets │ │ │ │ └── js │ │ │ │ └── bootstrap.js │ │ │ ├── disable_auto │ │ │ ├── assets │ │ │ │ ├── css │ │ │ │ │ ├── test1.css.scss │ │ │ │ │ └── test2.css.scss │ │ │ │ └── js │ │ │ │ │ ├── test1.js │ │ │ │ │ └── test2.js │ │ │ └── config │ │ │ │ └── dependencies.rb │ │ │ ├── main │ │ │ ├── assets │ │ │ │ ├── css │ │ │ │ │ └── test3.css │ │ │ │ └── js │ │ │ │ │ └── test1.js │ │ │ └── config │ │ │ │ └── dependencies.rb │ │ │ ├── missing_deps │ │ │ └── config │ │ │ │ └── dependencies.rb │ │ │ ├── shared │ │ │ ├── assets │ │ │ │ └── js │ │ │ │ │ └── test2.js │ │ │ └── config │ │ │ │ └── dependencies.rb │ │ │ └── slideshow │ │ │ └── assets │ │ │ └── js │ │ │ └── test3.js │ └── kitchen_sink │ │ ├── .gitignore │ │ ├── Gemfile │ │ ├── app │ │ └── main │ │ │ ├── assets │ │ │ ├── css │ │ │ │ ├── app.scss │ │ │ │ └── todos.css │ │ │ └── images │ │ │ │ └── volt-logo.jpg │ │ │ ├── config │ │ │ ├── dependencies.rb │ │ │ ├── initializers │ │ │ │ └── sample_model_extend.rb │ │ │ └── routes.rb │ │ │ ├── controllers │ │ │ ├── events_controller.rb │ │ │ ├── main_controller.rb │ │ │ ├── save_controller.rb │ │ │ ├── server │ │ │ │ └── simple_http_controller.rb │ │ │ ├── todos_controller.rb │ │ │ ├── upload_controller.rb │ │ │ └── yield_component_controller.rb │ │ │ ├── models │ │ │ ├── post.rb │ │ │ └── user.rb │ │ │ ├── tasks │ │ │ └── login_tasks.rb │ │ │ └── views │ │ │ ├── events │ │ │ └── index.html │ │ │ ├── mailers │ │ │ ├── reset_password.html │ │ │ └── welcome.email │ │ │ ├── main │ │ │ ├── bindings.html │ │ │ ├── callbacks.html │ │ │ ├── cookie_test.html │ │ │ ├── first_last.html │ │ │ ├── flash.html │ │ │ ├── form.html │ │ │ ├── html_safe.html │ │ │ ├── images.html │ │ │ ├── index.html │ │ │ ├── login_from_task.html │ │ │ ├── main.html │ │ │ ├── missing.html │ │ │ ├── require_test.html │ │ │ ├── store_demo.html │ │ │ └── yield.html │ │ │ ├── save │ │ │ └── index.html │ │ │ ├── todos │ │ │ └── index.html │ │ │ ├── upload │ │ │ └── index.html │ │ │ └── yield_component │ │ │ └── index.html │ │ ├── config.ru │ │ └── config │ │ ├── app.rb │ │ └── base │ │ └── index.html ├── controllers │ ├── http_controller_spec.rb │ ├── model_controller_spec.rb │ └── reactive_accessors_spec.rb ├── extra_core │ ├── array_spec.rb │ ├── blank_spec.rb │ ├── class_spec.rb │ ├── hash_spec.rb │ ├── inflector_spec.rb │ ├── logger_spec.rb │ ├── object_spec.rb │ ├── string_transformation_test_cases.rb │ ├── string_transformations_spec.rb │ └── symbol_spec.rb ├── helpers │ ├── distance_spec.rb │ ├── duration_spec.rb │ └── volt_time_spec.rb ├── integration │ ├── bindings_spec.rb │ ├── callbacks_spec.rb │ ├── client_require_spec.rb │ ├── cookies_spec.rb │ ├── event_spec.rb │ ├── first_last_spec.rb │ ├── flash_spec.rb │ ├── http_endpoints_spec.rb │ ├── images_spec.rb │ ├── list_spec.rb │ ├── missing_spec.rb │ ├── raw_html_binding.rb │ ├── save_spec.rb │ ├── store_spec.rb │ ├── templates_spec.rb │ ├── todos_spec.rb │ ├── url_spec.rb │ ├── user_spec.rb │ └── yield_spec.rb ├── models │ ├── array_model_spec.rb │ ├── associations_spec.rb │ ├── buffer_spec.rb │ ├── dirty_spec.rb │ ├── field_helpers_spec.rb │ ├── helpers │ │ ├── base_spec.rb │ │ └── model_spec.rb │ ├── model_spec.rb │ ├── model_state_spec.rb │ ├── permissions_spec.rb │ ├── persistors │ │ ├── flash_spec.rb │ │ ├── page_spec.rb │ │ ├── params_spec.rb │ │ └── store_spec.rb │ ├── url_spec.rb │ ├── user_spec.rb │ ├── user_validation_spec.rb │ ├── validations_spec.rb │ └── validators │ │ ├── block_validations_spec.rb │ │ ├── email_validator_spec.rb │ │ ├── format_validator_spec.rb │ │ ├── inclusion_validator_spec.rb │ │ ├── length_validator_spec.rb │ │ ├── phone_number_validator_spec.rb │ │ ├── shared_examples_for_validators.rb │ │ ├── type_validator_spec.rb │ │ └── unique_validator_spec.rb ├── page │ ├── bindings │ │ ├── content_binding_spec.rb │ │ ├── each_binding_spec.rb │ │ ├── if_binding_spec.rb │ │ ├── template_binding │ │ │ └── view_lookup_for_path_spec.rb │ │ └── template_binding_spec.rb │ ├── path_string_renderer_spec.rb │ └── sub_context_spec.rb ├── reactive │ ├── class_eventable_spec.rb │ ├── computation_spec.rb │ ├── dependency_spec.rb │ ├── eventable_spec.rb │ ├── reactive_array_spec.rb │ └── reactive_hash_spec.rb ├── router │ └── routes_spec.rb ├── server │ ├── component_templates_spec.rb │ ├── forking_server_spec.rb │ ├── html_parser │ │ ├── sample_page.html │ │ ├── sandlebars_parser_spec.rb │ │ ├── view_handler_spec.rb │ │ ├── view_parser_spec.rb │ │ └── view_scope_spec.rb │ ├── message_bus │ │ ├── message_encoder_spec.rb │ │ ├── peer_to_peer │ │ │ ├── peer_connection_spec.rb │ │ │ ├── peer_server_spec.rb │ │ │ └── socket_with_timeout_spec.rb │ │ └── peer_to_peer_spec.rb │ ├── middleware │ │ ├── middleware_stack_spec.rb │ │ └── rack_content_types_spec.rb │ ├── rack │ │ ├── asset_files_spec.rb │ │ ├── component_paths_spec.rb │ │ ├── http_request_spec.rb │ │ ├── http_resource_spec.rb │ │ ├── http_response_header_spec.rb │ │ ├── http_response_renderer_spec.rb │ │ ├── quite_common_logger_spec.rb │ │ ├── rack_requests_spec.rb │ │ └── sprockets_helpers_setup.rb │ └── socket_connection_handler_spec.rb ├── spec_helper.rb ├── tasks │ ├── dispatcher_spec.rb │ ├── live_query_spec.rb │ ├── query_tasks.rb │ ├── query_tracker_spec.rb │ └── user_tasks_spec.rb ├── templates │ └── targets │ │ └── binding_document │ │ └── component_node_spec.rb ├── utils │ ├── data_transformer_spec.rb │ ├── ejson_spec.rb │ ├── ejson_volt_time_spec.rb │ ├── generic_counting_pool_spec.rb │ ├── generic_pool_spec.rb │ ├── lifecycle_callbacks_spec.rb │ ├── parsing_spec.rb │ ├── promise_extensions_spec.rb │ └── task_argument_filtererer_spec.rb └── volt │ └── repos_spec.rb ├── templates ├── .gitignore ├── component │ ├── assets │ │ ├── css │ │ │ └── .empty_directory │ │ ├── images │ │ │ └── .empty_directory │ │ └── js │ │ │ └── .empty_directory │ ├── config │ │ ├── dependencies.rb │ │ ├── initializers │ │ │ ├── boot.rb │ │ │ ├── client │ │ │ │ └── .empty_directory │ │ │ └── server │ │ │ │ └── .empty_directory │ │ └── routes.rb │ ├── controllers │ │ ├── main_controller.rb.tt │ │ └── server │ │ │ └── .empty_directory │ ├── lib │ │ └── .empty_directory │ ├── models │ │ └── .empty_directory │ ├── tasks │ │ └── .empty_directory │ └── views │ │ └── main │ │ └── index.html.tt ├── component_specs │ ├── controllers │ │ └── server │ │ │ └── .empty_directory │ ├── integration │ │ └── .empty_directory │ ├── models │ │ └── .empty_directory │ └── tasks │ │ └── .empty_directory ├── controller │ ├── http_controller.rb.tt │ ├── http_controller_spec.rb.tt │ ├── model_controller.rb.tt │ └── model_controller_spec.rb.tt ├── model │ ├── model.rb.tt │ └── model_spec.rb.tt ├── newgem │ ├── CODE_OF_CONDUCT.md.tt │ ├── Gemfile.tt │ ├── LICENSE.txt.tt │ ├── README.md.tt │ ├── Rakefile.tt │ ├── app │ │ └── newgem │ │ │ ├── assets │ │ │ ├── css │ │ │ │ └── .empty_directory │ │ │ └── js │ │ │ │ └── .empty_directory │ │ │ ├── config │ │ │ ├── dependencies.rb │ │ │ ├── initializers │ │ │ │ ├── boot.rb │ │ │ │ ├── client │ │ │ │ │ └── .empty_directory │ │ │ │ └── server │ │ │ │ │ └── .empty_directory │ │ │ └── routes.rb │ │ │ ├── controllers │ │ │ ├── main_controller.rb.tt │ │ │ └── server │ │ │ │ └── .empty_directory │ │ │ ├── lib │ │ │ └── .empty_directory │ │ │ ├── models │ │ │ └── .empty_directory │ │ │ ├── tasks │ │ │ └── .empty_directory │ │ │ └── views │ │ │ └── main │ │ │ └── index.html │ ├── bin │ │ └── newgem.tt │ ├── gitignore.tt │ ├── lib │ │ ├── newgem.rb.tt │ │ └── newgem │ │ │ ├── .empty_directory │ │ │ └── version.rb.tt │ ├── newgem.gemspec.tt │ ├── rspec.tt │ ├── spec │ │ ├── integration │ │ │ └── sample_integration_spec.rb │ │ ├── newgem_spec.rb.tt │ │ └── spec_helper.rb.tt │ └── test │ │ ├── minitest_helper.rb.tt │ │ └── test_newgem.rb.tt ├── project │ ├── .gitignore │ ├── Gemfile.tt │ ├── README.md.tt │ ├── app │ │ └── main │ │ │ ├── assets │ │ │ ├── css │ │ │ │ ├── .empty_directory │ │ │ │ └── app.css.scss │ │ │ ├── images │ │ │ │ └── .empty_directory │ │ │ └── js │ │ │ │ └── .empty_directory │ │ │ ├── config │ │ │ ├── dependencies.rb │ │ │ ├── initializers │ │ │ │ ├── boot.rb │ │ │ │ ├── client │ │ │ │ │ └── .empty_directory │ │ │ │ └── server │ │ │ │ │ └── .empty_directory │ │ │ └── routes.rb │ │ │ ├── controllers │ │ │ └── main_controller.rb │ │ │ ├── lib │ │ │ └── .empty_directory │ │ │ ├── models │ │ │ └── user.rb │ │ │ ├── tasks │ │ │ └── .empty_directory │ │ │ └── views │ │ │ └── main │ │ │ ├── about.html │ │ │ ├── index.html │ │ │ └── main.html.tt │ ├── config.ru │ ├── config │ │ ├── app.rb.tt │ │ ├── base │ │ │ └── index.html │ │ └── initializers │ │ │ └── boot.rb │ └── spec │ │ ├── app │ │ └── main │ │ │ ├── controllers │ │ │ └── server │ │ │ │ └── sample_http_controller_spec.rb │ │ │ ├── integration │ │ │ └── sample_integration_spec.rb │ │ │ ├── models │ │ │ └── sample_model_spec.rb │ │ │ └── tasks │ │ │ └── sample_task_spec.rb │ │ └── spec_helper.rb ├── task │ ├── task.rb.tt │ └── task_spec.rb.tt └── view │ └── index.html.tt └── volt.gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | .idea 19 | .yardoc 20 | .sass-cache 21 | .DS_Store 22 | sauce_connect.log 23 | .ruby-gemset 24 | tags 25 | config/db/ -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format progress 3 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.2.0 -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | sudo: false 3 | cache: bundler 4 | services: 5 | - mongodb 6 | rvm: 7 | - 2.1.0 8 | - 2.1.1 9 | - 2.1.2 10 | - 2.1.3 11 | - 2.1.5 12 | - 2.2.0 13 | env: 14 | global: 15 | - secure: W03bt+hqLkAenymipqADIuRGZMiqu/sKx+9PXJJzCy0qAgmKs/PhPpHRpGpSmaYvVQQuiWX/rsw7xWXc2CHDJSp5aInd693xhJuSKXmnUp00r14/io+VWI9LE0lWjx4qdb6YQhdBTaxJB0+1sHDwU088yWBNnri/KwU4UlUgO5M= 16 | - secure: X95q9DUVJRLIoxd116xEbi/3xL85XiGbWdz0p5z/UawShQalMHxLfPNdU9u5gyw99LrgxTdPsJOTps8hB3vhzp8qIegrM8i2AICcicXC1QDOWi7McXSH9SBmE1AjhlyE/PLLHwLDeqfvwNMPAOrDH1GOisQp505D7SSXUZ9m0GI= 17 | matrix: 18 | - NO_BROWSER=true 19 | # - BROWSER=sauce OS="Windows 8" USE_BROWSER="Firefox" VERSION="33" 20 | script: bundle exec rake 21 | notifications: 22 | webhooks: 23 | urls: 24 | - https://webhooks.gitter.im/e/046f551739ef8cf19b8c 25 | on_success: change 26 | on_failure: always 27 | on_start: false 28 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # A sample Guardfile 2 | # More info at https://github.com/guard/guard#readme 3 | 4 | guard :rspec, cmd: 'bundle exec rspec' do 5 | watch(%r{^spec/.+_spec\.rb$}) 6 | watch(%r{^lib/volt/(.+)\.rb$}) { |m| puts 'RUN AGAIN'; "spec/#{m[1]}_spec.rb" } 7 | watch('spec/spec_helper.rb') { 'spec' } 8 | end 9 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Ryan Stout 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | require 'bundler/gem_tasks' 3 | require 'bundler/setup' 4 | require 'rubocop/rake_task' 5 | require 'opal' 6 | 7 | # Add our opal/ directory to the load path 8 | Opal.append_path(File.expand_path('../lib', __FILE__)) 9 | 10 | require 'opal/rspec/rake_task' 11 | 12 | task :docs do 13 | `bundle exec yardoc -r Readme.md --markup-provider=redcarpet --markup=markdown 'lib/**/*.rb' - Readme.md docs/*.md` 14 | end 15 | 16 | # Setup the opal:rspec task 17 | Opal::RSpec::RakeTask.new('opal:rspec') do |s| 18 | # Add the app folder to the opal load path. 19 | s.append_path('app') 20 | end 21 | 22 | task default: [:test] 23 | 24 | require 'rspec/core/rake_task' 25 | RSpec::Core::RakeTask.new('ruby:rspec') 26 | 27 | task :test do 28 | puts "--------------------------\nRun specs in Opal\n--------------------------" 29 | Rake::Task['opal:rspec'].invoke 30 | puts "--------------------------\nRun specs in normal ruby\n--------------------------" 31 | Rake::Task['ruby:rspec'].invoke 32 | end 33 | 34 | task :opal_specs_in_browser do 35 | require 'volt/server/websocket/rack_server_adaptor' 36 | require 'rack/cascade' 37 | 38 | server = Rack::Handler.get(RUNNING_SERVER) 39 | 40 | Opal::Processor.source_map_enabled = false 41 | app = Opal::Server.new { |s| 42 | s.main = 'opal/rspec/sprockets_runner' 43 | s.append_path 'spec' 44 | s.append_path 'app' 45 | s.debug = false 46 | } 47 | 48 | server.run(app, {}) 49 | end 50 | 51 | # Rubocop task 52 | RuboCop::RakeTask.new(:rubocop) do |task| 53 | task.options = ['--display-cop-names'] 54 | end 55 | -------------------------------------------------------------------------------- /app/volt/assets/css/notices.scss: -------------------------------------------------------------------------------- 1 | .notices { 2 | position: fixed; 3 | width: 600px; 4 | top: 3px; 5 | margin-left: -300px; 6 | left: 50%; 7 | z-index: 1000; 8 | cursor: pointer; 9 | } -------------------------------------------------------------------------------- /app/volt/config/dependencies.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/app/volt/config/dependencies.rb -------------------------------------------------------------------------------- /app/volt/controllers/notices_controller.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | class NoticesController < ModelController 3 | model :page 4 | 5 | def hey 6 | 'yep' 7 | end 8 | 9 | def map_key_class(key) 10 | case key 11 | when 'errors' 12 | 'danger' 13 | when 'warnings' 14 | 'warning' 15 | when 'successes' 16 | 'success' 17 | else 18 | # notices 19 | 'info' 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/volt/models/active_volt_instance.rb: -------------------------------------------------------------------------------- 1 | class ActiveVoltInstance < Volt::Model 2 | field :server_id, String 3 | field :ips, String 4 | field :port, Fixnum 5 | field :time#, Time 6 | end -------------------------------------------------------------------------------- /app/volt/models/user.rb: -------------------------------------------------------------------------------- 1 | require 'bcrypt' unless RUBY_PLATFORM == 'opal' 2 | 3 | module Volt 4 | class User < Model 5 | field :password 6 | 7 | # returns login field name depending on config settings 8 | def self.login_field 9 | if Volt.config.try(:public).try(:auth).try(:use_username) 10 | :username 11 | else 12 | :email 13 | end 14 | end 15 | 16 | permissions(:read) do 17 | # Never pass the hashed_password to the client 18 | deny :hashed_password 19 | 20 | # Deny all if this isn't the owner 21 | deny if !id == Volt.current_user_id && !new? 22 | end 23 | 24 | unless RUBY_PLATFORM == 'opal' 25 | permissions(:update) do 26 | deny unless id == Volt.current_user_id 27 | end 28 | end 29 | 30 | validations do 31 | # Only validate password when it has changed 32 | if changed?(:password) 33 | # Don't validate on the server 34 | validate :password, length: 8 35 | end 36 | end 37 | 38 | # On the server, we hash the password and remove it (so we just store the hash) 39 | unless RUBY_PLATFORM == 'opal' 40 | before_save :hash_password 41 | 42 | def hash_password 43 | password = get('password') 44 | 45 | if password.present? 46 | # Clear the password 47 | set('password', nil) 48 | 49 | # Set the hashed_password field instead 50 | set('hashed_password', BCrypt::Password.create(password)) 51 | end 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /app/volt/models/volt_app_property.rb: -------------------------------------------------------------------------------- 1 | # VoltAppProperty backs the volt_app.properties hash. properties is designed 2 | # to allow things like gems that have external setup processes (creating an 3 | # S3 bucket for example) to keep track of if that action has been completed on 4 | # a global level. 5 | class VoltAppProperty < Volt::Model 6 | field :name, String 7 | field :value, String 8 | end 9 | -------------------------------------------------------------------------------- /app/volt/tasks/live_query/live_query_pool.rb: -------------------------------------------------------------------------------- 1 | require_relative 'live_query' 2 | require 'volt/utils/generic_pool' 3 | 4 | # LiveQueryPool runs on the server and keeps track of all outstanding 5 | # queries. 6 | 7 | class LiveQueryPool < Volt::GenericPool 8 | def initialize(data_store, volt_app) 9 | @data_store = data_store 10 | @volt_app = volt_app 11 | super() 12 | end 13 | 14 | def lookup(collection, query) 15 | # collection = collection.to_sym 16 | query = normalize_query(query) 17 | 18 | super(collection, query) 19 | end 20 | 21 | def updated_collection(collection, skip_channel, from_message_bus=false) 22 | # collection = collection.to_sym 23 | lookup_all(collection).each do |live_query| 24 | live_query.run(skip_channel) 25 | end 26 | 27 | msg_bus = @volt_app.message_bus 28 | if !from_message_bus && collection != 'active_volt_instances' && msg_bus 29 | msg_bus.publish('volt_collection_update', collection) 30 | end 31 | end 32 | 33 | private 34 | 35 | # Creates the live query if it doesn't exist, and stores it so it 36 | # can be found later. 37 | def create(collection, query = {}) 38 | # collection = collection.to_sym 39 | # If not already setup, create a new one for this collection/query 40 | LiveQuery.new(self, @data_store, collection, query) 41 | end 42 | 43 | def normalize_query(query) 44 | # TODO: add something to sort query properties so the queries are 45 | # always compared the same. 46 | query 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /app/volt/tasks/user_tasks.rb: -------------------------------------------------------------------------------- 1 | class UserTasks < Volt::Task 2 | # Login a user, takes a login and password. Login can be either a username 3 | # or an e-mail based on Volt.config.public.auth.use_username 4 | # 5 | # login_info is a key with login and password (login may be e-mail) 6 | def login(login_info) 7 | login = login_info['login'] 8 | password = login_info['password'] 9 | 10 | query = { User.login_field => login } 11 | 12 | # During login we need access to the user's info even though we aren't the user 13 | Volt.skip_permissions do 14 | store._users.where(query).first.then do |user| 15 | fail VoltUserError, 'User could not be found' unless user 16 | 17 | match_pass = BCrypt::Password.new(user._hashed_password) 18 | fail 'Password did not match' unless match_pass == password 19 | 20 | next Volt.user_login_signature(user) 21 | end 22 | end 23 | end 24 | 25 | def logout 26 | # Remove user_id from user's channel 27 | @channel.update_user_id(nil) if @channel 28 | end 29 | 30 | end 31 | -------------------------------------------------------------------------------- /app/volt/views/notices/index.html: -------------------------------------------------------------------------------- 1 | <:Body> 2 | {{ if page._reloading }} 3 |
Reloading...
4 | {{ end }} 5 | {{ if channel.status == :reconnecting }} 6 |
7 | Connection Lost... {{ channel.error }}... 8 | {{ if channel.reconnect_interval }} Reconnecting in {{ (channel.reconnect_in / 1000.0).round }} sec{{ end }} 9 |
10 | {{ end }} 11 | {{ if page._reconnected }} 12 |
Reconnected!
13 | {{ end }} 14 | {{ flash.keys.each do |key| }} 15 | {{ if flash.send(:"_#{key}").present? }} 16 |
17 | {{ flash.send(:"_#{key}").each do |notice| }} 18 |

{{ notice }}

19 | {{ end }} 20 |
21 | {{ end }} 22 | {{ end }} 23 | -------------------------------------------------------------------------------- /bin/volt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), '../lib')) 4 | require 'volt/cli' 5 | -------------------------------------------------------------------------------- /docs/volt-logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/docs/volt-logo.jpg -------------------------------------------------------------------------------- /lib/volt/assets/test.rb: -------------------------------------------------------------------------------- 1 | require 'sass' 2 | 3 | sass_engine = Sass::Engine.new(template, syntax: :scss, filename: 'cool.css.scss', sourcemap: true); output = 4 | sass_engine.render_with_sourcemap('/source_maps/') 5 | -------------------------------------------------------------------------------- /lib/volt/benchmark/benchmark.rb: -------------------------------------------------------------------------------- 1 | if RUBY_PLATFORM == 'opal' 2 | class Benchmark 3 | def self.bm(iterations = 1) 4 | puts 'BM' 5 | 6 | times = [] 7 | total_time = nil 8 | result = nil 9 | 10 | iterations.times do 11 | start_time = `Date.now()` 12 | result = yield 13 | end_time = `Date.now()` 14 | total_time = `end_time - start_time` 15 | times << total_time 16 | end 17 | 18 | if iterations == 1 19 | puts "TOTAL TIME: #{total_time}ms" 20 | else 21 | puts "Times: #{times.inspect}" 22 | end 23 | 24 | result 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/volt/boot.rb: -------------------------------------------------------------------------------- 1 | unless RUBY_PLATFORM == 'opal' 2 | # An option to skip requiring. 3 | unless ENV['SKIP_BUNDLER_REQUIRE'] 4 | # Require in gems 5 | Bundler.require(:default, (ENV['VOLT_ENV'] || ENV['RACK_ENV'] || :development).to_sym) 6 | end 7 | end 8 | 9 | require 'volt/models' 10 | require 'volt/server/rack/component_paths' 11 | 12 | if RUBY_PLATFORM == 'opal' 13 | require 'volt' 14 | end 15 | require 'volt/volt/app' 16 | 17 | 18 | module Volt 19 | def self.boot(app_path) 20 | # Boot the app 21 | App.new(app_path) 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/volt/cli/base_index_renderer.rb: -------------------------------------------------------------------------------- 1 | # Render the config/base/index.html when precompiling. Here we only render 2 | # one js and one css file. 3 | 4 | module Volt 5 | class BaseIndexRenderer 6 | def initialize(volt_app, manifest) 7 | @volt_app = volt_app 8 | @manifest = manifest 9 | end 10 | 11 | def html 12 | index_path = File.expand_path(File.join(Volt.root, 'config/base/index.html')) 13 | html = File.read(index_path) 14 | 15 | ERB.new(html, nil, '-').result(binding) 16 | end 17 | 18 | # When writing the index, we render the 19 | def javascript_tags 20 | "" 21 | end 22 | 23 | def css_tags 24 | "" 25 | end 26 | end 27 | end -------------------------------------------------------------------------------- /lib/volt/cli/bundle.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | module Bundle 3 | # Run bundle from inside of cli, borrowed from rails: 4 | # https://github.com/rails/rails/blob/21f7bcbaa7709ed072bb2e1273d25c09eeaa26d9/railties/lib/rails/generators/app_base.rb 5 | def bundle_command(command) 6 | say_status :run, "bundle #{command}" 7 | 8 | # We are going to shell out rather than invoking Bundler::CLI.new(command) 9 | # because `volt new` loads the Thor gem and on the other hand bundler uses 10 | # its own vendored Thor, which could be a different version. Running both 11 | # things in the same process is a recipe for a night with paracetamol. 12 | # 13 | # We unset temporary bundler variables to load proper bundler and Gemfile. 14 | # 15 | # Thanks to James Tucker for the Gem tricks involved in this call. 16 | _bundle_command = Gem.bin_path('bundler', 'bundle') 17 | 18 | require 'bundler' 19 | Bundler.with_clean_env do 20 | full_command = %Q["#{Gem.ruby}" "#{_bundle_command}" #{command}] 21 | if options[:quiet] 22 | system(full_command, out: File::NULL) 23 | else 24 | system(full_command) 25 | end 26 | end 27 | end 28 | end 29 | end -------------------------------------------------------------------------------- /lib/volt/cli/destroy.rb: -------------------------------------------------------------------------------- 1 | class Destroy < Thor 2 | include Generators 3 | 4 | def initialize(*) 5 | super 6 | self.behavior = :revoke 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/volt/cli/generate.rb: -------------------------------------------------------------------------------- 1 | class Generate < Thor 2 | include Generators 3 | end 4 | -------------------------------------------------------------------------------- /lib/volt/cli/runner.rb: -------------------------------------------------------------------------------- 1 | require 'volt/boot' 2 | 3 | module Volt 4 | class CLI 5 | class Runner 6 | # Runs the ruby file at the path 7 | def self.run_file(path) 8 | app = Volt.boot(Dir.pwd) 9 | 10 | # Require in the file at path 11 | require './' + path 12 | 13 | # disconnect from the message bus and flush all messages 14 | app.message_bus.disconnect! 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/volt/controllers/collection_helpers.rb: -------------------------------------------------------------------------------- 1 | # Collection helpers provide methods to access methods of page directly. 2 | # @page is expected to be defined and a Volt::Page 3 | module Volt 4 | module CollectionHelpers 5 | def url 6 | Volt.current_app.url 7 | end 8 | 9 | def url_for(params) 10 | Volt.current_app.url.url_for(params) 11 | end 12 | 13 | def url_with(params) 14 | Volt.current_app.url.url_with(params) 15 | end 16 | 17 | def store 18 | Volt.current_app.store 19 | end 20 | 21 | def page 22 | Volt.current_app.page 23 | end 24 | 25 | def flash 26 | Volt.current_app.flash 27 | end 28 | 29 | def params 30 | Volt.current_app.params 31 | end 32 | 33 | def local_store 34 | Volt.current_app.local_store 35 | end 36 | 37 | def cookies 38 | Volt.current_app.cookies 39 | end 40 | 41 | def channel 42 | Volt.current_app.channel 43 | end 44 | 45 | def tasks 46 | Volt.current_app.tasks 47 | end 48 | end 49 | end -------------------------------------------------------------------------------- /lib/volt/controllers/http_controller/http_cookie_persistor.rb: -------------------------------------------------------------------------------- 1 | require 'volt/models/persistors/base' 2 | 3 | module Volt 4 | module Persistors 5 | class HttpCookiePersistor < Base 6 | attr_reader :changed_cookies 7 | 8 | def initialize(*args) 9 | super 10 | 11 | @changed_cookies = {} 12 | end 13 | 14 | def changed(attribute_name) 15 | value = @model.get(attribute_name) 16 | value = value.to_h if value.is_a?(Volt::Model) 17 | 18 | @changed_cookies[attribute_name] = value 19 | end 20 | end 21 | end 22 | end -------------------------------------------------------------------------------- /lib/volt/controllers/login_as_helper.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | module LoginAsHelper 3 | def login_as(user) 4 | unless user.is_a?(Volt::User) 5 | raise "login_as must be passed a user instance, you passed a #{user.class.to_s}" 6 | end 7 | 8 | # Assign the user_id cookie to the signature for the user id 9 | cookies._user_id = Volt.user_login_signature(user) 10 | end 11 | end 12 | end -------------------------------------------------------------------------------- /lib/volt/controllers/template_helpers.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | module TemplateHelpders 3 | module ClassMethods 4 | def template(name, template, bindings) 5 | @templates[name] = { 'html' => template, 'bindings' => bindings } 6 | end 7 | end 8 | 9 | def self.included(base) 10 | # Setup blank templates class variable 11 | base.class_attribute :__templates 12 | base.__templates = {} 13 | 14 | base.send :extend, ClassMethods 15 | end 16 | end 17 | 18 | 19 | end -------------------------------------------------------------------------------- /lib/volt/data_stores/base_adaptor_client.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | class DataStore 3 | class BaseAdaptorClient 4 | # normalize_query should take all parts of the query and return a 5 | # "normalized" version, so that two queries that are eseitnally the same 6 | # except for things like order are the same. 7 | # 8 | # Typically this means sorting parts of the query so that two queries 9 | # which do the same things are the same, so they can be uniquely 10 | # identified. 11 | # 12 | # The default implementation does no normalizing. This works, but results 13 | # in more queries being sent to the backend. 14 | def self.normalize_query(query) 15 | query 16 | end 17 | 18 | # A class method that takes an array of method names we want to provide 19 | # on the ArrayModel class. (These are typically query/sort/order/etc... 20 | # methods). This adds a proxy method to the store persistor for all 21 | # passed in method names, and then sets up a default tracking method 22 | # on the ArrayStore persistor. 23 | def self.data_store_methods(*method_names) 24 | Volt::ArrayModel.proxy_to_persistor(*method_names) 25 | 26 | method_names.each do |method_name| 27 | Volt::Persistors::ArrayStore.send(:define_method, method_name) do |*args| 28 | add_query_part(method_name, *args) 29 | end 30 | end 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/volt/data_stores/base_adaptor_server.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | class DataStore 3 | class BaseAdaptorServer 4 | end 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/volt/data_stores/data_store.rb: -------------------------------------------------------------------------------- 1 | require 'volt/data_stores/base_adaptor_server' 2 | 3 | module Volt 4 | class DataStore 5 | def self.fetch 6 | # Cache the driver 7 | return @adaptor if @adaptor 8 | 9 | database_name = Volt.config.db_driver 10 | adaptor_name = database_name.camelize + 'AdaptorServer' 11 | 12 | root = Volt::DataStore 13 | if root.const_defined?(adaptor_name) 14 | adaptor_name = root.const_get(adaptor_name) 15 | @adaptor = adaptor_name.new 16 | else 17 | raise "#{database_name} is not a supported database, you might be missing a volt-#{database_name} gem" 18 | end 19 | 20 | @adaptor 21 | end 22 | 23 | def self.adaptor_client 24 | # Load the client adaptor 25 | @adaptor_client ||= begin 26 | ds_name = Volt.config.public.datastore_name 27 | unless ds_name 28 | raise "No data store configured, please include volt-mongo or " + 29 | "another similar gem." 30 | end 31 | adaptor_class_name = ds_name.capitalize + "AdaptorClient" 32 | Volt::DataStore.const_get(adaptor_class_name) 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/volt/extra_core/array.rb: -------------------------------------------------------------------------------- 1 | class Array 2 | def sum 3 | inject(0, :+) 4 | end 5 | 6 | # For some reason .to_h doesn't show as defined in opal, but defined?(:to_h) 7 | # returns true. 8 | def to_h 9 | Hash[self] 10 | end 11 | 12 | 13 | # Converts an array to a sentence 14 | def to_sentence(options={}) 15 | conjunction = options.fetch(:conjunction, 'and') 16 | comma = options.fetch(:comma, ',') 17 | oxford = options.fetch(:oxford, true) # <- true is the right value 18 | 19 | case size 20 | when 0 21 | '' 22 | when 1 23 | self[0].to_s 24 | when 2 25 | self.join(" #{conjunction} ") 26 | else 27 | str = self[0..-2].join(comma + ' ') 28 | str += comma if oxford 29 | str += " #{conjunction} " + self[-1].to_s 30 | str 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/volt/extra_core/class.rb: -------------------------------------------------------------------------------- 1 | class Class 2 | # Provides a way to make class attributes that inherit. Pass 3 | # in symbols for attribute names 4 | def class_attribute(*attrs) 5 | attrs.each do |name| 6 | define_singleton_method(name) { nil } 7 | 8 | ivar = "@#{name}" 9 | 10 | define_singleton_method("#{name}=") do |val| 11 | singleton_class.class_eval do 12 | remove_possible_method(name) 13 | define_method(name) { val } 14 | end 15 | 16 | val 17 | end 18 | end 19 | end 20 | 21 | # Removes a method if it is defined. 22 | def remove_possible_method(method) 23 | if method_defined?(method) || private_method_defined?(method) 24 | undef_method(method) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/volt/extra_core/extra_core.rb: -------------------------------------------------------------------------------- 1 | require 'volt/extra_core/logger' 2 | require 'volt/extra_core/array' 3 | require 'volt/extra_core/object' 4 | require 'volt/extra_core/blank' 5 | require 'volt/extra_core/stringify_keys' 6 | require 'volt/extra_core/string' 7 | require 'volt/extra_core/hash' 8 | require 'volt/extra_core/class' 9 | if RUBY_PLATFORM == 'opal' 10 | # TODO: != does not work with opal for some reason 11 | else 12 | require 'volt/extra_core/symbol' 13 | end 14 | -------------------------------------------------------------------------------- /lib/volt/extra_core/hash.rb: -------------------------------------------------------------------------------- 1 | class Hash 2 | # Returns a hash excluding the keys passed in. 3 | def without(*keys) 4 | reject do |key, value| 5 | keys.include?(key) 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/volt/extra_core/inflector.rb: -------------------------------------------------------------------------------- 1 | # Copied from rails ActiveSupport: https://github.com/rails/rails/blob/feaa6e2048fe86bcf07e967d6e47b865e42e055b/activesupport/lib/active_support/inflector/inflections.rb 2 | require 'volt/extra_core/inflector/inflections' 3 | require 'volt/extra_core/inflector/methods' 4 | require 'volt/extra_core/inflections' 5 | -------------------------------------------------------------------------------- /lib/volt/extra_core/object.rb: -------------------------------------------------------------------------------- 1 | require 'volt/utils/ejson' 2 | 3 | class Object 4 | # Setup a default pretty_inspect 5 | # alias_method :pretty_inspect, :inspect 6 | 7 | def instance_values 8 | Hash[instance_variables.map { |name| [name[1..-1], instance_variable_get(name)] }] 9 | end 10 | 11 | def html_inspect 12 | inspect.gsub('<', '<').gsub('>', '>') 13 | end 14 | 15 | # TODO: Need a real implementation of this 16 | def deep_clone 17 | if RUBY_PLATFORM == 'opal' 18 | Volt::EJSON.parse(Volt::EJSON.stringify(self)) 19 | else 20 | Marshal.load(Marshal.dump(self)) 21 | end 22 | end 23 | 24 | # Convert a non-promise value into a resolved promise. Resolve the block if 25 | # it takes one. 26 | def then(&block) 27 | promisify_and_run_method(:then, &block) 28 | end 29 | 30 | # def fail(&block) 31 | # promisify_and_run_method(:fail, &block) 32 | # end 33 | 34 | def try(*a, &b) 35 | if a.empty? && block_given? 36 | yield self 37 | else 38 | public_send(*a, &b) if respond_to?(a.first) 39 | end 40 | end 41 | 42 | private 43 | def promisify_and_run_method(method_name, &block) 44 | promise = Promise.new.resolve(self) 45 | 46 | promise = promise.send(method_name, &block) if block 47 | 48 | promise 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/volt/extra_core/string.rb: -------------------------------------------------------------------------------- 1 | require 'volt/extra_core/inflector' 2 | 3 | class String 4 | # TODO: replace with better implementations 5 | # NOTE: strings are currently immutable in Opal, so no ! methods 6 | 7 | # Turns a string into the camel case version. If it is already camel case, it should 8 | # return the same string. 9 | def camelize(first_letter = :upper) 10 | new_str = gsub(/[_\-][a-z]/) { |a| a[1].upcase } 11 | new_str = new_str[0].capitalize + new_str[1..-1] if first_letter == :upper 12 | 13 | new_str 14 | end 15 | 16 | # Returns the underscore version of a string. If it is already underscore, it should 17 | # return the same string. 18 | def underscore 19 | gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase 20 | end 21 | 22 | def dasherize 23 | gsub('_', '-') 24 | end 25 | 26 | def pluralize 27 | Volt::Inflector.pluralize(self) 28 | end 29 | 30 | def singularize 31 | Volt::Inflector.singularize(self) 32 | end 33 | 34 | def titleize 35 | gsub('_', ' ').split(' ').map(&:capitalize).join(' ') 36 | end 37 | 38 | def headerize 39 | split(/[_-]/) 40 | .map { |new_str| new_str[0].capitalize + new_str[1..-1] } 41 | .join('-') 42 | end 43 | 44 | def plural? 45 | # TODO: Temp implementation 46 | pluralize == self 47 | end 48 | 49 | def singular? 50 | # TODO: Temp implementation 51 | singularize == self 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/volt/extra_core/stringify_keys.rb: -------------------------------------------------------------------------------- 1 | class Object 2 | def stringify_keys 3 | each_with_object({}) do |(key, value), hash| 4 | hash[key.to_s] = value 5 | end 6 | end 7 | 8 | def symbolize_keys 9 | each_with_object({}) do |(key, value), hash| 10 | hash[key.to_sym] = value 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/volt/extra_core/symbol.rb: -------------------------------------------------------------------------------- 1 | class Symbol 2 | def camelize 3 | to_s.camelize.to_sym 4 | end 5 | 6 | def underscore 7 | to_s.underscore.to_sym 8 | end 9 | 10 | def pluralize 11 | to_s.pluralize.to_sym 12 | end 13 | 14 | def singularize 15 | to_s.singularize.to_sym 16 | end 17 | 18 | def plural? 19 | to_s.plural? 20 | end 21 | 22 | def singular? 23 | to_s.singular? 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/volt/helpers/time.rb: -------------------------------------------------------------------------------- 1 | require 'time' 2 | require 'date' 3 | require 'volt/helpers/time/volt_time' 4 | require 'volt/helpers/time/local_volt_time' if RUBY_PLATFORM == 'opal' 5 | require 'volt/helpers/time/calculations' 6 | require 'volt/helpers/time/local_calculations' if RUBY_PLATFORM == 'opal' 7 | require 'volt/helpers/time/duration' 8 | require 'volt/helpers/time/numeric' 9 | require 'volt/helpers/time/distance' 10 | -------------------------------------------------------------------------------- /lib/volt/helpers/time/local_volt_time.rb: -------------------------------------------------------------------------------- 1 | class VoltTime 2 | 3 | # Returns a string representation of the local time 4 | def local_to_s 5 | @time.getlocal.to_s 6 | end 7 | 8 | # Returns a canonical representation of the local time 9 | def local_asctime 10 | @time.getlocal.asctime 11 | end 12 | 13 | # Returns a canonical representation of the local time 14 | def local_ctime 15 | @time.getlocal.ctime 16 | end 17 | 18 | # Formats the local time according to the provided string 19 | def local_strftime(string) 20 | @time.getlocal.strftime(string) 21 | end 22 | 23 | end 24 | -------------------------------------------------------------------------------- /lib/volt/models.rb: -------------------------------------------------------------------------------- 1 | require 'volt/extra_core/extra_core' 2 | require 'volt/models/model' 3 | require 'volt/models/cursor' 4 | require 'volt/models/persistors/store_factory' 5 | require 'volt/models/persistors/page' 6 | require 'volt/models/persistors/array_store' 7 | require 'volt/models/persistors/model_store' 8 | require 'volt/models/persistors/params' 9 | require 'volt/models/persistors/cookies' if RUBY_PLATFORM == 'opal' 10 | require 'volt/models/persistors/flash' 11 | require 'volt/models/persistors/local_store' 12 | require 'volt/models/root_models/root_models' 13 | # require 'volt/models/root_models/store_root' 14 | 15 | # Requrie in opal's promise library 16 | if RUBY_PLATFORM == 'opal' 17 | require 'promise' 18 | else 19 | require 'opal' 20 | # Opal doesn't expose its promise library directly 21 | gem_dir = File.join(Opal.gem_dir, '..') 22 | require(gem_dir + '/stdlib/promise') 23 | end 24 | require 'volt/utils/promise_extensions' 25 | -------------------------------------------------------------------------------- /lib/volt/models/cursor.rb: -------------------------------------------------------------------------------- 1 | require 'volt/models/array_model' 2 | 3 | module Volt 4 | class Cursor < ArrayModel 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/volt/models/errors.rb: -------------------------------------------------------------------------------- 1 | require 'volt/reactive/reactive_hash' 2 | 3 | module Volt 4 | class Errors < ReactiveHash 5 | def add(field, error) 6 | field_errors = (self[field] ||= []) 7 | field_errors << error unless field_errors.include?(error) 8 | end 9 | 10 | # Merge another set of errors in 11 | def merge!(errors) 12 | if errors 13 | errors.each_pair do |field, messages| 14 | messages.each do |message| 15 | add(field, message) 16 | end 17 | end 18 | end 19 | end 20 | 21 | # Generate a string version of all of the errors 22 | def to_s 23 | str = [] 24 | 25 | each_pair do |field, error| 26 | str << "#{field} #{error}" 27 | end 28 | 29 | str.join(', ') 30 | end 31 | 32 | # message returns a human readable string for the errors 33 | def message 34 | str = [] 35 | 36 | each_pair do |field, error| 37 | str << "#{field} #{error}" 38 | end 39 | 40 | str.to_sentence 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/volt/models/helpers/array_model.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | module Models 3 | module Helpers 4 | module ArrayModel 5 | def loaded_state 6 | state_for(:loaded_state) 7 | end 8 | 9 | def loaded? 10 | loaded_state == :loaded 11 | end 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/volt/models/helpers/listener_tracker.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | module Models 3 | module Helpers 4 | # Included in model's so they can inform the ArrayModel when new listeners are added or removed. 5 | module ListenerTracker 6 | # Called when data from this model begins being watched 7 | def listener_added 8 | @listener_count ||= 0 9 | @listener_count += 1 10 | end 11 | 12 | def listener_removed 13 | @listener_count ||= 0 14 | @listener_count -= 1 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/volt/models/helpers/model.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | module Models 3 | module Helpers 4 | module Model 5 | def saved_state 6 | state_for(:saved_state) || :saved 7 | end 8 | 9 | def saved? 10 | saved_state == :saved 11 | end 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/volt/models/location.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | class Location 3 | def host 4 | if RUBY_PLATFORM == 'opal' 5 | `document.location.host` 6 | else 7 | Volt.config.domain 8 | end 9 | end 10 | 11 | def protocol 12 | scheme + ':' 13 | end 14 | 15 | def scheme 16 | if RUBY_PLATFORM == 'opal' 17 | `document.location.protocol`[0..-2] 18 | else 19 | Volt.config.scheme || 'http' 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/volt/models/model_wrapper.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | module ModelWrapper 3 | # For cretain values, we wrap them to make the behave as a 4 | # model. 5 | def wrap_value(value, lookup) 6 | if value.is_a?(Array) 7 | new_array_model(value, @options.merge(parent: self, path: path + lookup)) 8 | elsif value.is_a?(Hash) 9 | new_model(value, @options.merge(parent: self, path: path + lookup)) 10 | else 11 | value 12 | end 13 | end 14 | 15 | def wrap_values(values, lookup = []) 16 | if values.is_a?(Array) 17 | # Coming from an array 18 | values.map { |v| wrap_value(v, lookup + [:[]]) } 19 | elsif values.is_a?(Hash) 20 | pairs = values.map do |k, v| 21 | # TODO: We should be able to move wrapping into the method_missing on model 22 | path = lookup + [k.to_sym] 23 | 24 | [k, wrap_value(v, path)] 25 | end 26 | Hash[pairs] 27 | else 28 | values 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/volt/models/persistors/flash.rb: -------------------------------------------------------------------------------- 1 | require 'volt/models/persistors/base' 2 | 3 | module Volt 4 | module Persistors 5 | class Flash < Base 6 | def added(model, index) 7 | if Volt.client? 8 | # Setup a new timer for clearing the flash. 9 | ` 10 | setTimeout(function() { 11 | self.$clear_model(model); 12 | }, 5000); 13 | ` 14 | end 15 | 16 | # Need to return nil to prevent non-opal object return 17 | nil 18 | end 19 | 20 | def clear_model(model) 21 | @model.delete(model) 22 | 23 | # Clear out the parent collection (usually the main flash) 24 | # Makes it so flash.empty? reflects if there is any outstanding 25 | # flashes. 26 | if @model.size == 0 27 | collection_name = @model.path[-1] 28 | @model.parent.delete(collection_name) 29 | end 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/volt/models/persistors/local_store.rb: -------------------------------------------------------------------------------- 1 | require 'volt/models/persistors/base' 2 | require 'volt/utils/local_storage' 3 | require 'volt/utils/ejson' 4 | 5 | module Volt 6 | module Persistors 7 | # Backs a collection in the local store 8 | class LocalStore < Base 9 | # Called when a model is added to the collection 10 | def added(model, index) 11 | root_model.persistor.save_all 12 | end 13 | 14 | def loaded(initial_state = nil) 15 | super 16 | # When the main model is first loaded, we pull in the data from the 17 | # store if it exists 18 | if @model.path == [] 19 | json_data = LocalStorage['volt-store'] 20 | if json_data 21 | root_attributes = EJSON.parse(json_data) 22 | 23 | @loading_data = true 24 | root_attributes.each_pair do |key, value| 25 | @model.send(:"_#{key}=", value) 26 | end 27 | @loading_data = nil 28 | end 29 | end 30 | end 31 | 32 | # Callled when an item is changed (or removed) 33 | def changed(attribute_name) 34 | root_model.persistor.save_all 35 | 36 | true 37 | end 38 | 39 | # Called on the root 40 | def save_all 41 | return if @loading_data 42 | 43 | json_data = EJSON.stringify(@model.to_h) 44 | 45 | LocalStorage['volt-store'] = json_data 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/volt/models/persistors/model_identity_map.rb: -------------------------------------------------------------------------------- 1 | require 'volt/utils/generic_counting_pool' 2 | 3 | module Volt 4 | # The identity map ensures that there is only one copy of a model 5 | # used on the front end at a time. 6 | class ModelIdentityMap < GenericCountingPool 7 | # add extends GenericCountingPool so it can add in a model without 8 | # a direct lookup. We use this when we create a model (without an id) 9 | # then save it and it gets assigned an id. 10 | def add(id, model) 11 | @pool[id] = [1, model] 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/volt/models/persistors/page.rb: -------------------------------------------------------------------------------- 1 | require 'volt/models/persistors/base' 2 | 3 | module Volt 4 | module Persistors 5 | class Page < Base 6 | def auto_generate_id 7 | true 8 | end 9 | 10 | def where(query) 11 | @model.select do |model| 12 | # Run through each key in the query and make sure the value matches. 13 | # We use .all? because once one fails to match, we can return false, 14 | # because it wouldn't match as a whole. 15 | query.all? do |key, value| 16 | model.get(key) == value 17 | end 18 | end 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/volt/models/persistors/params.rb: -------------------------------------------------------------------------------- 1 | require 'volt/models/persistors/base' 2 | 3 | module Volt 4 | module Persistors 5 | class Params < Base 6 | def changed(attribute_name) 7 | if RUBY_PLATFORM == 'opal' 8 | ` 9 | if (window.setTimeout && this.$run_update.bind) { 10 | if (window.paramsUpdateTimer) { 11 | clearTimeout(window.paramsUpdateTimer); 12 | } 13 | window.paramsUpdateTimer = setTimeout(this.$run_update.bind(this), 0); 14 | } 15 | ` 16 | 17 | end 18 | 19 | true 20 | end 21 | 22 | def run_update 23 | Volt.current_app.url.update! if Volt.client? 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/volt/models/persistors/query/normalizer.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | module Query 3 | # Normalizes queries so queries that are the same have the same order and parts 4 | class Normalizer 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/volt/models/persistors/query/query_listener_pool.rb: -------------------------------------------------------------------------------- 1 | require 'volt/utils/generic_pool' 2 | require 'volt/models/persistors/query/query_listener' 3 | 4 | module Volt 5 | # Keeps track of all query listeners, so they can be reused in different 6 | # places. Dynamically generated queries may end up producing the same 7 | # query in different places. This makes it so we only need to track a 8 | # single query at once. Data updates will only be sent once as well. 9 | class QueryListenerPool < GenericPool 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/volt/models/persistors/store.rb: -------------------------------------------------------------------------------- 1 | require 'volt/models/persistors/base' 2 | require 'volt/models/persistors/model_identity_map' 3 | 4 | module Volt 5 | module Persistors 6 | class Store < Base 7 | @@identity_map = ModelIdentityMap.new 8 | 9 | def initialize(model, tasks = nil) 10 | @tasks = tasks 11 | @model = model 12 | 13 | @saved = false 14 | end 15 | 16 | def saved? 17 | @saved 18 | end 19 | 20 | # On stores, we store the model so we don't have to look it up 21 | # every time we do a read. 22 | def read_new_model(method_name) 23 | # On stores, plural associations are automatically assumed to be 24 | # collections. 25 | options = @model.options.merge(parent: @model, path: @model.path + [method_name]) 26 | if method_name.plural? 27 | model = @model.new_array_model([], options) 28 | else 29 | options[:persistor] = @model.persistor 30 | model= @model.new_model(nil, options) 31 | 32 | # TODO: Might not need to assign this 33 | @model.attributes ||= {} 34 | @model.attributes[method_name] = model 35 | end 36 | 37 | model 38 | end 39 | 40 | def clear_identity_map 41 | @@identity_map.clear 42 | end 43 | 44 | def inspect 45 | "<#{self.class}:#{object_id}>" 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/volt/models/persistors/store_factory.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | module Persistors 3 | class StoreFactory 4 | def initialize(tasks) 5 | @tasks = tasks 6 | end 7 | 8 | def new(model) 9 | if model.is_a?(ArrayModel) 10 | ArrayStore.new(model, @tasks) 11 | else 12 | ModelStore.new(model, @tasks) 13 | end 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/volt/models/persistors/store_state.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | module Persistors 3 | # StoreState provides method for a store to track its loading state. 4 | module StoreState 5 | # Called when a collection loads 6 | def loaded(initial_state = nil) 7 | if initial_state 8 | @model.change_state_to(:loaded_state, initial_state) 9 | elsif !@loaded_state 10 | @model.change_state_to(:loaded_state, :not_loaded) 11 | end 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/volt/models/root_models/root_models.rb: -------------------------------------------------------------------------------- 1 | # When you get the root of a collection (.store, .page, etc...), it gives you 2 | # back a unique class depending on the collection. This allows you to add 3 | # things to the root easily. 4 | # 5 | # The name of the model will be {CollectionName}Root. All root model classes 6 | # inherit from BaseRootModel, which provides methods to access the model's 7 | # from the root. (store.items if you have an Item model for example) 8 | 9 | # Create a model that is above all of the root models. 10 | class BaseRootModel < Volt::Model 11 | end 12 | 13 | 14 | ROOT_MODEL_NAMES = [:Store, :Page, :Params, :Cookies, :LocalStore, :Flash] 15 | 16 | ROOT_MODEL_NAMES.each do |base_name| 17 | Object.const_set("#{base_name}Root", Class.new(BaseRootModel)) 18 | end 19 | 20 | module Volt 21 | class RootModels 22 | class_attribute :model_classes 23 | self.model_classes = [] 24 | 25 | def self.add_model_class(klass) 26 | self.model_classes << klass 27 | 28 | method_name = klass.to_s.underscore.pluralize 29 | 30 | # Create a getter for each model class off of root. 31 | BaseRootModel.send(:define_method, method_name) do 32 | get(method_name) 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/volt/models/root_models/store_root.rb: -------------------------------------------------------------------------------- 1 | # StoreRoot should already be setup when this class is already loaded. It is a 2 | # Volt::Model that is loaded as the base class for the root of store. 3 | # 4 | # In order to support setting properties directly on store, we create a table 5 | # called "root_store_models", and create a single 6 | 7 | require 'volt/models/root_models/root_models' 8 | 9 | module Volt 10 | module StoreRootHelpers 11 | def model_for_root 12 | root = nil 13 | Volt::Computation.run_without_tracking do 14 | root = get(:root_store_models).first_or_create 15 | end 16 | 17 | root 18 | end 19 | 20 | 21 | def get(attr_name, expand = false) 22 | res = if attr_name.singular? && attr_name.to_sym != :id 23 | model_for_root.get(attr_name, expand) 24 | else 25 | super 26 | end 27 | 28 | res 29 | end 30 | 31 | def set(attr_name, value, &block) 32 | if attr_name.singular? && attr_name.to_sym != :id 33 | Volt::Computation.run_without_tracking do 34 | model_for_root.then do |model| 35 | model.set(attr_name, value, &block) 36 | end 37 | end 38 | else 39 | super 40 | end 41 | # puts "SET---" 42 | end 43 | end 44 | end 45 | 46 | # StoreRoot.send(:include, Volt::StoreRootHelpers) -------------------------------------------------------------------------------- /lib/volt/models/state_manager.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | module StateManager 3 | def state_for(state_name) 4 | ivar_name = :"@#{state_name}" 5 | 6 | # Depend on the dep 7 | state_dep_for(state_name).depend 8 | 9 | instance_variable_get(ivar_name) 10 | end 11 | 12 | # Called from the QueryListener when the data is loaded 13 | def change_state_to(state_name, new_state, trigger = true) 14 | # use an instance variable for the state storage 15 | ivar_name = :"@#{state_name}" 16 | 17 | old_state = instance_variable_get(ivar_name) 18 | instance_variable_set(ivar_name, new_state) 19 | 20 | # Trigger changed on the 'state' method 21 | if old_state != new_state && trigger 22 | dep = state_dep_for(state_name, false) 23 | dep.changed! if dep 24 | end 25 | end 26 | 27 | private 28 | 29 | # Get a state ivar for state_name 30 | # @params [String] the name of the state variable 31 | # @params [Boolean] if true, one will be created if it does not exist 32 | def state_dep_for(state_name, create = true) 33 | dep_ivar_name = :"@#{state_name}_dep" 34 | dep = instance_variable_get(dep_ivar_name) 35 | if !dep && create 36 | dep = Dependency.new 37 | instance_variable_set(dep_ivar_name, dep) 38 | end 39 | 40 | dep 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/volt/models/validations/errors.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/lib/volt/models/validations/errors.rb -------------------------------------------------------------------------------- /lib/volt/models/validators/email_validator.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | class EmailValidator < FormatValidator 3 | DEFAULT_OPTIONS = { 4 | with: /^([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})$/i, 5 | message: 'must be an email address' 6 | } 7 | 8 | private 9 | 10 | def default_options 11 | DEFAULT_OPTIONS 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/volt/models/validators/inclusion_validator.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | class InclusionValidator 3 | def self.validate(model, field_name, args) 4 | errors = {} 5 | value = model.get(field_name) 6 | 7 | if args.is_a?(Array) 8 | list = args 9 | message = nil 10 | elsif args.is_a?(Hash) 11 | list = args[:in] 12 | message = args[:message] 13 | fail 'A list to match against must be specified' unless list.is_a?(Array) 14 | else 15 | fail 'The arguments to inclusion validator must be an array or a hash' 16 | end 17 | 18 | unless list.include?(value) 19 | errors[field_name] = [message || "must be one of #{list.join(', ')}"] 20 | end 21 | 22 | errors 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/volt/models/validators/length_validator.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | class LengthValidator 3 | def self.validate(model, field_name, args) 4 | errors = {} 5 | value = model.get(field_name) 6 | 7 | if args.is_a?(Fixnum) 8 | min = args 9 | max = nil 10 | message = nil 11 | elsif args.is_a?(Hash) 12 | min = args[:length] || args[:minimum] 13 | max = args[:maximum] 14 | fail 'length or minimum must be specified' unless min.is_a?(Fixnum) 15 | 16 | message = args[:message] 17 | else 18 | fail 'The arguments to length must be a number or a hash' 19 | end 20 | 21 | if !value || value.size < min 22 | errors[field_name] = [message || "must be at least #{min} characters"] 23 | elsif max && value.size > max 24 | errors[field_name] = [message || "must be less than #{max} characters"] 25 | end 26 | 27 | errors 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/volt/models/validators/phone_number_validator.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | class PhoneNumberValidator < FormatValidator 3 | DEFAULT_OPTIONS = { 4 | with: /^(\+?\d{1,2}[\.\-\ ]?\d{3}|\(\d{3}\)|\d{3})[\.\-\ ]?\d{3,4}[\.\-\ ]?\d{4}$/, 5 | message: 'must be a phone number with area or country code' 6 | } 7 | 8 | private 9 | 10 | def default_options 11 | DEFAULT_OPTIONS 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/volt/models/validators/presence_validator.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | class PresenceValidator 3 | def self.validate(model, field_name, args) 4 | errors = {} 5 | value = model.get(field_name) 6 | if !value || value.blank? 7 | if args.is_a?(Hash) && args[:message] 8 | message = args[:message] 9 | else 10 | message = 'must be specified' 11 | end 12 | 13 | errors[field_name] = [message] 14 | end 15 | 16 | errors 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/volt/models/validators/unique_validator.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | class UniqueValidator 3 | def self.validate(model, field_name, args) 4 | if RUBY_PLATFORM != 'opal' 5 | if args 6 | value = model.get(field_name) 7 | 8 | query = {} 9 | # Check to see if any other documents have this value. 10 | query[field_name.to_s] = value 11 | query['id'] = { '$ne' => model.id } 12 | 13 | # Check if the value is taken 14 | # TODO: need a way to handle scope for unique 15 | return Volt.current_app.store.get(model.path[-2]).where(query).first.then do |item| 16 | if item 17 | message = (args.is_a?(Hash) && args[:message]) || 'is already taken' 18 | 19 | # return the error 20 | next { field_name => [message] } 21 | end 22 | end 23 | end 24 | end 25 | 26 | # no errors 27 | {} 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/volt/models/validators/user_validation.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | # Provide methods on the model class to more easily setup user validations 3 | module UserValidatorHelpers 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/volt/page/bindings/bindings.rb: -------------------------------------------------------------------------------- 1 | # A file to require all client side bindings 2 | require 'volt/page/bindings/attribute_binding' 3 | require 'volt/page/bindings/content_binding' 4 | require 'volt/page/bindings/each_binding' 5 | require 'volt/page/bindings/if_binding' 6 | require 'volt/page/bindings/view_binding' 7 | require 'volt/page/bindings/yield_binding' 8 | require 'volt/page/bindings/component_binding' 9 | require 'volt/page/bindings/event_binding' 10 | -------------------------------------------------------------------------------- /lib/volt/page/bindings/component_binding.rb: -------------------------------------------------------------------------------- 1 | require 'volt/page/bindings/view_binding' 2 | 3 | # Component bindings are the same as template bindings, but handle components. 4 | module Volt 5 | class ComponentBinding < ViewBinding 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/volt/page/bindings/html_safe/string_extension.rb: -------------------------------------------------------------------------------- 1 | class String 2 | def html_safe 3 | # Convert to a real string (opal uses native strings normally, so wrap so we can 4 | # use instance variables) 5 | str = String.new(self) 6 | str.instance_variable_set('@html_safe', true) 7 | str 8 | end 9 | 10 | def html_safe? 11 | @html_safe 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/volt/page/bindings/view_binding/grouped_controllers.rb: -------------------------------------------------------------------------------- 1 | # Some template bindings share the controller with other template bindings based 2 | # on a name. This class creates a cache based on the group_controller name and the 3 | # controller class. 4 | module Volt 5 | class GroupedControllers 6 | def initialize(name) 7 | @name = name 8 | @@pool ||= GenericCountingPool.new 9 | end 10 | 11 | def lookup_or_create(controller, &block) 12 | @@pool.find(@name, controller, &block) 13 | end 14 | 15 | def remove(controller) 16 | @@pool.remove(@name, controller) 17 | end 18 | 19 | def clear 20 | @@pool.clear 21 | end 22 | 23 | def inspect 24 | "" 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/volt/page/bindings/yield_binding.rb: -------------------------------------------------------------------------------- 1 | # The yield binding renders the content of a tag which passes in 2 | 3 | require 'volt/page/bindings/base_binding' 4 | require 'volt/page/template_renderer' 5 | 6 | module Volt 7 | class YieldBinding < BaseBinding 8 | def initialize(volt_app, target, context, binding_name) 9 | super(volt_app, target, context, binding_name) 10 | 11 | # Get the path to the template to yield 12 | full_path = @context.attrs.content_template_path 13 | 14 | # Grab the controller for the content 15 | controller = @context.attrs.content_controller 16 | 17 | @current_template = TemplateRenderer.new(volt_app, @target, controller, @binding_name, full_path) 18 | end 19 | 20 | def remove 21 | if @current_template 22 | # Remove the template if one has been rendered, when the template binding is 23 | # removed. 24 | @current_template.remove 25 | end 26 | 27 | super 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/volt/page/channel_stub.rb: -------------------------------------------------------------------------------- 1 | # Acts the same as the Channel class on the front-end, but calls 2 | # directly instead of using sockjs. 3 | 4 | require 'volt/tasks/dispatcher' 5 | require 'volt/reactive/eventable' 6 | 7 | module Volt 8 | # Behaves the same as the Channel class, only the Channel class uses 9 | # sockjs to pass messages to the backend. ChannelStub, simply passes 10 | # them directly to SocketConnectionHandlerStub. 11 | class ChannelStub 12 | include Eventable 13 | 14 | attr_reader :state, :error, :reconnect_interval 15 | 16 | def initialize 17 | @state = :connected 18 | end 19 | 20 | def opened 21 | trigger!('open') 22 | trigger!('changed') 23 | end 24 | 25 | def message_received(*message) 26 | trigger!('message', *message) 27 | end 28 | 29 | def send_message(message) 30 | SocketConnectionHandlerStub.new(self).process_message(message) 31 | end 32 | 33 | def close! 34 | fail 'close! should not be called on the backend channel' 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/volt/page/document.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | # Should have 3 | # templates 4 | # events 5 | class Document 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/volt/page/path_string_renderer.rb: -------------------------------------------------------------------------------- 1 | # The PathRenderer is a simple way to render a string of the contents of a view 2 | # at the passed in path. 3 | 4 | require 'volt/page/bindings/view_binding/view_lookup_for_path' 5 | require 'volt/page/bindings/view_binding/controller_handler' 6 | require 'volt/page/string_template_renderer' 7 | 8 | module Volt 9 | class ViewLookupException < Exception; end 10 | class PathStringRenderer 11 | attr_reader :html 12 | def initialize(volt_app, path, attrs = nil, render_from_path = nil) 13 | # where to do the path lookup from 14 | render_from_path ||= 'main/main/main/body' 15 | 16 | # Make path into a full path 17 | @view_lookup = Volt::ViewLookupForPath.new(volt_app.templates, render_from_path) 18 | full_path, controller_path = @view_lookup.path_for_template(path, nil) 19 | 20 | if full_path.nil? 21 | fail ViewLookupException, "Unable to find view at `#{path}`" 22 | end 23 | 24 | controller_class, action = ControllerHandler.get_controller_and_action(controller_path) 25 | 26 | controller = controller_class.new(volt_app) # (SubContext.new(attrs, nil, true)) 27 | controller.model = SubContext.new(attrs, nil, true) 28 | 29 | renderer = StringTemplateRenderer.new(volt_app, controller, full_path) 30 | 31 | @html = renderer.html 32 | 33 | # remove when done 34 | renderer.remove 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/volt/page/string_template_renderer.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | # StringTemplateRenderer are used to render a template to a string. Call .html 3 | # to get the string. Be sure to call .remove when complete. 4 | # 5 | # StringTemplateRenderer will intellegently update the string in the same way 6 | # a normal bindings will update the dom. 7 | class StringTemplateRenderer 8 | def initialize(volt_app, context, template_path) 9 | @dependency = Dependency.new 10 | 11 | @template_path = template_path 12 | @target = AttributeTarget.new(nil, nil, self) 13 | @template = TemplateRenderer.new(volt_app, @target, context, 'main', template_path) 14 | end 15 | 16 | # Render the template and get the current value 17 | def html 18 | @dependency.depend 19 | 20 | html = nil 21 | Computation.run_without_tracking do 22 | html = @target.to_html 23 | end 24 | 25 | html 26 | end 27 | 28 | def changed! 29 | # if @dependency is missing, this template has been removed 30 | @dependency.changed! if @dependency 31 | end 32 | 33 | def remove 34 | @dependency.remove 35 | @dependency = nil 36 | 37 | Computation.run_without_tracking do 38 | @template.remove 39 | @template = nil 40 | end 41 | 42 | @target = nil 43 | @template_path = nil 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/volt/page/targets/attribute_target.rb: -------------------------------------------------------------------------------- 1 | require 'volt/page/targets/base_section' 2 | require 'volt/page/targets/attribute_section' 3 | require 'volt/page/targets/binding_document/component_node' 4 | require 'volt/page/targets/binding_document/html_node' 5 | 6 | module Volt 7 | # AttributeTarget's provide an interface that can render bindings into 8 | # a string that can then be used to update a attribute binding. 9 | class AttributeTarget < ComponentNode 10 | def dom_section(*args) 11 | AttributeSection.new(self, *args) 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/volt/page/targets/base_section.rb: -------------------------------------------------------------------------------- 1 | require 'volt/page/targets/dom_template' 2 | 3 | module Volt 4 | # Class to describe the interface for sections 5 | class BaseSection 6 | @@template_cache = {} 7 | 8 | def remove 9 | fail 'remove is not implemented' 10 | end 11 | 12 | def remove_anchors 13 | fail 'remove_anchors is not implemented' 14 | end 15 | 16 | def insert_anchor_before_end(binding_name) 17 | fail 'insert_anchor_before_end is not implemented' 18 | end 19 | 20 | def set_template 21 | fail 'set_template is not implemented' 22 | end 23 | 24 | def set_content_to_template(volt_app, template_name) 25 | if self.is_a?(DomSection) 26 | # DomTemplates are an optimization when working with the DOM (as opposed to other targets) 27 | dom_template = (@@template_cache[template_name] ||= DomTemplate.new(volt_app, template_name)) 28 | 29 | set_template(dom_template) 30 | else 31 | template = volt_app.templates[template_name] 32 | 33 | if template 34 | html = template['html'] 35 | bindings = template['bindings'] 36 | else 37 | html = "
-- < missing view or tag at #{template_name.inspect}, make sure it's component is included in dependencies.rb > --
" 38 | bindings = {} 39 | end 40 | 41 | set_content_and_rezero_bindings(html, bindings) 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/volt/page/targets/binding_document/base_node.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | class BaseNode 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /lib/volt/page/targets/binding_document/html_node.rb: -------------------------------------------------------------------------------- 1 | require 'volt/page/targets/binding_document/base_node' 2 | 3 | module Volt 4 | class HtmlNode < BaseNode 5 | def initialize(html) 6 | @html = html 7 | end 8 | 9 | def to_html 10 | @html 11 | end 12 | 13 | def inspect 14 | "" 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/volt/page/targets/binding_document/tag_node.rb: -------------------------------------------------------------------------------- 1 | # The tag node represents an html tag with a binding id in it. It provides an 2 | # api to change attribute values. 3 | 4 | require 'volt/page/targets/binding_document/base_node' 5 | 6 | module Volt 7 | class TagNode < BaseNode 8 | # We use some of the same parts from the sandlebars parser, but since this 9 | # has to ship to the client, we only take the parts we need. 10 | START_TAG = /^<([-!\:A-Za-z0-9_]+)((?:\s+[\w\-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/ 11 | ATTRIBUTES = /([-\:A-Za-z0-9_]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/ 12 | 13 | attr_reader :tag_id 14 | attr_reader :attributes 15 | 16 | def initialize(html) 17 | tag = html.scan(START_TAG).first 18 | @start_tag = tag[0] 19 | @attributes = tag[1].scan(ATTRIBUTES).map do |match| 20 | name = match[0] 21 | value = match[1] || match[2] || match[3] 22 | 23 | # Store the tag's id for quick lookup 24 | if name == 'id' 25 | @tag_id = value 26 | end 27 | 28 | [name, value] 29 | end.to_h 30 | @end_tag = tag[2] 31 | end 32 | 33 | def to_html 34 | attr_str = @attributes.map do |key, value| 35 | "#{key}=\"#{value}\"" 36 | end.join(' ') 37 | 38 | "<" + [@start_tag, attr_str, @end_tag].reject(&:blank?).join(' ') + ">" 39 | end 40 | end 41 | end -------------------------------------------------------------------------------- /lib/volt/page/targets/dom_target.rb: -------------------------------------------------------------------------------- 1 | require 'volt/page/targets/base_section' 2 | require 'volt/page/targets/dom_section' 3 | 4 | module Volt 5 | # DomTarget's provide an interface that can render bindings into 6 | # the dom. Currently only one "dom" is supported, but multiple 7 | # may be allowed in the future (iframes?) 8 | class DomTarget < BaseSection 9 | def dom_section(*args) 10 | DomSection.new(*args) 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/volt/page/template_renderer.rb: -------------------------------------------------------------------------------- 1 | require 'volt/page/bindings/base_binding' 2 | 3 | module Volt 4 | class TemplateRenderer < BaseBinding 5 | attr_reader :context 6 | 7 | def initialize(volt_app, target, context, binding_name, template_name) 8 | super(volt_app, target, context, binding_name) 9 | 10 | @sub_bindings = [] 11 | 12 | bindings = dom_section.set_content_to_template(volt_app, template_name) 13 | 14 | bindings.each_pair do |id, bindings_for_id| 15 | bindings_for_id.each do |binding| 16 | @sub_bindings << binding.call(volt_app, target, context, id) 17 | end 18 | end 19 | end 20 | 21 | def remove 22 | @sub_bindings.each(&:remove) 23 | @sub_bindings = [] 24 | 25 | super 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/volt/page/url_tracker.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | # The URLTracker is responsible for updating the url when 3 | # a param changes, or updating the url model/params when 4 | # the browser url changes. 5 | class UrlTracker 6 | def initialize(volt_app) 7 | @volt_app = volt_app 8 | 9 | if Volt.client? 10 | that = self 11 | 12 | # Setup popstate on the dom ready event. Prevents an extra 13 | # popstate trigger 14 | ` 15 | window.addEventListener("popstate", function(e) { 16 | that.$url_updated(); 17 | return true; 18 | }); 19 | ` 20 | end 21 | end 22 | 23 | def url_updated(first_call = false) 24 | @volt_app.url.parse(`document.location.href`) 25 | @volt_app.url.update! unless first_call 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/volt/reactive/hash_dependency.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | class HashDependency 3 | def initialize 4 | @hash_depedencies = {} 5 | end 6 | 7 | def depend(key) 8 | ensure_key(key).depend 9 | end 10 | 11 | def changed!(key) 12 | ensure_key(key).changed! 13 | end 14 | 15 | def delete(key) 16 | dep = @hash_depedencies.delete(key) 17 | 18 | if dep 19 | dep.changed! 20 | dep.remove 21 | end 22 | end 23 | 24 | def changed_all! 25 | @hash_depedencies.each_pair do |key, value| 26 | value.changed! 27 | end 28 | end 29 | 30 | private 31 | 32 | def ensure_key(key) 33 | @hash_depedencies[key] ||= Dependency.new 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/volt/reactive/reactive_accessors.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | module ReactiveAccessors 3 | module ClassMethods 4 | # Create a method to read a reactive value from an instance value. If it 5 | # is not setup, create it so it can be updated through the reactive value 6 | # at a later point. 7 | def reactive_reader(*names) 8 | names.each do |name| 9 | var_name = :"@#{name}" 10 | define_method(name.to_sym) do 11 | value = instance_variable_get(var_name) 12 | 13 | __reactive_dependency_get(name).depend 14 | 15 | value 16 | end 17 | end 18 | end 19 | 20 | def reactive_writer(*names) 21 | names.each do |name| 22 | var_name = :"@#{name}" 23 | define_method("#{name}=") do |new_value| 24 | instance_variable_set(var_name, new_value) 25 | 26 | __reactive_dependency_get(name).changed! 27 | end 28 | end 29 | end 30 | 31 | def reactive_accessor(*names) 32 | reactive_reader(*names) 33 | reactive_writer(*names) 34 | end 35 | end 36 | 37 | def self.included(base) 38 | base.send :extend, ClassMethods 39 | end 40 | 41 | def __reactive_dependency_get(var_name) 42 | value_dep = instance_variable_get(:"@__#{var_name}_dependency") 43 | value_dep ||= instance_variable_set(:"@__#{var_name}_dependency", Dependency.new) 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/volt/server/banner.txt: -------------------------------------------------------------------------------- 1 | ,--, ,----, 2 | ,----.. ,---.'| ,/ .`| 3 | / / \ | | : ,` .' : 4 | ,---. / . : : : | ; ; / 5 | /__./| . / ;. \| ' : .'___,/ ,' 6 | ,---.; ; |. ; / ` ;; ; ' | : | 7 | /___/ \ | |; | ; \ ; |' | |__ ; |.'; ; 8 | \ ; \ ' || : | ; | '| | :.'|`----' | | 9 | \ \ \: |. | ' ' ' :' : ; ' : ; 10 | ; \ ' .' ; \; / || | ./ | | ' 11 | \ \ ' \ \ ', / ; : ; ' : | 12 | \ ` ; ; : / | ,/ ; |.' 13 | : \ | \ \ .' '---' '---' 14 | '---" `---` 15 | 16 | -------------------------------------------------------------------------------- /lib/volt/server/forking_server/boot_error.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 32 | 33 | 34 |

<%= CGI::escapeHTML(error.inspect) %>

35 | 36 | <% if error.respond_to?(:backtrace) %> 37 |
38 | <%= error.backtrace.map {|l| CGI::escapeHTML(l) }.join("
") %> 39 |
40 | <% end %> 41 | 42 | 43 | -------------------------------------------------------------------------------- /lib/volt/server/html_parser/each_scope.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | class EachScope < ViewScope 3 | def initialize(handler, path, content, with_index) 4 | super(handler, path) 5 | 6 | if with_index 7 | @content, @variable_name = content.split(/.each_with_index\s+do\s+\|/) 8 | @variable_name, @index_name = @variable_name.gsub(/\|/, '').split(/\s*,\s*/) 9 | else 10 | @content, @variable_name = content.split(/.each\s+do\s+\|/) 11 | @variable_name = @variable_name.gsub(/\|/, '') 12 | end 13 | end 14 | 15 | def close_scope 16 | binding_number = @handler.scope[-2].binding_number 17 | @handler.scope[-2].binding_number += 1 18 | @path += "/__template/#{binding_number}" 19 | 20 | super 21 | 22 | @handler.html << "" 23 | @handler.scope.last.save_binding(binding_number, "lambda { |__p, __t, __c, __id| Volt::EachBinding.new(__p, __t, __c, __id, Proc.new { #{@content} }, #{@variable_name.try(:strip).inspect}, #{@index_name.try(:strip).inspect}, #{@path.inspect}) }") 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/volt/server/html_parser/textarea_scope.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | class TextareaScope < ViewScope 3 | def initialize(handler, path, attributes) 4 | super(handler, path) 5 | 6 | @attributes = attributes 7 | end 8 | 9 | def add_binding(content) 10 | @html << "{{#{content}}}" 11 | end 12 | 13 | def close_scope(pop = true) 14 | # Remove from the scope 15 | @handler.scope.pop 16 | 17 | attributes = @attributes 18 | 19 | if @html[/\{\{[^\}]+\}\}/] 20 | # If the html inside the textarea has a binding, process it as 21 | # a value attribute. 22 | attributes['value'] = @html 23 | @html = '' 24 | end 25 | 26 | # Normal tag 27 | attributes = @handler.last.process_attributes('textarea', attributes) 28 | 29 | @handler.last.html << "#{@html}" 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/volt/server/message_bus/peer_to_peer/socket_with_timeout.rb: -------------------------------------------------------------------------------- 1 | # Connect to a socket using the raw timeout, which responds better than the 2 | # builtin Timeout. 3 | 4 | require 'socket' 5 | 6 | module Volt 7 | class SocketWithTimeout 8 | def self.new(host, port, timeout=nil) 9 | if RUBY_PLATFORM == 'java' 10 | TCPSocket.new(host, port) 11 | else 12 | addr = Socket.getaddrinfo(host, nil) 13 | sock = Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0) 14 | 15 | if timeout 16 | secs = Integer(timeout) 17 | usecs = Integer((timeout - secs) * 1_000_000) 18 | optval = [secs, usecs].pack("l_2") 19 | sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval 20 | sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval 21 | end 22 | sock.connect(Socket.pack_sockaddr_in(port, addr[0][3])) 23 | sock 24 | end 25 | end 26 | end 27 | end -------------------------------------------------------------------------------- /lib/volt/server/message_bus/redis.rb: -------------------------------------------------------------------------------- 1 | require 'volt/server/message_bus/base_message_bus' 2 | -------------------------------------------------------------------------------- /lib/volt/server/middleware/middleware_stack.rb: -------------------------------------------------------------------------------- 1 | # Volt::MiddlewareStack provides an interface where app code can add custom 2 | # rack middleware. Volt.current_app.middleware returns an instance of 3 | # Volt::MiddlewareStack, and apps can call #use to add in more middleware. 4 | 5 | module Volt 6 | class MiddlewareStack 7 | attr_reader :middlewares 8 | 9 | def initialize 10 | # Setup the next app 11 | @middlewares = [] 12 | end 13 | 14 | def use(*args, &block) 15 | @middlewares << [args, block] 16 | 17 | # invalidate builder, so it gets built again 18 | @builder = nil 19 | end 20 | 21 | def map(path, &block) 22 | @middlewares << [:map, path, block] 23 | 24 | # invalidate builder 25 | @builder = nil 26 | end 27 | 28 | def run(app) 29 | @app = app 30 | end 31 | 32 | # Builds a new Rack::Builder with the middleware and the app 33 | def build 34 | @builder = Rack::Builder.new 35 | 36 | @middlewares.each do |middleware| 37 | if middleware[0] == :map 38 | @builder.map(middleware[1], &middleware[2]) 39 | else 40 | @builder.use(*middleware[0], &middleware[1]) 41 | end 42 | end 43 | 44 | @builder.run(@app) 45 | end 46 | 47 | def call(env) 48 | unless @builder 49 | build 50 | end 51 | 52 | @builder.call(env) 53 | end 54 | end 55 | end -------------------------------------------------------------------------------- /lib/volt/server/rack/component_code.rb: -------------------------------------------------------------------------------- 1 | require 'volt/server/html_parser/view_parser' 2 | require 'volt/server/component_templates' 3 | require 'volt/server/rack/asset_files' 4 | 5 | # Takes in the name and all component paths returns all of the ruby code for the 6 | # component and its dependencies. 7 | module Volt 8 | class ComponentCode 9 | def initialize(volt_app, component_name, component_paths, client = true) 10 | @volt_app = volt_app 11 | @component_name = component_name 12 | @component_paths = component_paths 13 | @client = client 14 | end 15 | 16 | # The client argument is for if this code is being generated for the client 17 | def code 18 | # Start with config code 19 | initializer_code = @client ? generate_config_code : '' 20 | component_code = '' 21 | 22 | asset_files = AssetFiles.from_cache(@volt_app.app_url, @component_name, @component_paths) 23 | asset_files.component_paths.each do |component_path, component_name| 24 | comp_template = ComponentTemplates.new(component_path, component_name, @client) 25 | initializer_code << comp_template.initializer_code + "\n\n" 26 | component_code << comp_template.component_code + "\n\n" 27 | end 28 | 29 | initializer_code + component_code 30 | end 31 | 32 | def generate_config_code 33 | # Setup Volt.config on the client 34 | "\nVolt.setup_client_config(#{Volt.config.public.to_h.inspect})\n" 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/volt/server/rack/http_request.rb: -------------------------------------------------------------------------------- 1 | require 'volt' 2 | require 'rack' if RUBY_PLATFORM != 'opal' 3 | 4 | module Volt 5 | # A request object for a HttpController. See Rack::Request for more details 6 | class HttpRequest < Rack::Request 7 | # Returns the request format 8 | # /acticles/index.html => html 9 | # Defaults to the media_type of the request 10 | def format 11 | path_format || media_type 12 | end 13 | 14 | # Returns the path_info without the format 15 | # /blub/index.html => /blub/index 16 | def path 17 | path_format ? path_info[0..path_format.size * -1 - 2] : path_info 18 | end 19 | 20 | # Returns the request method 21 | # Allows requests to override the http request method by setting _method 22 | def method 23 | if params[:_method] 24 | params[:_method].to_s.downcase.to_sym 25 | else 26 | request_method.downcase.to_sym 27 | end 28 | end 29 | 30 | # The request params with symbolized keys 31 | def params 32 | super.symbolize_keys 33 | end 34 | 35 | private 36 | 37 | # Returns the format given in the path_info 38 | # http://example.com/test.html => html 39 | # http://example.com/test => nil 40 | def path_format 41 | @path_format ||= extract_format_from_path 42 | end 43 | 44 | # Extract from the path 45 | def extract_format_from_path 46 | format = path_info.match(/\.(\w+)$/) 47 | format.present? ? format[1] : nil 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/volt/server/rack/http_response_header.rb: -------------------------------------------------------------------------------- 1 | require 'volt' 2 | 3 | module Volt 4 | # Wrapper around a Hash for easy http header creation / manipulation with 5 | # indifferent access. 6 | # header[:content_type] == header['Content-Type'] == 7 | # header['content-type'] == header ['Content_Type'] 8 | class HttpResponseHeader < Hash 9 | def []=(key, value) 10 | super(key.to_s.headerize, value) 11 | end 12 | 13 | def [](key) 14 | super(key.to_s.headerize) 15 | end 16 | 17 | def delete(key) 18 | super(key.to_s.headerize) 19 | end 20 | 21 | def merge(other) 22 | dup.merge!(other) 23 | end 24 | 25 | def merge!(other) 26 | new_hash = {} 27 | other.each_with_object(new_hash) do |(key, value), hash| 28 | self[key.to_s.headerize] = value 29 | end 30 | self 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/volt/server/rack/keep_alive.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | # TODO: For some reason in Rack (or maybe thin), 304 headers close 3 | # the http connection. We might need to make this check if keep 4 | # alive was in the request. 5 | class KeepAlive 6 | def initialize(app) 7 | @app = app 8 | end 9 | 10 | def call(env) 11 | status, headers, body = @app.call(env) 12 | 13 | if status == 304 && env['HTTP_CONNECTION'] && env['HTTP_CONNECTION'].downcase == 'keep-alive' 14 | headers['Connection'] = 'keep-alive' 15 | end 16 | 17 | [status, headers, body] 18 | end 19 | end 20 | end -------------------------------------------------------------------------------- /lib/volt/server/rack/quiet_common_logger.rb: -------------------------------------------------------------------------------- 1 | require 'rack' 2 | class QuietCommonLogger < Rack::CommonLogger 3 | include Rack 4 | 5 | @@ignore_extensions = %w(png jpg jpeg ico gif woff tff svg eot css js) 6 | 7 | def call(env) 8 | path = env['PATH_INFO'] 9 | began_at = Time.now 10 | status, header, body = @app.call(env) 11 | header = Utils::HeaderHash.new(header) 12 | base = ::File.basename(path) 13 | if base.index('.') 14 | ext = base.split('.').last 15 | else 16 | ext = nil 17 | end 18 | 19 | @logged = false 20 | 21 | body = BodyProxy.new(body) do 22 | # Don't log on ignored extensions 23 | if !@@ignore_extensions.include?(ext) && 24 | !path.start_with?('/__OPAL_SOURCE_MAPS__/') && 25 | !@logged 26 | log(env, status, header, began_at) 27 | end 28 | end 29 | 30 | # Because of web sockets, the initial request doesn't finish, so we 31 | # can just trigger it now. 32 | unless ext || path.start_with?('/channel') 33 | @logged = true 34 | log(env, status, header, began_at) 35 | end 36 | 37 | [status, header, body] 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/volt/server/rack/source_map_server.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | class SourceMapServer 3 | def initialize(sprockets) 4 | @sprockets = sprockets 5 | end 6 | 7 | attr_reader :sprockets 8 | 9 | attr_writer :prefix 10 | 11 | def prefix 12 | @prefix ||= '/__opal_source_maps__' 13 | end 14 | 15 | def inspect 16 | "#<#{self.class}:#{object_id}>" 17 | end 18 | 19 | def call(env) 20 | path_info = env['PATH_INFO'] 21 | 22 | if path_info =~ /\.js\.map$/ 23 | path = env['PATH_INFO'].gsub(/^\/|\.js\.map$/, '') 24 | asset = sprockets[path] 25 | return [404, {}, []] if asset.nil? 26 | 27 | return [200, { 'Content-Type' => 'text/json' }, [$OPAL_SOURCE_MAPS[asset.pathname].to_s]] 28 | else 29 | return [200, { 'Content-Type' => 'text/text' }, [File.read(sprockets.resolve(path_info))]] 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/volt/server/socket_connection_handler_stub.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | class SocketConnectionHandlerStub 3 | def self.dispatcher=(val) 4 | @@dispatcher = val 5 | end 6 | 7 | def self.dispatcher 8 | @@dispatcher 9 | end 10 | 11 | def initialize(channel_stub) 12 | @channel_stub = channel_stub 13 | end 14 | 15 | # Sends a message to all, optionally skipping a users channel 16 | def self.send_message_all(skip_channel = nil, *args) 17 | # Stub 18 | end 19 | 20 | def process_message(message) 21 | @@dispatcher.dispatch(self, message) 22 | end 23 | 24 | def send_message(*args) 25 | @channel_stub.message_received(*args) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/volt/server/template_handlers/preprocessors.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | class ComponentTemplates 3 | module Preprocessors #:nodoc: 4 | # Setup default handler on extend 5 | def self.extended(base) 6 | base.register_template_handler :html, BasicHandler.new 7 | base.register_template_handler :email, BasicHandler.new 8 | end 9 | 10 | @@template_handlers = {} 11 | 12 | def self.extensions 13 | @@template_handlers.keys 14 | end 15 | 16 | # Register an object that knows how to handle template files with the given 17 | # extensions. This can be used to implement new template types. 18 | # The handler must respond to +:call+, which will be passed the template 19 | # and should return the rendered template as a String. 20 | def register_template_handler(extension, handler) 21 | @@template_handlers[extension.to_sym] = handler 22 | end 23 | 24 | def registered_template_handler(extension) 25 | extension && @@template_handlers[extension.to_sym] 26 | end 27 | 28 | def handler_for_extension(extension) 29 | registered_template_handler(extension) 30 | end 31 | end 32 | end 33 | end -------------------------------------------------------------------------------- /lib/volt/server/websocket/rack_server_adaptor.rb: -------------------------------------------------------------------------------- 1 | begin 2 | require 'puma' 3 | RUNNING_SERVER = 'puma' 4 | rescue LoadError => e 5 | begin 6 | require 'thin' 7 | RUNNING_SERVER = 'thin' 8 | rescue LoadError => e 9 | Volt.logger.error('Unable to find a compatible rack server, please make sure your Gemfile includes one of the following: thin or puma') 10 | end 11 | end 12 | 13 | module Volt 14 | class RackServerAdaptor 15 | def self.load 16 | Faye::WebSocket.load_adapter(RUNNING_SERVER) 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/volt/server/websocket/websocket_handler.rb: -------------------------------------------------------------------------------- 1 | require 'faye/websocket' 2 | require 'volt/server/socket_connection_handler' 3 | 4 | # Load websocket options once at boot 5 | # The ENV['DYNO'] option lets it kick in automatically if we're on heroku. 6 | WEBSOCKET_OPTIONS = if !ENV['NO_WEBSOCKET_PING'] && 7 | (ENV['WEBSOCKET_PING_TIME'] || ENV['DYNO']) 8 | {ping: (ENV['WEBSOCKET_PING_TIME'] || 30).to_i} 9 | else 10 | {} 11 | end 12 | 13 | module Volt 14 | class WebsocketHandler 15 | def initialize(app) 16 | @app = app 17 | end 18 | 19 | def call(env) 20 | if Faye::WebSocket.websocket?(env) 21 | ws = Faye::WebSocket.new(env, nil, WEBSOCKET_OPTIONS) 22 | 23 | socket_connection_handler = SocketConnectionHandler.new(ws) 24 | 25 | ws.on :message do |event| 26 | socket_connection_handler.process_message(event.data) 27 | end 28 | 29 | ws.on :close do |event| 30 | socket_connection_handler.closed 31 | 32 | ws = nil 33 | end 34 | 35 | # Return async Rack response 36 | ws.rack_response 37 | else 38 | # Call down to the app 39 | @app.call(env) 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/volt/spec/sauce_labs.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | class << self 3 | def setup_sauce_labs 4 | require 'sauce' 5 | require 'sauce/capybara' 6 | 7 | Sauce.config do |c| 8 | if ENV['OS'] 9 | # Use a specifc OS, BROWSER, VERSION combo (for travis) 10 | c[:browsers] = [ 11 | [ENV['OS'], ENV['USE_BROWSER'], ENV['VERSION']] 12 | ] 13 | else 14 | # Run all 15 | c[:browsers] = [ 16 | # ["Windows 7", "Chrome", "30"], 17 | # ["Windows 8", "Firefox", "28"], 18 | ['Windows 8.1', 'Internet Explorer', '11'], 19 | ['Windows 8.0', 'Internet Explorer', '10'], 20 | ['Windows 7.0', 'Internet Explorer', '9'], 21 | # ["OSX 10.9", "iPhone", "8.1"], 22 | # ["OSX 10.8", "Safari", "6"], 23 | # ["Linux", "Chrome", "26"] 24 | ] 25 | end 26 | c[:start_local_application] = false 27 | end 28 | 29 | Capybara.default_driver = :sauce 30 | Capybara.javascript_driver = :sauce 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/volt/utils/boolean_patch.rb: -------------------------------------------------------------------------------- 1 | # A patch to fix https://github.com/opal/opal/issues/801 until it is fixed in (0.8) 2 | if RUBY_PLATFORM == 'opal' 3 | class Boolean 4 | def hash 5 | self ? 'Boolean:true' : 'Boolean:false' 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/volt/utils/csso_patch.rb: -------------------------------------------------------------------------------- 1 | # CSSO complains when using node as an execJS runtime, but we currently have 2 | # to due to a bug in therubyracer (or maybe execJS?) 3 | 4 | require 'execjs' 5 | 6 | module Csso 7 | class JsLib 8 | 9 | def initialize 10 | spec = Gem::Specification.find_by_name("csso-rails") 11 | path = spec.gem_dir 12 | 13 | lib = File.read(File.expand_path(path + "/" + CSSO_JS_LIB, File.dirname(__FILE__))) 14 | unless @csso = ExecJS.runtime.compile(lib) 15 | raise 'cannot compile or what?' 16 | end 17 | end 18 | 19 | def compress css, structural_optimization=true 20 | @csso.call("do_compression", css, !structural_optimization) 21 | end 22 | end 23 | 24 | 25 | # https://github.com/Vasfed/csso-rails/pull/23/files 26 | def self.install(sprockets) 27 | if sprockets.respond_to? :register_compressor 28 | compressor = Compressor.new 29 | sprockets.register_compressor('text/css', :csso, proc { |context, css| 30 | compressor.compress(css) 31 | }) 32 | sprockets.css_compressor = :csso 33 | else 34 | Sprockets::Compressors.register_css_compressor(:csso, 'Csso::Compressor', default: true) 35 | end 36 | end 37 | 38 | end -------------------------------------------------------------------------------- /lib/volt/utils/event_counter.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | # EventCounter has an #add and #remove method, and when the first one is added 3 | # will call the #start proc (passed to new), and when the last is removed will 4 | # call #stop. 5 | class EventCounter 6 | attr_reader :count 7 | 8 | def initialize(start, stop) 9 | @start = start 10 | @stop = stop 11 | 12 | @count = 0 13 | end 14 | 15 | def add 16 | @count += 1 17 | 18 | @start.call if @count == 1 19 | end 20 | 21 | def remove 22 | @count -= 1 23 | 24 | fail 'count below 0' if @count < 0 25 | 26 | @stop.call if @count == 0 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/volt/utils/generic_counting_pool.rb: -------------------------------------------------------------------------------- 1 | require 'volt/utils/generic_pool' 2 | 3 | module Volt 4 | # A counting pool behaves like a normal GenericPool, except for 5 | # each time lookup is called, remove should be called when complete. 6 | # The item will be completely removed from the GenericCountingPool 7 | # only when it has been removed an equal number of times it has been 8 | # looked up. 9 | class GenericCountingPool < GenericPool 10 | # return a created item with a count 11 | def generate_new(*args) 12 | [0, create(*args)] 13 | end 14 | 15 | # Finds an item and tracks that it was checked out. Use 16 | # #remove when the item is no longer needed. 17 | def find(*args, &block) 18 | item = __lookup(*args, &block) 19 | 20 | item[0] += 1 21 | 22 | item[1] 23 | end 24 | 25 | # Lookups an item 26 | def lookup(*args, &block) 27 | # Note: must call without args because of https://github.com/opal/opal/issues/500 28 | item = super 29 | 30 | item[1] 31 | end 32 | 33 | def transform_item(item) 34 | [0, item] 35 | end 36 | 37 | def remove(*args) 38 | item = lookup_without_generate(*args) 39 | if item 40 | item[0] -= 1 41 | 42 | if item[0] == 0 43 | # Last one using this item has removed it. 44 | super(*args) 45 | end 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/volt/utils/local_storage.rb: -------------------------------------------------------------------------------- 1 | if RUBY_PLATFORM == 'opal' 2 | module Volt 3 | module LocalStorage 4 | def self.[](key) 5 | ` 6 | var val = localStorage.getItem(key); 7 | return val === null ? nil : val; 8 | ` 9 | end 10 | 11 | def self.[]=(key, value) 12 | `localStorage.setItem(key, value)` 13 | end 14 | 15 | def self.clear 16 | `localStorage.clear()` 17 | self 18 | end 19 | 20 | def self.delete(key) 21 | ` 22 | var val = localStorage.getItem(key); 23 | localStorage.removeItem(key); 24 | return val === null ? nil : val; 25 | ` 26 | end 27 | end 28 | end 29 | else 30 | module Volt 31 | module LocalStorage 32 | @@store = {} 33 | 34 | def self.[](key) 35 | @@store[key] 36 | end 37 | 38 | def self.[]=(key, value) 39 | @@store[key] = value 40 | end 41 | 42 | def self.clear 43 | @@store = {} 44 | 45 | self 46 | end 47 | 48 | def self.delete(key) 49 | @@store.delete(key) 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/volt/utils/logging/task_argument_filterer.rb: -------------------------------------------------------------------------------- 1 | # TaskArgumentFilterer will recursively walk any arguemnts to a task and filter any 2 | # hashes with a filtered key. By default only :password is filtered, but you can add 3 | # more with Volt.config.filter_keys 4 | class TaskArgumentFilterer 5 | def self.filter(args) 6 | new(args).run 7 | end 8 | 9 | def initialize(args) 10 | # # Cache the filter args 11 | @@filter_args ||= begin 12 | # Load, with default, convert to symbols 13 | arg_names = (Volt.config.filter_keys || [:password]).map(&:to_sym) 14 | end 15 | 16 | @args = args 17 | end 18 | 19 | def run 20 | filter_args(@args) 21 | end 22 | 23 | private 24 | 25 | def filter_args(args) 26 | if args.is_a?(Array) 27 | args.map { |v| filter_args(v) } 28 | elsif args.is_a?(Hash) 29 | args.map do |k, v| 30 | if @@filter_args.include?(k.to_sym) 31 | # filter 32 | [k, '[FILTERED]'] 33 | else 34 | # retunr unfiltered 35 | [k, filter_args(v)] 36 | end 37 | end.to_h # <= convert back to hash 38 | else 39 | return args 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/volt/utils/logging/task_logger.rb: -------------------------------------------------------------------------------- 1 | require 'volt/utils/logging/task_argument_filterer' 2 | 3 | module Volt 4 | class TaskLogger 5 | def self.task_dispatch_message(logger, args) 6 | msg = "task #{logger.class_name}##{logger.method_name} in #{logger.run_time}\n" 7 | if args.size > 0 8 | arg_str = TaskArgumentFilterer.filter(args).map(&:inspect).join(', ') 9 | msg += "with args: #{arg_str}\n" 10 | end 11 | msg 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/volt/utils/modes.rb: -------------------------------------------------------------------------------- 1 | require 'thread' 2 | 3 | if RUBY_PLATFORM == 'opal' 4 | # Stub thread class 5 | class Thread 6 | def self.current 7 | @current ||= {} 8 | end 9 | end 10 | end 11 | 12 | module Volt 13 | # Modes provide a way to effect the state inside of a block that 14 | # can be checked from elsewhere. This is very useful if you have 15 | # some flag you may want to change without needing to pass all 16 | # of the way through some other code. 17 | module Modes 18 | module ClassMethods 19 | # Takes a block that when run, changes to mode inside of it 20 | def run_in_mode(mode_name) 21 | previous = Thread.current[mode_name] 22 | Thread.current[mode_name] = true 23 | begin 24 | yield 25 | ensure 26 | Thread.current[mode_name] = previous 27 | end 28 | end 29 | 30 | def run_in_mode_if(conditional, mode_name) 31 | if conditional 32 | # Yes, run in the mode 33 | Volt.run_in_mode(mode_name) do 34 | yield 35 | end 36 | else 37 | # Just run normally 38 | yield 39 | end 40 | end 41 | 42 | # Check to see if we are in the specified mode 43 | def in_mode?(mode_name) 44 | defined?(Thread) && Thread.current[mode_name] 45 | end 46 | end 47 | 48 | def self.included(base) 49 | base.send :extend, ClassMethods 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/volt/utils/parsing.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | module Parsing 3 | def self.decodeURI(value) 4 | if RUBY_PLATFORM == 'opal' 5 | `decodeURI(value)` 6 | else 7 | CGI.unescape(value.to_s) 8 | end 9 | end 10 | 11 | def self.encodeURI(value) 12 | if RUBY_PLATFORM == 'opal' 13 | `encodeURI(value)` 14 | else 15 | CGI.escape(value.to_s) 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/volt/utils/recursive_exists.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | module RecursiveExists 3 | def self.exists_here_or_up?(file) 4 | # Check for a gemfile here or up a directory 5 | pwd = Dir.pwd 6 | 7 | loop do 8 | if File.exists?("#{pwd}/#{file}") 9 | return true 10 | else 11 | pwd = pwd.gsub(/\/[^\/]+$/, '') 12 | return false if pwd == '' 13 | end 14 | end 15 | 16 | false 17 | end 18 | end 19 | end -------------------------------------------------------------------------------- /lib/volt/utils/tilt_patch.rb: -------------------------------------------------------------------------------- 1 | class Tilt::Template 2 | # Tilt outputs the following error: 3 | # WARN: tilt autoloading 'sass' in a non thread-safe way; explicit require 'sass' suggested. 4 | # I can't get rid of this no matter what I do. (If someone smarter wants to take a shot at 5 | # it, please do.) For now for my sanity, I'm just silencing its warnings. 6 | def warn(*args) 7 | # Kernel.warn(*args) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/volt/utils/time_patch.rb: -------------------------------------------------------------------------------- 1 | if RUBY_PLATFORM == 'opal' 2 | require 'time' 3 | 4 | class Time 5 | # Patchs a bug in the Time class, where two instances of time with the same 6 | # value do not hash to the same hash as they do in MRI. 7 | # https://github.com/opal/opal/issues/963 8 | def hash 9 | "Time:#{to_i}" 10 | end 11 | 12 | # Patches backported Opal Time because it's missing 13 | # this method 14 | def getlocal 15 | s = self.to_f - self.utc_offset 16 | ::Time.at(self.to_f) 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/volt/utils/volt_user_error.rb: -------------------------------------------------------------------------------- 1 | # VoltUserError is a base class for Volt errors that you don't need backtraces on. 2 | # These are errors that you simply need to communicate something to developer with. 3 | class VoltUserError < RuntimeError 4 | end 5 | -------------------------------------------------------------------------------- /lib/volt/version.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | module Version 3 | STRING = '0.9.7.pre1' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/volt/volt/core.rb: -------------------------------------------------------------------------------- 1 | # Require in the core volt classes that get used on the server 2 | require 'volt/controllers/http_controller' 3 | require 'volt/server/rack/http_request' 4 | -------------------------------------------------------------------------------- /lib/volt/volt/environment.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | class Environment 3 | def initialize 4 | # Use VOLT_ENV or RACK_ENV to set the environment 5 | @env = ENV['VOLT_ENV'] || ENV['RACK_ENV'] 6 | 7 | # If we're in opal, we can set the env from JS before opal loads 8 | if RUBY_PLATFORM == 'opal' 9 | unless @env 10 | `if (window.start_env) {` 11 | @env = `window.start_env` 12 | `}` 13 | end 14 | end 15 | 16 | @env ||= 'development' 17 | end 18 | 19 | def ==(val) 20 | @env == val 21 | end 22 | 23 | def production? 24 | self.==('production') 25 | end 26 | 27 | def test? 28 | self.==('test') 29 | end 30 | 31 | def development? 32 | self.==('development') 33 | end 34 | 35 | def inspect 36 | @env.inspect 37 | end 38 | 39 | def to_s 40 | @env 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/volt/volt/properties.rb: -------------------------------------------------------------------------------- 1 | # Volt.current_app.properties provides a hash like collection that backs to 2 | # the database. This is useful for storing global app property. (A common use 3 | # might be checking if something like an S3 bucket has been created without 4 | # the need to make an external API request.) 5 | class Properties 6 | def [](key) 7 | 8 | end 9 | 10 | def []=(key, value) 11 | prop = Volt.current_app.store.volt_app_properties.where({name: key}).first_or_create.sync 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/volt/volt/repos.rb: -------------------------------------------------------------------------------- 1 | # The Volt::Repos module provides access to each root collection (repo). 2 | 3 | module Volt 4 | module Repos 5 | def url 6 | @url ||= URL.new 7 | end 8 | 9 | def params 10 | @params ||= @url.params 11 | end 12 | 13 | def page 14 | @page ||= PageRoot.new 15 | end 16 | 17 | def store 18 | @store ||= StoreRoot.new({}, persistor: Persistors::StoreFactory.new(tasks)) 19 | end 20 | 21 | def flash 22 | @flash ||= begin 23 | check_for_client?('flash') 24 | FlashRoot.new({}, persistor: Persistors::Flash) 25 | end 26 | end 27 | 28 | def local_store 29 | @local_store ||= begin 30 | check_for_client?('local_store') 31 | LocalStoreRoot.new({}, persistor: Persistors::LocalStore) 32 | end 33 | end 34 | 35 | def cookies 36 | @cookies ||= begin 37 | check_for_client?('cookies') 38 | CookiesRoot.new({}, persistor: Persistors::Cookies) 39 | end 40 | end 41 | 42 | def check_for_client?(repo_name) 43 | unless Volt.client? 44 | fail "The #{repo_name} collection can only be accessed from the client side currently" 45 | end 46 | end 47 | end 48 | end -------------------------------------------------------------------------------- /lib/volt/volt/templates.rb: -------------------------------------------------------------------------------- 1 | # The Templates class holds all loaded templates. 2 | module Volt 3 | class Templates 4 | # On the server, we can delay loading the views until they are actually requeted. This 5 | # sets up an instance variable to call to load. 6 | attr_writer :template_loader 7 | 8 | def initialize 9 | @templates = {} 10 | end 11 | 12 | def [](key) 13 | templates[key] 14 | end 15 | 16 | def add_template(name, template, bindings) 17 | # First template gets priority. The backend will load templates in order so 18 | # that local templates come in before gems (so they can be overridden). 19 | # 20 | # TODO: Currently this means we will send templates to the client that will 21 | # not get used because they are being overridden. Need to detect that and 22 | # not send them. 23 | unless @templates[name] 24 | @templates[name] = { 'html' => template, 'bindings' => bindings } 25 | end 26 | end 27 | 28 | # Load the templates on first use if a loader was specified 29 | def templates 30 | if @template_loader 31 | # Load the templates 32 | @template_loader.call 33 | @template_loader = nil 34 | end 35 | 36 | @templates 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/apps/file_loading/app/bootstrap/assets/js/bootstrap.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/spec/apps/file_loading/app/bootstrap/assets/js/bootstrap.js -------------------------------------------------------------------------------- /spec/apps/file_loading/app/disable_auto/assets/css/test1.css.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/spec/apps/file_loading/app/disable_auto/assets/css/test1.css.scss -------------------------------------------------------------------------------- /spec/apps/file_loading/app/disable_auto/assets/css/test2.css.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/spec/apps/file_loading/app/disable_auto/assets/css/test2.css.scss -------------------------------------------------------------------------------- /spec/apps/file_loading/app/disable_auto/assets/js/test1.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/spec/apps/file_loading/app/disable_auto/assets/js/test1.js -------------------------------------------------------------------------------- /spec/apps/file_loading/app/disable_auto/assets/js/test2.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/spec/apps/file_loading/app/disable_auto/assets/js/test2.js -------------------------------------------------------------------------------- /spec/apps/file_loading/app/disable_auto/config/dependencies.rb: -------------------------------------------------------------------------------- 1 | disable_auto_import 2 | javascript_file '/app/disable_auto/assets/js/test1.js' 3 | css_file 'test1' -------------------------------------------------------------------------------- /spec/apps/file_loading/app/main/assets/css/test3.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/spec/apps/file_loading/app/main/assets/css/test3.css -------------------------------------------------------------------------------- /spec/apps/file_loading/app/main/assets/js/test1.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/spec/apps/file_loading/app/main/assets/js/test1.js -------------------------------------------------------------------------------- /spec/apps/file_loading/app/main/config/dependencies.rb: -------------------------------------------------------------------------------- 1 | component 'shared' 2 | component 'slideshow' 3 | -------------------------------------------------------------------------------- /spec/apps/file_loading/app/missing_deps/config/dependencies.rb: -------------------------------------------------------------------------------- 1 | component 'a-gem-that-isnt-in-the-gemfile' 2 | -------------------------------------------------------------------------------- /spec/apps/file_loading/app/shared/assets/js/test2.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/spec/apps/file_loading/app/shared/assets/js/test2.js -------------------------------------------------------------------------------- /spec/apps/file_loading/app/shared/config/dependencies.rb: -------------------------------------------------------------------------------- 1 | component 'bootstrap' -------------------------------------------------------------------------------- /spec/apps/file_loading/app/slideshow/assets/js/test3.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/spec/apps/file_loading/app/slideshow/assets/js/test3.js -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/.gitignore: -------------------------------------------------------------------------------- 1 | .bundle 2 | .config 3 | .yardoc 4 | tmp 5 | .idea 6 | .yardoc 7 | .sass-cache 8 | .DS_Store -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/app/main/assets/css/app.scss: -------------------------------------------------------------------------------- 1 | .logo1 { 2 | width: 129px; 3 | height: 50px; 4 | background: asset-url('../images/volt-logo.jpg') no-repeat center center; 5 | } 6 | 7 | .logo2 { 8 | width: 129px; 9 | height: 50px; 10 | background: asset-url('main/assets/images/volt-logo.jpg') no-repeat center center; 11 | } 12 | -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/app/main/assets/css/todos.css: -------------------------------------------------------------------------------- 1 | .todo-table { 2 | width: auto; 3 | } 4 | 5 | .todo-table td { 6 | padding: 5px; 7 | border-top: 1px solid #EEEEEE; 8 | } 9 | 10 | .complete, tr.selected td.complete { 11 | text-decoration: line-through; 12 | color: #CCCCCC; 13 | } 14 | 15 | tr.selected td { 16 | background-color: #428bca; 17 | color: #FFFFFF; 18 | } 19 | 20 | 21 | textarea { 22 | height: 140px; 23 | width: 400px; 24 | } 25 | 26 | tr.selected td button { 27 | color: #000000; 28 | } -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/app/main/assets/images/volt-logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/spec/apps/kitchen_sink/app/main/assets/images/volt-logo.jpg -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/app/main/config/dependencies.rb: -------------------------------------------------------------------------------- 1 | # Specify which components you wish to include when 2 | # the "home" component loads. 3 | 4 | # bootstrap css framework 5 | component 'bootstrap' 6 | 7 | # a default theme for the bootstrap framework 8 | component 'bootstrap_jumbotron_theme' 9 | 10 | # For testing user 11 | component 'fields' 12 | 13 | # For testing 14 | component 'user_templates' 15 | -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/app/main/config/initializers/sample_model_extend.rb: -------------------------------------------------------------------------------- 1 | module Volt 2 | class Model 3 | def self.acts_awesome 4 | true 5 | end 6 | end 7 | end -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/app/main/controllers/events_controller.rb: -------------------------------------------------------------------------------- 1 | module Main 2 | class EventsController < Volt::ModelController 3 | reactive_accessor :ran_some_event 4 | reactive_accessor :ran_other_event 5 | 6 | def trig_some_event 7 | trigger('some_event', 'yes') 8 | end 9 | 10 | def some_event(passes_args, event) 11 | if passes_args == 'yes' && event.is_a?(Volt::JSEvent) 12 | self.ran_some_event = true 13 | end 14 | end 15 | 16 | def trig_other_event 17 | trigger('other_event', 'yes') 18 | end 19 | 20 | def other_event(passes_args) 21 | if passes_args == 'yes' 22 | self.ran_other_event = true 23 | end 24 | end 25 | end 26 | end -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/app/main/controllers/save_controller.rb: -------------------------------------------------------------------------------- 1 | module Main 2 | class SaveController < Volt::ModelController 3 | def add_post 4 | store.posts.create(page._new_post.to_h).fail do |err| 5 | flash._notices << err.inspect 6 | end 7 | end 8 | end 9 | end -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/app/main/controllers/server/simple_http_controller.rb: -------------------------------------------------------------------------------- 1 | module Main 2 | class SimpleHttpController < Volt::HttpController 3 | def index 4 | render text: 'this is just some text' 5 | end 6 | 7 | def show 8 | render text: "You had me at #{store._simple_http_tests.first._name.sync}" 9 | end 10 | 11 | def create 12 | render text: params.inspect 13 | end 14 | 15 | def upload 16 | uploaded = params._file._tempfile 17 | File.open('tmp/uploaded_file', 'wb') { |f| f.write(uploaded.read) } 18 | render text: 'Thanks for uploading' 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/app/main/controllers/todos_controller.rb: -------------------------------------------------------------------------------- 1 | require 'volt/helpers/time' 2 | 3 | module Main 4 | class TodosController < Volt::ModelController 5 | model :store 6 | 7 | def add_todo 8 | _todos.create({ name: page._new_todo, created_at: VoltTime.new }) 9 | page._new_todo = '' 10 | end 11 | 12 | def remove_todo(todo) 13 | todo.destroy 14 | end 15 | 16 | def completed 17 | _todos.count(&:_completed) 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/app/main/controllers/upload_controller.rb: -------------------------------------------------------------------------------- 1 | module Main 2 | class UploadController < Volt::ModelController 3 | model :page 4 | 5 | def index 6 | # Nothing to setup here 7 | end 8 | 9 | def upload 10 | `form_data = new FormData(); 11 | form_data.append("file", $('#file')[0].files[0]); 12 | $.ajax({ 13 | url: '/simple_http/upload', 14 | data: form_data, 15 | processData: false, 16 | contentType: false, 17 | type: 'POST', 18 | success: function(data){ 19 | $('#status').html("successfully uploaded"); 20 | } 21 | });` 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/app/main/controllers/yield_component_controller.rb: -------------------------------------------------------------------------------- 1 | module Main 2 | class YieldComponentController < Volt::ModelController 3 | def content_string 4 | 'wrong' 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/app/main/models/post.rb: -------------------------------------------------------------------------------- 1 | class Post < Volt::Model 2 | field :title, String 3 | 4 | validate :title, length: 5 5 | 6 | end 7 | -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/app/main/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < Volt::User 2 | # login_field is set to :email by default and can be changed to :username 3 | # in config/app.rb 4 | field login_field 5 | field :name 6 | 7 | validate login_field, unique: true, length: 8 8 | validate :email, email: true 9 | 10 | unless RUBY_PLATFORM == "opal" 11 | Volt.current_app.on("user_connect") do |user_id| 12 | begin 13 | Volt.current_app.store.users.where(id: user_id).first.sync._event_triggered = "user_connect" 14 | rescue 15 | #we rescue as this callback will also get called from the SocketConnectionHandler specs (and will fail) 16 | end 17 | end 18 | 19 | Volt.current_app.on("user_disconnect") do |user_id| 20 | begin 21 | user = Volt.current_app.store.users.where(id: user_id).first.sync._event_triggered = "user_disconnect" 22 | rescue => e 23 | #we rescue as this callback will also get called from the SocketConnectionHandler specs (and will fail) 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/app/main/tasks/login_tasks.rb: -------------------------------------------------------------------------------- 1 | class LoginTasks < Volt::Task 2 | def login_first_user 3 | store.users.first.then do |first_user| 4 | login_as(first_user) 5 | end 6 | end 7 | end -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/app/main/views/events/index.html: -------------------------------------------------------------------------------- 1 | <:Title> 2 | Events 3 | 4 | <:Body> 5 |

Events

6 | 7 |
8 | <:some-event-button /> 9 |
10 | 11 | 12 | <:other-event-button e-other-event="other_event" /> 13 | 14 | 15 | {{ if ran_some_event }} 16 | ran some_event 17 | {{ end }} 18 | 19 | {{ if ran_other_event }} 20 | ran other_event 21 | {{ end }} 22 | 23 | 24 | 25 | 26 | <:SomeEventButton> 27 | 28 | 29 | <:OtherEventButton> 30 | 31 | -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/app/main/views/mailers/reset_password.html: -------------------------------------------------------------------------------- 1 | <:Subject> 2 | Password Reset 3 | 4 | <:Html> 5 | Reset your password here. 6 | 7 | Reset Password 8 | 9 | <:Text> 10 | Password Reset -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/app/main/views/mailers/welcome.email: -------------------------------------------------------------------------------- 1 | <:Subject> 2 | Welcome to the site! 3 | 4 | <:Html> 5 |

Welcome {{ name }}

6 | 7 |

Glad you signed up!

8 | 9 | <:Text> 10 | Welcome 11 | 12 | Glad you signed up! -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/app/main/views/main/callbacks.html: -------------------------------------------------------------------------------- 1 | <:Title> 2 | Lifecycle Callbacks 3 | 4 | <:Body> 5 |

Lifecycle Callbacks

6 | 7 | {{ store.users.first._event_triggered }} -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/app/main/views/main/cookie_test.html: -------------------------------------------------------------------------------- 1 | <:Title> 2 | Cookies 3 | 4 | <:Body> 5 |

Add Cookies

6 | 7 |
8 |
9 | 10 | 11 |
12 |
13 | 14 | 15 |
16 | 17 |
18 | 19 |

Cookies

20 | 21 |
    22 | {{ cookies.keys.each do |key| }} 23 |
  • {{ key }}: {{ cookies.get(key) }}
  • 24 | {{ end }} 25 |
-------------------------------------------------------------------------------- /spec/apps/kitchen_sink/app/main/views/main/first_last.html: -------------------------------------------------------------------------------- 1 | <:Title> 2 | Last 3 | 4 | <:Body> 5 |

First Last Test

6 | 7 | {{ store._messages.first.inspect }} 8 | 9 | --- 10 | 11 | {{ store._messages.each do |message| }} 12 |

{{ message._body }}

13 | {{ end }} -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/app/main/views/main/flash.html: -------------------------------------------------------------------------------- 1 | <:Title> 2 | Flash Messages 3 | 4 | <:Body> 5 |

Flash Messages

6 | 7 |

8 | Flash Notice
9 | Flash Success
10 | Flash Warning
11 | Flash Error
12 |

13 | 14 | -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/app/main/views/main/html_safe.html: -------------------------------------------------------------------------------- 1 | <:Title> 2 | HTML Safe 3 | 4 | <:Body> 5 |

Html Safe

6 | 7 | {{ raw example_html }} 8 | -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/app/main/views/main/images.html: -------------------------------------------------------------------------------- 1 | <:Title> 2 | Images 3 | 4 | <:Body> 5 |

Images

6 | 7 |
8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/app/main/views/main/index.html: -------------------------------------------------------------------------------- 1 | <:Title> 2 | KitchenSink 3 | 4 | <:Body> 5 |

Kitchen Sink

6 | 7 |

The kitchen sink app both showcases many of the features of Volt and acts as an app for integration testing.

8 | -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/app/main/views/main/login_from_task.html: -------------------------------------------------------------------------------- 1 | <:Title> 2 | Login From Task 3 | 4 | <:Body> 5 |

Login From Task

6 | 7 | -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/app/main/views/main/main.html: -------------------------------------------------------------------------------- 1 | <:Title> 2 | {{ view main_path, 'title', {controller_group: 'main'} }} - KitchenSink 3 | 4 | <:Body> 5 |
6 |
7 | 18 |

Project name

19 |
20 | 21 | <:volt:notices /> 22 | 23 | {{ view main_path, 'body', {controller_group: 'main'} }} 24 | 25 | 28 | 29 |
30 | 31 | <:Nav> 32 |
  • 33 | {{ attrs.text }} 34 |
  • 35 | 36 | -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/app/main/views/main/missing.html: -------------------------------------------------------------------------------- 1 | <:Title> 2 | Missing Tag and View 3 | 4 | <:Body> 5 |

    Missing Tag and View

    6 | 7 | {{ view 'some/wrong/path' }} 8 | 9 |
    10 | 11 | <:not:a:component /> 12 | 13 | 14 | -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/app/main/views/main/require_test.html: -------------------------------------------------------------------------------- 1 | <:Title> 2 | Require Test 3 | 4 | <:Body> 5 |

    Require Test

    6 | 7 |

    Date Class: {{ Date.respond_to?(:today).inspect }}

    8 | -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/app/main/views/main/store_demo.html: -------------------------------------------------------------------------------- 1 | <:Title> 2 | Store 3 | 4 | <:Body> 5 |

    Store Test

    6 | -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/app/main/views/main/yield.html: -------------------------------------------------------------------------------- 1 | <:Title> 2 | Yield Binding 3 | 4 | <:Body> 5 | <:yield-test>My yielded content {{ @i ||= 0 ; @i += 1 }} 6 | 7 | <:yield-component>This is my {{ content_string }} 8 | 9 | <:YieldTest> 10 |

    Yield Section

    11 | 12 |

    {{ yield }}

    13 |

    {{ yield }}

    14 | -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/app/main/views/save/index.html: -------------------------------------------------------------------------------- 1 | <:Title> 2 | Save 3 | 4 | <:Body> 5 |

    Save

    6 | 7 |
    8 | 9 | 10 |
    -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/app/main/views/todos/index.html: -------------------------------------------------------------------------------- 1 | <:Title> 2 | Todos 3 | 4 | <:Body> 5 |

    Todos Example

    6 | 7 |
    {{ completed }} of {{ _todos.size }}
    8 | 9 | 10 | {{ _todos.each_with_index do |todo, idx| }} 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {{ end }} 19 |
    {{ idx+1 }}.{{todo._name}}{{ todo._created_at.time_distance_in_words }}
    20 | 21 |
    22 |
    23 | 24 | 25 | 26 |
    27 |
    28 | -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/app/main/views/upload/index.html: -------------------------------------------------------------------------------- 1 | <:Title> 2 | File Upload 3 | 4 | <:Body> 5 |

    File Upload Example

    6 | 7 |
    8 | 9 | 10 | 11 |
    12 | 13 |
    14 | waiting... 15 |
    -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/app/main/views/yield_component/index.html: -------------------------------------------------------------------------------- 1 | <:Body> 2 |

    Yield Component

    3 | 4 | {{ yield }} -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/config.ru: -------------------------------------------------------------------------------- 1 | # Run via rack server 2 | require 'bundler/setup' 3 | require 'volt/server' 4 | run Volt::Server.new.app 5 | -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/config/app.rb: -------------------------------------------------------------------------------- 1 | Volt.setup do |config| 2 | config.app_secret = 'vFYUzMiPIdMw1Iox0ggDpBYorLm_d55YtRPc0eGrdCpoN4h9E5FcWySIT_D8JIEOllU' 3 | end 4 | -------------------------------------------------------------------------------- /spec/apps/kitchen_sink/config/base/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= javascript_tags %> 6 | 7 | <%= css_tags %> 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /spec/controllers/model_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | if RUBY_PLATFORM != 'opal' 4 | describe Volt::ModelController do 5 | it 'should accept a promise as a model and resolve it' do 6 | controller = Volt::ModelController.new(volt_app) 7 | 8 | promise = Promise.new 9 | 10 | controller.model = promise 11 | 12 | expect(controller.model).to eq(nil) 13 | 14 | promise.resolve(20) 15 | 16 | expect(controller.model).to eq(20) 17 | end 18 | 19 | it 'should not return true from loaded until the promise is resolved' do 20 | controller = Volt::ModelController.new(volt_app) 21 | 22 | promise = Promise.new 23 | controller.model = promise 24 | 25 | expect(controller.loaded?).to eq(false) 26 | 27 | promise.resolve(Volt::Model.new) 28 | expect(controller.loaded?).to eq(true) 29 | end 30 | 31 | it 'should provide a u method that disables reactive updates' do 32 | expect(Volt::Computation).to receive(:run_without_tracking) 33 | 34 | controller = Volt::ModelController.new(volt_app) 35 | controller.u { 5 } 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/controllers/reactive_accessors_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'volt/reactive/reactive_accessors' 3 | 4 | class TestReactiveAccessors 5 | include Volt::ReactiveAccessors 6 | 7 | reactive_accessor :_name 8 | end 9 | 10 | describe Volt::ReactiveAccessors do 11 | it 'should assign a reactive value' do 12 | inst = TestReactiveAccessors.new 13 | 14 | inst._name = 'Ryan' 15 | expect(inst._name).to eq('Ryan') 16 | end 17 | 18 | it 'should start nil' do 19 | inst = TestReactiveAccessors.new 20 | 21 | expect(inst._name).to eq(nil) 22 | end 23 | 24 | it 'should trigger changed when assigning a new value' do 25 | inst = TestReactiveAccessors.new 26 | values = [] 27 | 28 | -> { values << inst._name }.watch! 29 | 30 | expect(values).to eq([nil]) 31 | 32 | inst._name = 'Ryan' 33 | Volt::Computation.flush! 34 | expect(values).to eq([nil, 'Ryan']) 35 | 36 | inst._name = 'Stout' 37 | Volt::Computation.flush! 38 | expect(values).to eq([nil, 'Ryan', 'Stout']) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/extra_core/array_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'volt/extra_core/array' 3 | 4 | describe Array do 5 | describe '#sum' do 6 | it 'calculates sum of array of integers' do 7 | expect([1, 2, 3].sum).to eq(6) 8 | end 9 | end 10 | 11 | describe "#to_sentence" do 12 | it 'should return an empty string' do 13 | expect([].to_sentence).to eq('') 14 | end 15 | 16 | it 'should return a single entry' do 17 | expect([1].to_sentence).to eq('1') 18 | end 19 | 20 | it 'should combine an array into a string with a conjunection and commas' do 21 | expect([1,2,3].to_sentence).to eq('1, 2, and 3') 22 | end 23 | 24 | it 'should allow you to build an incorrect sentence' do 25 | expect([1,2,3].to_sentence(oxford: false)).to eq('1, 2 and 3') 26 | end 27 | 28 | it 'let you change the conjunction' do 29 | expect([1,2,3].to_sentence(conjunction: 'or')).to eq('1, 2, or 3') 30 | end 31 | 32 | it 'let you change the comma' do 33 | expect([1,2,3].to_sentence(comma: '!')).to eq('1! 2! and 3') 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/extra_core/blank_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'blank' do 4 | it 'should report blank when blank' do 5 | expect(' '.blank?).to eq(true) 6 | end 7 | 8 | it 'should report not blank when not blank' do 9 | expect(' text '.blank?).to eq(false) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/extra_core/class_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'volt/extra_core/array' 3 | 4 | class TestClassAttributes 5 | class_attribute :some_data 6 | class_attribute :attr2 7 | end 8 | 9 | class TestSubClassAttributes < TestClassAttributes 10 | end 11 | 12 | class TestSubClassAttributes2 < TestClassAttributes 13 | end 14 | 15 | describe 'extra_core class addons' do 16 | it 'should provide class_attributes that can be inherited' do 17 | expect(TestClassAttributes.some_data).to eq(nil) 18 | 19 | TestClassAttributes.some_data = 5 20 | expect(TestClassAttributes.some_data).to eq(5) 21 | expect(TestSubClassAttributes.some_data).to eq(5) 22 | expect(TestSubClassAttributes2.some_data).to eq(5) 23 | 24 | TestSubClassAttributes.some_data = 10 25 | expect(TestClassAttributes.some_data).to eq(5) 26 | expect(TestSubClassAttributes.some_data).to eq(10) 27 | expect(TestSubClassAttributes2.some_data).to eq(5) 28 | 29 | TestSubClassAttributes2.some_data = 15 30 | expect(TestClassAttributes.some_data).to eq(5) 31 | expect(TestSubClassAttributes.some_data).to eq(10) 32 | expect(TestSubClassAttributes2.some_data).to eq(15) 33 | end 34 | 35 | it 'should let you change a class attribute on the child without affecting the parent' do 36 | TestClassAttributes.attr2 = 1 37 | expect(TestSubClassAttributes.attr2).to eq(1) 38 | 39 | TestSubClassAttributes.attr2 = 2 40 | expect(TestClassAttributes.attr2).to eq(1) 41 | expect(TestSubClassAttributes.attr2).to eq(2) 42 | expect(TestSubClassAttributes2.attr2).to eq(1) 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/extra_core/hash_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Hash do 4 | it 'should return a hash without the speicified keys' do 5 | a = {one: 1, two: 2, three: 3} 6 | 7 | expect(a.without(:one, :three)).to eq({two: 2}) 8 | end 9 | end -------------------------------------------------------------------------------- /spec/extra_core/inflector_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'volt/extra_core/inflector' 3 | 4 | describe Volt::Inflector do 5 | it 'should pluralize correctly' do 6 | expect('car'.pluralize).to eq('cars') 7 | # expect('database'.pluralize).to eq('database') 8 | end 9 | 10 | it 'should singularize correctly' do 11 | expect('cars'.singularize).to eq('car') 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/extra_core/object_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'volt/extra_core/blank' 3 | 4 | describe Object do 5 | it 'should add blank? to all objects' do 6 | expect(Object.new.blank?).to eq(false) 7 | expect(nil.blank?).to eq(true) 8 | end 9 | 10 | it 'should add present? to all objects' do 11 | expect(Object.new.present?).to eq(true) 12 | expect(nil.present?).to eq(false) 13 | end 14 | 15 | it 'should allow you to call .then to get a Promise with the object resolved' do 16 | promise = 5.then 17 | 18 | expect(promise.resolved?).to eq(true) 19 | expect(promise.value).to eq(5) 20 | end 21 | 22 | it 'should allow you to call .then with a block that will yield the promise' do 23 | 5.then do |val| 24 | expect(val).to eq(5) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/extra_core/string_transformation_test_cases.rb: -------------------------------------------------------------------------------- 1 | CamelToUnderscore = { 2 | 'Product' => 'product', 3 | 'SpecialGuest' => 'special_guest', 4 | 'ApplicationController' => 'application_controller', 5 | 'Area51Controller' => 'area51_controller' 6 | } 7 | 8 | UnderscoreToLowerCamel = { 9 | 'product' => 'product', 10 | 'special_guest' => 'specialGuest', 11 | 'application_controller' => 'applicationController', 12 | 'area51_controller' => 'area51Controller' 13 | } 14 | 15 | UnderscoresToDashes = { 16 | 'street' => 'street', 17 | 'street_address' => 'street-address', 18 | 'person_street_address' => 'person-street-address' 19 | } 20 | 21 | UnderscoresToHeaders = { 22 | 'proxy_authenticate' => 'Proxy-Authenticate', 23 | 'set_cookie' => 'Set-Cookie', 24 | 'set-cookie' => 'Set-Cookie', 25 | 'via' => 'Via', 26 | 'WWW_Authenticate' => 'WWW-Authenticate' 27 | } 28 | -------------------------------------------------------------------------------- /spec/extra_core/symbol_spec.rb: -------------------------------------------------------------------------------- 1 | if RUBY_PLATFORM == 'opal' 2 | else 3 | require 'spec_helper' 4 | require 'volt/extra_core/symbol' 5 | 6 | describe Symbol do 7 | it 'should pluralize correctly' do 8 | expect(:car.pluralize).to eq(:cars) 9 | end 10 | 11 | it 'should singularize correctly' do 12 | expect(:cars.singularize).to eq(:car) 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/helpers/distance_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'volt/helpers/time' 3 | 4 | describe Volt::Duration do 5 | it 'should return a sentence describing the time' do 6 | dist = 1.hour + 2.days + 1.month + 305.seconds + 1.5.days 7 | 8 | expect(dist.duration_in_words(nil, :seconds)).to eq('1 month, 3 days, 13 hours, 5 minutes, and 5 seconds') 9 | end 10 | 11 | it 'should show a recent_message and' do 12 | expect(0.seconds.duration_in_words).to eq('just now') 13 | expect(1.minute.duration_in_words).to eq('1 minute') 14 | expect(0.seconds.duration_in_words(nil)).to eq('just now') 15 | end 16 | 17 | it 'should trim to the unit count' do 18 | dist = 1.hour + 2.days + 1.month + 305.seconds + 1.5.days 19 | expect(dist.duration_in_words(3)).to eq('1 month, 3 days, and 13 hours') 20 | expect(dist.duration_in_words(2)).to eq('1 month and 3 days') 21 | end 22 | 23 | it 'should return distance in words' do 24 | time1 = (1.hours + 5.minutes).ago 25 | expect(time1.time_distance_in_words).to eq('1 hour and 5 minutes ago') 26 | 27 | time2 = VoltTime.now 28 | time3 = time2 - (3.hours + 1.day + 2.seconds) 29 | 30 | expect(time3.time_distance_in_words(time2, nil, :seconds)).to eq('1 day, 3 hours, and 2 seconds ago') 31 | 32 | time4 = 1.second.ago 33 | expect(time4.time_distance_in_words).to eq('just now') 34 | end 35 | end -------------------------------------------------------------------------------- /spec/integration/callbacks_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'lifecycle callbacks', type: :feature, sauce: true do 4 | 5 | context 'with a user' do 6 | before do 7 | # Add the user 8 | store._users! << { email: 'test@test.com', password: 'awes0mesEcRet', name: 'Test Account 9550' } 9 | end 10 | 11 | it 'should trigger a user_connect event when a user logs in and a user_disconnect event when a user logs out' do 12 | visit '/' 13 | 14 | click_link 'Login' 15 | 16 | fields = all(:css, 'form .form-control') 17 | fields[0].set('test@test.com') 18 | fields[1].set('awes0mesEcRet') 19 | click_button 'Login' 20 | 21 | visit '/callbacks' 22 | 23 | expect(page).to have_content('user_connect') 24 | 25 | click_link 'Test Account 9550' 26 | click_link 'Logout' 27 | 28 | # TODO: This part of the spec fails for some reason. 29 | # expect(page).to have_content('user_disconnect') 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/integration/client_require_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'client require', type: :feature, sauce: true do 4 | it 'should require code in controllers' do 5 | visit '/require_test' 6 | 7 | expect(page).to have_content('Date Class: true') 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/integration/cookies_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'cookies collection', type: :feature, sauce: true do 4 | if ENV['BROWSER'] != 'phantom' 5 | # TODO: fails in phantom for some reason 6 | it 'should add' do 7 | visit '/' 8 | 9 | click_link 'Cookies' 10 | 11 | fill_in('cookieName', with: 'one') 12 | fill_in('cookieValue', with: 'one') 13 | click_button 'Add Cookie' 14 | 15 | expect(page).to have_content('one: one') 16 | 17 | # Reload the page 18 | page.evaluate_script('document.location.reload()') 19 | 20 | # Check again 21 | expect(page).to have_content('one: one') 22 | end 23 | end 24 | 25 | it 'should delete cookies' do 26 | visit '/' 27 | 28 | click_link 'Cookies' 29 | 30 | fill_in('cookieName', with: 'two') 31 | fill_in('cookieValue', with: 'two') 32 | click_button 'Add Cookie' 33 | 34 | expect(page).to have_content('two: two') 35 | 36 | find('.cookieDelete').click 37 | 38 | expect(page).to_not have_content('two: two') 39 | 40 | # Reload the page 41 | page.evaluate_script('document.location.reload()') 42 | 43 | expect(page).to_not have_content('two: two') 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /spec/integration/event_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'events and bubbling', type: :feature, sauce: true do 4 | it 'should bubble events through the dom' do 5 | visit '/events' 6 | 7 | click_button 'Run Some Event' 8 | 9 | expect(page).to have_content('ran some_event') 10 | end 11 | 12 | it 'should let you specify an e- handler on components' do 13 | visit '/events' 14 | 15 | click_button 'Run Other Event' 16 | 17 | expect(page).to have_content('ran other_event') 18 | end 19 | end -------------------------------------------------------------------------------- /spec/integration/first_last_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'first, last', type: :feature, sauce: true do 4 | # Currently failing, fixing 5 | # it 'should render first with a number argument' do 6 | # store._messages << {body: 'hey, sup?'} 7 | # store._messages << {body: 'not much'} 8 | # store._messages << {body: 'you?'} 9 | # store._messages << {body: 'just being awesome?'} 10 | # store._messages << {body: 'writing some specs'} 11 | # 12 | # visit '/first_last' 13 | # end 14 | end 15 | -------------------------------------------------------------------------------- /spec/integration/flash_spec.rb: -------------------------------------------------------------------------------- 1 | # This spec fails on sauce randomly, disable for now 2 | if ENV['BROWSER'] && ENV['BROWSER'] != 'sauce' 3 | require 'spec_helper' 4 | 5 | describe 'flash messages', type: :feature, sauce: true do 6 | it 'should flash on sucesses, notices, warnings, and errors' do 7 | visit '/' 8 | 9 | click_link 'Flash' 10 | 11 | click_link 'Flash Notice' 12 | expect(page).to have_content('A notice message') 13 | # sleep 40 14 | find('.alert').click 15 | expect(page).to_not have_content('A notice message') 16 | 17 | click_link 'Flash Success' 18 | expect(page).to have_content('A success message') 19 | find('.alert').click 20 | expect(page).to_not have_content('A success message') 21 | 22 | click_link 'Flash Warning' 23 | expect(page).to have_content('A warning message') 24 | find('.alert').click 25 | expect(page).to_not have_content('A warning message') 26 | 27 | click_link 'Flash Error' 28 | expect(page).to have_content('An error message') 29 | find('.alert').click 30 | expect(page).to_not have_content('An error message') 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/integration/http_endpoints_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'http endpoints', type: :feature, sauce: true do 4 | it 'should show the page' do 5 | visit '/simple_http' 6 | expect(page).to have_content('this is just some text') 7 | end 8 | 9 | it 'should have access to the store' do 10 | store._simple_http_tests << { name: 'hello' } 11 | visit '/simple_http/store' 12 | expect(page).to have_content('You had me at hello') 13 | end 14 | 15 | it 'should upload and store a file' do 16 | file = 'tmp/uploaded_file' 17 | FileUtils.rm(file) if File.exist?(file) 18 | visit '/upload' 19 | attach_file('file', __FILE__) 20 | find('#submit_file_upload').click 21 | expect(page).to have_content('successfully uploaded') 22 | expect(File.exist?(file)).to be(true) 23 | FileUtils.rm(file) if File.exist?(file) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/integration/images_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'image loading', type: :feature, sauce: true do 4 | it 'should load images in assets' do 5 | visit '/images' 6 | 7 | loaded = page.evaluate_script("$('img').get(0).complete") 8 | expect(loaded).to eq(true) 9 | end 10 | end -------------------------------------------------------------------------------- /spec/integration/missing_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "missing tags and view's", type: :feature do 4 | it 'should show a message about the missing tag/view' do 5 | visit '/missing' 6 | 7 | expect(page).to have_content('view or tag at "some/wrong/path"') 8 | expect(page).to have_content('view or tag at "not/a/component"') 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/integration/raw_html_binding.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'HTML safe/raw', type: :feature do 4 | it 'should render html with the raw helper' do 5 | visit '/html_safe' 6 | 7 | expect(page).to have_selector('button[id="examplebutton"]') 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/integration/save_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'saving', type: :feature, sauce: true do 4 | it 'should return an error as an instance of Volt::Error' do 5 | visit '/save' 6 | 7 | fill_in 'post_title', with: 'ok' 8 | 9 | click_button 'Save' 10 | 11 | expect(page).to have_content('#["must be at least 5 characters"]}>') 12 | end 13 | end -------------------------------------------------------------------------------- /spec/integration/store_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | # describe 'store', type: :feature, sauce: true do 4 | # it 'should sync between nested root properties on store' do 5 | # visit '/store' 6 | 7 | # fill_in('field1', with: 'should sync') 8 | # expect(find('#field2').value).to eq('should sync') 9 | # end 10 | # end -------------------------------------------------------------------------------- /spec/integration/templates_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'bindings test', type: :feature, sauce: true do 4 | it 'should change the title when changing pages' do 5 | visit '/' 6 | 7 | expect(page).to have_title 'KitchenSink - KitchenSink' 8 | click_link 'Bindings' 9 | 10 | expect(page).to have_title 'Bindings - KitchenSink' 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/integration/todos_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'todos app', type: :feature, sauce: true do 4 | ENTER_KEY = ENV["BROWSER"] == 'phantom' ? :Enter : :return 5 | it 'should add a todo and remove it' do 6 | visit '/todos' 7 | store._todos 8 | 9 | fill_in 'newtodo', with: 'Todo 1' 10 | find('#newtodo').native.send_keys(ENTER_KEY) 11 | 12 | expect(page).to have_content('Todo 1') 13 | 14 | expect(find('#newtodo').value).to eq('') 15 | 16 | click_button 'X' 17 | 18 | expect(page).to_not have_content('Todo 1') 19 | 20 | # Make sure it deleted 21 | if ENV['BROWSER'] == 'phantom' 22 | visit '/todos' 23 | else 24 | page.driver.browser.navigate.refresh 25 | end 26 | expect(page).to_not have_content('Todo 1') 27 | end 28 | 29 | it 'should update a todo check state and persist' do 30 | visit '/todos' 31 | store._todos 32 | 33 | fill_in 'newtodo', with: 'Todo 1' 34 | find('#newtodo').native.send_keys(ENTER_KEY) 35 | 36 | expect(page).to have_content('Todo 1') 37 | 38 | find("input[type='checkbox']").click 39 | 40 | if ENV['BROWSER'] == 'phantom' 41 | visit '/todos' 42 | else 43 | page.evaluate_script('document.location.reload()') 44 | end 45 | 46 | expect(find("input[type='checkbox']").checked?).to eq(true) 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/integration/url_spec.rb: -------------------------------------------------------------------------------- 1 | if ENV['BROWSER'] && ENV['BROWSER'] != 'phantom' 2 | require 'spec_helper' 3 | 4 | describe 'url features', type: :feature, sauce: true do 5 | it 'should update the page when using the back button' do 6 | visit '/' 7 | expect(current_url).to match(/\/$/) 8 | 9 | click_link 'Bindings' 10 | expect(current_url).to match(/\/bindings$/) 11 | expect(page).to have_content('Checkbox') 12 | 13 | click_link 'Todos' 14 | expect(current_url).to match(/\/todos$/) 15 | expect(page).to have_content('Todos Example') 16 | 17 | # "Click" back button 18 | page.evaluate_script('window.history.back()') 19 | 20 | click_link 'Bindings' 21 | expect(current_url).to match(/\/bindings$/) 22 | expect(page).to have_content('Checkbox') 23 | 24 | # "Click" back button 25 | page.evaluate_script('window.history.back()') 26 | 27 | expect(current_url).to match(/\/$/) 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/integration/yield_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'yield binding', type: :feature, sauce: true do 4 | before do 5 | visit '/yield' 6 | end 7 | 8 | it 'should render the yielded content multiple times' do 9 | expect(page).to have_content('My yielded content 1') 10 | expect(page).to have_content('My yielded content 2') 11 | end 12 | 13 | it 'should render the content from the tag\'s controller when yielding' do 14 | expect(page).to have_content('This is my content') 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/models/helpers/base_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Volt::Models::Helpers::Base do 4 | # it 'should have a root method on models that returns the root collection' do 5 | # model = store._posts.create 6 | # expect(model.root).to eq(store) 7 | # end 8 | end -------------------------------------------------------------------------------- /spec/models/helpers/model_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Volt::Models::Helpers::Model do 4 | describe "saved_state" do 5 | it 'should start not_saved for a buffer' do 6 | item = the_page._items.buffer 7 | expect(item.saved_state).to eq(:not_saved) 8 | end 9 | 10 | it 'should move to saved when the buffer is saved' do 11 | item = the_page._items.buffer 12 | item.save!.then do 13 | expect(item.saved_state).to eq(:saved) 14 | end 15 | end 16 | 17 | it 'should start as saved after create' do 18 | item = the_page._items.create({name: 'One'}) 19 | 20 | expect(item.saved_state).to eq(:saved) 21 | end 22 | 23 | # TODO: because server side model loading is done synchronusly, we can't 24 | # test the 25 | end 26 | end -------------------------------------------------------------------------------- /spec/models/model_state_spec.rb: -------------------------------------------------------------------------------- 1 | # Models automatically unload if no dependencies are listening and they have not been .keep (kept) 2 | 3 | if RUBY_PLATFORM != 'opal' 4 | require 'spec_helper' 5 | 6 | describe Volt::Model do 7 | it 'should stay loaded while a computaiton is watching some data' do 8 | expect(store._items!.loaded_state).to eq(:not_loaded) 9 | 10 | comp = -> { store._items.size }.watch! 11 | 12 | # On the server models do a blocking load 13 | expect(store._items.loaded_state).to eq(:loaded) 14 | 15 | comp.stop 16 | 17 | Volt::Timers.flush_next_tick_timers! 18 | 19 | # Computation stopped listening, so the collection should unload and be set to 20 | # a dirty state 21 | expect(store._items.loaded_state).to eq(:dirty) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/models/persistors/flash_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module Volt 4 | module Persistors 5 | describe Flash do 6 | let(:fake_parent) { double('Parent', delete: true) } 7 | let(:fake_passed_model) { double } 8 | 9 | let(:fake_model) do 10 | double( 11 | 'Model', 12 | size: 1, 13 | parent: fake_parent, 14 | path: '12', 15 | delete: true 16 | ) 17 | end 18 | 19 | describe '#added' do 20 | it 'returns nil' do 21 | flash = described_class.new double 22 | 23 | expect(flash.added(double, 0)).to be_nil 24 | end 25 | end 26 | 27 | describe '#clear_model' do 28 | it 'sends #delete to @model' do 29 | described_class.new(fake_model).clear_model fake_passed_model 30 | 31 | expect(fake_model).to have_received(:delete).with(fake_passed_model) 32 | end 33 | 34 | it 'with a size of zero, parent receives #delete' do 35 | collection_name = fake_model.path[-1] 36 | allow(fake_model).to receive(:size).and_return 0 37 | 38 | described_class.new(fake_model).clear_model fake_passed_model 39 | 40 | expect(fake_parent).to have_received(:delete).with(collection_name) 41 | end 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /spec/models/persistors/page_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module Volt 4 | module Persistors 5 | describe Page do 6 | describe '#where' do 7 | it 'searches for records in the page collection with the given values' do 8 | juan = Volt::Model.new(name: 'Juan', city: 'Quito', age: 13) 9 | pedro = Volt::Model.new(name: 'Pedro', city: 'Quito', age: 15) 10 | jose = Volt::Model.new(name: 'Jose', city: 'Quito', age: 13) 11 | 12 | page = described_class.new [jose, juan, pedro] 13 | 14 | expect(page.where age: 13, city: 'Quito').to match_array [juan, jose] 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/models/persistors/params_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'volt/models' 3 | 4 | describe Volt::Persistors::Params do 5 | it 'should stay as params classes when used' do 6 | a = Volt::Model.new({}, persistor: Volt::Persistors::Params) 7 | # expect(a._test!.class).to eq(Volt::Model) 8 | # 9 | # expect(a._test!._cool!.persistor.class).to eq(Volt::Persistors::Params) 10 | 11 | a._items << { name: 'Test' } 12 | # 13 | # expect(a._items.persistor.class).to eq(Volt::Persistors::Params) 14 | # expect(a._items[0].persistor.class).to eq(Volt::Persistors::Params) 15 | # expect(a._items[0]._name!.class).to eq(String) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/models/user_validation_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | class TestUserTodo < Volt::Model 4 | own_by_user 5 | 6 | permissions(:update) do 7 | deny :user_id 8 | end 9 | end 10 | 11 | describe Volt::UserValidatorHelpers do 12 | context 'with user' do 13 | before do 14 | allow(Volt).to receive(:current_user_id) { 294 } 15 | end 16 | 17 | it 'should assign user_id when owning by a user' do 18 | todo = TestUserTodo.new 19 | expect(todo._user_id).to eq(294) 20 | end 21 | 22 | it 'should not allow the user_id to be changed' do 23 | todo = TestUserTodo.new 24 | expect(todo._user_id).to eq(294) 25 | 26 | todo._user_id = 500 27 | 28 | expect(todo._user_id).to eq(294) 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/models/validators/block_validations_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | unless RUBY_PLATFORM == 'opal' 4 | describe 'validations block' do 5 | let(:model) { test_model_class.new } 6 | 7 | let(:test_model_class) do 8 | Class.new(Volt::Model) do 9 | validations do 10 | if _is_ready == true 11 | validate :name, length: 5 12 | end 13 | end 14 | end 15 | end 16 | 17 | let(:test_model_action_pass_class) do 18 | Class.new(Volt::Model) do 19 | validations do |action| 20 | # Only validation the name on update 21 | if action == :update 22 | validate :name, length: 5 23 | end 24 | end 25 | end 26 | end 27 | 28 | it 'should run conditional validations in the validations block' do 29 | a = test_model_class.new(name: 'Jo') 30 | 31 | a.validate!.sync 32 | expect(a.errors.size).to eq(0) 33 | 34 | a._is_ready = true 35 | a.validate!.sync 36 | 37 | expect(a.errors.size).to eq(1) 38 | end 39 | 40 | it 'should send the action name to the validations block' do 41 | jo = test_model_action_pass_class.new(name: 'Jo') 42 | 43 | jo.validate!.sync 44 | expect(jo.errors.size).to eq(0) 45 | 46 | store._people << jo 47 | 48 | jo.validate!.sync 49 | expect(jo.errors.size).to eq(1) 50 | 51 | end 52 | end 53 | end -------------------------------------------------------------------------------- /spec/models/validators/unique_validator_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | unless RUBY_PLATFORM == 'opal' 4 | class Fridge < Volt::Model 5 | validate :name, unique: true 6 | end 7 | 8 | describe 'unique spec' do 9 | it 'should reject save if there are records with existing attributes already' do 10 | store._fridges << { name: 'swift' } 11 | fridge = store._fridges.buffer name: 'swift' 12 | fridge.save!.then do 13 | expect(false).to be_true 14 | end.fail do 15 | expect(true).to be_true 16 | end 17 | end 18 | 19 | it 'should not increase count of the total records in the store' do 20 | store._fridges << { name: 'swift' } 21 | store._fridges << { name: 'swift' } 22 | expect(store._fridges.count.sync).to eq(1) 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/page/bindings/content_binding_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'volt/page/bindings/content_binding' 3 | require 'volt/page/targets/attribute_target' 4 | require 'volt/page/targets/dom_section' 5 | require 'volt/page/template_renderer' 6 | 7 | describe Volt::ContentBinding do 8 | it 'should render the content in a content binding' do 9 | dom = Volt::AttributeTarget.new(0) 10 | context = { name: 'jimmy' } 11 | binding = Volt::ContentBinding.new(nil, dom, context, 0, proc { self[:name] }) 12 | 13 | expect(dom.to_html).to eq('jimmy') 14 | end 15 | 16 | it 'should render with a template' do 17 | context = { name: 'jimmy' } 18 | binding = ->(volt_app, target, context, id) { Volt::ContentBinding.new(volt_app, target, context, id, proc { self[:name] }) } 19 | 20 | templates = { 21 | 'main/main' => { 22 | 'html' => 'hello ', 23 | 'bindings' => { 1 => [binding] } 24 | } 25 | } 26 | 27 | volt_app = double('volt/app') 28 | expect(volt_app).to receive(:templates).and_return(templates) 29 | 30 | 31 | 32 | dom = Volt::AttributeTarget.new(0) 33 | 34 | Volt::TemplateRenderer.new(volt_app, dom, context, 'main', 'main/main') 35 | 36 | expect(dom.to_html).to eq('hello jimmy') 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/page/bindings/if_binding_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | class ::TestIfBindingController < Volt::ModelController 4 | model :page 5 | end 6 | 7 | describe Volt::IfBinding do 8 | it 'should render an if' do 9 | dom = Volt::AttributeTarget.new(0) 10 | context = ::TestIfBindingController.new(volt_app) 11 | context._name = 'jimmy' 12 | 13 | branches = [ 14 | [ 15 | proc { _name == 'jimmy' }, 16 | 'main/if_true' 17 | ], 18 | [ 19 | nil, 20 | 'main/if_false' 21 | ] 22 | ] 23 | 24 | binding = ->(volt_app, target, context, id) do 25 | Volt::IfBinding.new(volt_app, target, context, 0, branches) 26 | end 27 | 28 | templates = { 29 | 'main/main' => { 30 | 'html' => 'hello ', 31 | 'bindings' => { 1 => [binding] } 32 | }, 33 | 'main/if_true' => { 34 | 'html' => 'yes, true', 35 | 'bindings' => {} 36 | }, 37 | 'main/if_false' => { 38 | 'html' => 'no, false', 39 | 'bindings' => {} 40 | } 41 | } 42 | 43 | volt_app = double('volt/app') 44 | expect(volt_app).to receive(:templates).and_return(templates).at_least(1).times 45 | 46 | Volt::TemplateRenderer.new(volt_app, dom, context, 'main', 'main/main') 47 | 48 | expect(dom.to_html).to eq('yes, true') 49 | 50 | context._name = 'bob' 51 | Volt::Computation.flush! 52 | expect(dom.to_html).to eq('no, false') 53 | end 54 | end -------------------------------------------------------------------------------- /spec/page/bindings/template_binding_spec.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/spec/page/bindings/template_binding_spec.rb -------------------------------------------------------------------------------- /spec/page/sub_context_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'volt/page/sub_context' 3 | 4 | describe Volt::SubContext do 5 | it 'should respond_to correctly on locals' do 6 | sub_context = Volt::SubContext.new(name: 'Name') 7 | 8 | expect(sub_context.respond_to?(:name)).to eq(true) 9 | expect(sub_context.respond_to?(:missing)).to eq(false) 10 | end 11 | 12 | it 'should return correctly for missing methods on SubContext' do 13 | sub_context = Volt::SubContext.new(name: 'Name') 14 | 15 | expect(sub_context.send(:name)).to eq('Name') 16 | expect { sub_context.send(:missing) }.to raise_error(NoMethodError) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/reactive/class_eventable_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | class TestClassEventable 4 | include Volt::ClassEventable 5 | 6 | attr_reader :run_count 7 | 8 | def initialize 9 | @run_count = 0 10 | end 11 | 12 | on(:works) do 13 | ran_works 14 | end 15 | 16 | def ran_works 17 | @run_count += 1 18 | end 19 | 20 | def trigger_works_event! 21 | trigger!(:works, 20) 22 | end 23 | end 24 | 25 | describe Volt::ClassEventable do 26 | it 'does something' do 27 | test_ev = TestClassEventable.new 28 | 29 | expect(test_ev.run_count).to eq(0) 30 | test_ev.trigger_works_event! 31 | 32 | expect(test_ev.run_count).to eq(1) 33 | test_ev.trigger_works_event! 34 | 35 | expect(test_ev.run_count).to eq(2) 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/reactive/reactive_hash_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Volt::ReactiveHash do 4 | it 'should clear' do 5 | a = Volt::ReactiveHash.new 6 | a[:name] = 'Bob' 7 | 8 | expect(a[:name]).to eq('Bob') 9 | a.clear 10 | expect(a[:name]).to eq(nil) 11 | end 12 | 13 | it 'should return to_json' do 14 | a = Volt::ReactiveHash.new({name: 'bob'}) 15 | expect(a.to_json).to eq("{\"name\":\"bob\"}") 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/server/component_templates_spec.rb: -------------------------------------------------------------------------------- 1 | if RUBY_PLATFORM == 'opal' 2 | else 3 | require 'spec_helper' 4 | require 'benchmark' 5 | require 'volt/server/component_templates' 6 | 7 | describe Volt::ComponentTemplates do 8 | let(:haml_handler) do 9 | double(:haml_handler) 10 | end 11 | 12 | it 'can be extended' do 13 | expect( Volt::ComponentTemplates::Preprocessors.extensions ).to eq([ :html, :email ]) 14 | 15 | Volt::ComponentTemplates.register_template_handler(:haml, haml_handler) 16 | 17 | expect( Volt::ComponentTemplates::Preprocessors.extensions ).to eq([ :html, :email, :haml ]) 18 | end 19 | end 20 | 21 | end 22 | -------------------------------------------------------------------------------- /spec/server/forking_server_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | unless RUBY_PLATFORM == 'opal' 3 | require 'volt/server' 4 | 5 | describe Volt::ForkingServer do 6 | it 'should set polling an an option when using POLL_FS env' do 7 | ENV['POLL_FS'] = 'true' 8 | forking_server = Volt::ForkingServer.allocate 9 | 10 | # Lots of stubs, since we're working with the FS 11 | listener = double('listener') 12 | expect(listener).to receive(:start) 13 | expect(listener).to receive(:stop) 14 | expect(Listen).to receive(:to).with('/app/', {force_polling: true}).and_return(listener) 15 | expect(forking_server).to receive(:sync_mod_time) 16 | 17 | server = double('server') 18 | expect(server).to receive(:app_path).and_return('/app') 19 | forking_server.instance_variable_set(:@server, server) 20 | 21 | forking_server.start_change_listener 22 | ENV.delete('POLL_FS') 23 | 24 | forking_server.stop_change_listener 25 | end 26 | end 27 | end -------------------------------------------------------------------------------- /spec/server/html_parser/view_handler_spec.rb: -------------------------------------------------------------------------------- 1 | if RUBY_PLATFORM == 'opal' 2 | else 3 | require 'benchmark' 4 | require 'volt/server/html_parser/view_handler' 5 | 6 | describe Volt::ViewHandler do 7 | let(:handler) { Volt::ViewHandler.new('main/main/main') } 8 | 9 | it 'handles tags' do 10 | handler.comment('Yowza!') 11 | handler.start_tag('a', { href: 'yahoo.com' }, false) 12 | handler.text('Cool in 1996') 13 | handler.end_tag('a') 14 | 15 | expectation = 'Cool in 1996' 16 | expect(handler.html).to eq(expectation) 17 | end 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /spec/server/html_parser/view_scope_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Volt::ViewScope do 4 | describe "methodize strings" do 5 | def methodize(str) 6 | Volt::ViewScope.methodize_string(str) 7 | end 8 | 9 | it 'should methodize a method without args' do 10 | code = methodize('something') 11 | expect(code).to eq('method(:something)') 12 | end 13 | 14 | it 'should methodize a method without args2' do 15 | code = methodize('something?') 16 | expect(code).to eq('method(:something?)') 17 | end 18 | 19 | it 'should methodize a method wit args1' do 20 | code = methodize('set_something(true)') 21 | expect(code).to eq('set_something(true)') 22 | end 23 | 24 | it 'should not methodize a method call with args' do 25 | code = methodize('something(item1, item2)') 26 | expect(code).to eq('something(item1, item2)') 27 | end 28 | 29 | it 'should not methodize on assignment' do 30 | code = methodize('params._something = 5') 31 | expect(code).to eq('params._something = 5') 32 | end 33 | 34 | it 'should not methodize on hash lookup' do 35 | code = methodize('hash[:something]') 36 | expect(code).to eq('hash[:something]') 37 | end 38 | 39 | it 'should not methodize on instance variables' do 40 | code = methodize('@something.call') 41 | expect(code).to eq('@something.call') 42 | end 43 | end 44 | end -------------------------------------------------------------------------------- /spec/server/message_bus/peer_to_peer/socket_with_timeout_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | unless RUBY_PLATFORM == 'opal' 4 | describe Volt::SocketWithTimeout do 5 | it 'should setup a connection manually and then specify a timeout' do 6 | allow_any_instance_of(Socket).to receive(:connect) 7 | 8 | Volt::SocketWithTimeout.new('google.com', 80, 10) 9 | end 10 | end 11 | end -------------------------------------------------------------------------------- /spec/server/message_bus/peer_to_peer_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | unless RUBY_PLATFORM == 'opal' 4 | describe Volt::MessageBus::PeerToPeer do 5 | before do 6 | # Stub socket stuff 7 | allow_any_instance_of(Volt::MessageBus::PeerToPeer).to receive(:connect_to_peers).and_return(nil) 8 | end 9 | 10 | end 11 | end -------------------------------------------------------------------------------- /spec/server/middleware/middleware_stack_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | unless RUBY_PLATFORM == 'opal' 4 | describe Volt::MiddlewareStack do 5 | before do 6 | @stack = Volt::MiddlewareStack.new 7 | end 8 | 9 | it 'should insert a middleware at the end of the stack when calling use' do 10 | middleware1 = double('middleware1') 11 | @stack.use(middleware1, 'arg1') 12 | 13 | expect(@stack.middlewares).to eq([ 14 | [[middleware1, 'arg1'], nil] 15 | ]) 16 | end 17 | end 18 | end -------------------------------------------------------------------------------- /spec/server/rack/component_paths_spec.rb: -------------------------------------------------------------------------------- 1 | if RUBY_PLATFORM != 'opal' 2 | require 'volt/server/rack/component_paths' 3 | 4 | describe Volt::ComponentPaths do 5 | before do 6 | spec_app_root = File.join(File.dirname(__FILE__), '../../apps/file_loading') 7 | 8 | path_to_main = File.join(File.dirname(__FILE__), '../../apps/file_loading/app/main') 9 | @component_paths = Volt::ComponentPaths.new(spec_app_root) 10 | end 11 | 12 | it 'should return the paths to all app folders' do 13 | match_count = 0 14 | @component_paths.app_folders do |app_folder| 15 | if app_folder[/spec\/apps\/file_loading\/app$/] || app_folder[/spec\/apps\/file_loading\/vendor\/app$/] 16 | match_count += 1 17 | end 18 | end 19 | 20 | expect(match_count).to eq(2) 21 | end 22 | 23 | it 'should return the path to a component' do 24 | main_path = @component_paths.component_paths('main').first 25 | expect(main_path).to match(/spec\/apps\/file_loading\/app\/main$/) 26 | end 27 | 28 | it 'should not return paths to non-volt gems' do 29 | Gem.loaded_specs['fake-gem'] = Gem::Specification.new do |s| 30 | s.name = 'fake-gem' 31 | s.version = Gem::Version.new('1.0') 32 | s.full_gem_path = '/home/volt/projects/volt-app' 33 | end 34 | app_folders = @component_paths.app_folders { |f| f } 35 | expect(app_folders).to_not include('/home/volt/projects/volt-app/app') 36 | Gem.loaded_specs.delete 'fake-gem' 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/server/rack/http_response_header_spec.rb: -------------------------------------------------------------------------------- 1 | require 'volt/server/rack/http_response_header' 2 | 3 | describe Volt::HttpResponseHeader do 4 | it 'it should headerize the keys' do 5 | header = Volt::HttpResponseHeader.new 6 | header[:content_type] = 'test' 7 | expect(header['Content-Type']).to eq('test') 8 | expect(header['content-type']).to eq('test') 9 | expect(header['content_type']).to eq('test') 10 | expect(header[:content_type]).to eq('test') 11 | expect(header.keys).to eq(['Content-Type']) 12 | end 13 | 14 | it 'should delete keys' do 15 | header = Volt::HttpResponseHeader.new 16 | header[:content_type] = 'test' 17 | expect(header.delete(:content_type)).to eq('test') 18 | expect(header.size).to eq 0 19 | end 20 | 21 | it 'should merge other plain hashes and headerize their keys' do 22 | header = Volt::HttpResponseHeader.new 23 | header[:content_type] = 'test' 24 | 25 | hash = {} 26 | hash[:transfer_encoding] = 'encoding' 27 | 28 | expect(header.merge(hash)).to be_a(Volt::HttpResponseHeader) 29 | expect(header.merge(hash)['Transfer-Encoding']).to eq('encoding') 30 | 31 | header.merge!(hash) 32 | expect(header['Transfer-Encoding']).to eq('encoding') 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/server/rack/http_response_renderer_spec.rb: -------------------------------------------------------------------------------- 1 | require 'volt/server/rack/http_response_renderer' 2 | 3 | describe Volt::HttpResponseRenderer do 4 | let(:renderer) { Volt::HttpResponseRenderer.new } 5 | 6 | it 'should render json' do 7 | hash = { a: 'aa', bb: 'bbb' } 8 | body, additional_headers = renderer.render json: hash 9 | expect(body).to eq(hash.to_json) 10 | expect(additional_headers[:content_type]).to eq('application/json') 11 | end 12 | 13 | it 'should render plain text' do 14 | text = 'just some text' 15 | body, additional_headers = renderer.render(text: text) 16 | expect(body).to eq(text) 17 | expect(additional_headers[:content_type]).to eq('text/plain') 18 | end 19 | 20 | it 'should give renderer error if no suitable renderer could be found' do 21 | body, additional_headers = renderer.render(some: 'text') 22 | expect(body).to eq('Error: render only supports json, text') 23 | expect(additional_headers[:content_type]).to eq('text/plain') 24 | end 25 | 26 | it 'should add all remaining keys as additional_headers' do 27 | text = 'just some text' 28 | body, additional_headers = renderer.render(text: text, 29 | additional: 'headers') 30 | expect(body).to eq(text) 31 | expect(additional_headers[:additional]).to eq('headers') 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/server/rack/rack_requests_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | if ENV['BROWSER'] && ENV['BROWSER'] == 'phantom' 4 | describe 'Rack Requests', type: :feature do 5 | it 'should send JS file with JS mimetype' do 6 | visit '/app/components/main.js' 7 | 8 | expect(page.response_headers['Content-Type']).to include 'application/javascript' 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/server/rack/sprockets_helpers_setup.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "sprockets helpers" do 4 | it 'should expand paths' do 5 | result = Volt::SprocketsHelpersSetup.expand('/something/cool/../awesome/beans/../../five/six/seven') 6 | 7 | expect(result).to eq('/something/five/six/seven') 8 | end 9 | 10 | it 'should expand paths2' do 11 | result = Volt::SprocketsHelpersSetup.expand('bootstrap/assets/css/../fonts/glyphicons-halflings-regular.svg') 12 | 13 | expect(result).to eq('bootstrap/assets/fonts/glyphicons-halflings-regular.svg') 14 | end 15 | end -------------------------------------------------------------------------------- /spec/tasks/live_query_spec.rb: -------------------------------------------------------------------------------- 1 | if RUBY_PLATFORM != 'opal' 2 | describe 'LiveQuery' do 3 | before do 4 | load File.join(File.dirname(__FILE__), '../../app/volt/tasks/live_query/live_query.rb') 5 | end 6 | 7 | it 'should run a query' do 8 | pool = double('volt/pool') 9 | data_store = double('volt/data store') 10 | 11 | expect(data_store).to receive(:query).with('_items', {}).and_return([ 12 | { 'id' => 0, '_name' => 'one' } 13 | ]) 14 | 15 | live_query = LiveQuery.new(pool, data_store, '_items', {}) 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/tasks/query_tasks.rb: -------------------------------------------------------------------------------- 1 | if RUBY_PLATFORM != 'opal' 2 | describe 'Volt::QueryTasks' do 3 | before do 4 | load File.join(File.dirname(__FILE__), '../../app/volt/tasks/query_tasks.rb') 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/templates/targets/binding_document/component_node_spec.rb: -------------------------------------------------------------------------------- 1 | require 'volt/page/targets/binding_document/component_node' 2 | 3 | describe Volt::ComponentNode do 4 | before do 5 | html = <<-END 6 | Before Inside After 7 | END 8 | 9 | @component = Volt::ComponentNode.new 10 | @component.html = html 11 | end 12 | 13 | it 'should find a component from a binding id' do 14 | expect(@component.find_by_binding_id(1).to_html).to eq('Inside') 15 | expect(@component.find_by_binding_id(0).to_html).to eq('Before Inside After') 16 | end 17 | 18 | # it "should render if blocks" do 19 | # view = <<-END 20 | # {#if _show}show{/} title 21 | # END 22 | # 23 | # page = Page.new 24 | # 25 | # template = ViewParser.new(view, main/main/main/index/index/title') 26 | # 27 | # page.add_template 28 | # end 29 | end 30 | -------------------------------------------------------------------------------- /spec/utils/data_transformer_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'volt/utils/data_transformer' 3 | 4 | describe Volt::DataTransformer do 5 | it 'should transform values' do 6 | data = { 7 | name: 'Bob', 8 | stuff: [ 9 | {key: /regex/} 10 | ], 11 | other: /another regex/, 12 | /some reg/ => 'value' 13 | } 14 | 15 | transformed = { 16 | :name=>"Bob", 17 | :stuff=>[ 18 | {:key=>"a regex"} 19 | ], 20 | :other=>"a regex", 21 | "a regex"=>"value" 22 | } 23 | 24 | result = Volt::DataTransformer.transform(data) do |value| 25 | if value.is_a?(Regexp) 26 | 'a regex' 27 | else 28 | value 29 | end 30 | end 31 | 32 | expect(result).to eq(transformed) 33 | end 34 | 35 | it 'should transform keys' do 36 | data = { 37 | 'name' => 'Ryan', 38 | 'info' => [ 39 | {'place' => 'Bozeman'} 40 | ] 41 | } 42 | transformed = {:name=>"Ryan", :info=>[{:place=>"Bozeman"}]} 43 | result = Volt::DataTransformer.transform_keys(data) do |key| 44 | key.to_sym 45 | end 46 | 47 | expect(result).to eq(transformed) 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/utils/generic_counting_pool_spec.rb: -------------------------------------------------------------------------------- 1 | require 'volt/utils/generic_counting_pool' 2 | 3 | class CountingPoolTest < Volt::GenericCountingPool 4 | def create(id, name = nil) 5 | Object.new 6 | end 7 | end 8 | 9 | describe Volt::GenericCountingPool do 10 | before do 11 | @count_pool = CountingPoolTest.new 12 | end 13 | 14 | it 'should lookup and retrieve' do 15 | item1 = @count_pool.find('one') 16 | 17 | item2 = @count_pool.find('one') 18 | item3 = @count_pool.find('two') 19 | 20 | expect(item1).to eq(item2) 21 | expect(item2).to_not eq(item3) 22 | end 23 | 24 | it 'should only remove items when the same number have been removed as have been added' do 25 | item1 = @count_pool.find('_items', 'one') 26 | item2 = @count_pool.find('_items', 'one') 27 | expect(@count_pool.instance_variable_get('@pool')).to_not eq({}) 28 | 29 | @count_pool.remove('_items', 'one') 30 | expect(@count_pool.instance_variable_get('@pool')).to_not eq({}) 31 | 32 | @count_pool.remove('_items', 'one') 33 | expect(@count_pool.instance_variable_get('@pool')).to eq({}) 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/utils/parsing_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Volt::Parsing do 4 | subject { Volt::Parsing } 5 | context 'surrounding encoding/decoding' do 6 | it 'does not mangle characters' do 7 | raw = ' !"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~''"' 8 | encoded = subject.encodeURI raw 9 | decoded = subject.decodeURI encoded 10 | 11 | expect(decoded).to eq raw 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/utils/task_argument_filtererer_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | if RUBY_PLATFORM != 'opal' 4 | describe TaskArgumentFilterer do 5 | it 'should filter arguments' do 6 | filtered_args = TaskArgumentFilterer.new(login: 'jim@jim.com', password: 'some password no one should see').run 7 | 8 | expect(filtered_args).to eq(login: 'jim@jim.com', password: '[FILTERED]') 9 | end 10 | 11 | it 'should filter in nested args' do 12 | filtered_args = TaskArgumentFilterer.new([:login, { login: 'jim@jim.com', password: 'some password' }]).run 13 | 14 | expect(filtered_args).to eq([:login, { login: 'jim@jim.com', password: '[FILTERED]' }]) 15 | end 16 | 17 | it 'should create and run a new TaskArgumentFilterer when its filter method is called' do 18 | filtered_args = TaskArgumentFilterer.filter([{login: 'jam@jam.com', password: 'some password'}]) 19 | expect(filtered_args).to eq([{login:"jam@jam.com", password:"[FILTERED]"}]) 20 | end 21 | 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/volt/repos_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Volt::App do 4 | [:cookies, :flash, :local_store].each do |repo| 5 | it "should raise an error when accessing #{repo} from the server" do 6 | expect do 7 | volt_app.send(repo) 8 | end.to raise_error("The #{repo} collection can only be accessed from the client side currently") 9 | end 10 | end 11 | end -------------------------------------------------------------------------------- /templates/.gitignore: -------------------------------------------------------------------------------- 1 | .bundle 2 | .config 3 | .yardoc 4 | _yardoc 5 | coverage 6 | doc/ 7 | rdoc 8 | tmp 9 | .idea 10 | .yardoc 11 | .sass-cache 12 | .DS_Store -------------------------------------------------------------------------------- /templates/component/assets/css/.empty_directory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/templates/component/assets/css/.empty_directory -------------------------------------------------------------------------------- /templates/component/assets/images/.empty_directory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/templates/component/assets/images/.empty_directory -------------------------------------------------------------------------------- /templates/component/assets/js/.empty_directory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/templates/component/assets/js/.empty_directory -------------------------------------------------------------------------------- /templates/component/config/dependencies.rb: -------------------------------------------------------------------------------- 1 | # Specify which components you wish to include when 2 | # this component loads. 3 | -------------------------------------------------------------------------------- /templates/component/config/initializers/boot.rb: -------------------------------------------------------------------------------- 1 | # Place any code you want to run when the component is included on the client 2 | # or server. 3 | 4 | # To include code only on the client use: 5 | # if RUBY_PLATFORM == 'opal' 6 | # 7 | # To include code only on the server, use: 8 | # unless RUBY_PLATFORM == 'opal' 9 | # ^^ this will not send compile in code in the conditional to the client. 10 | # ^^ this include code required in the conditional. -------------------------------------------------------------------------------- /templates/component/config/initializers/client/.empty_directory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/templates/component/config/initializers/client/.empty_directory -------------------------------------------------------------------------------- /templates/component/config/initializers/server/.empty_directory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/templates/component/config/initializers/server/.empty_directory -------------------------------------------------------------------------------- /templates/component/config/routes.rb: -------------------------------------------------------------------------------- 1 | # See https://github.com/voltrb/volt#routes for more info on routes 2 | -------------------------------------------------------------------------------- /templates/component/controllers/main_controller.rb.tt: -------------------------------------------------------------------------------- 1 | module <%= config[:component_name].camelize %> 2 | class MainController < Volt::ModelController 3 | def index 4 | # Add code for when the index view is loaded 5 | end 6 | 7 | def about 8 | # Add code for when the about view is loaded 9 | end 10 | 11 | private 12 | 13 | # the main template contains a #template binding that shows another 14 | # template. This is the path to that template. It may change based 15 | # on the params._controller and params._action values. 16 | def main_path 17 | "#{params._component || 'main'}/#{params._controller || 'main'}/#{params._action || 'index'}" 18 | end 19 | end 20 | end -------------------------------------------------------------------------------- /templates/component/controllers/server/.empty_directory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/templates/component/controllers/server/.empty_directory -------------------------------------------------------------------------------- /templates/component/lib/.empty_directory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/templates/component/lib/.empty_directory -------------------------------------------------------------------------------- /templates/component/models/.empty_directory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/templates/component/models/.empty_directory -------------------------------------------------------------------------------- /templates/component/tasks/.empty_directory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/templates/component/tasks/.empty_directory -------------------------------------------------------------------------------- /templates/component/views/main/index.html.tt: -------------------------------------------------------------------------------- 1 | <:Title> 2 | Component Index 3 | 4 | <:Body> 5 |

    Component Index

    -------------------------------------------------------------------------------- /templates/component_specs/controllers/server/.empty_directory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/templates/component_specs/controllers/server/.empty_directory -------------------------------------------------------------------------------- /templates/component_specs/integration/.empty_directory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/templates/component_specs/integration/.empty_directory -------------------------------------------------------------------------------- /templates/component_specs/models/.empty_directory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/templates/component_specs/models/.empty_directory -------------------------------------------------------------------------------- /templates/component_specs/tasks/.empty_directory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/templates/component_specs/tasks/.empty_directory -------------------------------------------------------------------------------- /templates/controller/http_controller.rb.tt: -------------------------------------------------------------------------------- 1 | module <%= config[:component_module] %> 2 | class <%= config[:http_controller_name] %> < Volt::HttpController 3 | 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /templates/controller/http_controller_spec.rb.tt: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe <%= config[:component_module] %>::<%= config[:http_controller_name] %>, type: :http_controller do 4 | # Specs here 5 | end 6 | -------------------------------------------------------------------------------- /templates/controller/model_controller.rb.tt: -------------------------------------------------------------------------------- 1 | module <%= config[:component_module] %> 2 | class <%= config[:model_controller_name] %> < Volt::ModelController 3 | 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /templates/controller/model_controller_spec.rb.tt: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe '<%= config[:name] %>', type: :feature do 4 | # Specs here 5 | end 6 | -------------------------------------------------------------------------------- /templates/model/model.rb.tt: -------------------------------------------------------------------------------- 1 | class <%= config[:model_name] %> < Volt::Model 2 | 3 | end 4 | -------------------------------------------------------------------------------- /templates/model/model_spec.rb.tt: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe <%= config[:model_name] %> do 4 | # Specs here 5 | end 6 | -------------------------------------------------------------------------------- /templates/newgem/CODE_OF_CONDUCT.md.tt: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /templates/newgem/Gemfile.tt: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in <%=config[:name]%>.gemspec 4 | gemspec 5 | 6 | # Optional Gems for testing/dev 7 | 8 | # The implementation of ReadWriteLock in Volt uses concurrent ruby and ext helps performance. 9 | gem 'concurrent-ruby-ext', '~> 0.8.0' 10 | 11 | # Gems you use for development should be added to the gemspec file as 12 | # development dependencies. -------------------------------------------------------------------------------- /templates/newgem/LICENSE.txt.tt: -------------------------------------------------------------------------------- 1 | Copyright (c) <%=Time.now.year%> <%=config[:author]%> 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /templates/newgem/README.md.tt: -------------------------------------------------------------------------------- 1 | # <%=config[:constant_name]%> 2 | 3 | TODO: Write a gem description 4 | 5 | ## Installation 6 | 7 | Add this line to your application's Gemfile: 8 | 9 | gem '<%=config[:name]%>' 10 | 11 | And then execute: 12 | 13 | $ bundle 14 | 15 | Or install it yourself as: 16 | 17 | $ gem install <%=config[:name]%> 18 | 19 | ## Usage 20 | 21 | TODO: Write usage instructions here 22 | 23 | ## Contributing 24 | 25 | 1. Fork it ( http://github.com/[my-github-username]/<%=config[:name]%>/fork ) 26 | 2. Create your feature branch (`git checkout -b my-new-feature`) 27 | 3. Commit your changes (`git commit -am 'Add some feature'`) 28 | 4. Push to the branch (`git push origin my-new-feature`) 29 | 5. Create new Pull Request 30 | -------------------------------------------------------------------------------- /templates/newgem/Rakefile.tt: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | <% if config[:test] == 'minitest' -%> 3 | require "rake/testtask" 4 | 5 | Rake::TestTask.new(:test) do |t| 6 | t.libs << "test" 7 | end 8 | 9 | task :default => :test 10 | <% elsif config[:test] == 'rspec' -%> 11 | require "rspec/core/rake_task" 12 | 13 | RSpec::Core::RakeTask.new(:spec) 14 | 15 | task :default => :spec 16 | <% end -%> 17 | -------------------------------------------------------------------------------- /templates/newgem/app/newgem/assets/css/.empty_directory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/templates/newgem/app/newgem/assets/css/.empty_directory -------------------------------------------------------------------------------- /templates/newgem/app/newgem/assets/js/.empty_directory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/templates/newgem/app/newgem/assets/js/.empty_directory -------------------------------------------------------------------------------- /templates/newgem/app/newgem/config/dependencies.rb: -------------------------------------------------------------------------------- 1 | # Component dependencies 2 | -------------------------------------------------------------------------------- /templates/newgem/app/newgem/config/initializers/boot.rb: -------------------------------------------------------------------------------- 1 | # Place any code you want to run when the component is included on the client 2 | # or server. 3 | 4 | # To include code only on the client use: 5 | # if RUBY_PLATFORM == 'opal' 6 | # 7 | # To include code only on the server, use: 8 | # unless RUBY_PLATFORM == 'opal' 9 | # ^^ this will not send compile in code in the conditional to the client. 10 | # ^^ this include code required in the conditional. -------------------------------------------------------------------------------- /templates/newgem/app/newgem/config/initializers/client/.empty_directory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/templates/newgem/app/newgem/config/initializers/client/.empty_directory -------------------------------------------------------------------------------- /templates/newgem/app/newgem/config/initializers/server/.empty_directory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/templates/newgem/app/newgem/config/initializers/server/.empty_directory -------------------------------------------------------------------------------- /templates/newgem/app/newgem/config/routes.rb: -------------------------------------------------------------------------------- 1 | # Component routes 2 | -------------------------------------------------------------------------------- /templates/newgem/app/newgem/controllers/main_controller.rb.tt: -------------------------------------------------------------------------------- 1 | module <%=config[:name].gsub(/^volt[-]/, '').gsub('-', '_').split("_").map {|s| s.capitalize }.join("") %> 2 | class MainController < Volt::ModelController 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /templates/newgem/app/newgem/controllers/server/.empty_directory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/templates/newgem/app/newgem/controllers/server/.empty_directory -------------------------------------------------------------------------------- /templates/newgem/app/newgem/lib/.empty_directory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/templates/newgem/app/newgem/lib/.empty_directory -------------------------------------------------------------------------------- /templates/newgem/app/newgem/models/.empty_directory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/templates/newgem/app/newgem/models/.empty_directory -------------------------------------------------------------------------------- /templates/newgem/app/newgem/tasks/.empty_directory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/templates/newgem/app/newgem/tasks/.empty_directory -------------------------------------------------------------------------------- /templates/newgem/app/newgem/views/main/index.html: -------------------------------------------------------------------------------- 1 | <:Body> 2 | -- component -- 3 | -------------------------------------------------------------------------------- /templates/newgem/bin/newgem.tt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require '<%= config[:namespaced_path] %>' 4 | -------------------------------------------------------------------------------- /templates/newgem/gitignore.tt: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | -------------------------------------------------------------------------------- /templates/newgem/lib/newgem.rb.tt: -------------------------------------------------------------------------------- 1 | # If you need to require in code in the gem's app folder, keep in mind that 2 | # the app is not on the load path when the gem is required. Use 3 | # app/{gemname}/config/initializers/boot.rb to require in client or server 4 | # code. 5 | # 6 | # Also, in volt apps, you typically use the lib folder in the 7 | # app/{componentname} folder instead of this lib folder. This lib folder is 8 | # for setting up gem code when Bundler.require is called. (or the gem is 9 | # required.) 10 | # 11 | # If you need to configure volt in some way, you can add a Volt.configure block 12 | # in this file. 13 | 14 | <%- config[:constant_array].each_with_index do |c,i| -%> 15 | <%= ' '*i %>module <%= c %> 16 | <%- end -%> 17 | <%= ' '*config[:constant_array].size %># Your code goes here... 18 | <%- (config[:constant_array].size-1).downto(0) do |i| -%> 19 | <%= ' '*i %>end 20 | <%- end -%> 21 | -------------------------------------------------------------------------------- /templates/newgem/lib/newgem/.empty_directory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/templates/newgem/lib/newgem/.empty_directory -------------------------------------------------------------------------------- /templates/newgem/lib/newgem/version.rb.tt: -------------------------------------------------------------------------------- 1 | <%- config[:constant_array].each_with_index do |c,i| -%> 2 | <%= ' '*i %>module <%= c %> 3 | <%- end -%> 4 | <%= ' '*config[:constant_array].size %>VERSION = "0.1.0" 5 | <%- (config[:constant_array].size-1).downto(0) do |i| -%> 6 | <%= ' '*i %>end 7 | <%- end -%> -------------------------------------------------------------------------------- /templates/newgem/rspec.tt: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | -------------------------------------------------------------------------------- /templates/newgem/spec/integration/sample_integration_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'sample integration test', type: :feature do 4 | # An example integration spec, this will only be run if ENV['BROWSER'] is 5 | # specified. Current values for ENV['BROWSER'] are 'firefox' and 'phantom' 6 | it 'should load the page' do 7 | visit '/' 8 | 9 | expect(page).to have_content('Home') 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /templates/newgem/spec/newgem_spec.rb.tt: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe <%= config[:constant_name] %> do 4 | it 'should do something useful' do 5 | expect(false).to be true 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /templates/newgem/spec/spec_helper.rb.tt: -------------------------------------------------------------------------------- 1 | # Volt sets up rspec and capybara for testing. 2 | require 'volt/spec/setup' 3 | 4 | # When testing Volt component gems, we boot up a dummy app first to run the 5 | # test in, so we have access to Volt itself. 6 | dummy_app_path = File.join(File.dirname(__FILE__), 'dummy') 7 | Volt.spec_setup(dummy_app_path) 8 | 9 | RSpec.configure do |config| 10 | config.run_all_when_everything_filtered = true 11 | config.filter_run :focus 12 | 13 | # Run specs in random order to surface order dependencies. If you find an 14 | # order dependency and want to debug it, you can fix the order by providing 15 | # the seed, which is printed after each run. 16 | # --seed 1234 17 | config.order = 'random' 18 | end 19 | -------------------------------------------------------------------------------- /templates/newgem/test/minitest_helper.rb.tt: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) 2 | require '<%= config[:namespaced_path] %>' 3 | 4 | require 'minitest/autorun' 5 | -------------------------------------------------------------------------------- /templates/newgem/test/test_newgem.rb.tt: -------------------------------------------------------------------------------- 1 | require 'minitest_helper' 2 | 3 | class Test<%= config[:constant_name] %> < MiniTest::Unit::TestCase 4 | def test_that_it_has_a_version_number 5 | refute_nil ::<%= config[:constant_name] %>::VERSION 6 | end 7 | 8 | def test_it_does_something_useful 9 | assert false 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /templates/project/.gitignore: -------------------------------------------------------------------------------- 1 | .bundle 2 | .config 3 | .yardoc 4 | tmp 5 | .idea 6 | .yardoc 7 | .sass-cache 8 | .DS_Store 9 | compiled -------------------------------------------------------------------------------- /templates/project/README.md.tt: -------------------------------------------------------------------------------- 1 | # Welcome to Volt 2 | 3 | You can start your volt app by running: 4 | 5 | ```bundle exec volt server``` 6 | 7 | ## New to Volt? 8 | 9 | Be sure to read the volt docs at http://voltframework.com/docs 10 | -------------------------------------------------------------------------------- /templates/project/app/main/assets/css/.empty_directory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/templates/project/app/main/assets/css/.empty_directory -------------------------------------------------------------------------------- /templates/project/app/main/assets/css/app.css.scss: -------------------------------------------------------------------------------- 1 | // Place your apps css here -------------------------------------------------------------------------------- /templates/project/app/main/assets/images/.empty_directory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/templates/project/app/main/assets/images/.empty_directory -------------------------------------------------------------------------------- /templates/project/app/main/assets/js/.empty_directory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/templates/project/app/main/assets/js/.empty_directory -------------------------------------------------------------------------------- /templates/project/app/main/config/dependencies.rb: -------------------------------------------------------------------------------- 1 | # Specify which components you wish to include when 2 | # the "home" component loads. 3 | 4 | # bootstrap css framework 5 | component 'bootstrap' 6 | 7 | # a default theme for the bootstrap framework 8 | component 'bootstrap_jumbotron_theme' 9 | 10 | # provides templates for login, signup, and logout 11 | component 'user_templates' 12 | -------------------------------------------------------------------------------- /templates/project/app/main/config/initializers/boot.rb: -------------------------------------------------------------------------------- 1 | # Place any code you want to run when the component is included on the client 2 | # or server. 3 | 4 | # To include code only on the client use: 5 | # if RUBY_PLATFORM == 'opal' 6 | # 7 | # To include code only on the server, use: 8 | # unless RUBY_PLATFORM == 'opal' 9 | # ^^ this will not send compile in code in the conditional to the client. 10 | # ^^ this include code required in the conditional. -------------------------------------------------------------------------------- /templates/project/app/main/config/initializers/client/.empty_directory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/templates/project/app/main/config/initializers/client/.empty_directory -------------------------------------------------------------------------------- /templates/project/app/main/config/initializers/server/.empty_directory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/templates/project/app/main/config/initializers/server/.empty_directory -------------------------------------------------------------------------------- /templates/project/app/main/config/routes.rb: -------------------------------------------------------------------------------- 1 | # See https://github.com/voltrb/volt#routes for more info on routes 2 | 3 | client '/about', action: 'about' 4 | 5 | # Routes for login and signup, provided by user_templates component gem 6 | client '/signup', component: 'user_templates', controller: 'signup' 7 | client '/login', component: 'user_templates', controller: 'login', action: 'index' 8 | client '/password_reset', component: 'user_templates', controller: 'password_reset', action: 'index' 9 | client '/forgot', component: 'user_templates', controller: 'login', action: 'forgot' 10 | client '/account', component: 'user_templates', controller: 'account', action: 'index' 11 | 12 | # The main route, this should be last. It will match any params not 13 | # previously matched. 14 | client '/', {} 15 | -------------------------------------------------------------------------------- /templates/project/app/main/controllers/main_controller.rb: -------------------------------------------------------------------------------- 1 | # By default Volt generates this controller for your Main component 2 | module Main 3 | class MainController < Volt::ModelController 4 | def index 5 | # Add code for when the index view is loaded 6 | end 7 | 8 | def about 9 | # Add code for when the about view is loaded 10 | end 11 | 12 | private 13 | 14 | # The main template contains a #template binding that shows another 15 | # template. This is the path to that template. It may change based 16 | # on the params._component, params._controller, and params._action values. 17 | def main_path 18 | "#{params._component || 'main'}/#{params._controller || 'main'}/#{params._action || 'index'}" 19 | end 20 | 21 | # Determine if the current nav component is the active one by looking 22 | # at the first part of the url against the href attribute. 23 | def active_tab? 24 | url.path.split('/')[1] == attrs.href.split('/')[1] 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /templates/project/app/main/lib/.empty_directory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/templates/project/app/main/lib/.empty_directory -------------------------------------------------------------------------------- /templates/project/app/main/models/user.rb: -------------------------------------------------------------------------------- 1 | # By default Volt generates this User model which inherits from Volt::User, 2 | # you can rename this if you want. 3 | class User < Volt::User 4 | # login_field is set to :email by default and can be changed to :username 5 | # in config/app.rb 6 | field login_field 7 | field :name 8 | 9 | validate login_field, unique: true, length: 8 10 | validate :email, email: true 11 | 12 | end 13 | -------------------------------------------------------------------------------- /templates/project/app/main/tasks/.empty_directory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voltrb/volt/f942b92385adbc894ee4a37903ee6a9c1a65e9a4/templates/project/app/main/tasks/.empty_directory -------------------------------------------------------------------------------- /templates/project/app/main/views/main/about.html: -------------------------------------------------------------------------------- 1 | <:Title> 2 | About 3 | 4 | <:Body> 5 |

    About

    6 | 7 |

    About page...

    8 | -------------------------------------------------------------------------------- /templates/project/app/main/views/main/index.html: -------------------------------------------------------------------------------- 1 | <:Title> 2 | Home 3 | 4 | <:Body> 5 |

    Home

    6 | 7 | -------------------------------------------------------------------------------- /templates/project/app/main/views/main/main.html.tt: -------------------------------------------------------------------------------- 1 | <:Title> 2 | {{ view main_path, "title", {controller_group: 'main'} }} 3 | 4 | <:Body> 5 |
    6 |
    7 | 12 |

    <%= config[:name] %>

    13 |
    14 | 15 | <:volt:notices /> 16 | 17 | {{ view main_path, 'body', {controller_group: 'main'} }} 18 | 19 | 22 | 23 |
    24 | 25 | <:Nav> 26 |
  • 27 | {{ yield }} 28 |
  • 29 | 30 | -------------------------------------------------------------------------------- /templates/project/config.ru: -------------------------------------------------------------------------------- 1 | # Run via rack server 2 | require 'bundler/setup' 3 | require 'volt/server' 4 | run Volt::Server.new.app 5 | -------------------------------------------------------------------------------- /templates/project/config/base/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%# IMPORTANT: Please read before changing! %> 4 | <%# This file is rendered on the server using ERB, so it does NOT use Volt's %> 5 | <%# normal template system. You can add to it, but keep in mind the template %> 6 | <%# language difference. This file handles auto-loading all JS/Opal and CSS. %> 7 | 8 | 9 | <%= javascript_tags %> 10 | <%= css_tags %> 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /templates/project/config/initializers/boot.rb: -------------------------------------------------------------------------------- 1 | # Any ./config/initializers/*.rb files will when the app starts up on the server. 2 | # To load code on the client (or client and server), you can use the 3 | # config/initializers folder in a component in the app directory. This folder 4 | # is only for things that are server only. (Usually for things like config) -------------------------------------------------------------------------------- /templates/project/spec/app/main/controllers/server/sample_http_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'sample http controller test', type: :http_controller do 4 | # Specs here 5 | end 6 | -------------------------------------------------------------------------------- /templates/project/spec/app/main/integration/sample_integration_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'sample integration test', type: :feature do 4 | # An example integration spec, this will only be run if ENV['BROWSER'] is 5 | # specified. Current values for ENV['BROWSER'] are 'firefox' and 'phantom' 6 | it 'should load the page' do 7 | visit '/' 8 | 9 | expect(page).to have_content('Home') 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /templates/project/spec/app/main/models/sample_model_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'sample model' do 4 | # Specs here 5 | end 6 | -------------------------------------------------------------------------------- /templates/project/spec/app/main/tasks/sample_task_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'sample task', type: :task do 4 | # Specs here 5 | end 6 | -------------------------------------------------------------------------------- /templates/project/spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # Volt sets up rspec and capybara for testing. 2 | require 'volt/spec/setup' 3 | Volt.spec_setup 4 | 5 | RSpec.configure do |config| 6 | config.run_all_when_everything_filtered = true 7 | config.filter_run :focus 8 | 9 | # Run specs in random order to surface order dependencies. If you find an 10 | # order dependency and want to debug it, you can fix the order by providing 11 | # the seed, which is printed after each run. 12 | # --seed 1234 13 | config.order = 'random' 14 | end 15 | -------------------------------------------------------------------------------- /templates/task/task.rb.tt: -------------------------------------------------------------------------------- 1 | class <%= config[:task_name] %> < Volt::Task 2 | 3 | end 4 | -------------------------------------------------------------------------------- /templates/task/task_spec.rb.tt: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe <%= config[:task_name] %>, type: :task do 4 | # Specs here 5 | end 6 | -------------------------------------------------------------------------------- /templates/view/index.html.tt: -------------------------------------------------------------------------------- 1 | <:Title> 2 | <%= config[:view_name] %> index 3 | 4 | <:Body> 5 |

    Find me in app/<%= config[:component] %>/views/<%= config[:view_name] %>/index.html

    6 | --------------------------------------------------------------------------------