hyper-mesh 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,4 @@
1
+ module ReactiveRecord
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,49 @@
1
+ require 'reactive_record/server_data_cache'
2
+
3
+ module ReactiveRecord
4
+
5
+ class ReactiveRecordController < ::ApplicationController
6
+
7
+ def fetch
8
+ render :json => ReactiveRecord::ServerDataCache[
9
+ (json_params[:models] || []).map(&:with_indifferent_access),
10
+ (json_params[:associations] || []).map(&:with_indifferent_access),
11
+ json_params[:pending_fetches],
12
+ acting_user
13
+ ]
14
+ rescue Exception => e
15
+ render json: {error: e.message, backtrace: e.backtrace}, status: 500
16
+ end
17
+
18
+ def save
19
+ render :json => ReactiveRecord::Base.save_records(
20
+ (json_params[:models] || []).map(&:with_indifferent_access),
21
+ (json_params[:associations] || []).map(&:with_indifferent_access),
22
+ acting_user,
23
+ json_params[:validate],
24
+ true
25
+ )
26
+ rescue Exception => e
27
+ render json: {error: e.message, backtrace: e.backtrace}, status: 500
28
+ end
29
+
30
+ def destroy
31
+ render :json => ReactiveRecord::Base.destroy_record(
32
+ json_params[:model],
33
+ json_params[:id],
34
+ json_params[:vector],
35
+ acting_user
36
+ )
37
+ rescue Exception => e
38
+ render json: {error: e.message, backtrace: e.backtrace}, status: 500
39
+ end
40
+
41
+ private
42
+
43
+ def json_params
44
+ JSON.parse(params[:json]).symbolize_keys
45
+ end
46
+
47
+ end
48
+
49
+ end
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "hyper-mesh"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,18 @@
1
+ development:
2
+ adapter: mysql2
3
+ host: localhost
4
+ encoding: utf8
5
+ pool: 10
6
+ username: <%= ENV['MYSQL_USER'] %>
7
+ password: <%= ENV['MYSQL_PASSWORD'] %>
8
+ database: development<%= ENV['TEST_ENV_NUMBER'] %>
9
+ socket: /var/run/mysqld/mysqld.sock
10
+ test:
11
+ adapter: mysql2
12
+ host: localhost
13
+ encoding: utf8
14
+ pool: 10
15
+ username: <%= ENV['MYSQL_USER'] %>
16
+ password: <%= ENV['MYSQL_PASSWORD'] %>
17
+ database: test<%= ENV['TEST_ENV_NUMBER'] %>
18
+ socket: /var/run/mysqld/mysqld.sock
data/config/routes.rb ADDED
@@ -0,0 +1,7 @@
1
+ ReactiveRecord::Engine.routes.draw do
2
+ root :to => "reactive_record#fetch", via: :post
3
+ match 'save', to: 'reactive_record#save', via: :post
4
+ match 'destroy', to: 'reactive_record#destroy', via: :post
5
+ match 'syncromesh-subscribe', to: 'syncromesh#subscribe', via: :get
6
+ match 'syncromesh-read/:subscriber', to: 'syncromesh#read', via: :get
7
+ end
@@ -0,0 +1,151 @@
1
+ ### Action Cable Quickstart
2
+
3
+ Action Cable is production ready transport built into Rails 5.
4
+
5
+ #### 1 Get Rails 5
6
+
7
+ You need to be on rails 5 to use ActionCable. Make sure you upgrade to rails 5 first.
8
+
9
+ #### 2 Add ReactRb
10
+
11
+ If you have not already installed the `reactrb` and `reactive-record` gems, then do so now using the [reactrb-rails-generator](https://github.com/hyper-react/reactrb-rails-generator) gem.
12
+
13
+ - add `gem 'reactrb-rails-generator'` to your gem file (in the development section)
14
+ - run `bundle install`
15
+ - run `rails g hyper-react:install --all` (make sure to use the --all option)
16
+ - run `bundle update`
17
+
18
+ #### 3 Reactrb and ReactiveRecord is replaced with HyperMesh
19
+
20
+ - replace the `'reactive-record'` and `'reactrb'` gems with
21
+ `gem 'hyper-mesh', git: 'https://github.com/ruby-hyperloop/hyper-mesh'`
22
+ **note:** you must remove `gem 'reactive-record'`
23
+ - then `bundle install`
24
+ - and in `app/views/components.rb` replace `require 'reactive-record'` with `require 'hyper-mesh'`
25
+
26
+ #### 4 Set the transport
27
+
28
+ Once you have HyperMesh installed then add this initializer:
29
+ ```ruby
30
+ #config/initializers/hyper_mesh.rb
31
+ HyperMesh.configuration do |config|
32
+ config.transport = :action_cable
33
+ end
34
+ ```
35
+
36
+ #### 5 Define Your Policies
37
+
38
+ To start just open everything up by adding a policies directory and defining a policy file like this:
39
+
40
+ ```ruby
41
+ # app/policies/application_policy.rb
42
+ class ApplicationPolicy
43
+ always_allow_connection
44
+ regulate_all_broadcasts { |policy| policy.send_all }
45
+ allow_change(to: :all, on: [:create, :update, :destroy]) { true }
46
+ end
47
+ ```
48
+
49
+ #### 6 Setup ActionCable
50
+
51
+ If you are already using ActionCable in your app that is fine, as HyperMesh will not interfere with your existing connections.
52
+
53
+ Otherwise go through the following steps to setup ActionCable.
54
+
55
+ ##### 6.1 Add the action_cable.js file
56
+
57
+ Include the `action_cable` js file in your assets
58
+
59
+ ```javascript
60
+ //app/assets/javascripts/application.js
61
+ ...
62
+ //= require action_cable
63
+ Opal.load('components');
64
+ ```
65
+
66
+ ##### 6.2 Make sure you have a cable.yml file
67
+
68
+ ```yml
69
+ # config/cable.yml
70
+ development:
71
+ adapter: async
72
+
73
+ test:
74
+ adapter: async
75
+
76
+ production:
77
+ adapter: redis
78
+ url: redis://localhost:6379/1
79
+ ```
80
+
81
+ ##### 6.3 Set allowed request origins (optional)
82
+
83
+ By default action cable will only allow connections from localhost:3000 in development. If you are going to something other than localhost:3000 you need to add something like this to your config:
84
+
85
+ ```ruby
86
+ # config/environments/development.rb
87
+ Rails.application.configure do
88
+ config.action_cable.allowed_request_origins = ['http://localhost:3000', 'http://localhost:4000']
89
+ end
90
+ ```
91
+
92
+ #### 7 Try It Out
93
+
94
+ If you don't already have a model to play with, add one now:
95
+
96
+ `bundle exec rails generate model Word text:string`
97
+
98
+ `bundle exec rake db:migrate`
99
+
100
+ Whatever model(s) you will plan to access on the client need to moved to the `app/models/public` directory. This allows reactive-record to build a client side proxy for the models. Models not moved will be completely invisible on the client side.
101
+
102
+ **Important** in rails 5 there is also a base `ApplicationRecord` class, that all other models are built from. This class must be moved to the public directory as well.
103
+
104
+ If you don't already have a simple component to play with, here is a simple one (make sure you add the Word model):
105
+
106
+ ```ruby
107
+ # app/views/components/app.rb
108
+ class App < React::Component::Base
109
+
110
+ def add_new_word
111
+ # for fun we will use setgetgo.com to get random words!
112
+ HTTP.get("http://randomword.setgetgo.com/get.php", dataType: :jsonp) do |response|
113
+ Word.new(text: response.json[:Word]).save
114
+ end
115
+ end
116
+
117
+ render(DIV) do
118
+ SPAN { "Count of Words: #{Word.count}" }
119
+ BUTTON { "add another" }.on(:click) { add_new_word }
120
+ UL do
121
+ Word.each { |word| LI { word.text } }
122
+ end
123
+ end
124
+ end
125
+ ```
126
+
127
+ Add a controller:
128
+
129
+ ```ruby
130
+ #app/controllers/test_controller.rb
131
+ class TestController < ApplicationController
132
+ def app
133
+ render_component
134
+ end
135
+ end
136
+ ```
137
+
138
+ Add the `test` route to your routes file:
139
+
140
+ ```ruby
141
+ #app/config/routes.rb
142
+
143
+ get 'test', to: 'test#app'
144
+
145
+ ```
146
+
147
+ Fire up rails with `bundle exec rails s` and open your app in a couple of browsers. As data changes you should see them all updating together.
148
+
149
+ You can also fire up a rails console, and then for example do a `Word.new(text: "Hello").save` and again see any browsers updating.
150
+
151
+ If you want to go into more details with example check out [words-example](/docs/words-example.md)
@@ -0,0 +1,449 @@
1
+ ### HyperMesh Authorization Policies
2
+
3
+ Each time an ActiveRecord model changes, HyperMesh broadcasts the changed attributes over *channels*.
4
+
5
+ An application can have several channels and each channel and each active record model can have different *policies* to determine which attributes are sent when a record changes.
6
+
7
+ For example a Todo application might have an *instance* of a channel for each currently logged in user; an instance of a channel for each team if that team has one or more logged in users; and a general `AdminUser` channel shared by all administrators that are logged in.
8
+
9
+ Lets say a specific `Todo` changes, which is part of team id 123's Todo list, and users 7 and 8 who are members of that team are currently logged in as well as two of the `AdminUsers`.
10
+
11
+ When the `Todo` changes we want all the attributes of the `Todo` broadcast on team 123's channel, as well on the `AdminUser`'s channel. Now lets say User 7 sends User 8 a private message, adding a new record to the `Message` model. This update should only be sent to user 7 and user 8's private channels, as well as to the AdminUser channel.
12
+
13
+ We can define all these policies by creating the following classes:
14
+
15
+ ```ruby
16
+ class UserPolicy # defines policies for the User class
17
+ # The regulate_instance_connections method enables instances of the User
18
+ # class to be treated as a channel.
19
+
20
+ # The policy is defined by a block that is executed in the context of the
21
+ # current acting_user.
22
+
23
+ # For our User instance connection the policy is that there must be logged in
24
+ # user, and the connection is made to that user:
25
+ regulate_instance_connections { self }
26
+ # If there is no logged in user self will be nil, and no connection will be
27
+ # made.
28
+ end
29
+
30
+ class TeamPolicy # defines policies for the Team class
31
+ # Users can only connect to Teams that they belong to
32
+ regulate_instance_connections { teams }
33
+ end
34
+
35
+ class AdminUserPolicy
36
+ # All AdminUsers share the same connection so we setup a class wide
37
+ # connection available to any users who are admins.
38
+ regulate_class_connection { admin? }
39
+
40
+ # The AdminUser channel will receive all attributes
41
+ # of all records, unless the attribute is named :password
42
+ regulate_all_broadcasts do |policy|
43
+ policy.send_all_but(:password)
44
+ end
45
+ end
46
+
47
+ class TodoPolicy
48
+ # Policies can be established for models that are not channels as well.
49
+
50
+ # The regulate_broadcast method will describe what attributes to send
51
+ # when a Todo model changes.
52
+
53
+ # The blocks of broadcast policies run in the context of the changed model
54
+ # so we have access to all the models methods. In this case Todo
55
+ # belongs to a Team through the 'team' relationship.
56
+ regulate_broadcast do |policy|
57
+ # send all Todo attributes to the todo's team channel
58
+ policy.send_all.to(team)
59
+ end
60
+ end
61
+
62
+ class MessagePolicy
63
+ # Broadcast policies can be arbitrarily complex. In this case we
64
+ # want to broadcast the entire message to the sender and the
65
+ # recipient's instance channels.
66
+ # In addition if the message is not private, then we want to send to all
67
+ # the team instance channels that are shared between the sender and
68
+ # recipient's teams.
69
+ regulate_broadcast do |policy|
70
+ policy.send_all.to(sender, recipient)
71
+ policy.send_all.to(sender.teams.merge(recipient.teams)) unless private?
72
+ end
73
+ end
74
+ ```
75
+
76
+ Before we begin using these channels and policies we need to first define the Reactive-Record `acting_user` method in our ApplicationController:
77
+
78
+ ```ruby
79
+ class ApplicationController < ActionController::Base
80
+ def acting_user
81
+ # The acting_user method should return nil, or some object that corresponds to a
82
+ # logged in user. Specifics will depend on your application and whatever other
83
+ # authentication mechanisms you are using.
84
+ @acting_user ||= session[:current_user_id] && User.find_by_id(session[:current_user_id])
85
+ end
86
+ end
87
+ end
88
+ ```
89
+
90
+ Note that `acting_user` is also used by Reactive-Record's permission system.
91
+
92
+ Our entire set of policies is defined in 29 lines of code of which 8 actually execute the policies. Our existing classes form the foundation, and we simply add synchromesh specific policy directives. Pretty sweet huh?
93
+
94
+ ### Details
95
+
96
+ HyperMesh uses *Policies* to *regulate* what *connections* are opened between clients and the server and what data is distributed over those connections.
97
+
98
+ Connections are made on *channels* of data flowing between the server and a number of clients. Each channel is associated with either a class or an instance of a class. Typically the channel class represents an entity (or is associated with an entity) that can be authenticated like a `User`, an `AdminUser`, or a `Team` of users. A channel associated with the class itself broadcasts data that is received by any member of that class. A channel associated with an instance is for data that is available only to that specific instance.
99
+
100
+ As models on the server change (i.e. created, updated, or destroyed) the changes are broadcast over open channels. What specific attributes are sent (if any) is determined by broadcast policies.
101
+
102
+ Broadcast policies can be associated with models. As the model changes the broadcast policy will regulate what attributes of the changed model will be sent over which channels.
103
+
104
+ Broadcast policies can also be associated with a channel and will regulate *all* model changes over specific channels. In other words this is just a convenient way to associate a common policy with *all* models.
105
+
106
+ Note that models that are associated with channels can also broadcast their changes on the same or different channels.
107
+
108
+ #### Defining Policies and Policy Classes
109
+
110
+ The best way to define policies is to use a *Policy Class*. A policy class has the same class name as the class it is regulating, with `Policy` added to the end. Policy classes are compatible with `Pundit`, and you can add regular pundit policies as well.
111
+
112
+ Policies are defined using four methods:
113
+ + `regulate_class_connection` controls connections to the class channels,
114
+ + `regulate_instance_connections` controls connections to instance channels,
115
+ + `regulate_broadcast` controls what data will be sent when a model or object changes and,
116
+ + `regulate_all_broadcasts` controls what data will be sent of some channels when any model changes.
117
+
118
+ In addition `always_allow_connection` is short hand for `regulate_class_connection { true }`
119
+
120
+ A policy class can be defined for which there is no regulated class. This is useful for application wide connections, which are typically open even if no one is logged in:
121
+
122
+ ```ruby
123
+ #app/policies/application.rb
124
+ class ApplicationPolicy
125
+ regulate_class_connection { true }
126
+ end
127
+ ```
128
+
129
+ Note that by default policy classes go in the `app/policies` directory. HyperMesh will require all the files in this directory.
130
+
131
+ If you wish, you can also add policies directly in your models by including the `HyperMesh::PolicyMethods` module in your model. You can then use the `regulate_class_connection`, `regulate_instance_connections`, `regulate_all_broadcasts` and `regulate_broadcast` methods directly in the model.
132
+
133
+ ```ruby
134
+ class User < ActiveRecord::Base
135
+ include HyperMesh::PolicyMethods
136
+ regulate_class_connection ...
137
+ regulate_instance_connections ...
138
+ regulate_all_broadcasts ...
139
+ regulate_broadcast ...
140
+ end
141
+ ```
142
+
143
+ Normally the policy methods are regulating the class with the prefix as the policy, but you can override this by providing specific class names to the policy method. This allows you to group several different class policies together, and to reuse policies:
144
+
145
+ ```ruby
146
+ class ApplicationPolicy
147
+ regulate_connection { ... } # Application is assumed
148
+ regulate_class_connection(User) { ... }
149
+ # regulate_class_connection, regulate_instance_connections and
150
+ # regulate_all_broadcasts can take a list of channels.
151
+ regulate_all_broadcasts(User, Application)
152
+ # regulate_broadcast takes a list of object classes which
153
+ # may also be channels.
154
+ regulate_broadcast(Todo, Message, User) { ... }
155
+ end
156
+ ```
157
+
158
+ #### Channels and the connection policies
159
+
160
+ Any ruby class that has a connection policy is a synchromesh channel. The fully scoped name of the class becomes the root of the channel name.
161
+
162
+ The purpose of having channels is to restrict what gets broadcast when models change, therefore typically channels represent *connections* to
163
+
164
+ + the application, or some function within the application
165
+ + or some class which *authenticated* like a User or Administrator,
166
+ + instances of those classes,
167
+ + or instances of related classes.
168
+
169
+ So a channel that is connected to the User class would get information readable by any logged-in user, while a channel that is connected to a specific User instance would get information readable by that specific user.
170
+
171
+ The `regulate_class_connection` takes a block that will execute in the context of the current acting_user (which may be nil), and if the block returns any truthy value, the connection will be made.
172
+
173
+ The `regulate_instance_connections` likewise takes a block that is executed in the context of the current acting_user. The block may do one of following:
174
+
175
+ + raise an error meaning the connection cannot be made,
176
+ + return a falsy value also meaning the connection cannot be made,
177
+ + return a single object meaning the connection can be made to that object,
178
+ + return a enumerable of objects meaning the connection can made to any member of the enumerable.
179
+
180
+ Note that the object (or objects) returned are expected to be of the same class as the regulated policy.
181
+
182
+ ```ruby
183
+ # Create a class connection only if the acting_user is non-nil (i.e. logged in:)
184
+ regulate_class_connection { self }
185
+ # Always open the connection:
186
+ regulate_class_connection { true }
187
+ # Which can be shortened to:
188
+ always_allow_connection
189
+ # Create a class level connection if the acting_user is an admin:
190
+ regulate_class_connection { admin? }
191
+ # Create an instance connection for the current user:
192
+ regulate_instance_connections { self }
193
+ # Create an instance connection for the current user if the user is an admin:
194
+ regulate_instance_connections { self if admin? }
195
+ # create an instance_connection to the users' group
196
+ regulate_instance_connections { group }
197
+ # create an instance connection for any team the user belongs to
198
+ regulate_instance_connections { teams }
199
+ ```
200
+
201
+ #### Class Names, Instances and Ids
202
+
203
+ While establishing connections, classes are represented as their fully scoped name, and instances are represented as the class name plus the result of calling `id` on the instance.
204
+
205
+ Typically connections are made to ActiveRecord models, and if those are in the `app/models/public` folder everything will work fine.
206
+
207
+ #### Acting User
208
+
209
+ HyperMesh uses the same `acting_user` method that reactive-record permissions uses. This method is typically defined in the ApplicationController and would normally pick up the current session user, and return an appropriate object.
210
+
211
+ ```ruby
212
+ class ApplicationController < ActiveController::Base
213
+ def acting_user
214
+ @acting_user ||= session[:current_user_id] && User.find_by_id(session[:current_user_id])
215
+ end
216
+ end
217
+ end
218
+ ```
219
+
220
+ #### Automatic Connection
221
+
222
+ Connections to channels available to the current `acting_user` are automatically made on the initial page load. This behavior can be turned off with the `auto_connect` option.
223
+
224
+ ```ruby
225
+ class TeamPolicy
226
+ # Allow current users to establish connections to any teams they are
227
+ # members of, but disable_auto_connect
228
+ regulate_instance_connections(auto_connect: false) { teams }
229
+ end
230
+ ```
231
+
232
+ Its important to consider turning off automatic connections for cases like the above where
233
+ the user is likely to be a member of many teams. Typically the client application will
234
+ want to dynamically determine which specific teams to connect to given the current state of
235
+ the application.
236
+
237
+ ### Manually Connecting to Channels
238
+
239
+ Normally the client will automatically connect to the available channels when a page loads, but you can also
240
+ manually connect on the client in response to some user action like logging in, or the user deciding to
241
+ display a specific team status on their dashboard.
242
+
243
+ To manually connect a client use the `HyperMesh.connect` method.
244
+
245
+ The `connect` method takes any number of arguments each of which is either a class, an object, a String or Array.
246
+
247
+ If the argument is a class then the connection will be made to the matching class channel on the server.
248
+
249
+ ```ruby
250
+ # connect the client to the AdminUser class channel
251
+ HyperMesh.connect(AdminUser)
252
+ # if the connection is successful the client will begin getting updates on the
253
+ # AdminUser class channel
254
+ ```
255
+
256
+ If the argument is an object then a connection will be made to the matching object on the server.
257
+
258
+ ```ruby
259
+ # assume current_user is an instance of class User
260
+ HyperMesh.connect(current_user)
261
+ # current_user.id is used to establish which User instance to connect to on the
262
+ # server
263
+ ```
264
+
265
+ The argument can also be a string, which matches the name of a class on the server
266
+
267
+ ```ruby
268
+ HyperMesh.connect('AdminUser')
269
+ # same as AdminUser class
270
+ ```
271
+
272
+ or the argument can be an array with a string and the id:
273
+
274
+ ```ruby
275
+ HyperMesh.connect(['User', current_user.id])
276
+ # same as saying current_user
277
+ ```
278
+
279
+ You can make several connections at once as well:
280
+ ```ruby
281
+ HyperMesh.connect(AdminUser, current_user)
282
+ ```
283
+
284
+ Finally falsy values are ignored.
285
+
286
+ You can also send `connect` directly to ActiveRecord models:
287
+
288
+ ```ruby
289
+ AdminUser.connect! # same as HyperMesh.connect(AdminUser)
290
+ current_user.connect! # same as HyperMesh.connect(current_user)
291
+ ```
292
+
293
+ #### Connection Sequence Summary
294
+
295
+ For class connections:
296
+
297
+ 1. The client calls `HyperMesh.connect`.
298
+ 2. HyperMesh sends the channel name to the server.
299
+ 3. HyperMesh has its own controller which will determine the `acting_user`,
300
+ 4. and call the channel's `regulate_class_connection` method.
301
+ 5. If `regulate_class_connection` returns a truthy value then the connetion is made,
302
+ 6. otherwise a 500 error is returned.
303
+
304
+ For instance connections:
305
+
306
+ 1. The process is the same but the channel name and id are sent to the server.
307
+ 2. The HyperMesh controller will do a find of the id passed to get the instance,
308
+ 3. and if successful `regulate_instance_connections` is called,
309
+ 4. which must return an either the same instance, or an enumerable with that instance as a member.
310
+ 5. Otherwise a 500 error is returned.
311
+
312
+ Note that the same sequence is used for auto connections and manually invoked connections.
313
+
314
+ #### Disconnecting
315
+
316
+ Calling `HyperMesh.disconnect(channel)` or `channel.disconnect!` will disconnect from the channel.
317
+
318
+ #### Broadcasting and Broadcast Policies
319
+
320
+ Broadcast policies can be defined for channels using the `regulate_all_broadcasts` method, and for individual objects (typically ActiveRecord models) using the `regulate_broadcast` method. A `regulate_all_broadcasts` policy is essentially a `regulate_broadcast` that will be run for every record that changes in the system.
321
+
322
+ After an ActiveRecord model change is committed, all active class channels run their channel broadcast policies, and then the instance broadcast policy associated with the changing model is run. So for any change there may be multiple channel broadcast policies involved, but only one (at most) regulate_broadcast.
323
+
324
+ The result is that each channel may get a filtered copy of the record which is broadcast on that channel.
325
+
326
+ The purpose of the policies then is to determine which channel sees what. Each broadcast policy receives the instance of the policy which responds to the following methods
327
+
328
+ + `send_all`: send all the attributes of the record.
329
+ + `send_only`: send only the listed attributes of the record.
330
+ + `send_all_but`: send all the attributes except the ones listed.
331
+
332
+ The result of the `send...` method is then directed to the set of channels using the `to` method:
333
+
334
+ ```ruby
335
+ policy.send_all_but(:password).to(AdminUser)
336
+ ```
337
+
338
+ Within channel broadcast policies the channel is assumed to be the channel in question:
339
+
340
+ ```ruby
341
+ class AdminUserPolicy
342
+ regulate_all_broadcasts do |policy|
343
+ policy.send_all_but(:password) #.to(AdminUser) is not needed.
344
+ end
345
+ end
346
+ ```
347
+
348
+ The `to` method can take any number of arguments:
349
+
350
+ + a class naming a channel,
351
+ + an object that is instance channel,
352
+ + an ActiveRecord collection,
353
+ + any falsy value which will be ignored,
354
+ + or an array that will be flattened and merged with the other arguments.
355
+
356
+ The broadcast policy executes in the context of the model that has just changed, so the policy can use all the methods of that model, especially relationships. For example:
357
+
358
+ ```ruby
359
+ class Message < ActiveRecord::Base
360
+ belongs_to :sender, class: "User"
361
+ belongs_to :recipient, class: "User"
362
+ end
363
+
364
+ class MessagePolicy
365
+ regulate_broadcast do |policy|
366
+ # send all attributes to both the sender, and recipient User instance channels
367
+ policy.send_all.to(sender, recipient)
368
+ # send all attributes to intersection
369
+ policy.send_all.to(sender.teams.merge(recipient.teams)) unless private?
370
+ end
371
+ end
372
+ ```
373
+
374
+ It is possible that the same channel may be sent a record from different policies, in this case the minimum set of attributes will be sent regardless of the order of the send operations. For example:
375
+
376
+ ```ruby
377
+ policy.send_all_but(:password).to(MyChannel)
378
+ # ... later
379
+ policy.send_all.to(MyChannel)
380
+ # MyChannel gets everything but the password
381
+ ```
382
+
383
+ or even
384
+
385
+ ```ruby
386
+ policy.send_only(:foo, :bar).to(MyChannel)
387
+ policy.send_only(:baz).to(MyChannel)
388
+ # MyChannel gets nothing
389
+ ```
390
+
391
+ Keep in mind that the broadcast policies are sent a copy of the policy object so you can use helper methods in your policies. Also you can add policy specific methods to your models using
392
+ `class_eval` thus keeping policy logic out of your models.
393
+
394
+ So we could for example we can rewrite the above MessagePolicy like this:
395
+
396
+ ```ruby
397
+ class MessagePolicy
398
+ Message.class_eval do
399
+ scope :teams_for_policy, -> () { sender.teams.merge(recipient.teams) }
400
+ end
401
+ def teams # the obj method returns the instance being regulated
402
+ [obj.sender, obj.recipient, !obj.private? && obj.teams_for_policy]
403
+ end
404
+ regulate_broadcast { |policy| policy.send_all.to(policy.teams) }
405
+ end
406
+ ```
407
+
408
+ #### Method Summary and Name Space Conflicts
409
+
410
+ Policy classes (and the HyperMesh::PolicyMethods module) define the following class methods:
411
+
412
+ + `regulate_connection`
413
+ + `regulate_all_broadcasts`
414
+ + `regulate_broadcast`
415
+
416
+ As well as the following instance methods:
417
+ + `send_all`
418
+ + `send_all_but`
419
+ + `send_only`
420
+ + `obj`
421
+
422
+ To avoid name space conflicts with your classes, synchromesh policy classes (and the HyperMesh::PolicyMethods module) maintain class and instance `attr_accessor`s named `synchromesh_internal_policy_object`. The above methods call methods of the same name in the appropriate internal policy object.
423
+
424
+ You may thus freely redefine of the class and instance methods if you have name space conflicts
425
+
426
+ ```ruby
427
+ class ProductionCenterPolicy < MyPolicyClass
428
+ # MyPolicyClass already defines our version of obj
429
+ # so we will call it 'this'
430
+ def this
431
+ synchromesh_internal_policy_object.obj
432
+ end
433
+ ...
434
+ end
435
+ ```
436
+
437
+ #### Setting the policy directory
438
+
439
+ *HyperMesh auto-connect needs to know about all policies ahead of time so cannot rely on rails auto loading. Sorry about that!*
440
+
441
+ By default HyperMesh will load all the files in the `app/policies` directory. To change the directory set the policy_directory in the synchromesh initializer.
442
+
443
+ ```ruby
444
+ HyperMesh.configuration do |config|
445
+ ...
446
+ config.policy_directory = File.join(Rails.root, 'app', 'synchromesh-authorization')
447
+ # can also be set to nil if you want to manually require your files
448
+ end
449
+ ```