lanes 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.jshintrc +3 -0
- data/Gemfile +9 -0
- data/Guardfile +15 -0
- data/LICENSE-MIT.txt +21 -0
- data/README.md +15 -0
- data/Rakefile +74 -0
- data/bin/lanes +5 -0
- data/client/images/ajax-loader.gif +0 -0
- data/client/images/dataTables/Sorting icons.psd +0 -0
- data/client/images/dataTables/back_disabled.png +0 -0
- data/client/images/dataTables/back_enabled.png +0 -0
- data/client/images/dataTables/back_enabled_hover.png +0 -0
- data/client/images/dataTables/favicon.ico +0 -0
- data/client/images/dataTables/forward_disabled.png +0 -0
- data/client/images/dataTables/forward_enabled.png +0 -0
- data/client/images/dataTables/forward_enabled_hover.png +0 -0
- data/client/images/dataTables/loading-background.png +0 -0
- data/client/images/dataTables/sort_asc.png +0 -0
- data/client/images/dataTables/sort_asc_disabled.png +0 -0
- data/client/images/dataTables/sort_both.png +0 -0
- data/client/images/dataTables/sort_desc.png +0 -0
- data/client/images/dataTables/sort_desc_disabled.png +0 -0
- data/client/images/logo-sm.png +0 -0
- data/client/javascripts/component/Base.coffee +39 -0
- data/client/javascripts/component/ChoicesInput.coffee +47 -0
- data/client/javascripts/component/Grid.coffee +199 -0
- data/client/javascripts/component/ModalDialog.coffee +44 -0
- data/client/javascripts/component/PopOver.coffee +52 -0
- data/client/javascripts/component/RadioGroup.coffee +59 -0
- data/client/javascripts/component/RecordFinder.coffee +143 -0
- data/client/javascripts/component/SelectField.coffee +43 -0
- data/client/javascripts/component/TaggedField.coffee +27 -0
- data/client/javascripts/component/grid/Editor.coffee +65 -0
- data/client/javascripts/component/grid/PopOverEditor.coffee +29 -0
- data/client/javascripts/component/grid/RowEditor.coffee +31 -0
- data/client/javascripts/component/grid/popover-editor.html +18 -0
- data/client/javascripts/component/grid/row-editor.html +16 -0
- data/client/javascripts/component/grid.html +4 -0
- data/client/javascripts/component/index.js +5 -0
- data/client/javascripts/component/modal.html +17 -0
- data/client/javascripts/component/popover.html +5 -0
- data/client/javascripts/component/record-finder/clause.skr +35 -0
- data/client/javascripts/component/record-finder/dialog.skr +4 -0
- data/client/javascripts/component/record-finder/field.skr +8 -0
- data/client/javascripts/data/Bootstrap.coffee +8 -0
- data/client/javascripts/data/ChangeSet.coffee +50 -0
- data/client/javascripts/data/Collection.coffee +111 -0
- data/client/javascripts/data/Config.coffee +15 -0
- data/client/javascripts/data/Model.coffee +269 -0
- data/client/javascripts/data/PubSub.coffee +68 -0
- data/client/javascripts/data/Query.coffee +184 -0
- data/client/javascripts/data/Roles.coffee +91 -0
- data/client/javascripts/data/Screens.coffee +157 -0
- data/client/javascripts/data/Sync.coffee +62 -0
- data/client/javascripts/data/User.coffee +70 -0
- data/client/javascripts/data/index.js +7 -0
- data/client/javascripts/data/mixins/HasCodeField.coffee +13 -0
- data/client/javascripts/extension/Base.coffee +9 -0
- data/client/javascripts/extension/Extensions.coffee +17 -0
- data/client/javascripts/extension/GlAccounts.coffee +9 -0
- data/client/javascripts/extension/index.js +6 -0
- data/client/javascripts/extension/load.js.erb +3 -0
- data/client/javascripts/lanes-complete.js +3 -0
- data/client/javascripts/lanes-workspace.js +1 -0
- data/client/javascripts/lib/MakeBaseClass.coffee +55 -0
- data/client/javascripts/lib/ModuleSupport.coffee +22 -0
- data/client/javascripts/lib/Templates.coffee +47 -0
- data/client/javascripts/lib/create-namespace.js +0 -0
- data/client/javascripts/lib/debounce.coffee +15 -0
- data/client/javascripts/lib/defer.coffee +7 -0
- data/client/javascripts/lib/el.js +115 -0
- data/client/javascripts/lib/index.js +9 -0
- data/client/javascripts/lib/loader.coffee +95 -0
- data/client/javascripts/lib/namespace.coffee +9 -0
- data/client/javascripts/lib/noConflict.coffee +14 -0
- data/client/javascripts/lib/promise_helpers.coffee +4 -0
- data/client/javascripts/lib/results.coffee +15 -0
- data/client/javascripts/lib/underscore.inflection.js +210 -0
- data/client/javascripts/lib/utilFunctions.coffee +51 -0
- data/client/javascripts/plugins/ResizeSensor.js +144 -0
- data/client/javascripts/plugins/index.js +4 -0
- data/client/javascripts/plugins/overlay.coffee +41 -0
- data/client/javascripts/plugins/trigger.coffee +15 -0
- data/client/javascripts/vendor/bootstrap/affix.js +142 -0
- data/client/javascripts/vendor/bootstrap/alert.js +92 -0
- data/client/javascripts/vendor/bootstrap/button.js +110 -0
- data/client/javascripts/vendor/bootstrap/carousel.js +223 -0
- data/client/javascripts/vendor/bootstrap/collapse.js +170 -0
- data/client/javascripts/vendor/bootstrap/dropdown.js +151 -0
- data/client/javascripts/vendor/bootstrap/modal.js +280 -0
- data/client/javascripts/vendor/bootstrap/popover.js +113 -0
- data/client/javascripts/vendor/bootstrap/scrollspy.js +170 -0
- data/client/javascripts/vendor/bootstrap/tab.js +128 -0
- data/client/javascripts/vendor/bootstrap/tooltip.js +457 -0
- data/client/javascripts/vendor/bootstrap/transition.js +59 -0
- data/client/javascripts/vendor/dataTables/dataTables.bootstrap.js +156 -0
- data/client/javascripts/vendor/dataTables/dataTables.scroller.js +1185 -0
- data/client/javascripts/vendor/dataTables/datatables.responsive.js +666 -0
- data/client/javascripts/vendor/dataTables/index.js +2 -0
- data/client/javascripts/vendor/dataTables/jquery.dataTables.js +14380 -0
- data/client/javascripts/vendor/jquery-2.js +9190 -0
- data/client/javascripts/vendor/jquery.tap.js +401 -0
- data/client/javascripts/vendor/magicsuggest.js +1565 -0
- data/client/javascripts/vendor/message-bus.js +285 -0
- data/client/javascripts/vendor/modern-stack.js +14 -0
- data/client/javascripts/vendor/packaged.js +13769 -0
- data/client/javascripts/view/Assets.coffee +9 -0
- data/client/javascripts/view/Base.coffee +231 -0
- data/client/javascripts/view/FormBindings.coffee +98 -0
- data/client/javascripts/view/Functions.coffee +13 -0
- data/client/javascripts/view/Helpers.coffee +77 -0
- data/client/javascripts/view/InterfaceState.coffee +88 -0
- data/client/javascripts/view/Keys.coffee +59 -0
- data/client/javascripts/view/ModelObserver.coffee +31 -0
- data/client/javascripts/view/ModelUpdate.coffee +8 -0
- data/client/javascripts/view/PubSub.coffee +29 -0
- data/client/javascripts/view/RenderContext.coffee +32 -0
- data/client/javascripts/view/SaveNotify.coffee +30 -0
- data/client/javascripts/view/Screen.coffee +30 -0
- data/client/javascripts/view/TimedHighlight.coffee +38 -0
- data/client/javascripts/view/TimedMask.coffee +65 -0
- data/client/javascripts/view/_button.html +3 -0
- data/client/javascripts/view/_toolbar.html +27 -0
- data/client/javascripts/view/index.js +10 -0
- data/client/javascripts/view/mixins/ScreenChangeListener.coffee +43 -0
- data/client/javascripts/view/model-update.html +13 -0
- data/client/javascripts/view/screen-definitions.js.erb +7 -0
- data/client/javascripts/workspace/ActiveScreensSwitcher.coffee +117 -0
- data/client/javascripts/workspace/Instance.es6 +60 -0
- data/client/javascripts/workspace/Layout.coffee +18 -0
- data/client/javascripts/workspace/LoginDialog.coffee +33 -0
- data/client/javascripts/workspace/Navbar.coffee +44 -0
- data/client/javascripts/workspace/Pages.coffee +46 -0
- data/client/javascripts/workspace/ScreensMenu.coffee +126 -0
- data/client/javascripts/workspace/index.js +12 -0
- data/client/javascripts/workspace/layout.html +4 -0
- data/client/javascripts/workspace/login-dialog.html +16 -0
- data/client/javascripts/workspace/menu.html +356 -0
- data/client/javascripts/workspace/menu_toggle.html +9 -0
- data/client/javascripts/workspace/navbar.html +19 -0
- data/client/javascripts/workspace/pages.html +6 -0
- data/client/javascripts/workspace/screens-menu.html +11 -0
- data/client/javascripts/workspace/screens-switcher.html +7 -0
- data/client/javascripts/workspace/tab.html +0 -0
- data/client/screens/user-management/UserEditScreen.coffee +21 -0
- data/client/screens/user-management/UserManagement.coffee +24 -0
- data/client/screens/user-management/grid-popover-editor.html +33 -0
- data/client/screens/user-management/index.css +4 -0
- data/client/screens/user-management/index.js +2 -0
- data/client/screens/user-management/user-management-styles.scss +7 -0
- data/client/screens/user-management/user-management.html +8 -0
- data/client/stylesheets/compoonents/all.scss +6 -0
- data/client/stylesheets/compoonents/changes-notification.scss +44 -0
- data/client/stylesheets/compoonents/grid-editors.scss +65 -0
- data/client/stylesheets/compoonents/grid.scss +301 -0
- data/client/stylesheets/compoonents/modal-dialog.scss +23 -0
- data/client/stylesheets/compoonents/record-finder.scss +71 -0
- data/client/stylesheets/compoonents/suggest.scss +266 -0
- data/client/stylesheets/fonts/icomoon.eot +0 -0
- data/client/stylesheets/fonts/icomoon.svg +160 -0
- data/client/stylesheets/fonts/icomoon.ttf +0 -0
- data/client/stylesheets/fonts/icomoon.woff +0 -0
- data/client/stylesheets/fonts/selection.json +3565 -0
- data/client/stylesheets/fonts/style.css +451 -0
- data/client/stylesheets/fonts.scss +38 -0
- data/client/stylesheets/forms.scss +75 -0
- data/client/stylesheets/index.css +4 -0
- data/client/stylesheets/keybindings.scss +6 -0
- data/client/stylesheets/lanes-workspace.scss +17 -0
- data/client/stylesheets/layout.scss +272 -0
- data/client/stylesheets/plugins/all.scss +2 -0
- data/client/stylesheets/plugins/overlay.scss +63 -0
- data/client/stylesheets/plugins/resize-sensor.scss +24 -0
- data/client/stylesheets/screens.scss +66 -0
- data/client/stylesheets/tabs.scss +148 -0
- data/client/stylesheets/vendor/bootstrap/_alerts.scss +68 -0
- data/client/stylesheets/vendor/bootstrap/_badges.scss +57 -0
- data/client/stylesheets/vendor/bootstrap/_breadcrumbs.scss +26 -0
- data/client/stylesheets/vendor/bootstrap/_button-groups.scss +240 -0
- data/client/stylesheets/vendor/bootstrap/_buttons.scss +157 -0
- data/client/stylesheets/vendor/bootstrap/_carousel.scss +243 -0
- data/client/stylesheets/vendor/bootstrap/_close.scss +35 -0
- data/client/stylesheets/vendor/bootstrap/_code.scss +68 -0
- data/client/stylesheets/vendor/bootstrap/_component-animations.scss +35 -0
- data/client/stylesheets/vendor/bootstrap/_dropdowns.scss +215 -0
- data/client/stylesheets/vendor/bootstrap/_forms.scss +538 -0
- data/client/stylesheets/vendor/bootstrap/_glyphicons.scss +237 -0
- data/client/stylesheets/vendor/bootstrap/_grid.scss +84 -0
- data/client/stylesheets/vendor/bootstrap/_input-groups.scss +166 -0
- data/client/stylesheets/vendor/bootstrap/_jumbotron.scss +48 -0
- data/client/stylesheets/vendor/bootstrap/_labels.scss +66 -0
- data/client/stylesheets/vendor/bootstrap/_list-group.scss +132 -0
- data/client/stylesheets/vendor/bootstrap/_media.scss +56 -0
- data/client/stylesheets/vendor/bootstrap/_mixins.scss +39 -0
- data/client/stylesheets/vendor/bootstrap/_modals.scss +150 -0
- data/client/stylesheets/vendor/bootstrap/_navbar.scss +659 -0
- data/client/stylesheets/vendor/bootstrap/_navs.scss +242 -0
- data/client/stylesheets/vendor/bootstrap/_normalize.scss +425 -0
- data/client/stylesheets/vendor/bootstrap/_pager.scss +55 -0
- data/client/stylesheets/vendor/bootstrap/_pagination.scss +88 -0
- data/client/stylesheets/vendor/bootstrap/_panels.scss +243 -0
- data/client/stylesheets/vendor/bootstrap/_popovers.scss +133 -0
- data/client/stylesheets/vendor/bootstrap/_print.scss +101 -0
- data/client/stylesheets/vendor/bootstrap/_progress-bars.scss +105 -0
- data/client/stylesheets/vendor/bootstrap/_responsive-embed.scss +34 -0
- data/client/stylesheets/vendor/bootstrap/_responsive-utilities.scss +174 -0
- data/client/stylesheets/vendor/bootstrap/_scaffolding.scss +150 -0
- data/client/stylesheets/vendor/bootstrap/_tables.scss +233 -0
- data/client/stylesheets/vendor/bootstrap/_theme.scss +258 -0
- data/client/stylesheets/vendor/bootstrap/_thumbnails.scss +38 -0
- data/client/stylesheets/vendor/bootstrap/_tooltip.scss +95 -0
- data/client/stylesheets/vendor/bootstrap/_type.scss +304 -0
- data/client/stylesheets/vendor/bootstrap/_utilities.scss +57 -0
- data/client/stylesheets/vendor/bootstrap/_variables.scss +850 -0
- data/client/stylesheets/vendor/bootstrap/_wells.scss +29 -0
- data/client/stylesheets/vendor/bootstrap/bootstrap.scss +50 -0
- data/client/stylesheets/vendor/bootstrap/mixins/_alerts.scss +14 -0
- data/client/stylesheets/vendor/bootstrap/mixins/_background-variant.scss +11 -0
- data/client/stylesheets/vendor/bootstrap/mixins/_border-radius.scss +18 -0
- data/client/stylesheets/vendor/bootstrap/mixins/_buttons.scss +50 -0
- data/client/stylesheets/vendor/bootstrap/mixins/_center-block.scss +7 -0
- data/client/stylesheets/vendor/bootstrap/mixins/_clearfix.scss +22 -0
- data/client/stylesheets/vendor/bootstrap/mixins/_forms.scss +84 -0
- data/client/stylesheets/vendor/bootstrap/mixins/_gradients.scss +58 -0
- data/client/stylesheets/vendor/bootstrap/mixins/_grid-framework.scss +81 -0
- data/client/stylesheets/vendor/bootstrap/mixins/_grid.scss +122 -0
- data/client/stylesheets/vendor/bootstrap/mixins/_hide-text.scss +21 -0
- data/client/stylesheets/vendor/bootstrap/mixins/_image.scss +34 -0
- data/client/stylesheets/vendor/bootstrap/mixins/_labels.scss +12 -0
- data/client/stylesheets/vendor/bootstrap/mixins/_list-group.scss +31 -0
- data/client/stylesheets/vendor/bootstrap/mixins/_nav-divider.scss +10 -0
- data/client/stylesheets/vendor/bootstrap/mixins/_nav-vertical-align.scss +9 -0
- data/client/stylesheets/vendor/bootstrap/mixins/_opacity.scss +8 -0
- data/client/stylesheets/vendor/bootstrap/mixins/_pagination.scss +23 -0
- data/client/stylesheets/vendor/bootstrap/mixins/_panels.scss +24 -0
- data/client/stylesheets/vendor/bootstrap/mixins/_progress-bar.scss +10 -0
- data/client/stylesheets/vendor/bootstrap/mixins/_reset-filter.scss +8 -0
- data/client/stylesheets/vendor/bootstrap/mixins/_resize.scss +6 -0
- data/client/stylesheets/vendor/bootstrap/mixins/_responsive-visibility.scss +21 -0
- data/client/stylesheets/vendor/bootstrap/mixins/_size.scss +10 -0
- data/client/stylesheets/vendor/bootstrap/mixins/_tab-focus.scss +9 -0
- data/client/stylesheets/vendor/bootstrap/mixins/_table-row.scss +28 -0
- data/client/stylesheets/vendor/bootstrap/mixins/_text-emphasis.scss +11 -0
- data/client/stylesheets/vendor/bootstrap/mixins/_text-overflow.scss +8 -0
- data/client/stylesheets/vendor/bootstrap/mixins/_vendor-prefixes.scss +219 -0
- data/client/stylesheets/vendor/bootstrap-custom-grid.scss +85 -0
- data/client/stylesheets/vendor/bootstrap-custom-modals.scss +150 -0
- data/client/stylesheets/vendor/bootstrap.scss +69 -0
- data/client/stylesheets/vendor/dataTables.scss +4 -0
- data/config/database.yml +9 -0
- data/config/puma.rb +7 -0
- data/config.ru +4 -0
- data/db/migrate/20140615031600_create_hip_users.rb +17 -0
- data/db/seed.rb +37 -0
- data/foo/Gemfile +5 -0
- data/foo/Guardfile +13 -0
- data/foo/foo/Gemfile +5 -0
- data/foo/foo/lib/foo.rb +7 -0
- data/foo/lib/foo/version.rb +3 -0
- data/foo/lib/foo.rb +8 -0
- data/lanes.gemspec +54 -0
- data/lib/generators/lanes/migrations/install_generator.rb +42 -0
- data/lib/lanes/access/locked_fields.rb +43 -0
- data/lib/lanes/access/role.rb +58 -0
- data/lib/lanes/access/role_collection.rb +75 -0
- data/lib/lanes/access/roles/administrator.rb +25 -0
- data/lib/lanes/access/roles/support.rb +13 -0
- data/lib/lanes/access/user_maint_screen.rb +32 -0
- data/lib/lanes/access.rb +50 -0
- data/lib/lanes/api/asset_pipeline.rb +59 -0
- data/lib/lanes/api/authentication_helper.rb +21 -0
- data/lib/lanes/api/authentication_provider.rb +45 -0
- data/lib/lanes/api/controller.rb +290 -0
- data/lib/lanes/api/default_routes.rb +35 -0
- data/lib/lanes/api/eco.js +516 -0
- data/lib/lanes/api/error_formatter.rb +37 -0
- data/lib/lanes/api/helper_methods.rb +32 -0
- data/lib/lanes/api/javascript_processor.rb +116 -0
- data/lib/lanes/api/pub_sub.rb +33 -0
- data/lib/lanes/api/request_wrapper.rb +42 -0
- data/lib/lanes/api/root.rb +103 -0
- data/lib/lanes/api/skr_templates.rb +60 -0
- data/lib/lanes/api/test_specs.rb +59 -0
- data/lib/lanes/api/updates.rb +38 -0
- data/lib/lanes/api.rb +27 -0
- data/lib/lanes/cli.rb +13 -0
- data/lib/lanes/concerns/all.rb +16 -0
- data/lib/lanes/concerns/api_path.rb +21 -0
- data/lib/lanes/concerns/association_extensions.rb +85 -0
- data/lib/lanes/concerns/attr_accessor_with_default.rb +62 -0
- data/lib/lanes/concerns/code_identifier.rb +43 -0
- data/lib/lanes/concerns/export_associations.rb +52 -0
- data/lib/lanes/concerns/export_join_tables.rb +39 -0
- data/lib/lanes/concerns/export_methods.rb +104 -0
- data/lib/lanes/concerns/export_scope.rb +66 -0
- data/lib/lanes/concerns/exported_limit_evaluator.rb +17 -0
- data/lib/lanes/concerns/immutable_model.rb +32 -0
- data/lib/lanes/concerns/locked_fields.rb +84 -0
- data/lib/lanes/concerns/pub_sub.rb +105 -0
- data/lib/lanes/concerns/queries.rb +20 -0
- data/lib/lanes/concerns/random_hash_code.rb +40 -0
- data/lib/lanes/concerns/sanitize_api_data.rb +15 -0
- data/lib/lanes/concerns/set_attribute_data.rb +154 -0
- data/lib/lanes/concerns/track_modifications.rb +51 -0
- data/lib/lanes/concerns/visible_id_identifier.rb +53 -0
- data/lib/lanes/configuration.rb +85 -0
- data/lib/lanes/db/migration_helpers.rb +178 -0
- data/lib/lanes/db/migrations.rb +13 -0
- data/lib/lanes/db/seed.rb +27 -0
- data/lib/lanes/db.rb +86 -0
- data/lib/lanes/environment.rb +19 -0
- data/lib/lanes/extension.rb +72 -0
- data/lib/lanes/generators/app/Gemfile +5 -0
- data/lib/lanes/generators/app/Guardfile +13 -0
- data/lib/lanes/generators/app/Rakefile +9 -0
- data/lib/lanes/generators/app/config/database.yml +9 -0
- data/lib/lanes/generators/app/config.ru +4 -0
- data/lib/lanes/generators/app/lib/main_class/version.rb +3 -0
- data/lib/lanes/generators/app/lib/main_class.rb +8 -0
- data/lib/lanes/generators/app.rb +36 -0
- data/lib/lanes/guard_tasks.rb +44 -0
- data/lib/lanes/logger.rb +37 -0
- data/lib/lanes/model.rb +26 -0
- data/lib/lanes/numbers.rb +72 -0
- data/lib/lanes/rails_engine.rb +5 -0
- data/lib/lanes/screens.rb +126 -0
- data/lib/lanes/spec_asset_expander.rb +43 -0
- data/lib/lanes/strings.rb +56 -0
- data/lib/lanes/user.rb +127 -0
- data/lib/lanes/validators/all.rb +2 -0
- data/lib/lanes/validators/email.rb +17 -0
- data/lib/lanes/validators/set.rb +18 -0
- data/lib/lanes/version.rb +5 -0
- data/lib/lanes.rb +22 -0
- data/npm-build/README +1 -0
- data/npm-build/compile.coffee +15 -0
- data/npm-build/package.json +59 -0
- data/npm-build/shims/underscore.js +1416 -0
- data/npm-build/template.js +33 -0
- data/public/javascripts/jasmine_examples/Player.js +22 -0
- data/public/javascripts/jasmine_examples/Song.js +7 -0
- data/spec/api/javascript_processor_spec.rb +107 -0
- data/spec/api/user_spec.rb +52 -0
- data/spec/client/component/ChoicesInputSpec.coffee +12 -0
- data/spec/client/component/foo_spec.coffee +4 -0
- data/spec/client/foo_spec.js +0 -0
- data/spec/client/jasmine_examples/PlayerSpec.js +0 -0
- data/spec/client/support/jasmine.yml +128 -0
- data/spec/client/support/jasmine_helper.rb +15 -0
- data/spec/concerns/api_path_spec.rb +14 -0
- data/spec/concerns/association_extensions_spec.rb +30 -0
- data/spec/concerns/attr_accessor_with_default_spec.rb +57 -0
- data/spec/concerns/code_identifier_spec.rb +45 -0
- data/spec/concerns/export_associations_spec.rb +7 -0
- data/spec/concerns/export_methods_spec.rb +43 -0
- data/spec/concerns/export_scope_spec.rb +15 -0
- data/spec/concerns/exported_limits_spec.rb +47 -0
- data/spec/concerns/pub_sub_spec.rb +83 -0
- data/spec/concerns/set_attribute_data_spec.rb +66 -0
- data/spec/configuration_spec.rb +26 -0
- data/spec/fixtures/lanes/users.yml +13 -0
- data/spec/helpers/.gitkeep +0 -0
- data/spec/helpers/SpecHelper.js +18 -0
- data/spec/locked_fields_spec.rb +27 -0
- data/spec/numbers_spec.rb +26 -0
- data/spec/role_collection_spec.rb +19 -0
- data/spec/spec_helper.rb +163 -0
- data/spec/strings_spec.rb +41 -0
- data/spec/testing_models.rb +54 -0
- data/spec/user_role_spec.rb +7 -0
- data/spec/user_spec.rb +53 -0
- data/tasks/migrations.rake +22 -0
- data/tasks/publish.rake +8 -0
- data/views/index.erb +29 -0
- data/views/specs.erb +25 -0
- data/yard_ext/all.rb +9 -0
- data/yard_ext/code_identifier_handler.rb +33 -0
- data/yard_ext/concern_meta_methods.rb +60 -0
- data/yard_ext/config_options.rb +27 -0
- data/yard_ext/exported_scope.rb +4 -0
- data/yard_ext/immutable_handler.rb +17 -0
- data/yard_ext/json_attr_accessor.rb +22 -0
- data/yard_ext/locked_fields_handler.rb +21 -0
- data/yard_ext/templates/default/layout/html/layout.erb +20 -0
- data/yard_ext/templates/default/method_details/html/github_link.erb +1 -0
- data/yard_ext/templates/default/method_details/setup.rb +3 -0
- data/yard_ext/validators.rb +1 -0
- data/yard_ext/visible_id_handler.rb +38 -0
- metadata +772 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module Lanes::Concerns
|
|
2
|
+
|
|
3
|
+
module ExportJoinTables
|
|
4
|
+
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
class_attribute :exported_join_tables
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
module ClassMethods
|
|
12
|
+
# Mark a joined table as safe to be included in a query
|
|
13
|
+
# Primarily used for joining a model to a view for access to summarized data
|
|
14
|
+
def export_join_tables( *tables )
|
|
15
|
+
include ExportedLimitEvaluator
|
|
16
|
+
self.exported_join_tables ||= []
|
|
17
|
+
tables.flatten!
|
|
18
|
+
options = tables.extract_options!
|
|
19
|
+
tables.each do | join_name |
|
|
20
|
+
self.exported_join_tables << {
|
|
21
|
+
name: join_name,
|
|
22
|
+
limit: options[:limit]
|
|
23
|
+
}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Has the join been marked as safe?
|
|
29
|
+
def has_exported_join_table?(name, user)
|
|
30
|
+
return true if name == 'details' # "details" is reserved for views and is always allowed
|
|
31
|
+
self.exported_join_tables && self.exported_join_tables.detect{ | join |
|
|
32
|
+
join[:name] == name && evaluate_export_limit( user, :join, join[:name], join[:limit] )
|
|
33
|
+
}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
module Lanes::Concerns
|
|
2
|
+
|
|
3
|
+
# @see ClassMethods
|
|
4
|
+
module ExportMethods
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
|
|
9
|
+
# methods that the model has exported. Not intended for querying directly.
|
|
10
|
+
# The API and interested parties should call {#has_exported_method?}
|
|
11
|
+
class_attribute :exported_methods
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module ClassMethods
|
|
15
|
+
|
|
16
|
+
# Called by a model to export methods to the API
|
|
17
|
+
# An exported method will be called and it's results returned along with the models
|
|
18
|
+
# JSON representation.
|
|
19
|
+
# @option options [Boolean] :optional if false, the method will always be called and included
|
|
20
|
+
# @option options [Symbol, Array of Symbols] :depends name(s) of associations that should
|
|
21
|
+
# be pre-loaded before calling method. Intended to prevent N+1 queries
|
|
22
|
+
def export_methods( *method_names )
|
|
23
|
+
method_names.flatten!
|
|
24
|
+
options = method_names.extract_options!
|
|
25
|
+
self.exported_methods ||= {}
|
|
26
|
+
method_names.map do | name |
|
|
27
|
+
exported_methods[ name.to_sym] = options
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Convenience method to create a Rails delegation and export the resulting method
|
|
32
|
+
# @example
|
|
33
|
+
# class Foo < Lanes::Model
|
|
34
|
+
# belongs_to :bar
|
|
35
|
+
# delegate_and_export :bar_name, :optional=>false
|
|
36
|
+
# end
|
|
37
|
+
#
|
|
38
|
+
# foo = Foo.new
|
|
39
|
+
# foo.bar_name #=> calls foo.bar.name
|
|
40
|
+
# Foo.has_exported_method?( :bar_name ) #=> true
|
|
41
|
+
# lanes_to_json( foo ) #=> will first load the bar association, then
|
|
42
|
+
# call foo.bar_name and include it's result in the JSON
|
|
43
|
+
def delegate_and_export( *names )
|
|
44
|
+
options = names.extract_options!
|
|
45
|
+
names.each do | name |
|
|
46
|
+
target,field = name.to_s.split(/_(?=[^_]+(?: |$))| /)
|
|
47
|
+
delegate_and_export_field( target, field, optional: options[:optional], limit: options[:limit] )
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# For situations where the delegate_and_export method guesses wrong on the association and field names
|
|
52
|
+
# @param target [Association] association to delegate to
|
|
53
|
+
# @param field [Symbol] method on Association to call
|
|
54
|
+
# @param optional [Boolean] should the method be called and results included all the time,
|
|
55
|
+
# or only if specifically requested
|
|
56
|
+
# @param limit [Symbol referring to a method, lambda] restrict to Users for whom true is returned
|
|
57
|
+
# @example
|
|
58
|
+
# class PoLine < Lanes::Model
|
|
59
|
+
# belongs_to :purchase_order
|
|
60
|
+
# delegate_and_export :purchase_order_visible_id
|
|
61
|
+
# end
|
|
62
|
+
# would generate a method ``purchase_order_visible_id`` that would call ``purchase_order_visible.id``
|
|
63
|
+
# This would do the right thing:
|
|
64
|
+
# class PoLine < Lanes::Model
|
|
65
|
+
# belongs_to :purchase_order
|
|
66
|
+
# delegate_and_export_field :purchase_order, :visible_id
|
|
67
|
+
# end
|
|
68
|
+
def delegate_and_export_field( target, field, optional: true, limit: nil )
|
|
69
|
+
delegate field, to: target, prefix: target, allow_nil: true
|
|
70
|
+
self.export_methods( "#{target}_#{field}", { depends: target.to_sym,
|
|
71
|
+
optional: optional,
|
|
72
|
+
limit: limit } )
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Check if the method can be called by user
|
|
76
|
+
def has_exported_method?( name, user )
|
|
77
|
+
if self.exported_methods && ( method_options = self.exported_methods[ name.to_sym ] )
|
|
78
|
+
return evaluate_export_limit( user, :method, name, method_options[:limit] )
|
|
79
|
+
else
|
|
80
|
+
return false
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Retrieve the list of dependent associations for the given methods
|
|
85
|
+
# Also includes methods that were exported as ``optional: false``
|
|
86
|
+
# @param requested_methods [Array] methods that the user has requested be executed and added to the JSON payload
|
|
87
|
+
# @return [Array] list of associations that should be included in query
|
|
88
|
+
def exported_method_dependancies( requested_methods )
|
|
89
|
+
requested_methods.map!(&:to_sym)
|
|
90
|
+
return [] if self.exported_methods.blank?
|
|
91
|
+
depends = self.exported_methods.each_with_object(Array.new) do | kv, result |
|
|
92
|
+
( export, options ) = kv
|
|
93
|
+
if options[:depends] && ( false == options[:optional] || requested_methods.include?(export) )
|
|
94
|
+
result << options[:depends]
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
depends.uniq
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
require_relative 'exported_limit_evaluator'
|
|
2
|
+
|
|
3
|
+
module Lanes::Concerns
|
|
4
|
+
|
|
5
|
+
# @see ClassMethods
|
|
6
|
+
module ExportScope
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
included do
|
|
10
|
+
# scopes that the model has exported. Not intended for querying directly.
|
|
11
|
+
# The API and interested parties should call {#has_exported_scope?}
|
|
12
|
+
class_attribute :exported_scopes
|
|
13
|
+
extend ExportedLimitEvaluator
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# ### Mark a scope as "exportable"
|
|
17
|
+
#
|
|
18
|
+
# An exported scope is safe for querying by external clients over the API.
|
|
19
|
+
# The scope should always:
|
|
20
|
+
#
|
|
21
|
+
# * Safely escape data *(should __ALWAYS__ do this anyway, but it bears mentioning again)*
|
|
22
|
+
# * Be relatively simple and complete quickly.
|
|
23
|
+
# * Provide value to the client that it cannot obtain by using normal query methods
|
|
24
|
+
module ClassMethods
|
|
25
|
+
def scope(name, body, options = {}, &block)
|
|
26
|
+
super(name, body, &block)
|
|
27
|
+
if (export = options[:export])
|
|
28
|
+
export_scope(name, body, limit: (export == true ? nil : export[:limit]))
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Mark scope as query-able by the API.
|
|
33
|
+
# @param name [Symbol,String] Rails will create a class method with this name
|
|
34
|
+
# @param query [lambda] Arel query. This is passed off to Rail's for setting up the scope.
|
|
35
|
+
# @param limit [Symbol referring to a Class method name, lambda]
|
|
36
|
+
# If given, this will be queried by the API to determining if a given user may call the scope
|
|
37
|
+
# @return nil
|
|
38
|
+
def export_scope(name, query, limit: nil)
|
|
39
|
+
include ExportedLimitEvaluator
|
|
40
|
+
|
|
41
|
+
self.exported_scopes ||= Hash.new
|
|
42
|
+
self.exported_scopes[name.to_sym] = {
|
|
43
|
+
scope: scope(name, query),
|
|
44
|
+
name: name,
|
|
45
|
+
limit: limit
|
|
46
|
+
}
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# The api can query this to determine if the scope is safe to be called
|
|
51
|
+
# from the API by [user]
|
|
52
|
+
# @param name [Symbol,String] name of scope
|
|
53
|
+
# @param user [User] who is performing the request.
|
|
54
|
+
# This is passed off to the method or lambda that was given as the limit argument in {#export_scope}
|
|
55
|
+
|
|
56
|
+
def has_exported_scope?(name, user)
|
|
57
|
+
if self.exported_scopes && ( scope_options = self.exported_scopes[ name.to_sym ] )
|
|
58
|
+
return evaluate_export_limit( user, :scope, name, scope_options[:limit] )
|
|
59
|
+
else
|
|
60
|
+
return false
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Lanes::Concerns
|
|
2
|
+
|
|
3
|
+
module ExportedLimitEvaluator
|
|
4
|
+
|
|
5
|
+
def evaluate_export_limit( user, type, name, limit )
|
|
6
|
+
if limit.nil?
|
|
7
|
+
true
|
|
8
|
+
elsif limit.is_a?(Symbol)
|
|
9
|
+
self.send( limit, user, type, name )
|
|
10
|
+
else
|
|
11
|
+
limit.call( user, type, name )
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module Lanes
|
|
2
|
+
module Concerns
|
|
3
|
+
|
|
4
|
+
module ImmutableModel
|
|
5
|
+
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
module ClassMethods
|
|
9
|
+
|
|
10
|
+
# Once it's created it may not be updated or destroyed.
|
|
11
|
+
# @raise [ActiveRecord::ReadOnlyRecord] if destroy, delete or save is called after it's been created.
|
|
12
|
+
def is_immutable(options = {})
|
|
13
|
+
|
|
14
|
+
options[:except] = [*options[:except]].map{|name| name.to_s } # make sure except is present and an array
|
|
15
|
+
|
|
16
|
+
before_destroy do
|
|
17
|
+
raise ActiveRecord::ReadOnlyRecord.new( "Can not destroy #{self.class.model_name}, only create is allowed" )
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
before_update do
|
|
21
|
+
unless ( changes.keys - change_tracking_fields - options[:except] ).blank?
|
|
22
|
+
raise ActiveRecord::ReadOnlyRecord.new( "Can not update, only create #{self.class.model_name}" )
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
module Lanes
|
|
2
|
+
module Concerns
|
|
3
|
+
|
|
4
|
+
# @see ClassMethods
|
|
5
|
+
module LockedFields
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
module ClassMethods
|
|
9
|
+
# Mark fields as locked, meaning they cannot be updated by using the
|
|
10
|
+
# regular attribute update methods. Instead it must be called in an unlock block
|
|
11
|
+
# Relies on attr_readonly internally
|
|
12
|
+
#
|
|
13
|
+
# Is used to designate sensitive fields that we want to make sure someone's thought about before updating
|
|
14
|
+
# Also solves the age old single equals vs double equals bug/typo.
|
|
15
|
+
#
|
|
16
|
+
# class BankAccount < Lanes::Model
|
|
17
|
+
#
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# bank=BankAccount.find(1)
|
|
21
|
+
# b.mark_as_super if bank.account_balance = 42 # a bit contrived, but you get the idea
|
|
22
|
+
# b.save # oops, what's our balance now?
|
|
23
|
+
#
|
|
24
|
+
# Now let's try it again with locked_fields
|
|
25
|
+
#
|
|
26
|
+
# class BankAccount < Lanes::Model
|
|
27
|
+
# attr_readonly :account_balance
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# bank=BankAccount.find(1)
|
|
31
|
+
# b.mark_as_super if bank.account_balance = 42
|
|
32
|
+
# b.save # Still not ideal since we marked the bank as super, but at least our balance is ok
|
|
33
|
+
#
|
|
34
|
+
# To update the balance we'd need to:
|
|
35
|
+
#
|
|
36
|
+
# b.unlock_fields( :account_balance ) do
|
|
37
|
+
# b.account_balance += 33
|
|
38
|
+
# end
|
|
39
|
+
# b.save
|
|
40
|
+
#
|
|
41
|
+
# This is still a bit contrived since we'd actually have
|
|
42
|
+
# an audit logger that would be involved and it'd be inside a transaction.
|
|
43
|
+
|
|
44
|
+
def locked_fields( *flds )
|
|
45
|
+
include InstanceMethods
|
|
46
|
+
attr_readonly( *flds )
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def has_locks( *locks )
|
|
50
|
+
locks.each do | lock |
|
|
51
|
+
define_method( "unlock_#{lock}" ) do | &block |
|
|
52
|
+
instance_variable_set "@_lock_#{lock}_unlocked", true
|
|
53
|
+
block.call
|
|
54
|
+
remove_instance_variable "@_lock_#{lock}_unlocked"
|
|
55
|
+
end
|
|
56
|
+
define_method( "is_#{lock}_unlocked?") do
|
|
57
|
+
instance_variable_defined? "@_lock_#{lock}_unlocked"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
module InstanceMethods
|
|
65
|
+
# Unlock the field for updates inside the block
|
|
66
|
+
# yields, then restores it.
|
|
67
|
+
# Is class wide, meaning it Will temporarily open all instances of the class up for access in a threaded environment
|
|
68
|
+
def unlock_fields( *flds, &block )
|
|
69
|
+
attr_syms = flds.map(&:to_s)
|
|
70
|
+
|
|
71
|
+
self.class.attr_readonly.subtract( attr_syms )
|
|
72
|
+
|
|
73
|
+
yield
|
|
74
|
+
|
|
75
|
+
attr_syms.each do | fld |
|
|
76
|
+
self.class.attr_readonly.add( fld )
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
module Lanes::Concerns
|
|
2
|
+
|
|
3
|
+
module PendingEventListeners
|
|
4
|
+
# @private
|
|
5
|
+
@@listeners = Hash.new{ |hash, klass| hash[klass] = Hash.new{ |kh, event| kh[event]=Array.new } }
|
|
6
|
+
# @api private
|
|
7
|
+
def self.all
|
|
8
|
+
@@listeners
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# Event subscription and publishing for Stockor Models
|
|
14
|
+
# Every model has certain built-in events (:save, :create, :update, :destroy)
|
|
15
|
+
# And may also implement custom events that reflect the models domain
|
|
16
|
+
# @example Send an email when a customer's name is updated
|
|
17
|
+
# Customer.observe(:update) do |customer|
|
|
18
|
+
# Mailer.notify_billing(customer).deliver if customer.name_changed?
|
|
19
|
+
# end
|
|
20
|
+
# @example Update some stats when a Sku's qty is changed
|
|
21
|
+
# Sku.observe(:qty_changed) do | sku, location, old_qty, new_qty |
|
|
22
|
+
# Stats.refresh( location )
|
|
23
|
+
# end
|
|
24
|
+
module PubSub
|
|
25
|
+
extend ActiveSupport::Concern
|
|
26
|
+
|
|
27
|
+
class InvalidEvent < RuntimeError
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
included do | base |
|
|
31
|
+
|
|
32
|
+
class_attribute :valid_event_names
|
|
33
|
+
class_attribute :_event_listeners
|
|
34
|
+
self.valid_event_names = [ :save, :create, :update, :destroy ]
|
|
35
|
+
|
|
36
|
+
after_save :fire_after_save_events
|
|
37
|
+
after_create :fire_after_create_events
|
|
38
|
+
after_update :fire_after_update_events
|
|
39
|
+
after_destroy :fire_after_destroy_events
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
module ClassMethods
|
|
43
|
+
def inherited(base)
|
|
44
|
+
super
|
|
45
|
+
klass = base.to_s.demodulize
|
|
46
|
+
if PendingEventListeners.all.has_key?( klass )
|
|
47
|
+
events = PendingEventListeners.all.delete(klass)
|
|
48
|
+
events.each{ | name, procs | base.event_listeners[name] += procs }
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def event_listeners
|
|
53
|
+
self._event_listeners ||= Hash.new{ |hash, key| hash[key]=Array.new }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def _add_event_listener( name, proc )
|
|
57
|
+
self.event_listeners[name].push( proc ) unless self.event_listeners[name].include?(proc)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def observe( event, &block )
|
|
61
|
+
_ensure_validate_event( event )
|
|
62
|
+
_add_event_listener( event.to_sym, block )
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def _ensure_validate_event(event)
|
|
66
|
+
unless self.valid_event_names.include?(event.to_sym)
|
|
67
|
+
raise InvalidEvent.new("#{event} is not a valid event for #{self}")
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
protected
|
|
72
|
+
|
|
73
|
+
def has_additional_events( *names )
|
|
74
|
+
self.valid_event_names += names.map{ |name| name.to_sym }
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
protected
|
|
79
|
+
def fire_after_destroy_events
|
|
80
|
+
_fire_event(:update, self )
|
|
81
|
+
end
|
|
82
|
+
def fire_after_update_events
|
|
83
|
+
_fire_event(:update, self )
|
|
84
|
+
end
|
|
85
|
+
def fire_after_create_events
|
|
86
|
+
_fire_event(:create, self )
|
|
87
|
+
end
|
|
88
|
+
def fire_after_save_events
|
|
89
|
+
_fire_event( :save, self )
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def fire_event( name, *arguments )
|
|
93
|
+
self.class._ensure_validate_event( name )
|
|
94
|
+
arguments.unshift( self )
|
|
95
|
+
_fire_event( name, *arguments )
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
def _fire_event( name, *arguments )
|
|
100
|
+
self.class.event_listeners[ name.to_sym ].each{ | block | block.call(*arguments) }
|
|
101
|
+
return true
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Lanes
|
|
2
|
+
module Concerns
|
|
3
|
+
|
|
4
|
+
# A collection of handly utility methods to generate queries
|
|
5
|
+
module Queries
|
|
6
|
+
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
module ClassMethods
|
|
10
|
+
|
|
11
|
+
def compose_query_using_detail_view( view: view, join_to: join_to )
|
|
12
|
+
view = Lanes.config.table_prefix + view.to_s
|
|
13
|
+
joins("join #{view} as details on details.#{join_to} = #{table_name}.#{primary_key}")
|
|
14
|
+
.select("#{table_name}.*, details.*")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module Lanes
|
|
2
|
+
module Concerns
|
|
3
|
+
|
|
4
|
+
# @see ClassMethods
|
|
5
|
+
module RandomHashCode
|
|
6
|
+
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
# ### Random Hash Code Concern
|
|
10
|
+
# This adds the {#has_random_hash_code} class method
|
|
11
|
+
module ClassMethods
|
|
12
|
+
|
|
13
|
+
# A random string that identifies an entity, such as a Customer, or Vendor
|
|
14
|
+
# The code is generated by {Lanes::Strings.random} for new records
|
|
15
|
+
# It's useful for generating *magic* links for access to an entity that cannot be guessed.
|
|
16
|
+
# @param field_name [Symbol] which field should the hash_code be stored in
|
|
17
|
+
# @param length [Integer] how long the hash_code should be
|
|
18
|
+
|
|
19
|
+
def has_random_hash_code( field_name: :hash_code, length: 12 )
|
|
20
|
+
|
|
21
|
+
validates field_name, :presence=>{
|
|
22
|
+
:message=>"hash code is not set (should be automatically chosen)"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
scope :with_hash_code, lambda{ | code |
|
|
26
|
+
where({ :hash_code=>code })
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
before_validation(:on=>:create) do
|
|
30
|
+
self[ field_name ] = Lanes::Strings.random( length ) if self[ field_name ].blank?
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Lanes::Concerns
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# SanitizeJson is responsible for only cleaning a hash of attributes that should not
|
|
5
|
+
# be written to the model by the given user
|
|
6
|
+
module SanitizeApiData
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
module ClassMethods
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
end
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
module Lanes::Concerns
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module ApiAttributeAccess
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
included do
|
|
10
|
+
class_attribute :blacklisted_attributes
|
|
11
|
+
class_attribute :whitelisted_attributes
|
|
12
|
+
include AccessChecks
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
module AccessChecks
|
|
16
|
+
# Can the API read data from the model?
|
|
17
|
+
def can_read_attributes?(params, user)
|
|
18
|
+
return user.can_write?(self, params)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Can the API write data to the model?
|
|
22
|
+
# This check is performed before we bother checking each attribute individually
|
|
23
|
+
def can_write_attributes?(params, user)
|
|
24
|
+
return user.can_write?(self, params)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Can the API delete the model?
|
|
28
|
+
# This check is performed before we bother checking each attribute individually
|
|
29
|
+
def can_delete_attributes?(params, user)
|
|
30
|
+
return user.can_delete?(self, params)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
module ClassMethods
|
|
35
|
+
|
|
36
|
+
# # An attribute accessor that allows access from the API
|
|
37
|
+
# def api_attr_accessor( *names )
|
|
38
|
+
# names.each do | attr |
|
|
39
|
+
# attr_accessor attr
|
|
40
|
+
# whitelist_attributes attr
|
|
41
|
+
# end
|
|
42
|
+
# end
|
|
43
|
+
|
|
44
|
+
# @param attributes [Array of symbols] attributes that are safe for the API to set
|
|
45
|
+
def whitelist_attributes( *attributes )
|
|
46
|
+
options = attributes.extract_options!
|
|
47
|
+
self.whitelisted_attributes ||= {}
|
|
48
|
+
attributes.each{|attr| self.whitelisted_attributes[ attr.to_sym ] = options }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @param attributes [Array of symbols] attributes that are not safe for the API to set
|
|
52
|
+
def blacklist_attributes( *attributes )
|
|
53
|
+
options = attributes.extract_options!
|
|
54
|
+
self.blacklisted_attributes ||= {}
|
|
55
|
+
attributes.each{|attr| self.blacklisted_attributes[ attr.to_sym ] = options }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def from_attribute_data(data,user=Lanes::User.current)
|
|
59
|
+
record = self.new
|
|
60
|
+
record.set_attribute_data(data, user)
|
|
61
|
+
record
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
include AccessChecks
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# An attribute is allowed if it's white listed
|
|
69
|
+
# or it's a valid attribute and not black listed
|
|
70
|
+
# @param name [Symbol]
|
|
71
|
+
# @param user [User] who is performing request
|
|
72
|
+
def setting_attribute_is_allowed?(name, user)
|
|
73
|
+
return false unless user.can_write?(self, name)
|
|
74
|
+
(self.whitelisted_attributes && self.whitelisted_attributes.has_key?( name.to_sym)) ||
|
|
75
|
+
(
|
|
76
|
+
self.attribute_names.include?( name.to_s ) &&
|
|
77
|
+
( self.blacklisted_attributes.nil? ||
|
|
78
|
+
! self.blacklisted_attributes.has_key?( name.to_sym ) )
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Takes in a hash containing attribute name/value pairs, as well as sub hashes/arrays.
|
|
83
|
+
# Sets all the attributes that are allowed and recursively sets sub-associations as well
|
|
84
|
+
# @param data [Hash]
|
|
85
|
+
# @param user [User] who is performing request
|
|
86
|
+
# @returns
|
|
87
|
+
def set_attribute_data(data, user = Lanes::User.current)
|
|
88
|
+
|
|
89
|
+
return {} unless self.can_write_attributes?(data, user)
|
|
90
|
+
|
|
91
|
+
data.each_with_object(Hash.new) do | kv, result |
|
|
92
|
+
( key, value ) = kv
|
|
93
|
+
|
|
94
|
+
# First we set all the attributes that are allowed
|
|
95
|
+
if self.setting_attribute_is_allowed?(key.to_sym, user)
|
|
96
|
+
public_send("#{key}=",result[key] = value)
|
|
97
|
+
else
|
|
98
|
+
# allow nested params to be specified using Rails _attributes
|
|
99
|
+
name = key.to_s.gsub(/_attributes$/,'').to_sym
|
|
100
|
+
|
|
101
|
+
next unless self.class.has_exported_nested_attribute?(name, user)
|
|
102
|
+
|
|
103
|
+
association = self.association(name)
|
|
104
|
+
|
|
105
|
+
result[name] = if value.is_a?(Hash) && [:belongs_to,:has_one].include?(association.reflection.macro)
|
|
106
|
+
target = association.target || association.build
|
|
107
|
+
target.set_attribute_data(value)
|
|
108
|
+
elsif value.is_a?(Array) && :has_many == association.reflection.macro
|
|
109
|
+
_set_attribute_data_from_collection(association, value)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def _set_attribute_data_from_collection(association, value)
|
|
116
|
+
|
|
117
|
+
records = if association.loaded?
|
|
118
|
+
association.target
|
|
119
|
+
else
|
|
120
|
+
attribute_ids = value.map {|a| a['id'] || a[:id] }.compact
|
|
121
|
+
attribute_ids.empty? ? [] : association.scope.where(
|
|
122
|
+
association.klass.primary_key => attribute_ids
|
|
123
|
+
)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
value.map do | association_data |
|
|
127
|
+
record = if association_data['id'].blank?
|
|
128
|
+
association.build
|
|
129
|
+
else
|
|
130
|
+
records.detect{ |r| r.id.to_s == value['id'].to_s }
|
|
131
|
+
end
|
|
132
|
+
record.set_attribute_data(association_data) if record
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# association = self.send(name)
|
|
138
|
+
|
|
139
|
+
# klass_name = self.class.reflections[ name.to_sym ].class_name
|
|
140
|
+
# klass = klass_name.safe_constantize || "Lanes::#{klass_name}".constantize
|
|
141
|
+
|
|
142
|
+
# # only Hash, Array & nil is valid for nesting attributes
|
|
143
|
+
# cleaned = case value
|
|
144
|
+
# when Hash then klass.sanitize_api_data( value, user, klass )
|
|
145
|
+
# when Array then value.map{ | nested |
|
|
146
|
+
# klass.sanitize_api_data( nested, user, klass )
|
|
147
|
+
# }
|
|
148
|
+
# else
|
|
149
|
+
# nil
|
|
150
|
+
# end
|
|
151
|
+
# result[ name.to_sym ] = cleaned unless cleaned.blank?
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
end
|