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,173 @@
1
+ require 'spec_helper'
2
+ require 'synchromesh/integration/test_components'
3
+ require 'rspec-steps'
4
+
5
+ RSpec::Steps.steps "has_many through relationships", js: true do
6
+
7
+ before(:each) do
8
+ require 'pusher'
9
+ require 'pusher-fake'
10
+ Pusher.app_id = "MY_TEST_ID"
11
+ Pusher.key = "MY_TEST_KEY"
12
+ Pusher.secret = "MY_TEST_SECRET"
13
+ require "pusher-fake/support/base"
14
+
15
+ HyperMesh.configuration do |config|
16
+ config.transport = :pusher
17
+ config.channel_prefix = "synchromesh"
18
+ config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options)
19
+ end
20
+
21
+ class Physician < ActiveRecord::Base
22
+ def self.build_tables
23
+ connection.create_table :physicians, force: true do |t|
24
+ t.string :name
25
+ t.timestamps
26
+ end
27
+ end
28
+ end
29
+
30
+ class Patient < ActiveRecord::Base
31
+ def self.build_tables
32
+ connection.create_table :patients, force: true do |t|
33
+ t.string :name
34
+ t.timestamps
35
+ end
36
+ end
37
+ end
38
+
39
+ class Appointment < ActiveRecord::Base
40
+ def self.build_tables
41
+ connection.create_table :appointments, force: true do |t|
42
+ t.belongs_to :physician, index: true
43
+ t.belongs_to :patient, index: true
44
+ t.datetime :appointment_date
45
+ t.timestamps
46
+ end
47
+ end
48
+ end
49
+
50
+ isomorphic do
51
+ class Physician < ActiveRecord::Base
52
+ has_many :appointments
53
+ has_many :patients, through: :appointments
54
+ end
55
+
56
+ class Appointment < ActiveRecord::Base
57
+ belongs_to :physician
58
+ belongs_to :patient
59
+ end
60
+
61
+ class Patient < ActiveRecord::Base
62
+ has_many :appointments
63
+ has_many :physicians, through: :appointments
64
+ end
65
+ end
66
+
67
+ Physician.build_tables rescue nil
68
+ Appointment.build_tables rescue nil
69
+ Patient.build_tables rescue nil
70
+ end
71
+
72
+ before(:step) do
73
+
74
+ stub_const 'ApplicationPolicy', Class.new
75
+ ApplicationPolicy.class_eval do
76
+ always_allow_connection
77
+ regulate_all_broadcasts { |policy| policy.send_all }
78
+ allow_change(to: :all, on: [:create, :update, :destroy]) { true }
79
+ end
80
+
81
+ size_window(:small, :portrait)
82
+ end
83
+
84
+ it "will load has_many :through relationships" do
85
+ mount "PhysicianSchedule" do
86
+ class PhysicianSchedule < React::Component::Base
87
+ render(DIV) do
88
+ Physician.all.each do |doc|
89
+ DIV do
90
+ if doc.appointments.any?
91
+ "Dr. #{doc.name} has a total of #{doc.appointments.count} appointments with: #{doc.patients.pluck(:name).join(", ")}"
92
+ else
93
+ "Dr. #{doc.name} has no appointments."
94
+ end
95
+ end
96
+ end
97
+ Patient.all.each do |patient|
98
+ DIV do
99
+ if patient.physicians.any?
100
+ "#{patient.name} - Doctors: #{patient.physicians.pluck(:name).join(', ')}"
101
+ else
102
+ "#{patient.name} is not seeing any doctors"
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ @ds = Physician.create(name: "Stop")
110
+ @df = Physician.create(name: "Faith")
111
+ @dq = Physician.create(name: "Quack")
112
+ @pc = Patient.create(name: "H. Chrondriac")
113
+ @pl = Patient.create(name: "B. Legg")
114
+ @ph = Patient.create(name: "N. Help")
115
+ @a1 = Appointment.create(appointment_date: Time.now+1.day, physician: @ds, patient: @pc)
116
+ page.should have_content("Dr. Stop has a total of 1 appointments with: H. Chrondriac")
117
+ page.should have_content("Dr. Faith has no appointments.")
118
+ page.should have_content("Dr. Quack has no appointments.")
119
+ page.should have_content("H. Chrondriac - Doctors: Stop")
120
+ page.should have_content("B. Legg is not seeing any doctors")
121
+ page.should have_content("N. Help is not seeing any doctors")
122
+ end
123
+
124
+ it "can add to existing relationships on the client" do
125
+ evaluate_ruby do
126
+ Appointment.create(
127
+ appointment_date: Time.now+1.day,
128
+ physician: Physician.find_by_name('Stop'),
129
+ patient: Patient.find_by_name('B. Legg')
130
+ )
131
+ end
132
+ page.should have_content("Dr. Stop has a total of 2 appointments with: H. Chrondriac, B. Legg")
133
+ page.should have_content("Dr. Faith has no appointments.")
134
+ page.should have_content("Dr. Quack has no appointments.")
135
+ page.should have_content("H. Chrondriac - Doctors: Stop")
136
+ page.should have_content("B. Legg - Doctors: Stop")
137
+ page.should have_content("N. Help is not seeing any doctors")
138
+ end
139
+
140
+ it "can add to existing relationships from the server" do
141
+ @a2 = Appointment.create(appointment_date: Time.now+1.day, physician: @df, patient: @pc)
142
+ page.should have_content("Dr. Stop has a total of 2 appointments with: H. Chrondriac, B. Legg")
143
+ page.should have_content("Dr. Faith has a total of 1 appointments with: H. Chrondriac")
144
+ page.should have_content("Dr. Quack has no appointments.")
145
+ page.should have_content("H. Chrondriac - Doctors: Stop, Faith")
146
+ page.should have_content("B. Legg - Doctors: Stop")
147
+ page.should have_content("N. Help is not seeing any doctors")
148
+ end
149
+
150
+ it "can update an existing relationship from the server" do
151
+ @a2.update(physician: @dq)
152
+ page.should have_content("Dr. Stop has a total of 2 appointments with: H. Chrondriac, B. Legg")
153
+ page.should have_content("Dr. Faith has no appointments.")
154
+ page.should have_content("Dr. Quack has a total of 1 appointments with: H. Chrondriac")
155
+ page.should have_content("H. Chrondriac - Doctors: Stop, Quack")
156
+ page.should have_content("B. Legg - Doctors: Stop")
157
+ page.should have_content("N. Help is not seeing any doctors")
158
+ end
159
+
160
+ it "can destroy an existing relationship from the server" do
161
+ evaluate_ruby do
162
+ PhysicianSchedule.hypertrace instrument: :all
163
+ ReactiveRecord::Base.hypertrace instrument: :all
164
+ end
165
+ @a1.destroy
166
+ page.should have_content("Dr. Stop has a total of 1 appointments with: B. Legg")
167
+ page.should have_content("Dr. Faith has no appointments.")
168
+ page.should have_content("Dr. Quack has a total of 1 appointments with: H. Chrondriac")
169
+ page.should have_content("H. Chrondriac - Doctors: Quack")
170
+ page.should have_content("B. Legg - Doctors: Stop")
171
+ page.should have_content("N. Help is not seeing any doctors")
172
+ end
173
+ end
@@ -0,0 +1,263 @@
1
+ require 'spec_helper'
2
+ require 'synchromesh/integration/test_components'
3
+
4
+ describe "synchronizing relationships", js: true do
5
+
6
+ before(:all) do
7
+ require 'pusher'
8
+ require 'pusher-fake'
9
+ Pusher.app_id = "MY_TEST_ID"
10
+ Pusher.key = "MY_TEST_KEY"
11
+ Pusher.secret = "MY_TEST_SECRET"
12
+ require "pusher-fake/support/base"
13
+
14
+ HyperMesh.configuration do |config|
15
+ config.transport = :pusher
16
+ config.channel_prefix = "synchromesh"
17
+ config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options)
18
+ end
19
+ end
20
+
21
+ before(:each) do
22
+ # spec_helper resets the policy system after each test so we have to setup
23
+ # before each test
24
+ stub_const 'TestApplication', Class.new
25
+ stub_const 'TestApplicationPolicy', Class.new
26
+ TestApplicationPolicy.class_eval do
27
+ always_allow_connection
28
+ regulate_all_broadcasts { |policy| policy.send_all }
29
+ allow_change(to: :all, on: [:create, :update, :destroy]) { true }
30
+ end
31
+ size_window(:small, :portrait)
32
+ end
33
+
34
+ it "belongs_to with count" do
35
+ parent = FactoryGirl.create(:test_model)
36
+ mount "TestComponent2", model: parent do
37
+ class TestComponent2 < React::Component::Base
38
+ param :model, type: TestModel
39
+ render(:div) do
40
+ puts "RENDERING! #{params.model.child_models.count} items"
41
+ div { "#{params.model.child_models.count} items" }
42
+ #ul { model.child_models.each { |model| li { model.child_attribute }}}
43
+ end
44
+ end
45
+ end
46
+
47
+ page.should have_content("0 items")
48
+ FactoryGirl.create(:child_model, test_model: parent, child_attribute: "first child")
49
+ page.should have_content("1 items")
50
+ parent.child_models << FactoryGirl.create(:child_model, child_attribute: "second child")
51
+ page.should have_content("2 items")
52
+ parent.child_models.first.destroy
53
+ page.should have_content("1 items")
54
+ end
55
+
56
+ it "belongs_to without the associated record" do
57
+ parent = FactoryGirl.create(:test_model)
58
+ mount "TestComponent2" do
59
+ class TestComponent2 < React::Component::Base
60
+ render(:div) do
61
+ div { "#{ChildModel.count} items" }
62
+ ul { ChildModel.each { |model| li { model.child_attribute }}}
63
+ end
64
+ end
65
+ end
66
+ page.should have_content("0 items")
67
+ FactoryGirl.create(:child_model, test_model: parent, child_attribute: "first child")
68
+ page.should have_content("1 items")
69
+ parent.child_models << FactoryGirl.create(:child_model, child_attribute: "second child")
70
+ page.should have_content("2 items")
71
+ parent.child_models.first.destroy
72
+ page.should have_content("1 items")
73
+ end
74
+
75
+ it "adding child to a new model on client" do
76
+ mount "TestComponent2" do
77
+ class TestComponent2 < React::Component::Base
78
+ define_state :foo
79
+ before_mount do
80
+ @parent = TestModel.new
81
+ @child = ChildModel.new
82
+ end
83
+ after_mount do
84
+ after(0) do # simulate external event updating system
85
+ @parent.child_models << @child
86
+ @parent.save
87
+ end
88
+ end
89
+ render(:div) do
90
+ "parent has #{@parent.child_models.count} children"
91
+ end
92
+ end
93
+ end
94
+ page.should have_content("parent has 1 children", wait: 1)
95
+ expect(TestModel.first.child_models.count).to eq(1)
96
+ end
97
+
98
+ it "adding child to a new model on client after render" do
99
+ # HyperMesh.configuration do |config|
100
+ # #config.transport = :none
101
+ # end
102
+ m = FactoryGirl.create(:test_model)
103
+ m.child_models << FactoryGirl.create(:child_model)
104
+ mount "TestComponent2" do
105
+ class TestComponent2 < React::Component::Base
106
+ def self.parent
107
+ @parent ||= TestModel.find(1)
108
+ end
109
+ def self.add_child
110
+ parent.child_models << ChildModel.new
111
+ parent.save
112
+ end
113
+ render(:div) do
114
+ "parent has #{TestComponent2.parent.child_models.count} children".tap { |s| puts s}
115
+ end
116
+ end
117
+ end
118
+ wait_for_ajax
119
+ expect(TestModel.first.child_models.count).to eq(1)
120
+ m.child_models << FactoryGirl.create(:child_model)
121
+ evaluate_ruby("TestComponent2.add_child")
122
+ page.should have_content("parent has 3 children")
123
+ end
124
+
125
+ it "will re-render the count after an item is added or removed from a model" do
126
+ m1 = FactoryGirl.create(:test_model)
127
+ mount "TestComponent2" do
128
+ class TestComponent2 < React::Component::Base
129
+ render(:div) do
130
+ "Count of TestModel: #{TestModel.count}".span
131
+ end
132
+ end
133
+ end
134
+ page.should have_content("Count of TestModel: 1")
135
+ m2 = FactoryGirl.create(:test_model)
136
+ page.should have_content("Count of TestModel: 2")
137
+ m1.destroy
138
+ page.should have_content("Count of TestModel: 1")
139
+ m2.destroy
140
+ page.should have_content("Count of TestModel: 0")
141
+ end
142
+
143
+ it "will re-render the model's all scope after an item is added or removed" do
144
+ m1 = FactoryGirl.create(:test_model)
145
+ mount "TestComponent2" do
146
+ class TestComponent2 < React::Component::Base
147
+ render(:div) do
148
+ puts "Count of TestModel: #{TestModel.collect { |i| i.id}.length}"
149
+ "Count of TestModel: #{TestModel.collect { |i| i.id}.length}".span
150
+ end
151
+ end
152
+ end
153
+ page.should have_content("Count of TestModel: 1")
154
+ m2 = FactoryGirl.create(:test_model)
155
+ page.should have_content("Count of TestModel: 2")
156
+ m1.destroy
157
+ page.should have_content("Count of TestModel: 1")
158
+ m2.destroy
159
+ page.should have_content("Count of TestModel: 0")
160
+ end
161
+
162
+ context "updating a client scoped method when applied to a collection" do
163
+
164
+ before(:each) do
165
+
166
+ isomorphic do
167
+ ChildModel.class_eval do
168
+ scope :boo_ha, -> { all }, client: -> { true }
169
+ end
170
+ end
171
+
172
+ m = FactoryGirl.create(:test_model, test_attribute: 'hello')
173
+ FactoryGirl.create(:child_model, test_model: m)
174
+
175
+ mount "TestComponent3" do
176
+ class TestComponent3 < React::Component::Base
177
+ render(OL) do
178
+ TestModel.all[0].child_models.boo_ha.each do |child|
179
+ LI { "child id = #{child.id} "}
180
+ end
181
+ end
182
+ end
183
+ end
184
+ page.should have_content('child id = 1')
185
+ end
186
+
187
+ it "will update when sent from the server" do
188
+ ChildModel.create(child_attribute: :foo, test_model: TestModel.find(1))
189
+ page.should have_content('child id = 2')
190
+ ChildModel.find(1).destroy
191
+ sleep 0.1 # necessary for poltergeist to work with pusher faker
192
+ page.should_not have_content('child id = 1', wait: 2)
193
+ end
194
+
195
+ it "will update when sent from the client" do
196
+ evaluate_ruby do
197
+ # ReactiveRecord::Collection.hypertrace instrument: :all
198
+ # ReactiveRecord::Collection.hypertrace :class, instrument: :all
199
+ # ReactiveRecord::Base.hypertrace instrument: :sync_unscoped_collection!
200
+ # ReactiveRecord::ScopeDescription.hypertrace instrument: :all
201
+
202
+ ChildModel.create(child_attribute: :foo, test_model: TestModel.find(1))
203
+ end
204
+ page.should have_content('child id = 2')
205
+ evaluate_ruby do
206
+ ChildModel.find(1).destroy
207
+ end
208
+ sleep 0.1 # necessary for poltergeist to work with pusher faker
209
+ page.should_not have_content('child id = 1', wait: 2)
210
+ end
211
+
212
+ end
213
+
214
+ context "updating a has_many relationship" do
215
+
216
+ before(:each) do
217
+
218
+ m = FactoryGirl.create(:test_model, test_attribute: 'hello')
219
+ FactoryGirl.create(:child_model, test_model: m)
220
+
221
+ mount "TestComponent3" do
222
+ class TestComponent3 < React::Component::Base
223
+ render(OL) do
224
+ TestModel.all[0].child_models.each do |child|
225
+ LI { "child id = #{child.id} "}
226
+ end
227
+ end
228
+ end
229
+ end
230
+ page.should have_content('child id = 1')
231
+ end
232
+
233
+ it "will update when sent from the server" do
234
+ ChildModel.create(child_attribute: :foo, test_model: TestModel.find(1))
235
+ page.should have_content('child id = 2')
236
+ ChildModel.find(1).destroy
237
+ sleep 0.1 # necessary for poltergeist to work with pusher faker
238
+ page.should_not have_content('child id = 1', wait: 2)
239
+ end
240
+
241
+ it "will update when sent from the client" do
242
+
243
+ evaluate_ruby do
244
+ ChildModel.create(child_attribute: :foo, test_model: TestModel.find(1))
245
+ end
246
+ page.should have_content('child id = 2')
247
+ evaluate_ruby do
248
+ # ReactiveRecord::Collection.hypertrace instrument: :all
249
+ # ReactiveRecord::Collection.hypertrace :class, instrument: :all
250
+ # ReactiveRecord::Base.hypertrace instrument: :sync_unscoped_collection!
251
+ # ReactiveRecord::ScopeDescription.hypertrace instrument: :all
252
+
253
+ ChildModel.find(1).destroy
254
+ end
255
+ sleep 0.1 # necessary for poltergeist to work with pusher faker
256
+ page.should_not have_content('child id = 1', wait: 2)
257
+ end
258
+
259
+ end
260
+
261
+ it "composed_of"
262
+
263
+ end
@@ -0,0 +1,553 @@
1
+ require 'spec_helper'
2
+ require 'synchromesh/integration/test_components'
3
+
4
+ describe "synchronized scopes", js: true do
5
+
6
+ before(:all) do
7
+ require 'pusher'
8
+ require 'pusher-fake'
9
+ Pusher.app_id = "MY_TEST_ID"
10
+ Pusher.key = "MY_TEST_KEY"
11
+ Pusher.secret = "MY_TEST_SECRET"
12
+ require "pusher-fake/support/base"
13
+
14
+ HyperMesh.configuration do |config|
15
+ config.transport = :pusher
16
+ config.channel_prefix = "synchromesh"
17
+ config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options)
18
+ end
19
+ end
20
+
21
+ before(:all) do
22
+ TestModel.class_eval do
23
+ class << self
24
+ alias pretest_scope scope
25
+ def scope(name, *args, &block)
26
+ opts = _synchromesh_scope_args_check(args)
27
+ self.class.class_eval do
28
+ attr_reader "#{name}_count".to_sym
29
+ end
30
+ instance_variable_set("@#{name}_count", 0)
31
+ original_proc = opts[:server]
32
+ opts[:server] = lambda do |*args|
33
+ instance_variable_set("@#{name}_count", send("#{name}_count")+1); original_proc.call(*args, &block)
34
+ end
35
+ pretest_scope name, opts, &block
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ before(:each) do
42
+ # spec_helper resets the policy system after each test so we have to setup
43
+ # before each test
44
+ stub_const 'TestApplicationPolicy', Class.new
45
+ TestApplicationPolicy.class_eval do
46
+ always_allow_connection
47
+ regulate_all_broadcasts { |policy| policy.send_all }
48
+ end
49
+ size_window(:small, :portrait)
50
+ end
51
+
52
+ it "can use the all scope" do
53
+ mount "TestComponent2" do
54
+ class TestComponent2 < React::Component::Base
55
+ render do
56
+ "count = #{TestModel.all.count}"
57
+ end
58
+ end
59
+ end
60
+ page.should have_content("count = 0")
61
+ m = FactoryGirl.create(:test_model)
62
+ page.should have_content("count = 1")
63
+ m.destroy
64
+ page.should have_content("count = 0")
65
+ end
66
+
67
+ it "will be updated only when needed" do
68
+ isomorphic do
69
+ TestModel.class_eval do
70
+ scope :scope1, -> { where(completed: true) }
71
+ scope :scope2, -> { where(completed: true) }
72
+ end
73
+ end
74
+ mount "TestComponent2" do
75
+ class TestComponent2 < React::Component::Base
76
+ before_mount do
77
+ @render_count = 1
78
+ end
79
+ before_update do
80
+ @render_count = @render_count + 1
81
+ end
82
+ render(:div) do
83
+ div { "rendered #{@render_count} times"}
84
+ div { "scope1 count = #{TestModel.scope1.count}" }
85
+ div { "scope2 count = #{TestModel.scope2.count}"} if TestModel.scope1.count < 2
86
+ TestModel.scope1.each do |model|
87
+ div { model.test_attribute }
88
+ end
89
+ end
90
+ end
91
+ end
92
+ page.should have_content('rendered 2 times')
93
+ page.should have_content('scope1 count = 0')
94
+ page.should have_content('scope2 count = 0')
95
+ TestModel.scope1_count.should eq(2) # once for scope1.count and once for scope1.each { .test_attribute }
96
+ TestModel.scope2_count.should eq(1)
97
+ m1 = FactoryGirl.create(:test_model, test_attribute: "model 1", completed: true)
98
+ page.should have_content('rendered 3 times')
99
+ page.should have_content('scope1 count = 1')
100
+ page.should have_content('scope2 count = 1')
101
+ page.should have_content('model 1')
102
+ TestModel.scope1_count.should eq(3)
103
+ TestModel.scope2_count.should eq(2)
104
+ m2 = FactoryGirl.create(:test_model, test_attribute: "model 2", completed: false)
105
+ page.should have_content('rendered 4 times')
106
+ page.should have_content('scope1 count = 1')
107
+ page.should have_content('scope2 count = 1')
108
+ page.should have_content('model 1')
109
+ TestModel.scope1_count.should eq(4)
110
+ TestModel.scope2_count.should eq(3)
111
+ FactoryGirl.create(:test_model, test_attribute: "model 3", completed: true)
112
+ page.should have_content('rendered 5 times')
113
+ page.should have_content('scope1 count = 2')
114
+ page.should_not have_content('scope2', wait: 0)
115
+ page.should have_content('model 1')
116
+ page.should have_content('model 3')
117
+ TestModel.scope1_count.should eq(5)
118
+ TestModel.scope2_count.should eq(4)
119
+ m2.update_attribute(:completed, true)
120
+ page.should have_content('rendered 6 times')
121
+ page.should have_content('scope1 count = 3')
122
+ page.should have_content('model 1')
123
+ page.should have_content('model 2')
124
+ page.should have_content('model 3')
125
+ TestModel.scope1_count.should eq(6)
126
+ TestModel.scope2_count.should eq(4) # will not change because nothing is viewing it
127
+ m2.update_attribute(:completed, false)
128
+ page.should have_content('rendered 7 times')
129
+ TestModel.scope1_count.should eq(7)
130
+ m1.update_attribute(:completed, false)
131
+ page.should have_content('rendered 9 times')
132
+ page.should have_content('scope1 count = 1')
133
+ page.should have_content('scope2 count = 1')
134
+ page.should have_content('model 3')
135
+ TestModel.scope1_count.should eq(8)
136
+ TestModel.scope2_count.should eq(5)
137
+ end
138
+
139
+ it "can have params" do
140
+ isomorphic do
141
+ TestModel.class_eval do
142
+ scope :with_args, ->(match, match2) { where(test_attribute: match) }
143
+ end
144
+ end
145
+ m1 = FactoryGirl.create(:test_model, test_attribute: "123")
146
+ mount "TestComponent2", m: m1 do
147
+ class TestComponent2 < React::Component::Base
148
+ param :m, type: TestModel
149
+ before_mount do
150
+ @render_count = 1
151
+ end
152
+ before_update do
153
+ @render_count = @render_count + 1
154
+ end
155
+ render(:div) do
156
+ div { "rendered #{@render_count} times"}
157
+ div { "with_args('match').count = #{TestModel.with_args(params.m.test_attribute, :foo).count}" }
158
+ end
159
+ end
160
+ end
161
+ m2 = FactoryGirl.create(:test_model)
162
+ page.should have_content('.count = 1')
163
+ m2.update_attribute(:test_attribute, m1.test_attribute)
164
+ page.should have_content('.count = 2')
165
+ m1.update_attribute(:test_attribute, '456')
166
+ page.should have_content('.count = 1')
167
+ end
168
+
169
+ it "scopes with params can be nested" do
170
+ isomorphic do
171
+ TestModel.class_eval do
172
+ scope :scope1, -> { where(completed: true) }
173
+ scope :with_args, -> (match) { where(test_attribute: match) }
174
+ end
175
+ end
176
+ mount "TestComponent2" do
177
+ class TestComponent2 < React::Component::Base
178
+ render(:div) do
179
+ div { "scope1.scope2.count = #{TestModel.scope1.with_args(:foo).count}" }
180
+ end
181
+ end
182
+ end
183
+ page.should have_content('scope1.scope2.count = 0')
184
+ FactoryGirl.create(:test_model, test_attribute: "foo", completed: true)
185
+ page.should have_content('scope1.scope2.count = 1')
186
+ end
187
+
188
+ context 'basic joins' do
189
+
190
+ it 'will not update a joined scope without a joins option' do
191
+ isomorphic do
192
+ TestModel.class_eval do
193
+ scope :joined,
194
+ -> { joins(:child_models).where("child_attribute = 'WHAAA'") }
195
+ end
196
+ end
197
+ mount 'TestComponent2' do
198
+ class TestComponent2 < React::Component::Base
199
+ render { "TestModel.joined.count = #{TestModel.joined.count}" }
200
+ end
201
+ end
202
+ parent = FactoryGirl.create(:test_model)
203
+ child = FactoryGirl.create(:child_model, test_model: parent )
204
+ page.should have_content('.count = 0')
205
+ child.update_attribute(:child_attribute, 'WHAAA')
206
+ page.should have_content('.count = 0')
207
+ end
208
+
209
+ it 'can have a joins option' do
210
+ isomorphic do
211
+ TestModel.class_eval do
212
+ scope :joined,
213
+ -> { joins(:child_models).where("child_attribute = 'WHAAA'") },
214
+ joins: 'child_models'
215
+ end
216
+ end
217
+ mount 'TestComponent2' do
218
+ class TestComponent2 < React::Component::Base
219
+ render { "TestModel.joined.count = #{TestModel.joined.count}" }
220
+ end
221
+ end
222
+ parent = FactoryGirl.create(:test_model)
223
+ child = FactoryGirl.create(:child_model, test_model: parent )
224
+ page.should have_content('.count = 0')
225
+ child.update_attribute(:child_attribute, 'WHAAA')
226
+ page.should have_content('.count = 1')
227
+ end
228
+ end
229
+
230
+ it 'can have a client filter method' do
231
+
232
+ isomorphic do
233
+ TestModel.class_eval do
234
+ scope :quicker, -> { where(completed: true) }, client: -> { completed }
235
+ end
236
+ end
237
+
238
+ mount 'TestComponent2' do
239
+ class TestComponent2 < React::Component::Base
240
+ before_mount do
241
+ @render_count = 1
242
+ end
243
+ before_update do
244
+ @render_count = @render_count + 1
245
+ end
246
+ render(:div) do
247
+ div { "rendered #{@render_count} times"}
248
+ div { "quicker.count = #{TestModel.quicker.count}" }
249
+ end
250
+ end
251
+ end
252
+ m1 = FactoryGirl.create(:test_model)
253
+ page.should have_content('.count = 0')
254
+ page.should have_content('rendered 2 times')
255
+ TestModel.quicker_count.should eq(1)
256
+ m1.update_attribute(:test_attribute, 'new_value')
257
+ wait_for_ajax
258
+ page.should have_content('.count = 0')
259
+ page.should have_content('rendered 2 times')
260
+ TestModel.quicker_count.should eq(1)
261
+ m1.update_attribute(:completed, true)
262
+ page.should have_content('.count = 1')
263
+ page.should have_content('rendered 3 times')
264
+ TestModel.quicker_count.should eq(1)
265
+ m2 = FactoryGirl.create(:test_model, completed: true)
266
+ page.should have_content('.count = 2')
267
+ page.should have_content('rendered 4 times')
268
+ TestModel.quicker_count.should eq(1)
269
+ m1.destroy
270
+ page.should have_content('.count = 1')
271
+ page.should have_content('rendered 5 times')
272
+ TestModel.quicker_count.should eq(1)
273
+ end
274
+
275
+ it 'can have a client collector method' do
276
+
277
+ isomorphic do
278
+ TestModel.class_eval do
279
+ def <=>(other); self.test_attribute <=> other.test_attribute; end
280
+ scope :filter_and_sort, -> { where(completed: true).order('test_attribute ASC') },
281
+ select: -> { select { |r| r.completed }.sort }
282
+ end
283
+ end
284
+
285
+ mount 'TestComponent2' do
286
+ class TestComponent2 < React::Component::Base
287
+ before_mount do
288
+ @render_count = 1
289
+ end
290
+ before_update do
291
+ @render_count = @render_count + 1
292
+ end
293
+ render(:div) do
294
+ div { "rendered #{@render_count} times"}
295
+ div { "filter_and_sort.count = #{TestModel.filter_and_sort.count}" }
296
+ if TestModel.filter_and_sort.any?
297
+ div { "test attributes: #{TestModel.filter_and_sort.collect { |r| r.test_attribute }.join(', ')}" }
298
+ else
299
+ div { "no test attributes" }
300
+ end
301
+ end
302
+ end
303
+ end
304
+ TestModel.filter_and_sort_count.should eq(2)
305
+ m1 = FactoryGirl.create(:test_model)
306
+ page.should have_content('.count = 0')
307
+ page.should have_content('rendered 2 times')
308
+ page.should have_content('no test attributes')
309
+ TestModel.filter_and_sort_count.should eq(2)
310
+ m1.update_attribute(:test_attribute, 'N')
311
+ wait_for_ajax
312
+ page.should have_content('.count = 0')
313
+ page.should have_content('rendered 2 times')
314
+ page.should have_content('no test attributes')
315
+ TestModel.filter_and_sort_count.should eq(2)
316
+ m1.update_attribute(:completed, true)
317
+ page.should have_content('.count = 1')
318
+ page.should have_content('rendered 3 times')
319
+ page.should have_content('test attributes: N')
320
+ TestModel.filter_and_sort_count.should eq(2)
321
+ m2 = FactoryGirl.create(:test_model, test_attribute: 'A', completed: true)
322
+ page.should have_content('.count = 2')
323
+ page.should have_content('rendered 4 times')
324
+ page.should have_content('test attributes: A, N')
325
+ TestModel.filter_and_sort_count.should eq(2)
326
+ m3 = FactoryGirl.create(:test_model, test_attribute: 'Z', completed: true)
327
+ page.should have_content('.count = 3')
328
+ page.should have_content('rendered 5 times')
329
+ page.should have_content('test attributes: A, N, Z')
330
+ TestModel.filter_and_sort_count.should eq(2)
331
+ m1.destroy
332
+ page.should have_content('.count = 2')
333
+ page.should have_content('rendered 6 times')
334
+ page.should have_content('test attributes: A, Z')
335
+ TestModel.filter_and_sort_count.should eq(2)
336
+ m3.update_attribute(:completed, false)
337
+ page.should have_content('.count = 1')
338
+ page.should have_content('rendered 7 times')
339
+ page.should have_content('test attributes: A')
340
+ TestModel.filter_and_sort_count.should eq(2)
341
+ end
342
+
343
+ it 'client side scoping methods can take args' do
344
+
345
+ isomorphic do
346
+ TestModel.class_eval do
347
+ def to_s
348
+ "<TestModel id: #{id} test_attribute: #{test_attribute}>"
349
+ end
350
+ def <=>(other)
351
+ (test_attribute || '') <=> (other.test_attribute || '')
352
+ end
353
+ scope :matches, -> (s) { where(['test_attribute LIKE ?', "%#{s}%"])},
354
+ client: -> (s) { puts "test_attribute =~ /#{s}/ is #{!!(test_attribute =~ /#{s}/)}"; test_attribute =~ /#{s}/ }
355
+ scope :sorted, -> (dir) { order("test_attribute #{dir==:asc ? 'ASC' : 'DESC'}") },
356
+ select: -> (dir) { sort { |a, b| dir == :asc ? a <=> b : b <=> a }.tap {|r| puts "sorted(#{dir==:asc}) as [#{r}]"} }
357
+ end
358
+ end
359
+
360
+ mount 'TestComponent2' do
361
+ class TestComponent2 < React::Component::Base
362
+ export_state pat: :foo
363
+ export_state dir: :asc
364
+ before_mount do
365
+ @render_count = 1
366
+ end
367
+ before_update do
368
+ @render_count = @render_count + 1
369
+ end
370
+ def scoped
371
+ puts "scoping: #{TestComponent2.pat}, #{TestComponent2.dir}"
372
+ TestModel.matches(TestComponent2.pat).sorted(TestComponent2.dir)
373
+ end
374
+ render(:div) do
375
+ div { "rendered #{@render_count} times"}
376
+ div { "matches(#{TestComponent2.pat}).count = #{TestModel.matches(TestComponent2.pat).count}" }
377
+ if scoped.any?
378
+ div { "test attributes: #{scoped.collect { |r| r.test_attribute[0] }.join(', ')}" }
379
+ else
380
+ div { "no test attributes" }
381
+ end
382
+ end
383
+ end
384
+ end
385
+ starting_fetch_time = evaluate_ruby("ReactiveRecord::Base.last_fetch_at")
386
+ m1 = FactoryGirl.create(:test_model)
387
+ page.should have_content('.count = 0')
388
+ page.should have_content('rendered 2 times')
389
+ page.should have_content('no test attributes')
390
+ m1.update_attribute(:test_attribute, 'N')
391
+ page.should have_content('rendered 2 times')
392
+ page.should have_content('.count = 0')
393
+ page.should have_content('no test attributes')
394
+ m1.update_attribute(:test_attribute, 'N - foobar')
395
+ page.should have_content('.count = 1')
396
+ page.should have_content('rendered 3 times')
397
+ page.should have_content('test attributes: N')
398
+ m2 = FactoryGirl.create(:test_model, test_attribute: 'A - is a foo')
399
+ page.should have_content('.count = 2')
400
+ page.should have_content('rendered 4 times')
401
+ page.should have_content('test attributes: A, N')
402
+ m3 = FactoryGirl.create(:test_model, test_attribute: 'Z - is also a foo')
403
+ page.should have_content('.count = 3')
404
+ page.should have_content('rendered 5 times')
405
+ page.should have_content('test attributes: A, N, Z')
406
+ evaluate_ruby("ReactiveRecord::Base.last_fetch_at").should eq(starting_fetch_time)
407
+ evaluate_ruby("TestComponent2.dir! :desc")
408
+ page.should have_content('test attributes: Z, N, A')
409
+ page.should have_content('rendered 7 times')
410
+ starting_fetch_time = evaluate_ruby("ReactiveRecord::Base.last_fetch_at")
411
+ m1.destroy
412
+ page.should have_content('.count = 2')
413
+ page.should have_content('rendered 8 times')
414
+ page.should have_content('test attributes: Z, A')
415
+ m3.update_attribute(:test_attribute, 'Z - is a bar')
416
+ page.should have_content('.count = 1')
417
+ page.should have_content('rendered 9 times')
418
+ page.should have_content('test attributes: A')
419
+ evaluate_ruby("ReactiveRecord::Base.last_fetch_at").should eq(starting_fetch_time)
420
+ evaluate_ruby('TestComponent2.pat! :bar')
421
+ page.should have_content('.count = 1')
422
+ page.should have_content('test attributes: Z')
423
+ page.should have_content('rendered 11 times')
424
+ starting_fetch_time = evaluate_ruby("ReactiveRecord::Base.last_fetch_at")
425
+ m2.update_attribute(:test_attribute, 'A is also a bar')
426
+ page.should have_content('.count = 2')
427
+ page.should have_content('rendered 12 times')
428
+ page.should have_content('test attributes: Z, A')
429
+ evaluate_ruby("ReactiveRecord::Base.last_fetch_at").should eq(starting_fetch_time)
430
+ end
431
+
432
+ it 'the joins array can be combined with the client proc' do
433
+ isomorphic do
434
+ TestModel.class_eval do
435
+ scope :has_children,
436
+ joins: 'child_models',
437
+ server: -> { joins(:child_models).distinct },
438
+ client: -> { child_models.count > 0 }
439
+ end
440
+ end
441
+ mount 'TestComponent2' do
442
+ class TestComponent2 < React::Component::Base
443
+ render { "TestModel.has_children.count = #{TestModel.has_children.count}" }
444
+ end
445
+ end
446
+ TestModel.has_children_count.should eq(1)
447
+ parent = FactoryGirl.create(:test_model)
448
+ page.should have_content('.count = 0')
449
+ child = FactoryGirl.create(:child_model)
450
+ page.should have_content('.count = 0')
451
+ child.update_attribute(:test_model, parent)
452
+ page.should have_content('.count = 1')
453
+ child.update_attribute(:child_attribute, 'WHAAA')
454
+ page.should have_content('.count = 1')
455
+ child.update_attribute(:test_model, nil)
456
+ page.should have_content('.count = 0')
457
+ child.update_attribute(:test_model, parent)
458
+ page.should have_content('.count = 1')
459
+ child.destroy
460
+ page.should have_content('.count = 0')
461
+ parent.destroy
462
+ page.should have_content('.count = 0')
463
+ TestModel.has_children_count.should eq(1)
464
+ end
465
+
466
+ it 'the joins option can join with all or no models' do
467
+ isomorphic do
468
+ TestModel.class_eval do
469
+ scope :has_children_joins_all,
470
+ joins: :all,
471
+ server: -> { joins(:child_models).distinct }
472
+ scope :has_children_no_joins,
473
+ joins: [],
474
+ server: -> { joins(:child_models).distinct }
475
+ end
476
+ end
477
+ mount 'TestComponent2' do
478
+ class TestComponent2 < React::Component::Base
479
+ render(DIV) do
480
+ DIV { "TestModel.has_children_joins_all.count = #{TestModel.has_children_joins_all.count}" }
481
+ DIV { "TestModel.has_children_no_joins.count = #{TestModel.has_children_no_joins.count}" }
482
+ end
483
+ end
484
+ end
485
+ TestModel.has_children_joins_all_count.should eq(1)
486
+ TestModel.has_children_no_joins_count.should eq(1)
487
+ parent = FactoryGirl.create(:test_model)
488
+ wait_for_ajax
489
+ TestModel.has_children_no_joins_count.should eq(1)
490
+ wait_for { TestModel.has_children_joins_all_count }.to eq(2)
491
+ child = FactoryGirl.create(:child_model)
492
+ wait_for { TestModel.has_children_joins_all_count }.to eq(3)
493
+ parent.child_models << child
494
+ wait_for { TestModel.has_children_joins_all_count }.to eq(4)
495
+ TestModel.has_children_no_joins_count.should eq(1)
496
+ page.should have_content('all.count = 1')
497
+ page.should have_content('joins.count = 0')
498
+ parent.update_attribute(:test_attribute, 'hello')
499
+ page.should have_content('joins.count = 0')
500
+ TestModel.has_children_no_joins_count.should eq(1)
501
+ end
502
+
503
+ it 'server side scopes can be procs returning arrays of elements' do
504
+ isomorphic do
505
+ TestModel.class_eval do
506
+ scope :rev,
507
+ server: -> { all.reverse }
508
+ end
509
+ end
510
+ mount 'TestComponent2' do
511
+ class TestComponent2 < React::Component::Base
512
+ render do
513
+ "test attributes: #{TestModel.rev.collect { |r| r.test_attribute }.join(', ')}"
514
+ end
515
+ end
516
+ end
517
+ FactoryGirl.create(:test_model, test_attribute: 1)
518
+ page.should have_content('test attributes: 1')
519
+ FactoryGirl.create(:test_model, test_attribute: 2)
520
+ page.should have_content('test attributes: 2, 1')
521
+ end
522
+
523
+ it 'can force reload using the ! suffix' do
524
+ isomorphic do
525
+ TestModel.class_eval do
526
+ scope :rev,
527
+ joins: [],
528
+ server: -> { all.reverse }
529
+ end
530
+ end
531
+ mount 'TestComponent2' do
532
+
533
+ class TestComponent2 < React::Component::Base
534
+ before_mount do
535
+ @reverse_collection = []
536
+ end
537
+ render do
538
+ if @current_count != TestModel.all.count # this forces a rerender...
539
+ puts "asking for reverse collection"
540
+ @reverse_collection = TestModel.rev!
541
+ @current_count = TestModel.all.count
542
+ end
543
+ puts "rendering #{@reverse_collection}"
544
+ "test attributes: #{@reverse_collection.collect { |r| r.test_attribute }.join(', ')}"
545
+ end
546
+ end
547
+ end
548
+ FactoryGirl.create(:test_model, test_attribute: 1)
549
+ page.should have_content('test attributes: 1')
550
+ FactoryGirl.create(:test_model, test_attribute: 2)
551
+ page.should have_content('test attributes: 2, 1')
552
+ end
553
+ end