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.
Files changed (391) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.jshintrc +3 -0
  4. data/Gemfile +9 -0
  5. data/Guardfile +15 -0
  6. data/LICENSE-MIT.txt +21 -0
  7. data/README.md +15 -0
  8. data/Rakefile +74 -0
  9. data/bin/lanes +5 -0
  10. data/client/images/ajax-loader.gif +0 -0
  11. data/client/images/dataTables/Sorting icons.psd +0 -0
  12. data/client/images/dataTables/back_disabled.png +0 -0
  13. data/client/images/dataTables/back_enabled.png +0 -0
  14. data/client/images/dataTables/back_enabled_hover.png +0 -0
  15. data/client/images/dataTables/favicon.ico +0 -0
  16. data/client/images/dataTables/forward_disabled.png +0 -0
  17. data/client/images/dataTables/forward_enabled.png +0 -0
  18. data/client/images/dataTables/forward_enabled_hover.png +0 -0
  19. data/client/images/dataTables/loading-background.png +0 -0
  20. data/client/images/dataTables/sort_asc.png +0 -0
  21. data/client/images/dataTables/sort_asc_disabled.png +0 -0
  22. data/client/images/dataTables/sort_both.png +0 -0
  23. data/client/images/dataTables/sort_desc.png +0 -0
  24. data/client/images/dataTables/sort_desc_disabled.png +0 -0
  25. data/client/images/logo-sm.png +0 -0
  26. data/client/javascripts/component/Base.coffee +39 -0
  27. data/client/javascripts/component/ChoicesInput.coffee +47 -0
  28. data/client/javascripts/component/Grid.coffee +199 -0
  29. data/client/javascripts/component/ModalDialog.coffee +44 -0
  30. data/client/javascripts/component/PopOver.coffee +52 -0
  31. data/client/javascripts/component/RadioGroup.coffee +59 -0
  32. data/client/javascripts/component/RecordFinder.coffee +143 -0
  33. data/client/javascripts/component/SelectField.coffee +43 -0
  34. data/client/javascripts/component/TaggedField.coffee +27 -0
  35. data/client/javascripts/component/grid/Editor.coffee +65 -0
  36. data/client/javascripts/component/grid/PopOverEditor.coffee +29 -0
  37. data/client/javascripts/component/grid/RowEditor.coffee +31 -0
  38. data/client/javascripts/component/grid/popover-editor.html +18 -0
  39. data/client/javascripts/component/grid/row-editor.html +16 -0
  40. data/client/javascripts/component/grid.html +4 -0
  41. data/client/javascripts/component/index.js +5 -0
  42. data/client/javascripts/component/modal.html +17 -0
  43. data/client/javascripts/component/popover.html +5 -0
  44. data/client/javascripts/component/record-finder/clause.skr +35 -0
  45. data/client/javascripts/component/record-finder/dialog.skr +4 -0
  46. data/client/javascripts/component/record-finder/field.skr +8 -0
  47. data/client/javascripts/data/Bootstrap.coffee +8 -0
  48. data/client/javascripts/data/ChangeSet.coffee +50 -0
  49. data/client/javascripts/data/Collection.coffee +111 -0
  50. data/client/javascripts/data/Config.coffee +15 -0
  51. data/client/javascripts/data/Model.coffee +269 -0
  52. data/client/javascripts/data/PubSub.coffee +68 -0
  53. data/client/javascripts/data/Query.coffee +184 -0
  54. data/client/javascripts/data/Roles.coffee +91 -0
  55. data/client/javascripts/data/Screens.coffee +157 -0
  56. data/client/javascripts/data/Sync.coffee +62 -0
  57. data/client/javascripts/data/User.coffee +70 -0
  58. data/client/javascripts/data/index.js +7 -0
  59. data/client/javascripts/data/mixins/HasCodeField.coffee +13 -0
  60. data/client/javascripts/extension/Base.coffee +9 -0
  61. data/client/javascripts/extension/Extensions.coffee +17 -0
  62. data/client/javascripts/extension/GlAccounts.coffee +9 -0
  63. data/client/javascripts/extension/index.js +6 -0
  64. data/client/javascripts/extension/load.js.erb +3 -0
  65. data/client/javascripts/lanes-complete.js +3 -0
  66. data/client/javascripts/lanes-workspace.js +1 -0
  67. data/client/javascripts/lib/MakeBaseClass.coffee +55 -0
  68. data/client/javascripts/lib/ModuleSupport.coffee +22 -0
  69. data/client/javascripts/lib/Templates.coffee +47 -0
  70. data/client/javascripts/lib/create-namespace.js +0 -0
  71. data/client/javascripts/lib/debounce.coffee +15 -0
  72. data/client/javascripts/lib/defer.coffee +7 -0
  73. data/client/javascripts/lib/el.js +115 -0
  74. data/client/javascripts/lib/index.js +9 -0
  75. data/client/javascripts/lib/loader.coffee +95 -0
  76. data/client/javascripts/lib/namespace.coffee +9 -0
  77. data/client/javascripts/lib/noConflict.coffee +14 -0
  78. data/client/javascripts/lib/promise_helpers.coffee +4 -0
  79. data/client/javascripts/lib/results.coffee +15 -0
  80. data/client/javascripts/lib/underscore.inflection.js +210 -0
  81. data/client/javascripts/lib/utilFunctions.coffee +51 -0
  82. data/client/javascripts/plugins/ResizeSensor.js +144 -0
  83. data/client/javascripts/plugins/index.js +4 -0
  84. data/client/javascripts/plugins/overlay.coffee +41 -0
  85. data/client/javascripts/plugins/trigger.coffee +15 -0
  86. data/client/javascripts/vendor/bootstrap/affix.js +142 -0
  87. data/client/javascripts/vendor/bootstrap/alert.js +92 -0
  88. data/client/javascripts/vendor/bootstrap/button.js +110 -0
  89. data/client/javascripts/vendor/bootstrap/carousel.js +223 -0
  90. data/client/javascripts/vendor/bootstrap/collapse.js +170 -0
  91. data/client/javascripts/vendor/bootstrap/dropdown.js +151 -0
  92. data/client/javascripts/vendor/bootstrap/modal.js +280 -0
  93. data/client/javascripts/vendor/bootstrap/popover.js +113 -0
  94. data/client/javascripts/vendor/bootstrap/scrollspy.js +170 -0
  95. data/client/javascripts/vendor/bootstrap/tab.js +128 -0
  96. data/client/javascripts/vendor/bootstrap/tooltip.js +457 -0
  97. data/client/javascripts/vendor/bootstrap/transition.js +59 -0
  98. data/client/javascripts/vendor/dataTables/dataTables.bootstrap.js +156 -0
  99. data/client/javascripts/vendor/dataTables/dataTables.scroller.js +1185 -0
  100. data/client/javascripts/vendor/dataTables/datatables.responsive.js +666 -0
  101. data/client/javascripts/vendor/dataTables/index.js +2 -0
  102. data/client/javascripts/vendor/dataTables/jquery.dataTables.js +14380 -0
  103. data/client/javascripts/vendor/jquery-2.js +9190 -0
  104. data/client/javascripts/vendor/jquery.tap.js +401 -0
  105. data/client/javascripts/vendor/magicsuggest.js +1565 -0
  106. data/client/javascripts/vendor/message-bus.js +285 -0
  107. data/client/javascripts/vendor/modern-stack.js +14 -0
  108. data/client/javascripts/vendor/packaged.js +13769 -0
  109. data/client/javascripts/view/Assets.coffee +9 -0
  110. data/client/javascripts/view/Base.coffee +231 -0
  111. data/client/javascripts/view/FormBindings.coffee +98 -0
  112. data/client/javascripts/view/Functions.coffee +13 -0
  113. data/client/javascripts/view/Helpers.coffee +77 -0
  114. data/client/javascripts/view/InterfaceState.coffee +88 -0
  115. data/client/javascripts/view/Keys.coffee +59 -0
  116. data/client/javascripts/view/ModelObserver.coffee +31 -0
  117. data/client/javascripts/view/ModelUpdate.coffee +8 -0
  118. data/client/javascripts/view/PubSub.coffee +29 -0
  119. data/client/javascripts/view/RenderContext.coffee +32 -0
  120. data/client/javascripts/view/SaveNotify.coffee +30 -0
  121. data/client/javascripts/view/Screen.coffee +30 -0
  122. data/client/javascripts/view/TimedHighlight.coffee +38 -0
  123. data/client/javascripts/view/TimedMask.coffee +65 -0
  124. data/client/javascripts/view/_button.html +3 -0
  125. data/client/javascripts/view/_toolbar.html +27 -0
  126. data/client/javascripts/view/index.js +10 -0
  127. data/client/javascripts/view/mixins/ScreenChangeListener.coffee +43 -0
  128. data/client/javascripts/view/model-update.html +13 -0
  129. data/client/javascripts/view/screen-definitions.js.erb +7 -0
  130. data/client/javascripts/workspace/ActiveScreensSwitcher.coffee +117 -0
  131. data/client/javascripts/workspace/Instance.es6 +60 -0
  132. data/client/javascripts/workspace/Layout.coffee +18 -0
  133. data/client/javascripts/workspace/LoginDialog.coffee +33 -0
  134. data/client/javascripts/workspace/Navbar.coffee +44 -0
  135. data/client/javascripts/workspace/Pages.coffee +46 -0
  136. data/client/javascripts/workspace/ScreensMenu.coffee +126 -0
  137. data/client/javascripts/workspace/index.js +12 -0
  138. data/client/javascripts/workspace/layout.html +4 -0
  139. data/client/javascripts/workspace/login-dialog.html +16 -0
  140. data/client/javascripts/workspace/menu.html +356 -0
  141. data/client/javascripts/workspace/menu_toggle.html +9 -0
  142. data/client/javascripts/workspace/navbar.html +19 -0
  143. data/client/javascripts/workspace/pages.html +6 -0
  144. data/client/javascripts/workspace/screens-menu.html +11 -0
  145. data/client/javascripts/workspace/screens-switcher.html +7 -0
  146. data/client/javascripts/workspace/tab.html +0 -0
  147. data/client/screens/user-management/UserEditScreen.coffee +21 -0
  148. data/client/screens/user-management/UserManagement.coffee +24 -0
  149. data/client/screens/user-management/grid-popover-editor.html +33 -0
  150. data/client/screens/user-management/index.css +4 -0
  151. data/client/screens/user-management/index.js +2 -0
  152. data/client/screens/user-management/user-management-styles.scss +7 -0
  153. data/client/screens/user-management/user-management.html +8 -0
  154. data/client/stylesheets/compoonents/all.scss +6 -0
  155. data/client/stylesheets/compoonents/changes-notification.scss +44 -0
  156. data/client/stylesheets/compoonents/grid-editors.scss +65 -0
  157. data/client/stylesheets/compoonents/grid.scss +301 -0
  158. data/client/stylesheets/compoonents/modal-dialog.scss +23 -0
  159. data/client/stylesheets/compoonents/record-finder.scss +71 -0
  160. data/client/stylesheets/compoonents/suggest.scss +266 -0
  161. data/client/stylesheets/fonts/icomoon.eot +0 -0
  162. data/client/stylesheets/fonts/icomoon.svg +160 -0
  163. data/client/stylesheets/fonts/icomoon.ttf +0 -0
  164. data/client/stylesheets/fonts/icomoon.woff +0 -0
  165. data/client/stylesheets/fonts/selection.json +3565 -0
  166. data/client/stylesheets/fonts/style.css +451 -0
  167. data/client/stylesheets/fonts.scss +38 -0
  168. data/client/stylesheets/forms.scss +75 -0
  169. data/client/stylesheets/index.css +4 -0
  170. data/client/stylesheets/keybindings.scss +6 -0
  171. data/client/stylesheets/lanes-workspace.scss +17 -0
  172. data/client/stylesheets/layout.scss +272 -0
  173. data/client/stylesheets/plugins/all.scss +2 -0
  174. data/client/stylesheets/plugins/overlay.scss +63 -0
  175. data/client/stylesheets/plugins/resize-sensor.scss +24 -0
  176. data/client/stylesheets/screens.scss +66 -0
  177. data/client/stylesheets/tabs.scss +148 -0
  178. data/client/stylesheets/vendor/bootstrap/_alerts.scss +68 -0
  179. data/client/stylesheets/vendor/bootstrap/_badges.scss +57 -0
  180. data/client/stylesheets/vendor/bootstrap/_breadcrumbs.scss +26 -0
  181. data/client/stylesheets/vendor/bootstrap/_button-groups.scss +240 -0
  182. data/client/stylesheets/vendor/bootstrap/_buttons.scss +157 -0
  183. data/client/stylesheets/vendor/bootstrap/_carousel.scss +243 -0
  184. data/client/stylesheets/vendor/bootstrap/_close.scss +35 -0
  185. data/client/stylesheets/vendor/bootstrap/_code.scss +68 -0
  186. data/client/stylesheets/vendor/bootstrap/_component-animations.scss +35 -0
  187. data/client/stylesheets/vendor/bootstrap/_dropdowns.scss +215 -0
  188. data/client/stylesheets/vendor/bootstrap/_forms.scss +538 -0
  189. data/client/stylesheets/vendor/bootstrap/_glyphicons.scss +237 -0
  190. data/client/stylesheets/vendor/bootstrap/_grid.scss +84 -0
  191. data/client/stylesheets/vendor/bootstrap/_input-groups.scss +166 -0
  192. data/client/stylesheets/vendor/bootstrap/_jumbotron.scss +48 -0
  193. data/client/stylesheets/vendor/bootstrap/_labels.scss +66 -0
  194. data/client/stylesheets/vendor/bootstrap/_list-group.scss +132 -0
  195. data/client/stylesheets/vendor/bootstrap/_media.scss +56 -0
  196. data/client/stylesheets/vendor/bootstrap/_mixins.scss +39 -0
  197. data/client/stylesheets/vendor/bootstrap/_modals.scss +150 -0
  198. data/client/stylesheets/vendor/bootstrap/_navbar.scss +659 -0
  199. data/client/stylesheets/vendor/bootstrap/_navs.scss +242 -0
  200. data/client/stylesheets/vendor/bootstrap/_normalize.scss +425 -0
  201. data/client/stylesheets/vendor/bootstrap/_pager.scss +55 -0
  202. data/client/stylesheets/vendor/bootstrap/_pagination.scss +88 -0
  203. data/client/stylesheets/vendor/bootstrap/_panels.scss +243 -0
  204. data/client/stylesheets/vendor/bootstrap/_popovers.scss +133 -0
  205. data/client/stylesheets/vendor/bootstrap/_print.scss +101 -0
  206. data/client/stylesheets/vendor/bootstrap/_progress-bars.scss +105 -0
  207. data/client/stylesheets/vendor/bootstrap/_responsive-embed.scss +34 -0
  208. data/client/stylesheets/vendor/bootstrap/_responsive-utilities.scss +174 -0
  209. data/client/stylesheets/vendor/bootstrap/_scaffolding.scss +150 -0
  210. data/client/stylesheets/vendor/bootstrap/_tables.scss +233 -0
  211. data/client/stylesheets/vendor/bootstrap/_theme.scss +258 -0
  212. data/client/stylesheets/vendor/bootstrap/_thumbnails.scss +38 -0
  213. data/client/stylesheets/vendor/bootstrap/_tooltip.scss +95 -0
  214. data/client/stylesheets/vendor/bootstrap/_type.scss +304 -0
  215. data/client/stylesheets/vendor/bootstrap/_utilities.scss +57 -0
  216. data/client/stylesheets/vendor/bootstrap/_variables.scss +850 -0
  217. data/client/stylesheets/vendor/bootstrap/_wells.scss +29 -0
  218. data/client/stylesheets/vendor/bootstrap/bootstrap.scss +50 -0
  219. data/client/stylesheets/vendor/bootstrap/mixins/_alerts.scss +14 -0
  220. data/client/stylesheets/vendor/bootstrap/mixins/_background-variant.scss +11 -0
  221. data/client/stylesheets/vendor/bootstrap/mixins/_border-radius.scss +18 -0
  222. data/client/stylesheets/vendor/bootstrap/mixins/_buttons.scss +50 -0
  223. data/client/stylesheets/vendor/bootstrap/mixins/_center-block.scss +7 -0
  224. data/client/stylesheets/vendor/bootstrap/mixins/_clearfix.scss +22 -0
  225. data/client/stylesheets/vendor/bootstrap/mixins/_forms.scss +84 -0
  226. data/client/stylesheets/vendor/bootstrap/mixins/_gradients.scss +58 -0
  227. data/client/stylesheets/vendor/bootstrap/mixins/_grid-framework.scss +81 -0
  228. data/client/stylesheets/vendor/bootstrap/mixins/_grid.scss +122 -0
  229. data/client/stylesheets/vendor/bootstrap/mixins/_hide-text.scss +21 -0
  230. data/client/stylesheets/vendor/bootstrap/mixins/_image.scss +34 -0
  231. data/client/stylesheets/vendor/bootstrap/mixins/_labels.scss +12 -0
  232. data/client/stylesheets/vendor/bootstrap/mixins/_list-group.scss +31 -0
  233. data/client/stylesheets/vendor/bootstrap/mixins/_nav-divider.scss +10 -0
  234. data/client/stylesheets/vendor/bootstrap/mixins/_nav-vertical-align.scss +9 -0
  235. data/client/stylesheets/vendor/bootstrap/mixins/_opacity.scss +8 -0
  236. data/client/stylesheets/vendor/bootstrap/mixins/_pagination.scss +23 -0
  237. data/client/stylesheets/vendor/bootstrap/mixins/_panels.scss +24 -0
  238. data/client/stylesheets/vendor/bootstrap/mixins/_progress-bar.scss +10 -0
  239. data/client/stylesheets/vendor/bootstrap/mixins/_reset-filter.scss +8 -0
  240. data/client/stylesheets/vendor/bootstrap/mixins/_resize.scss +6 -0
  241. data/client/stylesheets/vendor/bootstrap/mixins/_responsive-visibility.scss +21 -0
  242. data/client/stylesheets/vendor/bootstrap/mixins/_size.scss +10 -0
  243. data/client/stylesheets/vendor/bootstrap/mixins/_tab-focus.scss +9 -0
  244. data/client/stylesheets/vendor/bootstrap/mixins/_table-row.scss +28 -0
  245. data/client/stylesheets/vendor/bootstrap/mixins/_text-emphasis.scss +11 -0
  246. data/client/stylesheets/vendor/bootstrap/mixins/_text-overflow.scss +8 -0
  247. data/client/stylesheets/vendor/bootstrap/mixins/_vendor-prefixes.scss +219 -0
  248. data/client/stylesheets/vendor/bootstrap-custom-grid.scss +85 -0
  249. data/client/stylesheets/vendor/bootstrap-custom-modals.scss +150 -0
  250. data/client/stylesheets/vendor/bootstrap.scss +69 -0
  251. data/client/stylesheets/vendor/dataTables.scss +4 -0
  252. data/config/database.yml +9 -0
  253. data/config/puma.rb +7 -0
  254. data/config.ru +4 -0
  255. data/db/migrate/20140615031600_create_hip_users.rb +17 -0
  256. data/db/seed.rb +37 -0
  257. data/foo/Gemfile +5 -0
  258. data/foo/Guardfile +13 -0
  259. data/foo/foo/Gemfile +5 -0
  260. data/foo/foo/lib/foo.rb +7 -0
  261. data/foo/lib/foo/version.rb +3 -0
  262. data/foo/lib/foo.rb +8 -0
  263. data/lanes.gemspec +54 -0
  264. data/lib/generators/lanes/migrations/install_generator.rb +42 -0
  265. data/lib/lanes/access/locked_fields.rb +43 -0
  266. data/lib/lanes/access/role.rb +58 -0
  267. data/lib/lanes/access/role_collection.rb +75 -0
  268. data/lib/lanes/access/roles/administrator.rb +25 -0
  269. data/lib/lanes/access/roles/support.rb +13 -0
  270. data/lib/lanes/access/user_maint_screen.rb +32 -0
  271. data/lib/lanes/access.rb +50 -0
  272. data/lib/lanes/api/asset_pipeline.rb +59 -0
  273. data/lib/lanes/api/authentication_helper.rb +21 -0
  274. data/lib/lanes/api/authentication_provider.rb +45 -0
  275. data/lib/lanes/api/controller.rb +290 -0
  276. data/lib/lanes/api/default_routes.rb +35 -0
  277. data/lib/lanes/api/eco.js +516 -0
  278. data/lib/lanes/api/error_formatter.rb +37 -0
  279. data/lib/lanes/api/helper_methods.rb +32 -0
  280. data/lib/lanes/api/javascript_processor.rb +116 -0
  281. data/lib/lanes/api/pub_sub.rb +33 -0
  282. data/lib/lanes/api/request_wrapper.rb +42 -0
  283. data/lib/lanes/api/root.rb +103 -0
  284. data/lib/lanes/api/skr_templates.rb +60 -0
  285. data/lib/lanes/api/test_specs.rb +59 -0
  286. data/lib/lanes/api/updates.rb +38 -0
  287. data/lib/lanes/api.rb +27 -0
  288. data/lib/lanes/cli.rb +13 -0
  289. data/lib/lanes/concerns/all.rb +16 -0
  290. data/lib/lanes/concerns/api_path.rb +21 -0
  291. data/lib/lanes/concerns/association_extensions.rb +85 -0
  292. data/lib/lanes/concerns/attr_accessor_with_default.rb +62 -0
  293. data/lib/lanes/concerns/code_identifier.rb +43 -0
  294. data/lib/lanes/concerns/export_associations.rb +52 -0
  295. data/lib/lanes/concerns/export_join_tables.rb +39 -0
  296. data/lib/lanes/concerns/export_methods.rb +104 -0
  297. data/lib/lanes/concerns/export_scope.rb +66 -0
  298. data/lib/lanes/concerns/exported_limit_evaluator.rb +17 -0
  299. data/lib/lanes/concerns/immutable_model.rb +32 -0
  300. data/lib/lanes/concerns/locked_fields.rb +84 -0
  301. data/lib/lanes/concerns/pub_sub.rb +105 -0
  302. data/lib/lanes/concerns/queries.rb +20 -0
  303. data/lib/lanes/concerns/random_hash_code.rb +40 -0
  304. data/lib/lanes/concerns/sanitize_api_data.rb +15 -0
  305. data/lib/lanes/concerns/set_attribute_data.rb +154 -0
  306. data/lib/lanes/concerns/track_modifications.rb +51 -0
  307. data/lib/lanes/concerns/visible_id_identifier.rb +53 -0
  308. data/lib/lanes/configuration.rb +85 -0
  309. data/lib/lanes/db/migration_helpers.rb +178 -0
  310. data/lib/lanes/db/migrations.rb +13 -0
  311. data/lib/lanes/db/seed.rb +27 -0
  312. data/lib/lanes/db.rb +86 -0
  313. data/lib/lanes/environment.rb +19 -0
  314. data/lib/lanes/extension.rb +72 -0
  315. data/lib/lanes/generators/app/Gemfile +5 -0
  316. data/lib/lanes/generators/app/Guardfile +13 -0
  317. data/lib/lanes/generators/app/Rakefile +9 -0
  318. data/lib/lanes/generators/app/config/database.yml +9 -0
  319. data/lib/lanes/generators/app/config.ru +4 -0
  320. data/lib/lanes/generators/app/lib/main_class/version.rb +3 -0
  321. data/lib/lanes/generators/app/lib/main_class.rb +8 -0
  322. data/lib/lanes/generators/app.rb +36 -0
  323. data/lib/lanes/guard_tasks.rb +44 -0
  324. data/lib/lanes/logger.rb +37 -0
  325. data/lib/lanes/model.rb +26 -0
  326. data/lib/lanes/numbers.rb +72 -0
  327. data/lib/lanes/rails_engine.rb +5 -0
  328. data/lib/lanes/screens.rb +126 -0
  329. data/lib/lanes/spec_asset_expander.rb +43 -0
  330. data/lib/lanes/strings.rb +56 -0
  331. data/lib/lanes/user.rb +127 -0
  332. data/lib/lanes/validators/all.rb +2 -0
  333. data/lib/lanes/validators/email.rb +17 -0
  334. data/lib/lanes/validators/set.rb +18 -0
  335. data/lib/lanes/version.rb +5 -0
  336. data/lib/lanes.rb +22 -0
  337. data/npm-build/README +1 -0
  338. data/npm-build/compile.coffee +15 -0
  339. data/npm-build/package.json +59 -0
  340. data/npm-build/shims/underscore.js +1416 -0
  341. data/npm-build/template.js +33 -0
  342. data/public/javascripts/jasmine_examples/Player.js +22 -0
  343. data/public/javascripts/jasmine_examples/Song.js +7 -0
  344. data/spec/api/javascript_processor_spec.rb +107 -0
  345. data/spec/api/user_spec.rb +52 -0
  346. data/spec/client/component/ChoicesInputSpec.coffee +12 -0
  347. data/spec/client/component/foo_spec.coffee +4 -0
  348. data/spec/client/foo_spec.js +0 -0
  349. data/spec/client/jasmine_examples/PlayerSpec.js +0 -0
  350. data/spec/client/support/jasmine.yml +128 -0
  351. data/spec/client/support/jasmine_helper.rb +15 -0
  352. data/spec/concerns/api_path_spec.rb +14 -0
  353. data/spec/concerns/association_extensions_spec.rb +30 -0
  354. data/spec/concerns/attr_accessor_with_default_spec.rb +57 -0
  355. data/spec/concerns/code_identifier_spec.rb +45 -0
  356. data/spec/concerns/export_associations_spec.rb +7 -0
  357. data/spec/concerns/export_methods_spec.rb +43 -0
  358. data/spec/concerns/export_scope_spec.rb +15 -0
  359. data/spec/concerns/exported_limits_spec.rb +47 -0
  360. data/spec/concerns/pub_sub_spec.rb +83 -0
  361. data/spec/concerns/set_attribute_data_spec.rb +66 -0
  362. data/spec/configuration_spec.rb +26 -0
  363. data/spec/fixtures/lanes/users.yml +13 -0
  364. data/spec/helpers/.gitkeep +0 -0
  365. data/spec/helpers/SpecHelper.js +18 -0
  366. data/spec/locked_fields_spec.rb +27 -0
  367. data/spec/numbers_spec.rb +26 -0
  368. data/spec/role_collection_spec.rb +19 -0
  369. data/spec/spec_helper.rb +163 -0
  370. data/spec/strings_spec.rb +41 -0
  371. data/spec/testing_models.rb +54 -0
  372. data/spec/user_role_spec.rb +7 -0
  373. data/spec/user_spec.rb +53 -0
  374. data/tasks/migrations.rake +22 -0
  375. data/tasks/publish.rake +8 -0
  376. data/views/index.erb +29 -0
  377. data/views/specs.erb +25 -0
  378. data/yard_ext/all.rb +9 -0
  379. data/yard_ext/code_identifier_handler.rb +33 -0
  380. data/yard_ext/concern_meta_methods.rb +60 -0
  381. data/yard_ext/config_options.rb +27 -0
  382. data/yard_ext/exported_scope.rb +4 -0
  383. data/yard_ext/immutable_handler.rb +17 -0
  384. data/yard_ext/json_attr_accessor.rb +22 -0
  385. data/yard_ext/locked_fields_handler.rb +21 -0
  386. data/yard_ext/templates/default/layout/html/layout.erb +20 -0
  387. data/yard_ext/templates/default/method_details/html/github_link.erb +1 -0
  388. data/yard_ext/templates/default/method_details/setup.rb +3 -0
  389. data/yard_ext/validators.rb +1 -0
  390. data/yard_ext/visible_id_handler.rb +38 -0
  391. 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