hyper-mesh 0.4.0

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 (469) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +27 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +11 -0
  5. data/.travis.yml +4 -0
  6. data/CODE_OF_CONDUCT.md +13 -0
  7. data/Gemfile +5 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +396 -0
  10. data/Rakefile +6 -0
  11. data/app/controllers/reactive_record/application_controller.rb +4 -0
  12. data/app/controllers/reactive_record/reactive_record_controller.rb +49 -0
  13. data/bin/console +14 -0
  14. data/bin/setup +7 -0
  15. data/codeship.database.yml +18 -0
  16. data/config/routes.rb +7 -0
  17. data/docs/action_cable_quickstart.md +151 -0
  18. data/docs/authorization-policies.md +449 -0
  19. data/docs/client_side_scoping.md +103 -0
  20. data/docs/pusher_quickstart.md +0 -0
  21. data/docs/simple_poller_quickstart.md +121 -0
  22. data/docs/todo-example.md +116 -0
  23. data/docs/words-example.md +65 -0
  24. data/examples/action-cable/.gitignore +21 -0
  25. data/examples/action-cable/Gemfile +58 -0
  26. data/examples/action-cable/Gemfile.lock +247 -0
  27. data/examples/action-cable/README.md +24 -0
  28. data/examples/action-cable/Rakefile +6 -0
  29. data/examples/action-cable/app/assets/config/manifest.js +3 -0
  30. data/examples/action-cable/app/assets/javascripts/application.js +18 -0
  31. data/examples/action-cable/app/assets/stylesheets/application.css +15 -0
  32. data/examples/action-cable/app/controllers/application_controller.rb +3 -0
  33. data/examples/action-cable/app/controllers/test_controller.rb +6 -0
  34. data/examples/action-cable/app/models/models.rb +2 -0
  35. data/examples/action-cable/app/models/public/application_record.rb +3 -0
  36. data/examples/action-cable/app/models/public/word.rb +2 -0
  37. data/examples/action-cable/app/policies/application_policy.rb +6 -0
  38. data/examples/action-cable/app/views/components.rb +17 -0
  39. data/examples/action-cable/app/views/components/app.rb +18 -0
  40. data/examples/action-cable/app/views/layouts/application.html.erb +14 -0
  41. data/examples/action-cable/app/views/layouts/mailer.html.erb +13 -0
  42. data/examples/action-cable/app/views/layouts/mailer.text.erb +1 -0
  43. data/examples/action-cable/bin/bundle +3 -0
  44. data/examples/action-cable/bin/rails +9 -0
  45. data/examples/action-cable/bin/rake +9 -0
  46. data/examples/action-cable/bin/setup +34 -0
  47. data/examples/action-cable/bin/spring +15 -0
  48. data/examples/action-cable/bin/update +29 -0
  49. data/examples/action-cable/config.ru +5 -0
  50. data/examples/action-cable/config/application.rb +30 -0
  51. data/examples/action-cable/config/boot.rb +3 -0
  52. data/examples/action-cable/config/cable.yml +9 -0
  53. data/examples/action-cable/config/database.yml +25 -0
  54. data/examples/action-cable/config/environment.rb +5 -0
  55. data/examples/action-cable/config/environments/development.rb +45 -0
  56. data/examples/action-cable/config/environments/production.rb +86 -0
  57. data/examples/action-cable/config/environments/test.rb +42 -0
  58. data/examples/action-cable/config/initializers/application_controller_renderer.rb +6 -0
  59. data/examples/action-cable/config/initializers/assets.rb +11 -0
  60. data/examples/action-cable/config/initializers/backtrace_silencers.rb +7 -0
  61. data/examples/action-cable/config/initializers/cookies_serializer.rb +5 -0
  62. data/examples/action-cable/config/initializers/filter_parameter_logging.rb +4 -0
  63. data/examples/action-cable/config/initializers/inflections.rb +16 -0
  64. data/examples/action-cable/config/initializers/mime_types.rb +4 -0
  65. data/examples/action-cable/config/initializers/new_framework_defaults.rb +24 -0
  66. data/examples/action-cable/config/initializers/session_store.rb +3 -0
  67. data/examples/action-cable/config/initializers/synchromesh.rb +5 -0
  68. data/examples/action-cable/config/initializers/wrap_parameters.rb +14 -0
  69. data/examples/action-cable/config/locales/en.yml +23 -0
  70. data/examples/action-cable/config/puma.rb +47 -0
  71. data/examples/action-cable/config/routes.rb +5 -0
  72. data/examples/action-cable/config/secrets.yml +22 -0
  73. data/examples/action-cable/config/spring.rb +6 -0
  74. data/examples/action-cable/db/migrate/20160921223808_create_words.rb +9 -0
  75. data/examples/action-cable/db/schema.rb +21 -0
  76. data/examples/action-cable/log/.keep +0 -0
  77. data/examples/action-cable/public/404.html +67 -0
  78. data/examples/action-cable/public/422.html +67 -0
  79. data/examples/action-cable/public/500.html +66 -0
  80. data/examples/action-cable/public/apple-touch-icon-precomposed.png +0 -0
  81. data/examples/action-cable/public/apple-touch-icon.png +0 -0
  82. data/examples/action-cable/public/favicon.ico +0 -0
  83. data/examples/action-cable/public/robots.txt +5 -0
  84. data/examples/action-cable/rails_cache_dir2/C91/480/synchromesh_active_connections +0 -0
  85. data/examples/action-cable/tmp/.keep +0 -0
  86. data/examples/action-cable/vendor/assets/javascripts/.keep +0 -0
  87. data/examples/action-cable/vendor/assets/stylesheets/.keep +0 -0
  88. data/examples/simple-poller/.gitignore +21 -0
  89. data/examples/simple-poller/Gemfile +64 -0
  90. data/examples/simple-poller/Gemfile.lock +272 -0
  91. data/examples/simple-poller/README.md +24 -0
  92. data/examples/simple-poller/Rakefile +6 -0
  93. data/examples/simple-poller/app/assets/config/manifest.js +3 -0
  94. data/examples/simple-poller/app/assets/images/.keep +0 -0
  95. data/examples/simple-poller/app/assets/javascripts/application.js +20 -0
  96. data/examples/simple-poller/app/assets/javascripts/channels/.keep +0 -0
  97. data/examples/simple-poller/app/assets/stylesheets/application.css +15 -0
  98. data/examples/simple-poller/app/channels/application_cable/channel.rb +4 -0
  99. data/examples/simple-poller/app/channels/application_cable/connection.rb +4 -0
  100. data/examples/simple-poller/app/controllers/application_controller.rb +3 -0
  101. data/examples/simple-poller/app/controllers/concerns/.keep +0 -0
  102. data/examples/simple-poller/app/controllers/test_controller.rb +6 -0
  103. data/examples/simple-poller/app/helpers/application_helper.rb +2 -0
  104. data/examples/simple-poller/app/jobs/application_job.rb +2 -0
  105. data/examples/simple-poller/app/mailers/application_mailer.rb +4 -0
  106. data/examples/simple-poller/app/models/concerns/.keep +0 -0
  107. data/examples/simple-poller/app/models/models.rb +2 -0
  108. data/examples/simple-poller/app/models/public/.keep +0 -0
  109. data/examples/simple-poller/app/models/public/application_record.rb +3 -0
  110. data/examples/simple-poller/app/models/public/word.rb +2 -0
  111. data/examples/simple-poller/app/policies/application_policy.rb +5 -0
  112. data/examples/simple-poller/app/views/components.rb +18 -0
  113. data/examples/simple-poller/app/views/components/.keep +0 -0
  114. data/examples/simple-poller/app/views/components/app.rb +40 -0
  115. data/examples/simple-poller/app/views/layouts/application.html.erb +14 -0
  116. data/examples/simple-poller/app/views/layouts/mailer.html.erb +13 -0
  117. data/examples/simple-poller/app/views/layouts/mailer.text.erb +1 -0
  118. data/examples/simple-poller/bin/bundle +3 -0
  119. data/examples/simple-poller/bin/rails +9 -0
  120. data/examples/simple-poller/bin/rake +9 -0
  121. data/examples/simple-poller/bin/setup +34 -0
  122. data/examples/simple-poller/bin/spring +16 -0
  123. data/examples/simple-poller/bin/update +29 -0
  124. data/examples/simple-poller/config.ru +5 -0
  125. data/examples/simple-poller/config/application.rb +20 -0
  126. data/examples/simple-poller/config/boot.rb +3 -0
  127. data/examples/simple-poller/config/cable.yml +9 -0
  128. data/examples/simple-poller/config/database.yml +25 -0
  129. data/examples/simple-poller/config/environment.rb +5 -0
  130. data/examples/simple-poller/config/environments/development.rb +44 -0
  131. data/examples/simple-poller/config/environments/production.rb +86 -0
  132. data/examples/simple-poller/config/environments/test.rb +42 -0
  133. data/examples/simple-poller/config/initializers/application_controller_renderer.rb +6 -0
  134. data/examples/simple-poller/config/initializers/assets.rb +11 -0
  135. data/examples/simple-poller/config/initializers/backtrace_silencers.rb +7 -0
  136. data/examples/simple-poller/config/initializers/cookies_serializer.rb +5 -0
  137. data/examples/simple-poller/config/initializers/filter_parameter_logging.rb +4 -0
  138. data/examples/simple-poller/config/initializers/inflections.rb +16 -0
  139. data/examples/simple-poller/config/initializers/mime_types.rb +4 -0
  140. data/examples/simple-poller/config/initializers/new_framework_defaults.rb +24 -0
  141. data/examples/simple-poller/config/initializers/session_store.rb +3 -0
  142. data/examples/simple-poller/config/initializers/synchromesh.rb +15 -0
  143. data/examples/simple-poller/config/initializers/wrap_parameters.rb +14 -0
  144. data/examples/simple-poller/config/locales/en.yml +23 -0
  145. data/examples/simple-poller/config/puma.rb +47 -0
  146. data/examples/simple-poller/config/routes.rb +5 -0
  147. data/examples/simple-poller/config/secrets.yml +22 -0
  148. data/examples/simple-poller/config/spring.rb +6 -0
  149. data/examples/simple-poller/db/migrate/20161013220135_create_words.rb +9 -0
  150. data/examples/simple-poller/db/schema.rb +21 -0
  151. data/examples/simple-poller/db/seeds.rb +7 -0
  152. data/examples/simple-poller/lib/assets/.keep +0 -0
  153. data/examples/simple-poller/lib/tasks/.keep +0 -0
  154. data/examples/simple-poller/log/.keep +0 -0
  155. data/examples/simple-poller/public/404.html +67 -0
  156. data/examples/simple-poller/public/422.html +67 -0
  157. data/examples/simple-poller/public/500.html +66 -0
  158. data/examples/simple-poller/public/apple-touch-icon-precomposed.png +0 -0
  159. data/examples/simple-poller/public/apple-touch-icon.png +0 -0
  160. data/examples/simple-poller/public/favicon.ico +0 -0
  161. data/examples/simple-poller/public/robots.txt +5 -0
  162. data/examples/simple-poller/test/controllers/.keep +0 -0
  163. data/examples/simple-poller/test/fixtures/.keep +0 -0
  164. data/examples/simple-poller/test/fixtures/files/.keep +0 -0
  165. data/examples/simple-poller/test/fixtures/words.yml +7 -0
  166. data/examples/simple-poller/test/helpers/.keep +0 -0
  167. data/examples/simple-poller/test/integration/.keep +0 -0
  168. data/examples/simple-poller/test/mailers/.keep +0 -0
  169. data/examples/simple-poller/test/models/.keep +0 -0
  170. data/examples/simple-poller/test/models/word_test.rb +7 -0
  171. data/examples/simple-poller/test/test_helper.rb +10 -0
  172. data/examples/simple-poller/tmp/.keep +0 -0
  173. data/examples/simple-poller/vendor/assets/javascripts/.keep +0 -0
  174. data/examples/simple-poller/vendor/assets/stylesheets/.keep +0 -0
  175. data/examples/words/.gitignore +21 -0
  176. data/examples/words/Gemfile +58 -0
  177. data/examples/words/Gemfile.lock +247 -0
  178. data/examples/words/README.md +24 -0
  179. data/examples/words/Rakefile +6 -0
  180. data/examples/words/app/assets/config/manifest.js +3 -0
  181. data/examples/words/app/assets/javascripts/application.js +18 -0
  182. data/examples/words/app/assets/stylesheets/application.css +15 -0
  183. data/examples/words/app/controllers/application_controller.rb +3 -0
  184. data/examples/words/app/controllers/test_controller.rb +6 -0
  185. data/examples/words/app/models/models.rb +2 -0
  186. data/examples/words/app/models/public/application_record.rb +3 -0
  187. data/examples/words/app/models/public/word.rb +10 -0
  188. data/examples/words/app/policies/application_policy.rb +6 -0
  189. data/examples/words/app/views/components.rb +16 -0
  190. data/examples/words/app/views/components/app.rb +31 -0
  191. data/examples/words/app/views/layouts/application.html.erb +14 -0
  192. data/examples/words/app/views/layouts/mailer.html.erb +13 -0
  193. data/examples/words/app/views/layouts/mailer.text.erb +1 -0
  194. data/examples/words/bin/bundle +3 -0
  195. data/examples/words/bin/rails +9 -0
  196. data/examples/words/bin/rake +9 -0
  197. data/examples/words/bin/setup +34 -0
  198. data/examples/words/bin/spring +15 -0
  199. data/examples/words/bin/update +29 -0
  200. data/examples/words/config.ru +5 -0
  201. data/examples/words/config/application.rb +30 -0
  202. data/examples/words/config/boot.rb +3 -0
  203. data/examples/words/config/cable.yml +9 -0
  204. data/examples/words/config/database.yml +25 -0
  205. data/examples/words/config/environment.rb +5 -0
  206. data/examples/words/config/environments/development.rb +45 -0
  207. data/examples/words/config/environments/production.rb +86 -0
  208. data/examples/words/config/environments/test.rb +42 -0
  209. data/examples/words/config/initializers/application_controller_renderer.rb +6 -0
  210. data/examples/words/config/initializers/assets.rb +11 -0
  211. data/examples/words/config/initializers/backtrace_silencers.rb +7 -0
  212. data/examples/words/config/initializers/cookies_serializer.rb +5 -0
  213. data/examples/words/config/initializers/filter_parameter_logging.rb +4 -0
  214. data/examples/words/config/initializers/inflections.rb +16 -0
  215. data/examples/words/config/initializers/mime_types.rb +4 -0
  216. data/examples/words/config/initializers/new_framework_defaults.rb +24 -0
  217. data/examples/words/config/initializers/session_store.rb +3 -0
  218. data/examples/words/config/initializers/synchromesh.rb +5 -0
  219. data/examples/words/config/initializers/wrap_parameters.rb +14 -0
  220. data/examples/words/config/locales/en.yml +23 -0
  221. data/examples/words/config/puma.rb +47 -0
  222. data/examples/words/config/routes.rb +5 -0
  223. data/examples/words/config/secrets.yml +22 -0
  224. data/examples/words/config/spring.rb +6 -0
  225. data/examples/words/db/migrate/20160921223808_create_words.rb +9 -0
  226. data/examples/words/db/schema.rb +28 -0
  227. data/examples/words/log/.keep +0 -0
  228. data/examples/words/public/404.html +67 -0
  229. data/examples/words/public/422.html +67 -0
  230. data/examples/words/public/500.html +66 -0
  231. data/examples/words/public/apple-touch-icon-precomposed.png +0 -0
  232. data/examples/words/public/apple-touch-icon.png +0 -0
  233. data/examples/words/public/favicon.ico +0 -0
  234. data/examples/words/public/robots.txt +5 -0
  235. data/examples/words/tmp/.keep +0 -0
  236. data/examples/words/vendor/assets/javascripts/.keep +0 -0
  237. data/examples/words/vendor/assets/stylesheets/.keep +0 -0
  238. data/hyper-mesh.gemspec +82 -0
  239. data/lib/active_record_base.rb +152 -0
  240. data/lib/enumerable/pluck.rb +6 -0
  241. data/lib/hyper-mesh.rb +62 -0
  242. data/lib/hypermesh/version.rb +3 -0
  243. data/lib/opal/equality_patches.rb +15 -0
  244. data/lib/opal/parse_patch.rb +14 -0
  245. data/lib/opal/set_patches.rb +8 -0
  246. data/lib/reactive_record/active_record/aggregations.rb +69 -0
  247. data/lib/reactive_record/active_record/associations.rb +111 -0
  248. data/lib/reactive_record/active_record/base.rb +9 -0
  249. data/lib/reactive_record/active_record/class_methods.rb +177 -0
  250. data/lib/reactive_record/active_record/error.rb +26 -0
  251. data/lib/reactive_record/active_record/instance_methods.rb +129 -0
  252. data/lib/reactive_record/active_record/public_columns_hash.rb +19 -0
  253. data/lib/reactive_record/active_record/reactive_record/base.rb +520 -0
  254. data/lib/reactive_record/active_record/reactive_record/collection.rb +517 -0
  255. data/lib/reactive_record/active_record/reactive_record/column_types.rb +67 -0
  256. data/lib/reactive_record/active_record/reactive_record/dummy_value.rb +218 -0
  257. data/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb +532 -0
  258. data/lib/reactive_record/active_record/reactive_record/reactive_set_relationship_helpers.rb +189 -0
  259. data/lib/reactive_record/active_record/reactive_record/scoped_collection.rb +62 -0
  260. data/lib/reactive_record/active_record/reactive_record/unscoped_collection.rb +16 -0
  261. data/lib/reactive_record/active_record/reactive_record/while_loading.rb +299 -0
  262. data/lib/reactive_record/engine.rb +13 -0
  263. data/lib/reactive_record/interval.rb +190 -0
  264. data/lib/reactive_record/permissions.rb +102 -0
  265. data/lib/reactive_record/pry.rb +13 -0
  266. data/lib/reactive_record/reactive_scope.rb +18 -0
  267. data/lib/reactive_record/scope_description.rb +108 -0
  268. data/lib/reactive_record/serializers.rb +7 -0
  269. data/lib/reactive_record/server_data_cache.rb +347 -0
  270. data/lib/reactive_record/version.rb +3 -0
  271. data/lib/sources/hyper-mesh/pusher.js +98 -0
  272. data/lib/synchromesh/action_cable.rb +39 -0
  273. data/lib/synchromesh/client_drivers.rb +357 -0
  274. data/lib/synchromesh/configuration.rb +40 -0
  275. data/lib/synchromesh/connection.rb +144 -0
  276. data/lib/synchromesh/policy.rb +504 -0
  277. data/lib/synchromesh/reactive_record/permission_patches.rb +43 -0
  278. data/lib/synchromesh/synchromesh.rb +155 -0
  279. data/lib/synchromesh/synchromesh_controller.rb +154 -0
  280. data/logo.jpg +0 -0
  281. data/path_release_steps.md +0 -0
  282. data/reactive_record_test_app/Gemfile +15 -0
  283. data/reactive_record_test_app/Gemfile.lock +209 -0
  284. data/reactive_record_test_app/README.rdoc +261 -0
  285. data/reactive_record_test_app/Rakefile +7 -0
  286. data/reactive_record_test_app/app/assets/javascripts/application.rb +5 -0
  287. data/reactive_record_test_app/app/assets/javascripts/components/another_component.rb +24 -0
  288. data/reactive_record_test_app/app/assets/javascripts/components/empty_component.rb +6 -0
  289. data/reactive_record_test_app/app/assets/javascripts/components/todo_item_component.js.rb +16 -0
  290. data/reactive_record_test_app/app/assets/javascripts/components/todos_component.js.rb +42 -0
  291. data/reactive_record_test_app/app/assets/javascripts/components/todos_main_component.rb +49 -0
  292. data/reactive_record_test_app/app/assets/javascripts/react_js_test_only.js +21618 -0
  293. data/reactive_record_test_app/app/assets/javascripts/reactive_record_config.js +2 -0
  294. data/reactive_record_test_app/app/assets/javascripts/spec/reactive_record_xspec.js.rb +42 -0
  295. data/reactive_record_test_app/app/assets/stylesheets/application.css +13 -0
  296. data/reactive_record_test_app/app/controllers/application_controller.rb +8 -0
  297. data/reactive_record_test_app/app/controllers/home_controller.rb +7 -0
  298. data/reactive_record_test_app/app/controllers/test_controller.rb +7 -0
  299. data/reactive_record_test_app/app/helpers/application_helper.rb +2 -0
  300. data/reactive_record_test_app/app/mailers/.gitkeep +0 -0
  301. data/reactive_record_test_app/app/models/.gitkeep +0 -0
  302. data/reactive_record_test_app/app/policies/application_policy.rb +5 -0
  303. data/reactive_record_test_app/app/views/components.rb +4 -0
  304. data/reactive_record_test_app/app/views/components/test.rb +18 -0
  305. data/reactive_record_test_app/app/views/home/index.html.erb +1 -0
  306. data/reactive_record_test_app/app/views/layouts/application.html.erb +17 -0
  307. data/reactive_record_test_app/app/views/models.rb +1 -0
  308. data/reactive_record_test_app/app/views/models/address.rb +13 -0
  309. data/reactive_record_test_app/app/views/models/comment.rb +19 -0
  310. data/reactive_record_test_app/app/views/models/todo_item.rb +36 -0
  311. data/reactive_record_test_app/app/views/models/user.rb +78 -0
  312. data/reactive_record_test_app/config.ru +33 -0
  313. data/reactive_record_test_app/config/application.rb +73 -0
  314. data/reactive_record_test_app/config/boot.rb +10 -0
  315. data/reactive_record_test_app/config/database.yml +25 -0
  316. data/reactive_record_test_app/config/environment.rb +5 -0
  317. data/reactive_record_test_app/config/environments/development.rb +43 -0
  318. data/reactive_record_test_app/config/environments/production.rb +70 -0
  319. data/reactive_record_test_app/config/environments/test.rb +41 -0
  320. data/reactive_record_test_app/config/initializers/backtrace_silencers.rb +7 -0
  321. data/reactive_record_test_app/config/initializers/inflections.rb +15 -0
  322. data/reactive_record_test_app/config/initializers/mime_types.rb +5 -0
  323. data/reactive_record_test_app/config/initializers/secret_token.rb +7 -0
  324. data/reactive_record_test_app/config/initializers/session_store.rb +8 -0
  325. data/reactive_record_test_app/config/initializers/wrap_parameters.rb +14 -0
  326. data/reactive_record_test_app/config/locales/en.yml +5 -0
  327. data/reactive_record_test_app/config/routes.rb +7 -0
  328. data/reactive_record_test_app/db/migrate/20150617002932_create_todo_items.rb +11 -0
  329. data/reactive_record_test_app/db/migrate/20150617134028_create_users.rb +14 -0
  330. data/reactive_record_test_app/db/migrate/20150729195556_add_address_to_user.rb +20 -0
  331. data/reactive_record_test_app/db/migrate/20150826142045_create_comments.rb +10 -0
  332. data/reactive_record_test_app/db/migrate/20150828172008_add_single_comment_to_todo_item.rb +5 -0
  333. data/reactive_record_test_app/db/migrate/20150908184118_add_address_id_to_user.rb +5 -0
  334. data/reactive_record_test_app/db/migrate/20150917220236_add_second_address_to_user.rb +10 -0
  335. data/reactive_record_test_app/db/migrate/20151009000111_add_test_data_attributes_to_user.rb +6 -0
  336. data/reactive_record_test_app/db/migrate/20160129182544_add_test_enum_to_user.rb +5 -0
  337. data/reactive_record_test_app/db/schema.rb +63 -0
  338. data/reactive_record_test_app/db/seeds.rb +60 -0
  339. data/reactive_record_test_app/public/404.html +26 -0
  340. data/reactive_record_test_app/public/422.html +26 -0
  341. data/reactive_record_test_app/public/500.html +25 -0
  342. data/reactive_record_test_app/public/favicon.ico +0 -0
  343. data/reactive_record_test_app/script/rails +6 -0
  344. data/reactive_record_test_app/spec-opal/active-record/aggregations_spec.rb +41 -0
  345. data/reactive_record_test_app/spec-opal/active-record/associations_spec.rb +75 -0
  346. data/reactive_record_test_app/spec-opal/active-record/base_spec.rb +126 -0
  347. data/reactive_record_test_app/spec-opal/active-record/dummy_value_spec.rb +27 -0
  348. data/reactive_record_test_app/spec-opal/active-record/edge_cases_spec.rb +116 -0
  349. data/reactive_record_test_app/spec-opal/active-record/enum_spec.rb +43 -0
  350. data/reactive_record_test_app/spec-opal/active-record/instance_methods_spec.rb +53 -0
  351. data/reactive_record_test_app/spec-opal/active-record/non_ar_aggregations_spec.rb +74 -0
  352. data/reactive_record_test_app/spec-opal/active-record/permissions_spec.rb +170 -0
  353. data/reactive_record_test_app/spec-opal/active-record/prerendering_spec.rb +49 -0
  354. data/reactive_record_test_app/spec-opal/active-record/reactive_record_load_spec.rb +23 -0
  355. data/reactive_record_test_app/spec-opal/active-record/rendering_spec.rb +221 -0
  356. data/reactive_record_test_app/spec-opal/active-record/save_spec.rb +125 -0
  357. data/reactive_record_test_app/spec-opal/active-record/scope_spec.rb +85 -0
  358. data/reactive_record_test_app/spec-opal/active-record/update_aggregations_spec.rb +76 -0
  359. data/reactive_record_test_app/spec-opal/active-record/update_attributes_spec.rb +186 -0
  360. data/reactive_record_test_app/spec-opal/active-record/virtual_methods_spec.rb +71 -0
  361. data/reactive_record_test_app/spec-opal/index.html.erb +11 -0
  362. data/reactive_record_test_app/spec-opal/spec_helper.js.rb +268 -0
  363. data/reactive_record_test_app/spec-opal/vendor/es5-shim.min.js +6 -0
  364. data/reactive_record_test_app/spec_dont_run/README.md +26 -0
  365. data/reactive_record_test_app/spec_dont_run/moved_to_main_spec_dir/zzzmany_to_many_spec_moved_to_main_suite.rb +30 -0
  366. data/reactive_record_test_app/spec_dont_run/moved_to_main_spec_dir/zzzrevert_record_spec_moved.rb +78 -0
  367. data/reactive_record_test_app/spec_dont_run/moved_to_main_spec_dir/zzzupdate_associations_spec_moved.rb +142 -0
  368. data/reactive_record_test_app/spec_dont_run/moved_to_main_spec_dir/zzzupdate_scopes_movedspec.rb +48 -0
  369. data/remote.md +115 -0
  370. data/spec/bin/firebug-2.0.13-fx.xpi +0 -0
  371. data/spec/component_helpers_xspec.rb +49 -0
  372. data/spec/factories/child_model.rb +5 -0
  373. data/spec/factories/comment.rb +5 -0
  374. data/spec/factories/test_models.rb +5 -0
  375. data/spec/factories/todo.rb +5 -0
  376. data/spec/factories/user.rb +5 -0
  377. data/spec/reactive_record/edge_cases_spec.rb +31 -0
  378. data/spec/reactive_record/factory.rb +62 -0
  379. data/spec/reactive_record/many_to_many_spec.rb +50 -0
  380. data/spec/reactive_record/play.rb +64 -0
  381. data/spec/reactive_record/pry_rescue_xspec.rb +48 -0
  382. data/spec/reactive_record/revert_spec.rb +112 -0
  383. data/spec/reactive_record/update_associations_spec.rb +189 -0
  384. data/spec/reactive_record/update_scopes_spec.rb +53 -0
  385. data/spec/spec_helper.rb +366 -0
  386. data/spec/support/component_helpers.rb +351 -0
  387. data/spec/synchromesh/column_types/column_type_spec.rb +302 -0
  388. data/spec/synchromesh/connection_spec.rb +144 -0
  389. data/spec/synchromesh/crud_access_regulation/broadcast_controls_access_spec.rb +105 -0
  390. data/spec/synchromesh/crud_access_regulation/model_policies_spec.rb +131 -0
  391. data/spec/synchromesh/examples/dictionary.rb +239 -0
  392. data/spec/synchromesh/examples/dictionary_with_client_scopes.rb +196 -0
  393. data/spec/synchromesh/examples/random_examples.rb +100 -0
  394. data/spec/synchromesh/examples/scoped_todos_spec.rb +167 -0
  395. data/spec/synchromesh/integration/authorization_spec.rb +111 -0
  396. data/spec/synchromesh/integration/default_scope_spec.rb +121 -0
  397. data/spec/synchromesh/integration/has_many_through_spec.rb +173 -0
  398. data/spec/synchromesh/integration/relationships_spec.rb +263 -0
  399. data/spec/synchromesh/integration/scope_spec.rb +553 -0
  400. data/spec/synchromesh/integration/synchromesh_spec.rb +80 -0
  401. data/spec/synchromesh/integration/test_components.rb +18 -0
  402. data/spec/synchromesh/integration/transports_spec.rb +308 -0
  403. data/spec/synchromesh/policies/auto_connect_spec.rb +60 -0
  404. data/spec/synchromesh/policies/auto_loader_spec.rb +34 -0
  405. data/spec/synchromesh/policies/policy_methods_spec.rb +85 -0
  406. data/spec/synchromesh/policies/regulate_all_broadcasts_spec.rb +315 -0
  407. data/spec/synchromesh/policies/regulate_broadcast_spec.rb +370 -0
  408. data/spec/synchromesh/policies/regulate_class_connection_spec.rb +50 -0
  409. data/spec/synchromesh/policies/regulate_instance_connection_spec.rb +66 -0
  410. data/spec/test_app/Gemfile +61 -0
  411. data/spec/test_app/Gemfile.lock +253 -0
  412. data/spec/test_app/Rakefile +6 -0
  413. data/spec/test_app/app/assets/javascripts/application.js +6 -0
  414. data/spec/test_app/app/assets/stylesheets/application.css +15 -0
  415. data/spec/test_app/app/controllers/application_controller.rb +13 -0
  416. data/spec/test_app/app/controllers/test_controller.rb +5 -0
  417. data/spec/test_app/app/models/_react_public_models.rb +2 -0
  418. data/spec/test_app/app/models/public/address.rb +13 -0
  419. data/spec/test_app/app/models/public/child_model.rb +3 -0
  420. data/spec/test_app/app/models/public/comment.rb +25 -0
  421. data/spec/test_app/app/models/public/test_model.rb +5 -0
  422. data/spec/test_app/app/models/public/todo.rb +6 -0
  423. data/spec/test_app/app/models/public/todo_item.rb +36 -0
  424. data/spec/test_app/app/models/public/user.rb +88 -0
  425. data/spec/test_app/app/policies/auto_loader_test_classa_policy.rb +3 -0
  426. data/spec/test_app/app/policies/auto_loader_test_classb_policy.rb +3 -0
  427. data/spec/test_app/app/policies/auto_loader_test_classc_policy.rb +3 -0
  428. data/spec/test_app/app/policies/auto_loader_test_classd_policy.rb +3 -0
  429. data/spec/test_app/app/views/components.rb +14 -0
  430. data/spec/test_app/app/views/components/show.rb +5 -0
  431. data/spec/test_app/app/views/layouts/application.html.erb +14 -0
  432. data/spec/test_app/bin/bundle +3 -0
  433. data/spec/test_app/bin/rails +4 -0
  434. data/spec/test_app/bin/rake +4 -0
  435. data/spec/test_app/bin/setup +29 -0
  436. data/spec/test_app/config.ru +4 -0
  437. data/spec/test_app/config/application.rb +42 -0
  438. data/spec/test_app/config/boot.rb +6 -0
  439. data/spec/test_app/config/cable.yml +10 -0
  440. data/spec/test_app/config/database.yml +47 -0
  441. data/spec/test_app/config/environment.rb +5 -0
  442. data/spec/test_app/config/environments/development.rb +41 -0
  443. data/spec/test_app/config/environments/production.rb +79 -0
  444. data/spec/test_app/config/environments/test.rb +42 -0
  445. data/spec/test_app/config/initializers/assets.rb +11 -0
  446. data/spec/test_app/config/initializers/backtrace_silencers.rb +7 -0
  447. data/spec/test_app/config/initializers/cookies_serializer.rb +3 -0
  448. data/spec/test_app/config/initializers/filter_parameter_logging.rb +4 -0
  449. data/spec/test_app/config/initializers/inflections.rb +16 -0
  450. data/spec/test_app/config/initializers/mime_types.rb +4 -0
  451. data/spec/test_app/config/initializers/session_store.rb +3 -0
  452. data/spec/test_app/config/initializers/synchromesh.rb +11 -0
  453. data/spec/test_app/config/initializers/wrap_parameters.rb +14 -0
  454. data/spec/test_app/config/locales/en.yml +23 -0
  455. data/spec/test_app/config/routes.rb +61 -0
  456. data/spec/test_app/config/secrets.yml +22 -0
  457. data/spec/test_app/db/development.sqlite3 +0 -0
  458. data/spec/test_app/db/migrate/20160731182106_create_test_models.rb +75 -0
  459. data/spec/test_app/db/schema.rb +88 -0
  460. data/spec/test_app/db/seeds.rb +7 -0
  461. data/spec/test_app/lib/assets/.keep +0 -0
  462. data/spec/test_app/log/.keep +0 -0
  463. data/spec/test_app/public/404.html +67 -0
  464. data/spec/test_app/public/422.html +67 -0
  465. data/spec/test_app/public/500.html +66 -0
  466. data/spec/test_app/public/favicon.ico +0 -0
  467. data/spec/vendor/es5-shim.min.js +6 -0
  468. data/terminal.md +66 -0
  469. metadata +1175 -0
@@ -0,0 +1,67 @@
1
+ module ReactiveRecord
2
+ # ActiveRecord column access and conversion helpers
3
+ class Base
4
+ def columns_hash
5
+ model.columns_hash
6
+ end
7
+
8
+ def column_type(attr)
9
+ column_hash = columns_hash[attr]
10
+ return nil unless column_hash
11
+ column_hash[:sql_type_metadata] && column_hash[:sql_type_metadata][:type]
12
+ end
13
+
14
+ def convert_datetime(val)
15
+ if val.is_a?(Numeric)
16
+ Time.at(val)
17
+ elsif val.is_a?(Time)
18
+ val
19
+ else
20
+ Time.parse(val)
21
+ end
22
+ end
23
+
24
+ alias convert_time convert_datetime
25
+ alias convert_timestamp convert_datetime
26
+
27
+ def convert_date(val)
28
+ if val.is_a?(Time)
29
+ Date.parse(val.strftime('%d/%m/%Y'))
30
+ elsif val.is_a?(Date)
31
+ val
32
+ else
33
+ Date.parse(val)
34
+ end
35
+ end
36
+
37
+ def convert_boolean(val)
38
+ !['false', false, nil, 0].include?(val)
39
+ end
40
+
41
+ def convert_integer(val)
42
+ Integer(`parseInt(#{val})`)
43
+ end
44
+
45
+ alias convert_bigint convert_integer
46
+
47
+ def convert_float(val)
48
+ Float(val)
49
+ end
50
+
51
+ alias convert_decimal convert_float
52
+
53
+ def convert_text(val)
54
+ val.to_s
55
+ end
56
+
57
+ alias convert_string convert_text
58
+
59
+ def convert(attr, val)
60
+ column_type = column_type(attr)
61
+ return val if !column_type || val.loading? || (!val && column_type != :boolean)
62
+ conversion_method = "convert_#{column_type}"
63
+ return send(conversion_method, val) if respond_to? conversion_method
64
+ val
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,218 @@
1
+ # add mehods to Object to determine if this is a dummy object or not
2
+ class Object
3
+ def loaded?
4
+ !loading?
5
+ end
6
+
7
+ def loading?
8
+ false
9
+ end
10
+
11
+ def present?
12
+ !!self
13
+ end
14
+ end
15
+
16
+ module ReactiveRecord
17
+ class Base
18
+ # A DummyValue stands in for actual value while waiting for load from
19
+ # server. when value is accessed by most methods it notifies hyper-react
20
+ # so when the actual value loads react will update.
21
+
22
+ # DummyValue uses the ActiveRecord type info to act like an appropriate
23
+ # loaded value.
24
+ class DummyValue < BasicObject
25
+ def initialize(column_hash = nil)
26
+ column_hash ||= {}
27
+ notify
28
+ @column_hash = column_hash
29
+ column_type = (
30
+ @column_hash[:sql_type_metadata] &&
31
+ @column_hash[:sql_type_metadata][:type]
32
+ ) || 'nil'
33
+ default_value_method = "build_default_value_for_#{column_type}"
34
+ @object = __send__ default_value_method
35
+ rescue Exception
36
+ end
37
+
38
+ def build_default_value_for_nil
39
+ nil
40
+ end
41
+
42
+ def build_default_value_for_datetime
43
+ if @column_hash[:default]
44
+ Time.parse(@column_hash[:default].gsub(' ','T')+'+00:00')
45
+ else
46
+ ::ReactiveRecord::Base::DummyValue.dummy_time
47
+ end
48
+ end
49
+
50
+ alias build_default_value_for_time build_default_value_for_datetime
51
+ alias build_default_value_for_timestamp build_default_value_for_datetime
52
+
53
+ def build_default_value_for_date
54
+ if @column_hash[:default]
55
+ Date.parse(@column_hash[:default])
56
+ else
57
+ ::ReactiveRecord::Base::DummyValue.dummy_date
58
+ end
59
+ end
60
+
61
+ def build_default_value_for_boolean
62
+ @column_hash[:default] || false
63
+ end
64
+
65
+ def build_default_value_for_float
66
+ @column_hash[:default] || Float(0.0)
67
+ end
68
+
69
+ alias build_default_value_for_decimal build_default_value_for_float
70
+
71
+ def build_default_value_for_integer
72
+ @column_hash[:default] || Integer(0)
73
+ end
74
+
75
+ alias build_default_value_for_bigint build_default_value_for_integer
76
+
77
+ def build_default_value_for_string
78
+ @column_hash[:default] || ''
79
+ end
80
+
81
+ alias build_default_value_for_text build_default_value_for_string
82
+
83
+ def notify
84
+ return if ReactiveRecord::Base.data_loading?
85
+ ReactiveRecord.loads_pending!
86
+ ReactiveRecord::WhileLoading.loading!
87
+ end
88
+
89
+ def loading?
90
+ true
91
+ end
92
+
93
+ def loaded?
94
+ false
95
+ end
96
+
97
+ def present?
98
+ false
99
+ end
100
+
101
+ def nil?
102
+ true
103
+ end
104
+
105
+ def !
106
+ true
107
+ end
108
+
109
+ def method_missing(method, *args, &block)
110
+ if method =~ /^build_default_value_for_/
111
+ nil
112
+ elsif @object || @object.respond_to?(method)
113
+ notify
114
+ @object.send method, *args, &block
115
+ elsif 0.respond_to? method
116
+ notify
117
+ 0.send(method, *args, &block)
118
+ elsif ''.respond_to? method
119
+ notify
120
+ ''.send(method, *args, &block)
121
+ else
122
+ super
123
+ end
124
+ end
125
+
126
+ def coerce(s)
127
+ # notify # why are we not notifying here
128
+ return @object.coerce(s) if @object
129
+ [__send__("to_#{s.class.name.downcase}"), s]
130
+ end
131
+
132
+ def ==(other)
133
+ # notify # why are we not notifying here
134
+ other.object_id == object_id
135
+ end
136
+
137
+ def object_id
138
+ `self.$$id`
139
+ end
140
+
141
+ def is_a?(klass)
142
+ klass == ::ReactiveRecord::Base::DummyValue
143
+ end
144
+
145
+ def zero?
146
+ return @object.zero? if @object
147
+ false
148
+ end
149
+
150
+ def to_s
151
+ notify
152
+ return @object.to_s if @object
153
+ ''
154
+ end
155
+
156
+ alias inspect to_s
157
+
158
+ def to_f
159
+ notify
160
+ return @object.to_f if @object
161
+ 0.0
162
+ end
163
+
164
+ def to_i
165
+ notify
166
+ return @object.to_i if @object
167
+ 0
168
+ end
169
+
170
+ def to_numeric
171
+ notify
172
+ return @object.to_numeric if @object
173
+ 0
174
+ end
175
+
176
+ def to_number
177
+ notify
178
+ return @object.to_number if @object
179
+ 0
180
+ end
181
+
182
+ def self.dummy_time
183
+ @dummy_time ||= Time.parse('2001-01-01T00:00:00.000-00:00')
184
+ end
185
+
186
+ def self.dummy_date
187
+ @dummy_date ||= Date.parse('1/1/2001')
188
+ end
189
+
190
+ def to_date
191
+ notify
192
+ return @object.to_date if @object
193
+ ReactiveRecord::Base::DummyValue.dummy_date
194
+ end
195
+
196
+ def to_time
197
+ notify
198
+ return @object.to_time if @object
199
+ ReactiveRecord::Base::DummyValue.dummy_time
200
+ end
201
+
202
+ def acts_as_string?
203
+ return @object.acts_as_string if @object
204
+ true
205
+ end
206
+
207
+ def try(*args, &b)
208
+ if args.empty? && block_given?
209
+ yield self
210
+ else
211
+ __send__(*args, &b)
212
+ end
213
+ rescue
214
+ nil
215
+ end
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,532 @@
1
+ require 'json'
2
+
3
+ module ReactiveRecord
4
+
5
+ class Base
6
+
7
+ include React::IsomorphicHelpers
8
+
9
+ before_first_mount do |context|
10
+ if RUBY_ENGINE != 'opal'
11
+ @server_data_cache = ReactiveRecord::ServerDataCache.new(context.controller.acting_user, {})
12
+ else
13
+ @outer_scopes = Set.new
14
+ @fetch_scheduled = nil
15
+ @records = Hash.new { |hash, key| hash[key] = [] }
16
+ @class_scopes = Hash.new { |hash, key| hash[key] = {} }
17
+ if on_opal_client?
18
+ @pending_fetches = []
19
+ @pending_records = []
20
+ @last_fetch_at = Time.now
21
+ unless `typeof window.ReactiveRecordInitialData === 'undefined'`
22
+ log(["Reactive record prerendered data being loaded: %o", `window.ReactiveRecordInitialData`])
23
+ JSON.from_object(`window.ReactiveRecordInitialData`).each do |hash|
24
+ load_from_json hash
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ def records
32
+ self.class.instance_variable_get(:@records)
33
+ end
34
+
35
+ # Prerendering db access (returns nil if on client):
36
+ # at end of prerendering dumps all accessed records in the footer
37
+
38
+ isomorphic_method(:fetch_from_db) do |f, vector|
39
+ # vector must end with either "*all", or be a simple attribute
40
+ f.send_to_server [vector.shift.name, *vector] if RUBY_ENGINE == 'opal'
41
+ f.when_on_server { @server_data_cache[*vector] }
42
+ end
43
+
44
+ isomorphic_method(:find_in_db) do |f, klass, attribute, value|
45
+ f.send_to_server klass.name, attribute, value if RUBY_ENGINE == 'opal'
46
+ f.when_on_server { @server_data_cache[klass, ["find_by_#{attribute}", value], :id] }
47
+ end
48
+
49
+ prerender_footer do
50
+ if @server_data_cache
51
+ json = @server_data_cache.as_json.to_json # can this just be to_json?
52
+ @server_data_cache.clear_requests
53
+ else
54
+ json = {}.to_json
55
+ end
56
+ path = ::Rails.application.routes.routes.detect do |route|
57
+ # not sure why the second check is needed. It happens in the test app
58
+ route.app == ReactiveRecord::Engine or (route.app.respond_to?(:app) and route.app.app == ReactiveRecord::Engine)
59
+ end.path.spec
60
+ "<script type='text/javascript'>\n"+
61
+ "window.ReactiveRecordEnginePath = '#{path}';\n"+
62
+ "if (typeof window.ReactiveRecordInitialData === 'undefined') { window.ReactiveRecordInitialData = [] }\n" +
63
+ "window.ReactiveRecordInitialData.push(#{json})\n"+
64
+ "</script>\n"
65
+ end if RUBY_ENGINE != 'opal'
66
+
67
+ # Client side db access (never called during prerendering):
68
+
69
+ # Always returns an object of class DummyValue which will act like most standard AR field types
70
+ # Whenever a dummy value is accessed it notify React that there are loads pending so appropriate rerenders
71
+ # will occur when the value is eventually loaded.
72
+
73
+ # queue up fetches, and at the end of each rendering cycle fetch the records
74
+ # notify that loads are pending
75
+
76
+ def self.load_from_db(record, *vector)
77
+ return nil unless on_opal_client? # this can happen when we are on the server and a nil value is returned for an attribute
78
+ # only called from the client side
79
+ # pushes the value of vector onto the a list of vectors that will be loaded from the server when the next
80
+ # rendering cycle completes.
81
+ # takes care of informing react that there are things to load, and schedules the loader to run
82
+ # Note there is no equivilent to find_in_db, because each vector implicitly does a find.
83
+ raise "attempt to do a find_by_id of nil. This will return all records, and is not allowed" if vector[1] == ["find_by_id", nil]
84
+ vector = [record.model.model_name, ["new", record.object_id]]+vector[1..-1] if vector[0].nil?
85
+ unless data_loading?
86
+ @pending_fetches << vector
87
+ @pending_records << record if record
88
+ schedule_fetch
89
+ end
90
+ DummyValue.new(record && record.model.columns_hash[vector.last])
91
+ end
92
+
93
+ class << self
94
+
95
+ attr_reader :pending_fetches
96
+ attr_reader :last_fetch_at
97
+
98
+ end
99
+
100
+ def self.schedule_fetch
101
+ @fetch_scheduled ||= after(0) do
102
+ if @pending_fetches.count > 0 # during testing we might reset the context while there are pending fetches otherwise this would never normally happen
103
+ last_fetch_at = @last_fetch_at
104
+ @last_fetch_at = Time.now
105
+ pending_fetches = @pending_fetches.uniq
106
+ models, associations = gather_records(@pending_records, false, nil)
107
+ log(["Server Fetching: %o", pending_fetches.to_n])
108
+ start_time = Time.now
109
+ HTTP.post(`window.ReactiveRecordEnginePath`,
110
+ payload: {
111
+ json: {
112
+ models: models,
113
+ associations: associations,
114
+ pending_fetches: pending_fetches
115
+ }.to_json
116
+ }
117
+ ).then do |response|
118
+ fetch_time = Time.now
119
+ log(" Fetched in: #{(fetch_time-start_time).to_i}s")
120
+ begin
121
+ ReactiveRecord::Base.load_from_json(response.json)
122
+ rescue Exception => e
123
+ log("Unexpected exception raised while loading json from server: #{e}", :error)
124
+ end
125
+ log(" Processed in: #{(Time.now-fetch_time).to_i}s")
126
+ log([" Returned: %o", response.json.to_n])
127
+ ReactiveRecord.run_blocks_to_load last_fetch_at
128
+ ReactiveRecord::WhileLoading.loaded_at last_fetch_at
129
+ ReactiveRecord::WhileLoading.quiet! if @pending_fetches.empty?
130
+ end.fail do |response|
131
+ log("Fetch failed", :error)
132
+ ReactiveRecord.run_blocks_to_load(last_fetch_at, response.body)
133
+ end
134
+ @pending_fetches = []
135
+ @pending_records = []
136
+ @fetch_scheduled = nil
137
+ end
138
+ end
139
+ end
140
+
141
+ def self.get_type_hash(record)
142
+ {record.class.inheritance_column => record[record.class.inheritance_column]}
143
+ end
144
+
145
+ if RUBY_ENGINE == 'opal'
146
+
147
+ def self.gather_records(records_to_process, force, record_being_saved)
148
+ # we want to pass not just the model data to save, but also enough information so that on return from the server
149
+ # we can update the models on the client
150
+
151
+ # input
152
+ # list of records to process, will grow as we chase associations
153
+ # outputs
154
+ models = [] # the actual data to save {id: record.object_id, model: record.model.model_name, attributes: changed_attributes}
155
+ associations = [] # {parent_id: record.object_id, attribute: attribute, child_id: assoc_record.object_id}
156
+
157
+ # used to keep track of records that have been processed for effeciency
158
+ # for quick lookup of records that have been or will be processed [record.object_id] => record
159
+ records_to_process = records_to_process.uniq
160
+ backing_records = Hash[*records_to_process.collect { |record| [record.object_id, record] }.flatten(1)]
161
+
162
+ add_new_association = lambda do |record, attribute, assoc_record|
163
+ unless backing_records[assoc_record.object_id]
164
+ records_to_process << assoc_record
165
+ backing_records[assoc_record.object_id] = assoc_record
166
+ end
167
+ associations << {parent_id: record.object_id, attribute: attribute, child_id: assoc_record.object_id}
168
+ end
169
+
170
+ record_index = 0
171
+ while(record_index < records_to_process.count)
172
+ record = records_to_process[record_index]
173
+ if record.id.loading? and record_being_saved
174
+ raise "Attempt to save a model while it or an associated model is still loading: model being saved: #{record_being_saved.model}:#{record_being_saved.id}#{', associated model: '+record.model.to_s if record != record_being_saved}"
175
+ end
176
+ output_attributes = {record.model.primary_key => record.id.loading? ? nil : record.id}
177
+ vector = record.vector || [record.model.model_name, ["new", record.object_id]]
178
+ models << {id: record.object_id, model: record.model.model_name, attributes: output_attributes, vector: vector}
179
+ record.attributes.each do |attribute, value|
180
+ if association = record.model.reflect_on_association(attribute)
181
+ if association.collection?
182
+ # following line changed from .all to .collection on 10/28
183
+ [*value.collection, *value.unsaved_children].each do |assoc|
184
+ add_new_association.call(record, attribute, assoc.backing_record) if assoc.changed?(association.inverse_of) or assoc.new?
185
+ end
186
+ elsif record.new? || record.changed?(attribute) || (record == record_being_saved && force)
187
+ if value.nil?
188
+ output_attributes[attribute] = nil
189
+ else
190
+ add_new_association.call record, attribute, value.backing_record
191
+ end
192
+ end
193
+ elsif aggregation = record.model.reflect_on_aggregation(attribute) and (aggregation.klass < ActiveRecord::Base)
194
+ add_new_association.call record, attribute, value.backing_record unless value.nil?
195
+ elsif aggregation
196
+ new_value = aggregation.serialize(value)
197
+ output_attributes[attribute] = new_value if record.changed?(attribute) or new_value != aggregation.serialize(record.synced_attributes[attribute])
198
+ elsif record.new? or record.changed?(attribute)
199
+ output_attributes[attribute] = value
200
+ end
201
+ end if record.new? || record.changed? || (record == record_being_saved && force)
202
+ record_index += 1
203
+ end
204
+ [models, associations, backing_records]
205
+ end
206
+
207
+ def save(validate, force, &block)
208
+
209
+ if data_loading?
210
+
211
+ sync!
212
+
213
+ elsif force or changed?
214
+
215
+ begin
216
+
217
+ models, associations, backing_records = self.class.gather_records([self], force, self)
218
+
219
+ backing_records.each { |id, record| record.saving! }
220
+
221
+ promise = Promise.new
222
+
223
+ HTTP.post(`window.ReactiveRecordEnginePath`+"/save",
224
+ payload: {
225
+ json: {
226
+ models: models,
227
+ associations: associations,
228
+ validate: validate
229
+ }.to_json
230
+ }
231
+ ).then do |response|
232
+ begin
233
+ response.json[:models] = response.json[:saved_models].collect do |item|
234
+ backing_records[item[0]].ar_instance
235
+ end
236
+
237
+ if response.json[:success]
238
+ response.json[:saved_models].each do | item |
239
+ # was backing_records[item[0]].sync!(item[2])
240
+ HyperMesh::LocalSync.after_save backing_records[item[0]].ar_instance, item[2]
241
+ end
242
+ else
243
+ log("Reactive Record Save Failed: #{response.json[:message]}", :error)
244
+ response.json[:saved_models].each do | item |
245
+ log(" Model: #{item[1]}[#{item[0]}] Attributes: #{item[2]} Errors: #{item[3]}", :error) if item[3]
246
+ end
247
+ end
248
+
249
+ response.json[:saved_models].each do | item |
250
+ backing_records[item[0]].sync_unscoped_collection!
251
+ backing_records[item[0]].errors! item[3]
252
+ end
253
+
254
+ yield response.json[:success], response.json[:message], response.json[:models] if block
255
+ promise.resolve response.json
256
+
257
+ backing_records.each { |id, record| record.saved! }
258
+
259
+ rescue Exception => e
260
+ log("Exception raised while saving - #{e}", :error)
261
+ end
262
+ end
263
+ promise
264
+ rescue Exception => e
265
+ log("Exception raised while saving - #{e}", :error)
266
+ yield false, e.message, [] if block
267
+ promise.resolve({success: false, message: e.message, models: []})
268
+ promise
269
+ end
270
+ else
271
+ promise = Promise.new
272
+ yield true, nil, [] if block
273
+ promise.resolve({success: true})
274
+ promise
275
+ end
276
+ end
277
+
278
+ else
279
+
280
+ def self.find_record(model, id, vector, save)
281
+ if !save
282
+ found = vector[1..-1].inject(vector[0]) do |object, method|
283
+ if object.nil? # happens if you try to do an all on empty scope followed by more scopes
284
+ object
285
+ elsif method.is_a? Array
286
+ if method[0] == 'new'
287
+ object.new
288
+ else
289
+ object.send(*method)
290
+ end
291
+ elsif method.is_a? String and method[0] == '*'
292
+ object[method.gsub(/^\*/,'').to_i]
293
+ else
294
+ object.send(method)
295
+ end
296
+ end
297
+ if id and (found.nil? or !(found.class <= model) or (found.id and found.id.to_s != id.to_s))
298
+ raise "Inconsistent data sent to server - #{model.name}.find(#{id}) != [#{vector}]"
299
+ end
300
+ found
301
+ elsif id
302
+ model.find(id)
303
+ else
304
+ model.new
305
+ end
306
+ end
307
+
308
+
309
+ def self.is_enum?(record, key)
310
+ record.class.respond_to?(:defined_enums) && record.class.defined_enums[key]
311
+ end
312
+
313
+ def self.save_records(models, associations, acting_user, validate, save)
314
+ reactive_records = {}
315
+ vectors = {}
316
+ new_models = []
317
+ saved_models = []
318
+ dont_save_list = []
319
+
320
+ models.each do |model_to_save|
321
+ attributes = model_to_save[:attributes]
322
+ model = Object.const_get(model_to_save[:model])
323
+ id = attributes.delete(model.primary_key) if model.respond_to? :primary_key # if we are saving existing model primary key value will be present
324
+ vector = model_to_save[:vector]
325
+ vector = [vector[0].constantize] + vector[1..-1].collect do |method|
326
+ if method.is_a?(Array) and method.first == "find_by_id"
327
+ ["find", method.last]
328
+ else
329
+ method
330
+ end
331
+ end
332
+ reactive_records[model_to_save[:id]] = vectors[vector] = record = find_record(model, id, vector, save)
333
+ if record and record.respond_to?(:id) and record.id
334
+ # we have an already exising activerecord model
335
+ keys = record.attributes.keys
336
+ attributes.each do |key, value|
337
+ if is_enum?(record, key)
338
+ record.send("#{key}=",value)
339
+ elsif keys.include? key
340
+ record[key] = value
341
+ elsif !value.nil? and aggregation = record.class.reflect_on_aggregation(key.to_sym) and !(aggregation.klass < ActiveRecord::Base)
342
+ aggregation.mapping.each_with_index do |pair, i|
343
+ record[pair.first] = value[i]
344
+ end
345
+ elsif record.respond_to? "#{key}="
346
+ record.send("#{key}=",value)
347
+ else
348
+ # TODO once reading schema.rb on client is implemented throw an error here
349
+ end
350
+ end
351
+ elsif record
352
+ # either the model is new, or its not even an active record model
353
+ dont_save_list << record unless save
354
+ keys = record.attributes.keys
355
+ attributes.each do |key, value|
356
+ if is_enum?(record, key)
357
+ record.send("#{key}=",value)
358
+ elsif keys.include? key
359
+ record[key] = value
360
+ elsif !value.nil? and aggregation = record.class.reflect_on_aggregation(key) and !(aggregation.klass < ActiveRecord::Base)
361
+ aggregation.mapping.each_with_index do |pair, i|
362
+ record[pair.first] = value[i]
363
+ end
364
+ elsif key.to_s != "id" and record.respond_to?("#{key}=") # server side methods can get included and we won't be able to write them...
365
+ # for example if you have a server side method foo, that you "get" on a new record, then later that value will get sent to the server
366
+ # we should track better these server side methods so this does not happen
367
+ record.send("#{key}=",value)
368
+ end
369
+ end
370
+ new_models << record
371
+ end
372
+ end
373
+
374
+ #puts "!!!!!!!!!!!!!!attributes updated"
375
+ ActiveRecord::Base.transaction do
376
+ associations.each do |association|
377
+ parent = reactive_records[association[:parent_id]]
378
+ next unless parent
379
+ #parent.instance_variable_set("@reactive_record_#{association[:attribute]}_changed", true) remove this????
380
+ if parent.class.reflect_on_aggregation(association[:attribute].to_sym)
381
+ #puts ">>>>>>AGGREGATE>>>> #{parent.class.name}.send('#{association[:attribute]}=', #{reactive_records[association[:child_id]]})"
382
+ aggregate = reactive_records[association[:child_id]]
383
+ dont_save_list << aggregate
384
+ current_attributes = parent.send(association[:attribute]).attributes
385
+ #puts "current parent attributes = #{current_attributes}"
386
+ new_attributes = aggregate.attributes
387
+ #puts "current child attributes = #{new_attributes}"
388
+ merged_attributes = current_attributes.merge(new_attributes) { |k, current_attr, new_attr| aggregate.send("#{k}_changed?") ? new_attr : current_attr}
389
+ #puts "merged attributes = #{merged_attributes}"
390
+ aggregate.assign_attributes(merged_attributes)
391
+ #puts "aggregate attributes after merge = #{aggregate.attributes}"
392
+ parent.send("#{association[:attribute]}=", aggregate)
393
+ #puts "updated is frozen? #{aggregate.frozen?}, parent attributes = #{parent.send(association[:attribute]).attributes}"
394
+ elsif parent.class.reflect_on_association(association[:attribute].to_sym).nil?
395
+ raise "Missing association :#{association[:attribute]} for #{parent.class.name}. Was association defined on opal side only?"
396
+ elsif parent.class.reflect_on_association(association[:attribute].to_sym).collection?
397
+ #puts ">>>>>>>>>> #{parent.class.name}.send('#{association[:attribute]}') << #{reactive_records[association[:child_id]]})"
398
+ dont_save_list.delete(parent)
399
+ if false and parent.new?
400
+ parent.send("#{association[:attribute]}") << reactive_records[association[:child_id]] if parent.new?
401
+ #puts "updated"
402
+ else
403
+ #puts "skipped"
404
+ end
405
+ else
406
+ #puts ">>>>ASSOCIATION>>>> #{parent.class.name}.send('#{association[:attribute]}=', #{reactive_records[association[:child_id]]})"
407
+ parent.send("#{association[:attribute]}=", reactive_records[association[:child_id]])
408
+ dont_save_list.delete(parent)
409
+ #puts "updated"
410
+ end
411
+ end if associations
412
+
413
+ #puts "!!!!!!!!!!!!associations updated"
414
+
415
+ has_errors = false
416
+
417
+ #puts "ready to start saving... dont_save_list = #{dont_save_list}"
418
+
419
+ saved_models = reactive_records.collect do |reactive_record_id, model|
420
+ #puts "saving rr_id: #{reactive_record_id} model.object_id: #{model.object_id} frozen? <#{model.frozen?}>"
421
+ if model and (model.frozen? or dont_save_list.include?(model) or model.changed.include?(model.class.primary_key))
422
+ # the above check for changed including the private key happens if you have an aggregate that includes its own id
423
+ #puts "validating frozen model #{model.class.name} #{model} (reactive_record_id = #{reactive_record_id})"
424
+ valid = model.valid?
425
+ #puts "has_errors before = #{has_errors}, validate= #{validate}, !valid= #{!valid} (validate and !valid) #{validate and !valid}"
426
+ has_errors ||= (validate and !valid)
427
+ #puts "validation complete errors = <#{!valid}>, #{model.errors.messages} has_errors #{has_errors}"
428
+ [reactive_record_id, model.class.name, model.attributes, (valid ? nil : model.errors.messages)]
429
+ elsif model and (!model.id or model.changed?)
430
+ #puts "saving #{model.class.name} #{model} (reactive_record_id = #{reactive_record_id})"
431
+ saved = model.check_permission_with_acting_user(acting_user, new_models.include?(model) ? :create_permitted? : :update_permitted?).save(validate: validate)
432
+ has_errors ||= !saved
433
+ messages = model.errors.messages if (validate and !saved) or (!validate and !model.valid?)
434
+ #puts "saved complete errors = <#{!saved}>, #{messages} has_errors #{has_errors}"
435
+ [reactive_record_id, model.class.name, model.attributes, messages]
436
+ end
437
+ end.compact
438
+
439
+ raise "Could not save all models" if has_errors
440
+
441
+ if save
442
+
443
+ {success: true, saved_models: saved_models }
444
+
445
+ else
446
+
447
+ vectors.each { |vector, model| model.reload unless model.nil? or model.new_record? or model.frozen? }
448
+ vectors
449
+
450
+ end
451
+
452
+ end
453
+
454
+ rescue Exception => e
455
+ ReactiveRecord::Pry.rescued(e)
456
+ if save
457
+ {success: false, saved_models: saved_models, message: e}
458
+ else
459
+ {}
460
+ end
461
+ end
462
+
463
+ end
464
+
465
+ # destroy records
466
+
467
+ if RUBY_ENGINE == 'opal'
468
+
469
+ def destroy(&block)
470
+
471
+ return if @destroyed
472
+
473
+ #destroy_associations
474
+
475
+ promise = Promise.new
476
+
477
+ if !data_loading? and (id or vector)
478
+ HTTP.post(`window.ReactiveRecordEnginePath`+"/destroy",
479
+ payload: {
480
+ json: {
481
+ model: ar_instance.model_name,
482
+ id: id,
483
+ vector: vector
484
+ }.to_json
485
+ }
486
+ ).then do |response|
487
+ #sync_unscoped_collection!
488
+ HyperMesh::LocalSync.after_save ar_instance
489
+ yield response.json[:success], response.json[:message] if block
490
+ promise.resolve response.json
491
+ end
492
+ else
493
+ destroy_associations
494
+ # sync_unscoped_collection! # ? should we do this here was NOT being done before hypermesh integration
495
+ yield true, nil if block
496
+ promise.resolve({success: true})
497
+ end
498
+
499
+ # DO NOT CLEAR ATTRIBUTES. Records that are not found, are destroyed, and if they are searched for again, we want to make
500
+ # sure to find them. We may want to change this, and provide a separate flag called not_found. In this case you
501
+ # would put these lines here:
502
+ # @attributes = {}
503
+ # sync!
504
+ # and modify server_data_cache so that it does NOT call destroy
505
+
506
+ @destroyed = true
507
+
508
+ promise
509
+ end
510
+
511
+ else
512
+
513
+ def self.destroy_record(model, id, vector, acting_user)
514
+ model = Object.const_get(model)
515
+ record = if id
516
+ model.find(id)
517
+ else
518
+ ServerDataCache.new(acting_user, {})[*vector]
519
+ end
520
+
521
+
522
+ record.check_permission_with_acting_user(acting_user, :destroy_permitted?).destroy
523
+ {success: true, attributes: {}}
524
+
525
+ rescue Exception => e
526
+ ReactiveRecord::Pry.rescued(e)
527
+ {success: false, record: record, message: e}
528
+ end
529
+ end
530
+ end
531
+
532
+ end