riojs 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (270) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +24 -0
  3. data/VERSION +1 -0
  4. data/bin/rio +5 -0
  5. data/generators/rio_app/USAGE +19 -0
  6. data/generators/rio_app/rio_app_generator.rb +40 -0
  7. data/generators/rio_app/templates/app.build +10 -0
  8. data/generators/rio_app/templates/app.css +0 -0
  9. data/generators/rio_app/templates/app.js +20 -0
  10. data/generators/rio_app/templates/app_view.html.erb +19 -0
  11. data/generators/rio_app/templates/fixture.js +4 -0
  12. data/generators/rio_app/templates/rio.html.erb +18 -0
  13. data/generators/rio_app/templates/rio_controller.rb +2 -0
  14. data/generators/rio_app/templates/spec.js +7 -0
  15. data/generators/rio_component/USAGE +13 -0
  16. data/generators/rio_component/rio_component_generator.rb +16 -0
  17. data/generators/rio_component/templates/component.css +0 -0
  18. data/generators/rio_component/templates/component.js +11 -0
  19. data/generators/rio_component/templates/fixture.js +3 -0
  20. data/generators/rio_component/templates/spec.js +6 -0
  21. data/generators/rio_model/USAGE +12 -0
  22. data/generators/rio_model/rio_model_generator.rb +21 -0
  23. data/generators/rio_model/templates/fixture.js +3 -0
  24. data/generators/rio_model/templates/model.js +9 -0
  25. data/generators/rio_model/templates/spec.js +6 -0
  26. data/generators/rio_page/USAGE +14 -0
  27. data/generators/rio_page/rio_page_generator.rb +16 -0
  28. data/generators/rio_page/templates/fixture.js +3 -0
  29. data/generators/rio_page/templates/page.css +12 -0
  30. data/generators/rio_page/templates/page.js +12 -0
  31. data/generators/rio_page/templates/page.jst +64 -0
  32. data/generators/rio_page/templates/spec.js +6 -0
  33. data/generators/rio_resource/USAGE +20 -0
  34. data/generators/rio_resource/rio_resource_generator.rb +10 -0
  35. data/generators/rio_resource/templates/controller.rb +3 -0
  36. data/init.rb +4 -0
  37. data/install/config/juggernaut_hosts.yml +18 -0
  38. data/install/lib/tasks/rio.rake +1 -0
  39. data/install/script/rio_server +37 -0
  40. data/lib/rio/autospec.rb +86 -0
  41. data/lib/rio/install.rb +90 -0
  42. data/lib/rio/juggernaut.rb +212 -0
  43. data/lib/rio/path.rb +3 -0
  44. data/lib/rio/rio_compressor.rb +219 -0
  45. data/lib/rio/rio_file_controller.rb +16 -0
  46. data/lib/rio/rio_on_rails.rb +586 -0
  47. data/lib/rio/rio_proxy_controller.rb +60 -0
  48. data/lib/rio/rio_push_controller.rb +48 -0
  49. data/lib/rio/rio_routes.rb +24 -0
  50. data/lib/rio/rio_spec_controller.rb +70 -0
  51. data/lib/riojs.rb +14 -0
  52. data/lib/tasks/rio.rb +63 -0
  53. data/public/images/background-chiffon.png +0 -0
  54. data/public/images/button-gradient-overlay-down.png +0 -0
  55. data/public/images/button-gradient-overlay.png +0 -0
  56. data/public/images/icons/add.png +0 -0
  57. data/public/images/icons/error-big.png +0 -0
  58. data/public/images/icons/warning-big.png +0 -0
  59. data/public/images/rio-logo-big.png +0 -0
  60. data/public/images/rio-logo.png +0 -0
  61. data/public/images/splitter-handle-horizontal.png +0 -0
  62. data/public/images/splitter-handle-vertical.png +0 -0
  63. data/public/images/tab-bar-gradient-overlay.png +0 -0
  64. data/public/images/title-gradient-overlay.png +0 -0
  65. data/public/images/trash.gif +0 -0
  66. data/public/javascripts/components/accordion.js +144 -0
  67. data/public/javascripts/components/alert_box.js +59 -0
  68. data/public/javascripts/components/base.js +47 -0
  69. data/public/javascripts/components/box.js +63 -0
  70. data/public/javascripts/components/button.js +98 -0
  71. data/public/javascripts/components/checkbox.js +44 -0
  72. data/public/javascripts/components/container.js +265 -0
  73. data/public/javascripts/components/grid_view.js +107 -0
  74. data/public/javascripts/components/image.js +19 -0
  75. data/public/javascripts/components/input.js +171 -0
  76. data/public/javascripts/components/label.js +15 -0
  77. data/public/javascripts/components/lightbox.js +160 -0
  78. data/public/javascripts/components/link.js +43 -0
  79. data/public/javascripts/components/list_item.js +44 -0
  80. data/public/javascripts/components/list_view.js +192 -0
  81. data/public/javascripts/components/marquee.js +131 -0
  82. data/public/javascripts/components/menu.js +89 -0
  83. data/public/javascripts/components/notification.js +75 -0
  84. data/public/javascripts/components/overlay.js +134 -0
  85. data/public/javascripts/components/panel.js +146 -0
  86. data/public/javascripts/components/radio.js +46 -0
  87. data/public/javascripts/components/splitter.js +65 -0
  88. data/public/javascripts/components/tab_bar.js +64 -0
  89. data/public/javascripts/components/tab_panel.js +57 -0
  90. data/public/javascripts/components/textarea.js +223 -0
  91. data/public/javascripts/components/toggle_button.js +22 -0
  92. data/public/javascripts/components/tooltip.js +80 -0
  93. data/public/javascripts/lib/application.js +482 -0
  94. data/public/javascripts/lib/attr.js +760 -0
  95. data/public/javascripts/lib/benchmark.js +235 -0
  96. data/public/javascripts/lib/blank.html +39 -0
  97. data/public/javascripts/lib/boot.js +300 -0
  98. data/public/javascripts/lib/clipboard.js +96 -0
  99. data/public/javascripts/lib/collection_entity.js +46 -0
  100. data/public/javascripts/lib/component.js +129 -0
  101. data/public/javascripts/lib/console.js +75 -0
  102. data/public/javascripts/lib/console/apps/console.build +43 -0
  103. data/public/javascripts/lib/console/apps/console.js +28 -0
  104. data/public/javascripts/lib/console/blank.html +39 -0
  105. data/public/javascripts/lib/console/components/benchmark.js +196 -0
  106. data/public/javascripts/lib/console/components/console.js +352 -0
  107. data/public/javascripts/lib/console/components/dependencies_list.js +17 -0
  108. data/public/javascripts/lib/console/components/docs.js +66 -0
  109. data/public/javascripts/lib/console/components/playground.js +30 -0
  110. data/public/javascripts/lib/console/console.html +27 -0
  111. data/public/javascripts/lib/console/console_commands.js +287 -0
  112. data/public/javascripts/lib/console/console_commands.js.rej +21 -0
  113. data/public/javascripts/lib/console/console_mixin.js +22 -0
  114. data/public/javascripts/lib/console/docs/files.html +579 -0
  115. data/public/javascripts/lib/console/docs/index.html +323 -0
  116. data/public/javascripts/lib/console/docs/symbols/Object.html +291 -0
  117. data/public/javascripts/lib/console/docs/symbols/_global_.html +413 -0
  118. data/public/javascripts/lib/console/docs/symbols/rio.AIM.html +490 -0
  119. data/public/javascripts/lib/console/docs/symbols/rio.Application.html +841 -0
  120. data/public/javascripts/lib/console/docs/symbols/rio.Attr.html +1075 -0
  121. data/public/javascripts/lib/console/docs/symbols/rio.Binding.html +272 -0
  122. data/public/javascripts/lib/console/docs/symbols/rio.Component.html +419 -0
  123. data/public/javascripts/lib/console/docs/symbols/rio.Cookie.html +543 -0
  124. data/public/javascripts/lib/console/docs/symbols/rio.DelayedTask#initialize.html +270 -0
  125. data/public/javascripts/lib/console/docs/symbols/rio.DelayedTask.html +391 -0
  126. data/public/javascripts/lib/console/docs/symbols/rio.JsTemplate.html +271 -0
  127. data/public/javascripts/lib/console/docs/symbols/rio.Juggernaut.html +329 -0
  128. data/public/javascripts/lib/console/docs/symbols/rio.Model.html +822 -0
  129. data/public/javascripts/lib/console/docs/symbols/rio.Page.html +383 -0
  130. data/public/javascripts/lib/console/docs/symbols/rio.Template.html +328 -0
  131. data/public/javascripts/lib/console/docs/symbols/rio.Utils.html +617 -0
  132. data/public/javascripts/lib/console/docs/symbols/rio.html +506 -0
  133. data/public/javascripts/lib/console/docs/symbols/src/public_javascripts_components_base.js.html +54 -0
  134. data/public/javascripts/lib/console/docs/symbols/src/public_javascripts_lib_application.js.html +490 -0
  135. data/public/javascripts/lib/console/docs/symbols/src/public_javascripts_lib_attr.js.html +768 -0
  136. data/public/javascripts/lib/console/docs/symbols/src/public_javascripts_lib_boot.js.html +308 -0
  137. data/public/javascripts/lib/console/docs/symbols/src/public_javascripts_lib_clipboard.js.html +103 -0
  138. data/public/javascripts/lib/console/docs/symbols/src/public_javascripts_lib_collection_entity.js.html +53 -0
  139. data/public/javascripts/lib/console/docs/symbols/src/public_javascripts_lib_component.js.html +137 -0
  140. data/public/javascripts/lib/console/docs/symbols/src/public_javascripts_lib_cookie.js.html +81 -0
  141. data/public/javascripts/lib/console/docs/symbols/src/public_javascripts_lib_delayed_task.js.html +68 -0
  142. data/public/javascripts/lib/console/docs/symbols/src/public_javascripts_lib_file.js.html +80 -0
  143. data/public/javascripts/lib/console/docs/symbols/src/public_javascripts_lib_flash_detect.js.html +129 -0
  144. data/public/javascripts/lib/console/docs/symbols/src/public_javascripts_lib_form.js.html +95 -0
  145. data/public/javascripts/lib/console/docs/symbols/src/public_javascripts_lib_id.js.html +50 -0
  146. data/public/javascripts/lib/console/docs/symbols/src/public_javascripts_lib_inflector.js.html +167 -0
  147. data/public/javascripts/lib/console/docs/symbols/src/public_javascripts_lib_js_template.js.html +283 -0
  148. data/public/javascripts/lib/console/docs/symbols/src/public_javascripts_lib_juggernaut.js.html +303 -0
  149. data/public/javascripts/lib/console/docs/symbols/src/public_javascripts_lib_key_map.js.html +68 -0
  150. data/public/javascripts/lib/console/docs/symbols/src/public_javascripts_lib_layout_manager.js.html +175 -0
  151. data/public/javascripts/lib/console/docs/symbols/src/public_javascripts_lib_log.js.html +17 -0
  152. data/public/javascripts/lib/console/docs/symbols/src/public_javascripts_lib_model.js.html +1074 -0
  153. data/public/javascripts/lib/console/docs/symbols/src/public_javascripts_lib_page.js.html +246 -0
  154. data/public/javascripts/lib/console/docs/symbols/src/public_javascripts_lib_parameters.js.html +66 -0
  155. data/public/javascripts/lib/console/docs/symbols/src/public_javascripts_lib_protohack.js.html +305 -0
  156. data/public/javascripts/lib/console/docs/symbols/src/public_javascripts_lib_push.js.html +12 -0
  157. data/public/javascripts/lib/console/docs/symbols/src/public_javascripts_lib_rsh.js.html +659 -0
  158. data/public/javascripts/lib/console/docs/symbols/src/public_javascripts_lib_swfobject.js.html +12 -0
  159. data/public/javascripts/lib/console/docs/symbols/src/public_javascripts_lib_tag.js.html +60 -0
  160. data/public/javascripts/lib/console/docs/symbols/src/public_javascripts_lib_template.js.html +64 -0
  161. data/public/javascripts/lib/console/docs/symbols/src/public_javascripts_lib_theme.js.html +105 -0
  162. data/public/javascripts/lib/console/docs/symbols/src/public_javascripts_lib_undo.js.html +142 -0
  163. data/public/javascripts/lib/console/docs/symbols/src/public_javascripts_lib_utils.js.html +87 -0
  164. data/public/javascripts/lib/console/docs/symbols/src/public_javascripts_lib_yaml.js.html +88 -0
  165. data/public/javascripts/lib/console/file-small.png +0 -0
  166. data/public/javascripts/lib/console/green-circle.png +0 -0
  167. data/public/javascripts/lib/console/loading.gif +0 -0
  168. data/public/javascripts/lib/console/pages/console_page.js +149 -0
  169. data/public/javascripts/lib/console/pages/console_page.jst +27 -0
  170. data/public/javascripts/lib/console/red-circle.png +0 -0
  171. data/public/javascripts/lib/cookie.js +74 -0
  172. data/public/javascripts/lib/delayed_task.js +61 -0
  173. data/public/javascripts/lib/dependencies.js +76 -0
  174. data/public/javascripts/lib/environment.js +30 -0
  175. data/public/javascripts/lib/event.simulate.js +137 -0
  176. data/public/javascripts/lib/expressinstall.swf +0 -0
  177. data/public/javascripts/lib/file.js +72 -0
  178. data/public/javascripts/lib/flash_detect.js +122 -0
  179. data/public/javascripts/lib/flashembed.min.js +16 -0
  180. data/public/javascripts/lib/form.js +88 -0
  181. data/public/javascripts/lib/id.js +43 -0
  182. data/public/javascripts/lib/inflector.js +160 -0
  183. data/public/javascripts/lib/instrumenter.js +106 -0
  184. data/public/javascripts/lib/js_template.js +275 -0
  185. data/public/javascripts/lib/jslint.js +4950 -0
  186. data/public/javascripts/lib/juggernaut.js +295 -0
  187. data/public/javascripts/lib/juggernaut.swf +0 -0
  188. data/public/javascripts/lib/key_map.js +60 -0
  189. data/public/javascripts/lib/layout_manager.js +167 -0
  190. data/public/javascripts/lib/model.js +1067 -0
  191. data/public/javascripts/lib/page.js +238 -0
  192. data/public/javascripts/lib/parameters.js +59 -0
  193. data/public/javascripts/lib/png_fix.js +75 -0
  194. data/public/javascripts/lib/protohack.js +297 -0
  195. data/public/javascripts/lib/push.js +5 -0
  196. data/public/javascripts/lib/rio.build +28 -0
  197. data/public/javascripts/lib/rio_development.build +5 -0
  198. data/public/javascripts/lib/rio_lint.js +66 -0
  199. data/public/javascripts/lib/rsh.js +651 -0
  200. data/public/javascripts/lib/spec.js +545 -0
  201. data/public/javascripts/lib/spec_runner.js +242 -0
  202. data/public/javascripts/lib/swfobject.js +5 -0
  203. data/public/javascripts/lib/tag.js +52 -0
  204. data/public/javascripts/lib/undo.js +134 -0
  205. data/public/javascripts/lib/utils.js +80 -0
  206. data/public/javascripts/lib/yaml.js +80 -0
  207. data/public/javascripts/pages/playground_page.js +15 -0
  208. data/public/javascripts/prototype/builder.js +146 -0
  209. data/public/javascripts/prototype/controls.js +1004 -0
  210. data/public/javascripts/prototype/dragdrop.js +1030 -0
  211. data/public/javascripts/prototype/effects.js +1137 -0
  212. data/public/javascripts/prototype/prototype.js +4320 -0
  213. data/public/javascripts/prototype/slider.js +283 -0
  214. data/public/javascripts/prototype/sound.js +67 -0
  215. data/public/javascripts/specs/components/box_spec.js +6 -0
  216. data/public/javascripts/specs/components/checkbox_spec.js +26 -0
  217. data/public/javascripts/specs/components/container_spec.js +6 -0
  218. data/public/javascripts/specs/components/input_spec.js +71 -0
  219. data/public/javascripts/specs/components/panel_spec.js +6 -0
  220. data/public/javascripts/specs/components/radio_spec.js +40 -0
  221. data/public/javascripts/specs/fixtures/components/box.js +3 -0
  222. data/public/javascripts/specs/fixtures/components/checkbox.js +9 -0
  223. data/public/javascripts/specs/fixtures/components/container.js +3 -0
  224. data/public/javascripts/specs/fixtures/components/input.js +12 -0
  225. data/public/javascripts/specs/fixtures/components/menu.js +19 -0
  226. data/public/javascripts/specs/fixtures/components/menu_item.js +18 -0
  227. data/public/javascripts/specs/fixtures/components/radio.js +11 -0
  228. data/public/javascripts/specs/lib/application_spec.js +281 -0
  229. data/public/javascripts/specs/lib/attr_spec.js +1514 -0
  230. data/public/javascripts/specs/lib/benchmark_spec.js +361 -0
  231. data/public/javascripts/specs/lib/collection_entity_spec.js +131 -0
  232. data/public/javascripts/specs/lib/component_spec.js +86 -0
  233. data/public/javascripts/specs/lib/form_spec.js +171 -0
  234. data/public/javascripts/specs/lib/id_spec.js +21 -0
  235. data/public/javascripts/specs/lib/instrumenter_spec.js +5 -0
  236. data/public/javascripts/specs/lib/js_template_spec.js +131 -0
  237. data/public/javascripts/specs/lib/key_map_spec.js +227 -0
  238. data/public/javascripts/specs/lib/model_spec.js +2268 -0
  239. data/public/javascripts/specs/lib/parameters_spec.js +94 -0
  240. data/public/javascripts/specs/lib/spec_spec.js +943 -0
  241. data/public/javascripts/specs/lib/undo_spec.js +105 -0
  242. data/public/javascripts/specs/lib/yaml_spec.js +127 -0
  243. data/public/sounds/basso.wav +0 -0
  244. data/public/sounds/purr.wav +0 -0
  245. data/public/stylesheets/components/accordion.css +24 -0
  246. data/public/stylesheets/components/alert_box.css +35 -0
  247. data/public/stylesheets/components/box.css +0 -0
  248. data/public/stylesheets/components/button.css +39 -0
  249. data/public/stylesheets/components/checkbox.css +9 -0
  250. data/public/stylesheets/components/container.css +3 -0
  251. data/public/stylesheets/components/grid_view.css +52 -0
  252. data/public/stylesheets/components/input.css +10 -0
  253. data/public/stylesheets/components/label.css +3 -0
  254. data/public/stylesheets/components/lightbox.css +31 -0
  255. data/public/stylesheets/components/link.css +4 -0
  256. data/public/stylesheets/components/list_view.css +23 -0
  257. data/public/stylesheets/components/marquee.css +29 -0
  258. data/public/stylesheets/components/menu.css +34 -0
  259. data/public/stylesheets/components/notification.css +52 -0
  260. data/public/stylesheets/components/overlay.css +8 -0
  261. data/public/stylesheets/components/panel.css +36 -0
  262. data/public/stylesheets/components/radio.css +9 -0
  263. data/public/stylesheets/components/splitter.css +35 -0
  264. data/public/stylesheets/components/tab_bar.css +59 -0
  265. data/public/stylesheets/components/tab_panel.css +15 -0
  266. data/public/stylesheets/components/textarea.css +11 -0
  267. data/public/stylesheets/components/tooltip.css +10 -0
  268. data/public/stylesheets/console.css +151 -0
  269. data/public/stylesheets/css_reset.css +55 -0
  270. metadata +343 -0
@@ -0,0 +1,2268 @@
1
+ /* Test coverage up to about half-way through the save method */
2
+ describe(rio.Model, {
3
+ beforeEach: function() {
4
+ this.model = rio.Model.create("SomeModel", {
5
+ attrAccessors: ["id", "name"]
6
+ });
7
+ this.buildResponse = function(json) {
8
+ return rio.environment.includeRootInJson ? { some_test_model: json } : json;
9
+ };
10
+ },
11
+
12
+ "should keep a reference to itself in it's instances": function() {
13
+ (this.model == new this.model().__model).shouldBeTrue();
14
+ },
15
+
16
+ "should return false for hasChannel": function() {
17
+ this.model.hasChannel().shouldBeFalse();
18
+ },
19
+
20
+ "with a backing resource": {
21
+ beforeEach: function() {
22
+ stub(rio.push || {}, "addChannel");
23
+
24
+ this.model = rio.Model.create("SomeModel", {
25
+ resource: "/resource_url",
26
+ undoEnabled: true,
27
+ attrReaders: ["readOnly"],
28
+ attrAccessors: ["projectId", "clientOnly"],
29
+ clientOnlyAttrs: ["clientOnly"],
30
+ channel: true,
31
+ methods: {
32
+ }
33
+ });
34
+ this.model.prepareTransaction = function() {
35
+ this.executeTransaction(rio.Undo.isProcessingUndo(), rio.Undo.isProcessingRedo());
36
+ };
37
+ stub(rio.models, "SomeModel").withValue(this.model);
38
+ },
39
+
40
+ "should be new before saving": function() {
41
+ var modelInstance = new this.model();
42
+ modelInstance.isNew().shouldBeTrue();
43
+ },
44
+
45
+ "should not be new after saving": function() {
46
+ var response = rio.environment.includeRootInJson ? { resource: { id: 1, projectId: 1 } } : { id: 1, projectId: 1 };
47
+ stub(Ajax, "Request").andDo(function(url, options) {
48
+ options.onSuccess({
49
+ responseJSON: response
50
+ });
51
+ });
52
+ var modelInstance = new this.model();
53
+ modelInstance.save();
54
+ modelInstance.isNew().shouldBeFalse();
55
+ },
56
+
57
+ "should start with an id attrAccessor": function() {
58
+ var modelInstance = new this.model({ id: 3 });
59
+ modelInstance.getId().shouldEqual(3);
60
+ modelInstance.setId(1);
61
+ modelInstance.getId().shouldEqual(1);
62
+ modelInstance.id.constructor.shouldEqual(rio.Binding);
63
+ },
64
+
65
+ "should be initialized with an unreified id if not provided": function() {
66
+ var modelInstance = new this.model();
67
+ shouldBeDefined(modelInstance.getId());
68
+ modelInstance.getId().temporary().shouldBeTrue();
69
+ },
70
+
71
+ "should not be new if initialized with an id": function() {
72
+ var modelInstance = new this.model({ id: 1 });
73
+ modelInstance.isNew().shouldBeFalse();
74
+ },
75
+
76
+ "should be given an id object from the idCache if initialized with an id": function() {
77
+ var modelInstance = new this.model({ id: 1 });
78
+ modelInstance.getId().constructor.shouldEqual(rio.Id);
79
+
80
+ this.model.id(1).shouldEqual(modelInstance.getId());
81
+ },
82
+
83
+ "should not create an id in the idCache when asking for an id": function() {
84
+ this.model.id(14);
85
+ shouldBeUndefined(this.model.id(14));
86
+ },
87
+
88
+ "should create an id in the idCache when asking for an id and telling it to create if not found": function() {
89
+ var id = this.model.id(14, true);
90
+ this.model.id(14).shouldEqual(id);
91
+ },
92
+
93
+ "should expose an undoEnabled flag that is false by default": function() {
94
+ var model = rio.Model.create("SomeModel", {
95
+ resource: "/resource_url"
96
+ });
97
+ model.undoEnabled.shouldBeFalse();
98
+ },
99
+
100
+ "should expose an undoEnabled flag that can be configured to true": function() {
101
+ var model = rio.Model.create("SomeModel", {
102
+ resource: "/resource_url",
103
+ undoEnabled: true
104
+ });
105
+ model.undoEnabled.shouldBeTrue();
106
+ },
107
+
108
+ "should warn if reifying an id twice": function() {
109
+ stub(rio, "warn").andDo(function() {}.shouldBeCalled());
110
+ var id = this.model.id(14, true);
111
+ this.model.reifyId(id, 14);
112
+ },
113
+
114
+ "should have an url that matches the models url plus its id": function() {
115
+ var modelInstance = new this.model({ id: 1 });
116
+ modelInstance.url().shouldEqual("/resource_url/1");
117
+ },
118
+
119
+ "should immediately set the __destroying flag when an entity is destroyed": function() {
120
+ stub(this.model, "prepareTransaction");
121
+ var modelInstance = new this.model({ id: 4 });
122
+ shouldBeUndefined(modelInstance.__destroying);
123
+ modelInstance.destroy();
124
+ modelInstance.__destroying.shouldBeTrue();
125
+ },
126
+
127
+ "should not attempt to persist an entity that is being destroyed": function() {
128
+ var modelInstance = new this.model();
129
+ modelInstance.__destroying = true;
130
+ stub(Ajax, "Request").andDo(function(url, options) {
131
+ fail("incorrectly attempted to persist");
132
+ });
133
+ modelInstance.save();
134
+ },
135
+
136
+ "should execute the onSuccess function after a successful Ajax update": function() {
137
+ var modelInstance = new this.model({ id: 1 });
138
+ stub(Ajax, "Request").andDo(function(url, options) {
139
+ options.onSuccess({});
140
+ }.shouldBeCalled());
141
+ modelInstance.save({ onSuccess: function(entity) {
142
+ (entity == modelInstance).shouldBeTrue();
143
+ }.shouldBeCalled() });
144
+ },
145
+
146
+ "should execute the onFailure function after a failed Ajax update": function() {
147
+ var modelInstance = new this.model({ id: 1 });
148
+ stub(Ajax, "Request").andDo(function(url, options) {
149
+ options.onFailure();
150
+ }.shouldBeCalled());
151
+ modelInstance.save({ onFailure: function() {}.shouldBeCalled() });
152
+ },
153
+
154
+ "should perform an Ajax put on update": function() {
155
+ var modelInstance = new this.model({ id: 1 });
156
+ stub(Ajax, "Request").andDo(function(url, options) {
157
+ options.method.shouldEqual("put");
158
+ }.shouldBeCalled());
159
+ modelInstance.save();
160
+ },
161
+
162
+ "should put updates to the entity url": function() {
163
+ var modelInstance = new this.model({ id: 1 });
164
+ stub(Ajax, "Request").andDo(function(url, options) {
165
+ url.shouldEqual(modelInstance.url());
166
+ }.shouldBeCalled());
167
+ modelInstance.save();
168
+ },
169
+
170
+ "should allow you to access an entity in the identity cache before the id has been reified": function() {
171
+ var id = this.model.id();
172
+ var modelInstance = new this.model({ id: id });
173
+ (this.model.getFromCache(id) == modelInstance).shouldBeTrue();
174
+ },
175
+
176
+ "should allow you to access an entity in the identity cache after the id has been reified": function() {
177
+ var id = this.model.id();
178
+ var modelInstance = new this.model({ id: id });
179
+ this.model.reifyId(id, 17);
180
+ (id == this.model.id(17)).shouldBeTrue();
181
+ (this.model.getFromCache(id) == modelInstance).shouldBeTrue();
182
+ },
183
+
184
+ "should return undefined if you try to get the entity from the identity cache with id undefined": function() {
185
+ shouldBeUndefined(this.model.getFromCache());
186
+ },
187
+
188
+ "should not attempt to destroy an entity twice": function() {
189
+ stub(Ajax, "Request").andDo(function(url, options) {
190
+ options.method.shouldEqual("delete");
191
+ }.shouldBeCalled().once());
192
+ var modelInstance = new this.model({ id: 3 });
193
+ modelInstance.destroy();
194
+ modelInstance.destroy();
195
+ },
196
+
197
+ "should call the beforeDestroy method before destroying the entity": function() {
198
+ var val = 1;
199
+ stub(Ajax, "Request").andDo(function() {
200
+ val = 2;
201
+ });
202
+ var modelInstance = new this.model({ id: 3 });
203
+ modelInstance.beforeDestroy = function() {
204
+ val.shouldEqual(1);
205
+ }.shouldBeCalled();
206
+ modelInstance.destroy();
207
+ },
208
+
209
+ "should provide a destroy event that is fired after destroying": function() {
210
+ stub(Ajax, "Request");
211
+ var modelInstance = new this.model({ id: 3 });
212
+ modelInstance.observe("destroy", function() {}.shouldBeCalled());
213
+ modelInstance.destroy();
214
+ },
215
+
216
+ "should perform an Ajax delete on destroy": function() {
217
+ var modelInstance = new this.model({ id: 1 });
218
+ stub(Ajax, "Request").andDo(function(url, options) {
219
+ options.method.shouldEqual("delete");
220
+ }.shouldBeCalled());
221
+ modelInstance.destroy();
222
+ },
223
+
224
+ "should delete to the entity url": function() {
225
+ var modelInstance = new this.model({ id: 1 });
226
+ stub(Ajax, "Request").andDo(function(url, options) {
227
+ url.shouldEqual(modelInstance.url());
228
+ }.shouldBeCalled());
229
+ modelInstance.destroy();
230
+ },
231
+
232
+ "should remove an entity from the identity cache on destroy": function() {
233
+ stub(Ajax, "Request");
234
+ var modelInstance = new this.model({ id: 1 });
235
+ (this.model.getFromCache(1) == modelInstance).shouldBeTrue();
236
+ modelInstance.destroy();
237
+ (this.model.getFromCache(1) == undefined).shouldBeTrue();
238
+ },
239
+
240
+ "should remove an entity from the collection entities on destroy": function() {
241
+ var collectionEntity = rio.CollectionEntity.create({
242
+ model: this.model,
243
+ values: [],
244
+ condition: function(e) { return e.getProjectId() == 72; }
245
+ });
246
+ this.model.putCollectionEntity(this.model.url() + "#{project_id: 72}", collectionEntity);
247
+
248
+ var modelInstance = new this.model({ id: 1, projectId: 72 });
249
+ (collectionEntity.first() == modelInstance).shouldBeTrue();
250
+
251
+ stub(Ajax, "Request").andDo(function(url, options) {
252
+ options.onSuccess({});
253
+ });
254
+
255
+ modelInstance.destroy();
256
+
257
+ collectionEntity.shouldBeEmpty();
258
+ },
259
+
260
+ "should call the passed in onSuccess method on destroy": function() {
261
+ var modelInstance = new this.model({ id: 1 });
262
+
263
+ stub(Ajax, "Request").andDo(function(url, options) {
264
+ options.onSuccess({});
265
+ });
266
+
267
+ modelInstance.destroy({ onSuccess: function() {}.shouldBeCalled() });
268
+ },
269
+
270
+ "should provide an attribute state method that returns a hash of the persistable attribute values": function() {
271
+ var modelInstance = new this.model({ id: 1, projectId: 2, clientOnly: 3, readOnly: 4 });
272
+ (modelInstance.attributeState().id == undefined).shouldBeTrue();
273
+ modelInstance.attributeState().projectId.shouldEqual(2);
274
+ (modelInstance.attributeState().clientOnly == undefined).shouldBeTrue();
275
+ (modelInstance.attributeState().readOnly == undefined).shouldBeTrue();
276
+ },
277
+
278
+ "should provide an attributeStateChange methods that returns a hash of the changed persistable attribute values": function() {
279
+ var modelInstance = new this.model({ id: 1, projectId: 2, clientOnly: 3, readOnly: 4 });
280
+ stub(this.model, "prepareTransaction");
281
+
282
+
283
+ Object.keys(modelInstance.attributeStateChange()).shouldNotInclude("projectId");
284
+
285
+ modelInstance.setId(6);
286
+ modelInstance.setProjectId(4);
287
+ modelInstance.setClientOnly(7);
288
+
289
+ modelInstance.attributeStateChange().projectId.shouldEqual(4);
290
+ shouldBeUndefined(modelInstance.attributeStateChange().id);
291
+ shouldBeUndefined(modelInstance.attributeStateChange().clientOnly);
292
+ shouldBeUndefined(modelInstance.attributeStateChange().readOnly);
293
+ },
294
+
295
+ "should initialize the entity's last saved state when an id is provided": function() {
296
+ var modelInstance = new this.model({ id: 1, projectId: 2, clientOnly: 3, readOnly: 4 });
297
+ modelInstance._lastSavedState.projectId.shouldEqual(2);
298
+ },
299
+
300
+ "should not initialize the entity's last saved state when an id is provided": function() {
301
+ var modelInstance = new this.model({ projectId: 2, clientOnly: 3, readOnly: 4 });
302
+ (modelInstance._lastSavedState == undefined).shouldBeTrue();
303
+ },
304
+
305
+ "should be able to remove from caches": function() {
306
+ var all = this.model.findAll();
307
+ var modelInstance = new this.model({ id: 1, projectId: 2, clientOnly: 3, readOnly: 4 });
308
+ (this.model.getFromCache(1) == modelInstance).shouldBeTrue();
309
+ all.shouldInclude(modelInstance);
310
+
311
+ modelInstance.removeFromCaches();
312
+ shouldBeUndefined(this.model.getFromCache(1));
313
+ all.shouldNotInclude(modelInstance);
314
+ },
315
+
316
+ "and a channel": {
317
+ beforeEach: function() {
318
+
319
+ },
320
+
321
+ "should return true for hasChannel": function() {
322
+ this.model.hasChannel().shouldBeTrue();
323
+ },
324
+
325
+ "should have a channel name of ModelName.id": function() {
326
+ var modelInstance = new this.model({ id: 1 });
327
+ modelInstance.channelName().shouldEqual("SomeModel.1");
328
+ },
329
+
330
+ "should subscribe to a channel when the id is specified": function() {
331
+ stub(rio, "push").withValue({});
332
+ stub(rio.push, "addChannel").withValue(function(channel) {
333
+ channel.shouldEqual("SomeModel.1");
334
+ }.shouldBeCalled());
335
+
336
+ new this.model({ id: 1 });
337
+ },
338
+
339
+ "should not subscribe to a channel for an entity with a temporary id until after reification": function() {
340
+ var id = this.model.id();
341
+ stub(rio, "push").withValue({});
342
+ stub(rio.push, "addChannel").shouldNotBeCalled();
343
+
344
+ var modelInstance = new this.model({ id: id });
345
+
346
+ stub(rio.push, "addChannel").withValue(function(channel) {
347
+ channel.shouldEqual("SomeModel.1");
348
+ }.shouldBeCalled());
349
+ id.reify(1);
350
+ }
351
+ /* TODO: Add specs for the broadcast and receive broadcast methods */
352
+ },
353
+
354
+ "should facilitate undo and redo": {
355
+ beforeEach: function() {
356
+ stub(Ajax, "Request");
357
+ this.oldQueue = rio.Undo._queue;
358
+ rio.Undo.setQueue(new rio.UndoQueue());
359
+ },
360
+
361
+ "by registering an undo function that rolls back a change": function() {
362
+ var modelInstance = new this.model({ id: 1, projectId: 2, clientOnly: 3, readOnly: 4 });
363
+ modelInstance.setProjectId(5);
364
+ rio.Undo.undo();
365
+ modelInstance.getProjectId().shouldEqual(2);
366
+ },
367
+
368
+ "by registering a redo function after an undo": function() {
369
+ var modelInstance = new this.model({ id: 1, projectId: 2, clientOnly: 3, readOnly: 4 });
370
+ modelInstance.setProjectId(5);
371
+ modelInstance.getProjectId().shouldEqual(5);
372
+
373
+ /* Manually remove the last transaction since we are stubbing the AJAX interactions */
374
+ this.model.__queuedTransactions.pop();
375
+ this.model.__transactionInProgress = false;
376
+
377
+ rio.Undo.undo();
378
+ modelInstance.getProjectId().shouldEqual(2);
379
+ rio.Undo.redo();
380
+ modelInstance.getProjectId().shouldEqual(5);
381
+ },
382
+
383
+ "by not registering an undo function for a non undoEnabled model": function() {
384
+ this.model.undoEnabled = false;
385
+ var modelInstance = new this.model({ id: 1, projectId: 2, clientOnly: 3, readOnly: 4 });
386
+ modelInstance.setProjectId(5);
387
+ rio.Undo.undo();
388
+ modelInstance.getProjectId().shouldEqual(5);
389
+ },
390
+
391
+ "by registering an undo function that will destroy a created entity": function() {
392
+ var modelInstance = this.model.create({ id: this.model.id(), projectId: 2, clientOnly: 3, readOnly: 4 });
393
+ stub(modelInstance, "destroy").andDo(function() {}.shouldBeCalled());
394
+ rio.Undo.undo();
395
+ },
396
+
397
+ "by registering a redo function that will re-destroy an undone destroy": function() {
398
+ var modelInstance = new this.model({ id: 1, projectId: 2, clientOnly: 3, readOnly: 4 });
399
+ modelInstance.destroy();
400
+
401
+ /* Manually remove the last transaction since we are stubbing the AJAX interactions */
402
+ this.model.__queuedTransactions.pop();
403
+ this.model.__transactionInProgress = false;
404
+
405
+ rio.Undo.undo();
406
+
407
+ /* Manually remove the last transaction since we are stubbing the AJAX interactions */
408
+ this.model.__queuedTransactions.pop();
409
+ this.model.__transactionInProgress = false;
410
+
411
+ /* Need to get the new instance from the cache to add the destroy expectation */
412
+ var newModelInstance = this.model.getFromCache(1);
413
+
414
+ stub(newModelInstance, "destroy").andDo(function() {}.shouldBeCalled());
415
+ rio.Undo.redo();
416
+ },
417
+
418
+ "by being able to undo changes to something that was destroyed and then un-destroyed": function() {
419
+ var modelInstance = new this.model({ id: 1, projectId: 2, clientOnly: 3, readOnly: 4 });
420
+
421
+ modelInstance.setProjectId(7);
422
+
423
+ /* Manually remove the last transaction since we are stubbing the AJAX interactions */
424
+ this.model.__queuedTransactions.pop();
425
+ this.model.__transactionInProgress = false;
426
+
427
+ modelInstance.destroy();
428
+
429
+ /* Manually remove the last transaction since we are stubbing the AJAX interactions */
430
+ this.model.__queuedTransactions.pop();
431
+ this.model.__transactionInProgress = false;
432
+
433
+ rio.Undo.undo();
434
+
435
+ /* Manually remove the last transaction since we are stubbing the AJAX interactions */
436
+ this.model.__queuedTransactions.pop();
437
+ this.model.__transactionInProgress = false;
438
+
439
+ var newInstance = this.model.getFromCache(1);
440
+ newInstance.getProjectId().shouldEqual(7);
441
+
442
+ rio.Undo.undo();
443
+
444
+ newInstance.getProjectId().shouldEqual(2);
445
+ },
446
+
447
+ "by registering an undo function that will create a destroyed entity": function() {
448
+ var modelInstance = this.model.create({ id: this.model.id(), projectId: 2, clientOnly: 3, readOnly: 4 });
449
+ var id = modelInstance.getId();
450
+ /* Manually remove the last transaction since we are stubbing the AJAX interactions */
451
+ this.model.__queuedTransactions.pop();
452
+ this.model.__transactionInProgress = false;
453
+
454
+ rio.Undo.undo();
455
+
456
+ /* Manually remove the last transaction since we are stubbing the AJAX interactions */
457
+ this.model.__queuedTransactions.pop();
458
+ this.model.__transactionInProgress = false;
459
+
460
+ (this.model.getFromCache(id) == undefined).shouldBeTrue();
461
+
462
+ rio.Undo.redo();
463
+
464
+ var newModelInstance = this.model.getFromCache(id);
465
+ (newModelInstance != modelInstance).shouldBeTrue();
466
+ newModelInstance.getId().shouldEqual(id);
467
+ newModelInstance.getProjectId().shouldEqual(2);
468
+ },
469
+
470
+ "by registering a redo function that will re-create an un-created entity": function() {
471
+ var modelInstance = new this.model({ id: 1, projectId: 2, clientOnly: 3, readOnly: 4 });
472
+ modelInstance.destroy();
473
+
474
+ /* Manually remove the last transaction since we are stubbing the AJAX interactions */
475
+ this.model.__queuedTransactions.pop();
476
+ this.model.__transactionInProgress = false;
477
+
478
+ rio.Undo.undo();
479
+
480
+ var newModelInstance = this.model.getFromCache(1);
481
+ (newModelInstance != modelInstance).shouldBeTrue();
482
+ newModelInstance.getId().shouldEqual(1);
483
+ newModelInstance.getProjectId().shouldEqual(2);
484
+ },
485
+
486
+ afterEach: function() {
487
+ rio.Undo.setQueue(this.oldQueue);
488
+ }
489
+ },
490
+
491
+ /*
492
+
493
+ THIS FUNCTIONALITY IS NO LONGER SUPPORTED
494
+
495
+ "should bundle updates with a delay of 250 milliseconds": function() {
496
+ /DelayedTask needs to be overriden to prevent this test from failing asynchronously/
497
+ var modelInstance = new this.model({ id: 1, projectId: this.projectId });
498
+ var oldDelayedTask = rio.DelayedTask;
499
+ try {
500
+ rio.DelayedTask = Class.create({
501
+ delay: function(timeout, fcn) {
502
+ timeout.shouldEqual(250);
503
+ }.shouldBeCalled().times(2)
504
+ });
505
+ modelInstance.save();
506
+ modelInstance.save();
507
+ } finally {
508
+ rio.DelayedTask = oldDelayedTask;
509
+ }
510
+ },
511
+
512
+ "should bundle updates with a customizable delay": function() {
513
+ /DelayedTask needs to be overriden to prevent this test from failing asynchronously/
514
+ var modelInstance = new this.model({ id: 1, projectId: this.projectId });
515
+ var oldDelayedTask = rio.DelayedTask;
516
+ try {
517
+ var delaySave = 500;
518
+ rio.DelayedTask = Class.create({
519
+ delay: function(timeout, fcn) {
520
+ timeout.shouldEqual(delaySave);
521
+ }.shouldBeCalled().times(2)
522
+ });
523
+ modelInstance.save({ delaySave: delaySave });
524
+ modelInstance.save({ delaySave: delaySave });
525
+ } finally {
526
+ rio.DelayedTask = oldDelayedTask;
527
+ }
528
+ },
529
+ */
530
+
531
+ "should use the correct AJAX parameters on save": function() {
532
+ this.model.prepareTransaction = Prototype.emptyFunction;
533
+
534
+ var modelInstance = new this.model({ projectId: 10 });
535
+
536
+ stub(Ajax, "Request").andDo(function(url, options) {
537
+ options.parameters["resource_url[project_id]"].shouldEqual(10);
538
+ }.shouldBeCalled());
539
+
540
+ modelInstance.save();
541
+
542
+ this.model.executeTransaction();
543
+ },
544
+
545
+ "should use overriden AJAX parameters on save": function() {
546
+ this.model.prepareTransaction = Prototype.emptyFunction;
547
+
548
+ var modelInstance = new this.model({ projectId: 10 });
549
+
550
+ modelInstance.parameters = function() {
551
+ return {
552
+ hello: "world"
553
+ };
554
+ };
555
+
556
+ stub(Ajax, "Request").andDo(function(url, options) {
557
+ shouldBeUndefined(options.parameters["resource_url[project_id]"]);
558
+
559
+ options.parameters.hello.shouldEqual("world");
560
+ }.shouldBeCalled());
561
+
562
+ modelInstance.save();
563
+
564
+ this.model.executeTransaction();
565
+ },
566
+
567
+ "should use only the changed AJAX parameters on an update": function() {
568
+ this.model.prepareTransaction = Prototype.emptyFunction;
569
+
570
+ var modelInstance = new this.model({ id: 1, projectId: 10, hello: "world" });
571
+
572
+ modelInstance.setProjectId(4);
573
+ stub(Ajax, "Request").andDo(function(url, options) {
574
+ options.parameters["resource_url[project_id]"].shouldEqual(4);
575
+ }.shouldBeCalled());
576
+
577
+ modelInstance.save();
578
+
579
+ this.model.executeTransaction();
580
+ },
581
+
582
+ "should not use unchanged AJAX parameters on an update": function() {
583
+ this.model.prepareTransaction = Prototype.emptyFunction;
584
+
585
+ var modelInstance = new this.model({ id: 1, projectId: 10, hello: "world" });
586
+
587
+ stub(Ajax, "Request").andDo(function(url, options) {
588
+ Object.keys(options.parameters).shouldNotInclude("resource_url[project_id]");
589
+ }.shouldBeCalled());
590
+
591
+ modelInstance.save();
592
+
593
+ this.model.executeTransaction();
594
+ },
595
+
596
+ "should use the correct AJAX parameters on save even if the entity is updated before the transaction is executed": function() {
597
+ this.model.prepareTransaction = Prototype.emptyFunction;
598
+
599
+ var modelInstance = new this.model({ projectId: 10 });
600
+
601
+ stub(Ajax, "Request").andDo(function(url, options) {
602
+ options.parameters["resource_url[project_id]"].shouldEqual(10);
603
+ }.shouldBeCalled());
604
+
605
+ modelInstance.save();
606
+
607
+ var doExecuteTransaction = this.model._doExecuteTransaction;
608
+ this.model._doExecuteTransaction = function(transaction) {
609
+ doExecuteTransaction = doExecuteTransaction.curry(transaction);
610
+ };
611
+ this.model.executeTransaction();
612
+
613
+ modelInstance.setProjectId(5);
614
+
615
+ doExecuteTransaction();
616
+ },
617
+
618
+ "should provide a default parameters object": {
619
+ beforeEach: function() {
620
+ this.model = rio.Model.create("SomeModel", {
621
+ resource: "/big_projects",
622
+ undoEnabled: true,
623
+ attrReaders: ["readOnly"],
624
+ attrAccessors: ["id", "projectId", "name", "clientOnly"],
625
+ clientOnlyAttrs: ["clientOnly"]
626
+ });
627
+ this.modelInstance = new this.model({ id: 1, projectId: 2, name: "Awesome", clientOnly: 3, readOnly: 4 });
628
+ },
629
+
630
+ "that should include all of non-id, non-clientOnly attrAccessors that are updated": function() {
631
+ shouldBeUndefined(this.modelInstance.parameters()["big_project[project_id]"]);
632
+ shouldBeUndefined(this.modelInstance.parameters()["big_project[name]"]);
633
+
634
+ stub(this.model, "prepareTransaction");
635
+
636
+ this.modelInstance.setProjectId(12);
637
+ this.modelInstance.parameters()["big_project[project_id]"].shouldEqual(12);
638
+ shouldBeUndefined(this.modelInstance.parameters()["big_project[name]"]);
639
+
640
+ this.modelInstance.setName("Hello");
641
+ this.modelInstance.parameters()["big_project[name]"].shouldEqual("Hello");
642
+ },
643
+
644
+ "that should not include attrReaders": function() {
645
+ shouldBeUndefined(this.modelInstance.parameters()["big_project[read_only]"]);
646
+ },
647
+
648
+ "that should not include clientOnlyAttrs": function() {
649
+ shouldBeUndefined(this.modelInstance.parameters()["big_project[client_only]"]);
650
+ },
651
+
652
+ "that should not include id": function() {
653
+ shouldBeUndefined(this.modelInstance.parameters()["big_project[id]"]);
654
+ }
655
+ },
656
+
657
+ "should not save if the model provides a valid function and it returns false": function() {
658
+ var modelInstance = new this.model({ projectId: this.projectId });
659
+ modelInstance.valid = function() { return false; };
660
+
661
+ stub(Ajax, "Request").shouldNotBeCalled();
662
+
663
+ modelInstance.save();
664
+ },
665
+
666
+ "should save if the model provides a valid function and it returns true": function() {
667
+ var modelInstance = new this.model({ projectId: this.projectId });
668
+ modelInstance.valid = function() { return true; };
669
+
670
+ stub(Ajax, "Request").shouldBeCalled();
671
+
672
+ modelInstance.save();
673
+ },
674
+
675
+ "should provide an authenticity token on save": function() {
676
+ var modelInstance = new this.model({ projectId: this.projectId });
677
+
678
+ stub(Ajax, "Request").andDo(function(url, options) {
679
+ options.parameters.authenticity_token.shouldEqual(rio.environment.railsToken);
680
+ }.shouldBeCalled());
681
+
682
+ modelInstance.save();
683
+ },
684
+
685
+ "should provide the transaction key on save": function() {
686
+ var modelInstance = new this.model({ projectId: this.projectId });
687
+
688
+ stub(Ajax, "Request").andDo(function(url, options) {
689
+ options.parameters.transaction_key.shouldEqual(rio.environment.transactionKey);
690
+ }.shouldBeCalled());
691
+
692
+ modelInstance.save();
693
+ },
694
+
695
+ "should allow you to override parameters on save": function() {
696
+ var modelInstance = new this.model({ projectId: this.projectId });
697
+
698
+ stub(Ajax, "Request").andDo(function(url, options) {
699
+ options.parameters.a.shouldEqual(1);
700
+ options.parameters.b.shouldEqual(2);
701
+ }.shouldBeCalled());
702
+
703
+ modelInstance.save({ parameters: { a: 1, b: 2 } });
704
+ },
705
+
706
+ "with a temporary id in one of its fields": {
707
+ beforeEach: function() {
708
+ this.projectId = new rio.Id();
709
+ this.modelInstance = new this.model({ projectId: this.projectId });
710
+ },
711
+
712
+ /*
713
+
714
+ ALLOW THIS FOR NOW BECAUSE TRANSACTION SHOULD MAKE IT OKAY.
715
+
716
+ "should not attempt to persist the entity": function() {
717
+ stub(Ajax, "Request").andDo(function(url, options) {
718
+ fail("incorrectly attempted to persist");
719
+ });
720
+ this.modelInstance.save();
721
+ },
722
+ */
723
+
724
+ "should persist after the temporary id is reified": function() {
725
+ var called = false;
726
+ stub(Ajax, "Request").andDo(function(url, options) {
727
+ called = true;
728
+ });
729
+ this.modelInstance.save();
730
+ this.projectId.reify(5);
731
+ called.shouldBeTrue();
732
+ },
733
+
734
+ "should not prevent persistence if the id is already reified": function() {
735
+ this.projectId.reify(5);
736
+ stub(Ajax, "Request").andDo(function(url, options) {}.shouldBeCalled());
737
+ this.modelInstance.save();
738
+ }
739
+ },
740
+
741
+ "with a temporary id, should still persist": function() {
742
+ var modelInstance = new this.model({ id: new rio.Id() });
743
+ var called = false;
744
+ stub(Ajax, "Request").andDo(function(url, options) {
745
+ called = true;
746
+ });
747
+ modelInstance.save();
748
+ called.shouldBeTrue();
749
+ },
750
+
751
+ "should support transactions": {
752
+ "by bundling multiple updates together in a single AJAX interaction": function() {
753
+ stub(Ajax, "Request").andDo(function(){}.shouldBeCalled().once());
754
+ this.model.prepareTransaction = Prototype.emptyFunction;
755
+ new this.model({ id: 1 }).save();
756
+ new this.model({ id: 2 }).save();
757
+ new this.model({ id: 3 }).destroy();
758
+ this.model.executeTransaction();
759
+ },
760
+
761
+ "with the proper parameters not including unchanged attributes": function() {
762
+ stub(Ajax, "Request").andDo(function(url, options) {
763
+ var p = options.parameters;
764
+ Object.keys(p).shouldNotInclude("transaction[1][project_id]");
765
+ Object.keys(p).shouldNotInclude("transaction[2][project_id]");
766
+ p["transaction[3]"].shouldEqual("delete");
767
+ }.shouldBeCalled());
768
+ this.model.prepareTransaction = Prototype.emptyFunction;
769
+ new this.model({ id: 1, projectId: 15 }).save();
770
+ new this.model({ id: 2, projectId: 13 }).save();
771
+ new this.model({ id: 3, projectId: 13 }).destroy();
772
+ this.model.executeTransaction();
773
+ },
774
+
775
+ "with the proper parameters including changed attributes": function() {
776
+ stub(Ajax, "Request").andDo(function(url, options) {
777
+ var p = options.parameters;
778
+ p["transaction[1][project_id]"].shouldEqual(10);
779
+ p["transaction[2][project_id]"].shouldEqual(15);
780
+ }.shouldBeCalled());
781
+ this.model.prepareTransaction = Prototype.emptyFunction;
782
+ var m1 = new this.model({ id: 1, projectId: 15 });
783
+ m1.setProjectId(10);
784
+ m1.save();
785
+ var m2 = new this.model({ id: 2, projectId: 13 });
786
+ m2.setProjectId(15);
787
+ m2.save();
788
+
789
+ this.model.executeTransaction();
790
+ },
791
+
792
+ "with the proper parameters including an authenticity_token": function() {
793
+ stub(Ajax, "Request").andDo(function(url, options) {
794
+ options.parameters.authenticity_token.shouldEqual(rio.environment.railsToken);
795
+ }.shouldBeCalled());
796
+ this.model.prepareTransaction = Prototype.emptyFunction;
797
+ new this.model({ id: 1, projectId: 15 }).save();
798
+ new this.model({ id: 2, projectId: 13 }).save();
799
+ new this.model({ id: 3, projectId: 13 }).destroy();
800
+ this.model.executeTransaction();
801
+ },
802
+
803
+ "with the proper parameters including the transaction key": function() {
804
+ stub(Ajax, "Request").andDo(function(url, options) {
805
+ options.parameters.transaction_key.shouldEqual(rio.environment.transactionKey);
806
+ }.shouldBeCalled());
807
+ this.model.prepareTransaction = Prototype.emptyFunction;
808
+ new this.model({ id: 1, projectId: 15 }).save();
809
+ new this.model({ id: 2, projectId: 13 }).save();
810
+ new this.model({ id: 3, projectId: 13 }).destroy();
811
+ this.model.executeTransaction();
812
+ },
813
+
814
+ "with the proper parameters not including client only attributes": function() {
815
+ stub(Ajax, "Request").andDo(function(url, options) {
816
+ var p = options.parameters;
817
+ (p["transaction[1][client_only]"] == undefined).shouldBeTrue();
818
+ (p["transaction[2][client_only]"] == undefined).shouldBeTrue();
819
+ }.shouldBeCalled());
820
+ this.model.prepareTransaction = Prototype.emptyFunction;
821
+ new this.model({ id: 1, clientOnly: "Hello" }).save();
822
+ new this.model({ id: 2, clientOnly: "World" }).save();
823
+ this.model.executeTransaction();
824
+ },
825
+
826
+ "with the proper parameters for unreified id's": function() {
827
+ var id = new rio.Id();
828
+
829
+ stub(Ajax, "Request").andDo(function(url, options) {
830
+ var p = options.parameters;
831
+ p["transaction[" + id + "][project_id]"].shouldEqual(15);
832
+ p["transaction[2][project_id]"].shouldEqual(id.toString());
833
+ }.shouldBeCalled());
834
+ this.model.prepareTransaction = Prototype.emptyFunction;
835
+ new this.model({ id: id, projectId: 15 }).save();
836
+ var m = new this.model({ id: 2 });
837
+ m.setProjectId(id);
838
+ m.save();
839
+ this.model.executeTransaction();
840
+ },
841
+
842
+ "by postponing the invocation of an AJAX call until the end of the thread": function() {
843
+ /* This test egregiously violates encapsulation */
844
+ var model = rio.Model.create("MyModel", {
845
+ resource: "/resource_url",
846
+ attrAccessors: ["id"]
847
+ });
848
+ model.executeTransaction = { defer: function() {}.shouldBeCalled(), bind: function() { return model.executeTransaction; } };
849
+ new model({ id: 12345 }).save();
850
+ },
851
+
852
+ "by collecting all changes to an entity into a single AJAX interaction": function() {
853
+ this.model.prepareTransaction = function() {}.shouldBeCalled().once();
854
+ var modelInstance = new this.model({ id: 12345 });
855
+ modelInstance.save();
856
+ modelInstance.save();
857
+ },
858
+
859
+ "by favoring delete actions when collecting changes to an entity": function() {
860
+ this.model.prepareTransaction = function() {}.shouldBeCalled().once();
861
+ var modelInstance = new this.model({ id: 12345 });
862
+ modelInstance.save();
863
+ modelInstance.destroy();
864
+ modelInstance.save();
865
+
866
+ this.model._transaction.first().options.destroy.shouldEqual(true);
867
+ },
868
+
869
+ "using an Ajax post": function() {
870
+ stub(Ajax, "Request").andDo(function(url, options) {
871
+ options.method.shouldEqual("post");
872
+ }.shouldBeCalled().once());
873
+
874
+ this.model.prepareTransaction = Prototype.emptyFunction;
875
+
876
+ new this.model({ id: 1 }).save();
877
+ new this.model({ id: 2 }).save();
878
+ new this.model({ id: 3 }).destroy();
879
+ this.model.executeTransaction();
880
+ },
881
+
882
+ "by posting to the model url": function() {
883
+ stub(Ajax, "Request").andDo(function(url, options) {
884
+ url.shouldEqual(this.model.url());
885
+ }.bind(this).shouldBeCalled().once());
886
+
887
+ this.model.prepareTransaction = Prototype.emptyFunction;
888
+
889
+ new this.model({ id: 1 }).save();
890
+ new this.model({ id: 2 }).save();
891
+ new this.model({ id: 3 }).destroy();
892
+ this.model.executeTransaction();
893
+ },
894
+
895
+ "by capturing the state of an entity when queuing a transaction": function() {
896
+ this.model.prepareTransaction = Prototype.emptyFunction;
897
+ var instance = this.model.create({ id: 1 });
898
+ instance.setProjectId(15);
899
+ this.model.create({ id: 2, projectId: 11 });
900
+
901
+ this.model._doExecuteTransaction = this.model._doExecuteTransaction.wrap(function(proceed) {
902
+ instance.setProjectId(123);
903
+ return proceed.apply(this, $A(arguments).slice(1));
904
+ });
905
+ stub(Ajax, "Request").andDo(function(url, options) {
906
+ p = options.parameters;
907
+ p["transaction[1][project_id]"].shouldEqual(15);
908
+ }.shouldBeCalled());
909
+ this.model.executeTransaction();
910
+ },
911
+
912
+ "by recapturing the state of an entity in a queued transaction that is updated": function() {
913
+ this.model.prepareTransaction = Prototype.emptyFunction;
914
+ var instance = this.model.create({ id: 1, projectId: 15 });
915
+ this.model.create({ id: 2, projectId: 11 });
916
+
917
+ instance.setProjectId(321);
918
+
919
+ this.model._doExecuteTransaction = this.model._doExecuteTransaction.wrap(function(proceed) {
920
+ instance.setProjectId(123);
921
+ return proceed.apply(this, $A(arguments).slice(1));
922
+ });
923
+ stub(Ajax, "Request").andDo(function(url, options) {
924
+ p = options.parameters;
925
+ p["transaction[1][project_id]"].shouldEqual(321);
926
+ }.shouldBeCalled());
927
+ this.model.executeTransaction();
928
+ },
929
+
930
+ "by marking all new entities as _creating": function() {
931
+ stub(Ajax, "Request");
932
+ this.model.prepareTransaction = Prototype.emptyFunction;
933
+
934
+ var m1 = this.model.create();
935
+ var m2 = this.model.create();
936
+ this.model.executeTransaction();
937
+
938
+ m1._creating.shouldBeTrue();
939
+ m2._creating.shouldBeTrue();
940
+ },
941
+
942
+ "by marking all destroyed entities as __destroying": function() {
943
+ stub(Ajax, "Request");
944
+ this.model.prepareTransaction = Prototype.emptyFunction;
945
+
946
+ var m1 = new this.model({ id: 1 });
947
+ var m2 = new this.model({ id: 2 });
948
+ m1.destroy();
949
+ m2.destroy();
950
+
951
+ this.model.executeTransaction();
952
+
953
+
954
+ m1.__destroying.shouldBeTrue();
955
+ m2.__destroying.shouldBeTrue();
956
+ },
957
+
958
+ "by executing all entities onFailure functions after a failure": function() {
959
+ stub(Ajax, "Request").andDo(function(url, options) {
960
+ options.onFailure();
961
+ });
962
+ this.model.prepareTransaction = Prototype.emptyFunction;
963
+
964
+ this.model.create({ onFailure: function() {}.shouldBeCalled() });
965
+ this.model.create({ onFailure: function() {}.shouldBeCalled() });
966
+ new this.model({id: 1}).destroy({ onFailure: function() {}.shouldBeCalled() });
967
+ this.model.executeTransaction();
968
+ },
969
+
970
+ "by calling Application.fail with 'Connection Failure' when there is a response code of 0": function() {
971
+ stub(Ajax, "Request").andDo(function(url, options) {
972
+ options.onSuccess({ status: 0 });
973
+ });
974
+ this.model.prepareTransaction = Prototype.emptyFunction;
975
+
976
+ this.model.create();
977
+
978
+ stub(rio.Application, "fail").andDo(function(m) { m.shouldEqual("Connection Failure"); }.shouldBeCalled());
979
+ this.model.executeTransaction();
980
+ },
981
+
982
+ "by executing onConnectionFailure, if provided, when there is a response code of 0": function() {
983
+ stub(Ajax, "Request").andDo(function(url, options) {
984
+ options.onSuccess({ status: 0 });
985
+ });
986
+ this.model.prepareTransaction = Prototype.emptyFunction;
987
+
988
+ this.model.create({
989
+ onConnectionFailure: function() {}.shouldBeCalled()
990
+ });
991
+
992
+ stub(rio.Application, "fail").shouldNotBeCalled();
993
+ this.model.executeTransaction();
994
+ },
995
+
996
+ "by executing all entities onConnectionFailure functions when there is a response code of 0": function() {
997
+ stub(Ajax, "Request").andDo(function(url, options) {
998
+ options.onSuccess({ status: 0 });
999
+ });
1000
+ this.model.prepareTransaction = Prototype.emptyFunction;
1001
+
1002
+ this.model.create({ onConnectionFailure: function() {}.shouldBeCalled() });
1003
+ this.model.create({ onConnectionFailure: function() {}.shouldBeCalled() });
1004
+ this.model.create();
1005
+
1006
+ stub(rio.Application, "fail").shouldNotBeCalled();
1007
+ this.model.executeTransaction();
1008
+ },
1009
+
1010
+ "by executing Application.fail after a failure when onFailure isn't specified": function() {
1011
+ stub(Ajax, "Request").andDo(function(url, options) {
1012
+ options.onFailure();
1013
+ });
1014
+ this.model.prepareTransaction = Prototype.emptyFunction;
1015
+
1016
+ this.model.create({ onFailure: function() {}.shouldBeCalled() });
1017
+ this.model.create();
1018
+
1019
+ stub(rio.Application, "fail").andDo(function() {}.shouldBeCalled());
1020
+ this.model.executeTransaction();
1021
+ },
1022
+
1023
+ "by executing all entities onSuccess functions after a success": function() {
1024
+ this.model.prepareTransaction = Prototype.emptyFunction;
1025
+ var m1 = this.model.create({ onSuccess: function() {}.shouldBeCalled() });
1026
+ new this.model({ id: 1 }).save({ onSuccess: function() {}.shouldBeCalled() });
1027
+ new this.model({ id: 1 }).destroy({ onSuccess: function() {}.shouldBeCalled() });
1028
+
1029
+ var transactionResponse = {};
1030
+ transactionResponse[m1.getId()] = { id: 123 };
1031
+ transactionResponse[1] = { id: 1 };
1032
+
1033
+ stub(Ajax, "Request").andDo(function(url, options) {
1034
+ options.onSuccess({
1035
+ responseJSON: { transaction: transactionResponse }
1036
+ });
1037
+ });
1038
+
1039
+ this.model.executeTransaction();
1040
+ },
1041
+
1042
+ "by removing entities from transactions that are both created and deleted": function() {
1043
+ stub(Ajax, "Request").andDo(function(url, options) {
1044
+ var p = options.parameters;
1045
+
1046
+ Object.keys(p).size().shouldEqual(4);
1047
+ shouldBeUndefined(p["transaction[2][id]"]);
1048
+ p["transaction[2][project_id]"].shouldEqual(11);
1049
+ p["transaction[3]"].shouldEqual("delete");
1050
+ (p.transaction_key != undefined).shouldBeTrue();
1051
+ (p.authenticity_token != undefined).shouldBeTrue();
1052
+ }.shouldBeCalled());
1053
+ this.model.prepareTransaction = Prototype.emptyFunction;
1054
+
1055
+ var model = this.model.create({ id: this.model.id(), projectId: 15 });
1056
+ model.destroy();
1057
+ var m = new this.model({ id: 2 });
1058
+ m.setProjectId(11);
1059
+ m.save();
1060
+ new this.model({ id: 3, projectId: 13 }).destroy();
1061
+ this.model.executeTransaction();
1062
+ },
1063
+
1064
+ "by removing entities from collection entities that are both created and deleted": function() {
1065
+ stub(Ajax, "Request").andDo(function(url, options) {}.shouldNotBeCalled());
1066
+ stub(this.model, "removeFromCollectionEntities").andDo(function(item) {
1067
+ (item == model).shouldBeTrue();
1068
+ }.shouldBeCalled());
1069
+
1070
+ this.model.prepareTransaction = Prototype.emptyFunction;
1071
+
1072
+ var model = this.model.create({ id: this.model.id(), projectId: 15 });
1073
+ model.destroy();
1074
+
1075
+ this.model.executeTransaction();
1076
+ },
1077
+
1078
+ "by waiting to execute subsequent transactions until after the current transaction is complete": function() {
1079
+ var onComplete;
1080
+ stub(Ajax, "Request").andDo(function(url, options) {
1081
+ onComplete = options.onComplete;
1082
+ }.bind(this).shouldBeCalled());
1083
+
1084
+ this.model.prepareTransaction = Prototype.emptyFunction;
1085
+
1086
+ new this.model({ id: 1 }).save();
1087
+ new this.model({ id: 2 }).save();
1088
+ this.model.executeTransaction();
1089
+
1090
+ stub(Ajax, "Request").andDo(function() {}.shouldNotBeCalled());
1091
+ var m1 = new this.model({ id: 3 });
1092
+ m1.setProjectId(5);
1093
+ m1.save();
1094
+
1095
+ var m2 = new this.model({ id: 4 });
1096
+ m2.setProjectId(6);
1097
+ m2.save();
1098
+ this.model.executeTransaction();
1099
+
1100
+ stub(Ajax, "Request").andDo(function(url, options) {
1101
+ var p = options.parameters;
1102
+ p["transaction[3][project_id]"].shouldEqual(5);
1103
+ p["transaction[4][project_id]"].shouldEqual(6);
1104
+ }.shouldBeCalled());
1105
+ onComplete();
1106
+ },
1107
+
1108
+ "by waiting to process collection entities until after all of the transaction entities have been processed": function() {
1109
+ this.model.prepareTransaction = Prototype.emptyFunction;
1110
+
1111
+ var id2 = this.model.id();
1112
+ var m1 = this.model.create({ id: this.model.id(), onSuccess: function() {}.shouldBeCalled() });
1113
+ var m2 = this.model.create({ id: id2, onSuccess: function() {}.shouldBeCalled() });
1114
+
1115
+ var buildResponse = function(json) {
1116
+ return rio.environment.includeRootInJson ? { resource: json } : json;
1117
+ };
1118
+
1119
+ stub(rio.models, "Resource").withValue(this.model);
1120
+
1121
+ var transactionResponse = {};
1122
+ transactionResponse[m1.getId()] = { _set: { self: buildResponse({ id: 1 }), include: [{ parameters: { parentId: 2 }, className: "Resource", json: [buildResponse({ id: 2 })] }] } };
1123
+ transactionResponse[m2.getId()] = buildResponse({ id: 2 });
1124
+
1125
+ stub(Ajax, "Request").andDo(function(url, options) {
1126
+ options.onSuccess({
1127
+ responseJSON: { transaction: transactionResponse }
1128
+ });
1129
+ });
1130
+
1131
+ this.model.executeTransaction();
1132
+
1133
+ var collection = this.model.findAll({parameters: { parentId: 2 }});
1134
+
1135
+ (collection[0] == m2).shouldBeTrue();
1136
+ },
1137
+
1138
+ "by processing collection entities after single entity interactions": function() {
1139
+ this.model.prepareTransaction = Prototype.emptyFunction;
1140
+
1141
+
1142
+ var m1 = this.model.create({ id: this.model.id(), onSuccess: function() {}.shouldBeCalled() });
1143
+ var m2 = new this.model({ id: 2 });
1144
+
1145
+ var buildResponse = function(json) {
1146
+ return rio.environment.includeRootInJson ? { resource: json } : json;
1147
+ };
1148
+
1149
+ stub(rio.models, "Resource").withValue(this.model);
1150
+
1151
+ var response = { _set: { self: buildResponse({ id: 1 }), include: [{ parameters: { parentId: 2 }, className: "Resource", json: [buildResponse({ id: 2 })] }] } };
1152
+
1153
+ stub(Ajax, "Request").andDo(function(url, options) {
1154
+ options.onSuccess({
1155
+ responseJSON: response
1156
+ });
1157
+ });
1158
+
1159
+ this.model.executeTransaction();
1160
+
1161
+ var collection = this.model.findAll({parameters: { parentId: 2 }});
1162
+
1163
+ (collection[0] == m2).shouldBeTrue();
1164
+ }
1165
+ },
1166
+
1167
+ "in the process of creating": {
1168
+ beforeEach: function() {
1169
+ this.modelInstance = new this.model();
1170
+ stub(this.model, '_filterAndProcessJsonWhileAccumulatingCollectionEntities').andReturn([{id: '234'}, Prototype.emptyFunction]);
1171
+ },
1172
+
1173
+ "should not attempt to persist updates": function() {
1174
+ stub(Ajax, "Request");
1175
+ this.modelInstance.save();
1176
+ stub(Ajax, "Request").andDo(function(url, options) {
1177
+ fail("incorrectly attempted to persist");
1178
+ });
1179
+ this.modelInstance.save();
1180
+ },
1181
+
1182
+ "should persist pending updates after creation is complete": function() {
1183
+ stub(Ajax, "Request").andDo(function(url, options) {
1184
+ options.onComplete();
1185
+ this.onSuccess = options.onSuccess;
1186
+ }.bind(this));
1187
+ this.modelInstance.save();
1188
+ stub(Ajax, "Request").andDo(function(url, options) {}.shouldBeCalled());
1189
+ this.modelInstance.save();
1190
+ this.onSuccess({ responseJSON: { id: 1 } });
1191
+ },
1192
+
1193
+ "should call the instance's before create": function() {
1194
+ stub(this.modelInstance, "beforeCreate").shouldBeCalled();
1195
+ stub(Ajax, "Request").andDo(function(url, options) {
1196
+ options.onSuccess({});
1197
+ });
1198
+ this.modelInstance.save();
1199
+ },
1200
+
1201
+ "should not call the instance's before create method while already in the process of creating": function() {
1202
+ stub(this.modelInstance, "beforeCreate").shouldNotBeCalled();
1203
+ stub(Ajax, "Request").andDo(function(url, options) {
1204
+ options.onSuccess({});
1205
+ });
1206
+ this.modelInstance._creating = true;
1207
+ this.modelInstance.save();
1208
+ }
1209
+ },
1210
+
1211
+ "on creation without a fake id": {
1212
+ beforeEach: function(){
1213
+ this.modelInstance = new this.model();
1214
+ stub(this.model, '_filterAndProcessJsonWhileAccumulatingCollectionEntities').andReturn([{id: '234'}, Prototype.emptyFunction]);
1215
+ },
1216
+ "should send a POST": function() {
1217
+ stub(Ajax, "Request").andDo(function(url, options) {
1218
+ options.method.shouldEqual("post");
1219
+ }.shouldBeCalled());
1220
+ this.modelInstance.save();
1221
+ },
1222
+ "should post to the correct url": function() {
1223
+ stub(Ajax, "Request").andDo(function(url, options) {
1224
+ url.shouldEqual('/resource_url');
1225
+ }.shouldBeCalled());
1226
+ this.modelInstance.save();
1227
+ },
1228
+ "should set its id from the json response": function() {
1229
+ stub(Ajax, "Request").andDo(function(url, options) {
1230
+ options.onSuccess({});
1231
+ });
1232
+ this.modelInstance.save();
1233
+ (234 == this.modelInstance.getId()).shouldBeTrue();
1234
+ },
1235
+ "should put itself in the identity cache": function(){
1236
+ stub(Ajax, "Request").andDo(function(url, options) {
1237
+ options.onSuccess({});
1238
+ });
1239
+ this.modelInstance.save();
1240
+ (this.modelInstance == this.model.getFromCache(234)).shouldBeTrue();
1241
+ },
1242
+ "should call the options on success": function(){
1243
+ /* this is a little confusing, but we're making sure the function that's called
1244
+ on ajax success eventually calls the onSuccess function that's passed into save*/
1245
+ stub(Ajax, "Request").andDo(function(url, options) {
1246
+ options.onSuccess({});
1247
+ });
1248
+ this.modelInstance.save({onSuccess: function(){}.shouldBeCalled()});
1249
+ },
1250
+ "should call the instance's after create": function(){
1251
+ this.modelInstance.afterCreate = function(){}.shouldBeCalled();
1252
+ stub(Ajax, "Request").andDo(function(url, options) {
1253
+ options.onSuccess({});
1254
+ });
1255
+ this.modelInstance.save();
1256
+ }
1257
+ },
1258
+
1259
+ "on creation with a fake id": {
1260
+ beforeEach: function(){
1261
+ this.modelInstance = new this.model({id: this.model.id()});
1262
+ stub(this.model, '_filterAndProcessJsonWhileAccumulatingCollectionEntities').andReturn([{id: '234'}, Prototype.emptyFunction]);
1263
+ },
1264
+ "should map the number id to the object id": function(){
1265
+ stub(Ajax, "Request").andDo(function(url, options) {
1266
+ options.onSuccess({});
1267
+ });
1268
+ this.modelInstance.save();
1269
+ (this.model.id('234') == this.modelInstance.getId()).shouldBeTrue();
1270
+ (this.model.id(234) == this.modelInstance.getId()).shouldBeTrue();
1271
+ },
1272
+ "should reify the id with the number id from the server": function(){
1273
+ (this.modelInstance.getId() == 234).shouldNotBeTrue();
1274
+ stub(Ajax, "Request").andDo(function(url, options) {
1275
+ options.onSuccess({});
1276
+ });
1277
+ this.modelInstance.save();
1278
+ (this.modelInstance.getId() == 234).shouldBeTrue();
1279
+ }
1280
+ },
1281
+
1282
+ "should create a collection entity during a findAll": {
1283
+ beforeEach: function() {
1284
+ this.model = rio.Model.create("SomeModel", {
1285
+ resource: "/resource_url",
1286
+ attrAccessors: ["id", "projectId", "name"]
1287
+ });
1288
+
1289
+ stub(rio.models, "SomeModel").withValue(this.model);
1290
+
1291
+ this.allInstances = [
1292
+ new this.model({ id: 1, projectId: 1 }),
1293
+ new this.model({ id: 2, projectId: 1 }),
1294
+ new this.model({ id: 3, projectId: 1 })
1295
+ ];
1296
+ var buildResponse = function(json) {
1297
+ return rio.environment.includeRootInJson ? { resource: json } : json;
1298
+ };
1299
+ stub(Ajax, "Request").andDo(function(url, options) {
1300
+ options.onSuccess({
1301
+ responseJSON: [
1302
+ buildResponse({ id: 1, projectId: 1 }),
1303
+ buildResponse({ id: 2, projectId: 1 }),
1304
+ {
1305
+ "_set": {
1306
+ "self": buildResponse({ id: 3, projectId: 1 }),
1307
+ "include": [
1308
+ {
1309
+ "json": [
1310
+ buildResponse({ id: 14, projectId: 3 })
1311
+ ],
1312
+ "parameters": {"project_id": 3},
1313
+ "className": "SomeModel"
1314
+ }
1315
+ ]
1316
+ }
1317
+ }
1318
+ ]
1319
+ });
1320
+ }.bind(this));
1321
+ this.model.findAll({
1322
+ onSuccess: function(results) {
1323
+ this.findAllResults = results;
1324
+ }.bind(this),
1325
+ parameters: { projectId: 1 },
1326
+ nonAjaxParameters: { name: { not: "Paulie Shore" } }
1327
+ });
1328
+ },
1329
+
1330
+ "with the correct results": function() {
1331
+ this.findAllResults.shouldEqual(this.allInstances);
1332
+ },
1333
+
1334
+ "that is updated as new entities are added": function() {
1335
+ var newEntity = new this.model({ id: 3, projectId: 1 });
1336
+ this.findAllResults.shouldInclude(newEntity);
1337
+ },
1338
+
1339
+ "that is not updated when an entity that does not match the parameters is added": function() {
1340
+ var newEntity = new this.model({ id: 4, projectId: 2 });
1341
+ this.findAllResults.shouldNotInclude(newEntity);
1342
+ },
1343
+
1344
+ "that uses the nonAjaxParameters to build the condition function": function() {
1345
+ var newEntity = new this.model({ id: 5, projectId: 1, name: "Paulie Shore" });
1346
+ this.findAllResults.shouldNotInclude(newEntity);
1347
+ },
1348
+
1349
+ "that supports the _set style response": {
1350
+ "and creates an entity for the 'self' item": function() {
1351
+ this.model.getFromCache(3).getProjectId().shouldEqual(1);
1352
+ },
1353
+
1354
+ "and creates a collection entity for the include items": function() {
1355
+ stub(Ajax, "Request").shouldNotBeCalled();
1356
+
1357
+ var ce = this.model.findAll({ parameters: { projectId: 3 } });
1358
+ ce.length.shouldEqual(1);
1359
+ ce[0].getProjectId().shouldEqual(3);
1360
+ }
1361
+ },
1362
+
1363
+ "that prevents unnecessary Ajax requests from being made in initializers called while processing collection entities": function() {
1364
+ var model = rio.Model.create("ComplexModel", {
1365
+ resource: "/complex_url",
1366
+ attrAccessors: ["id", "parentId"],
1367
+ methods: {
1368
+ initialize: function() {
1369
+ var results = rio.models.ComplexModel.findAll({
1370
+ parameters: { parentId: this.getId() }
1371
+ });
1372
+ results.each(function(r) {
1373
+ this.getId().shouldEqual(r.getParentId());
1374
+ }.bind(this));
1375
+ }
1376
+ }
1377
+ });
1378
+ stub(rio.models, "ComplexModel").withValue(model);
1379
+
1380
+ var buildResponse = function(json) {
1381
+ return rio.environment.includeRootInJson ? { resource: json } : json;
1382
+ };
1383
+ stub(Ajax, "Request").andDo(function(url, options) {
1384
+ options.onSuccess({
1385
+ responseJSON: [
1386
+ {
1387
+ "_set": {
1388
+ "self": buildResponse({ id: 3, parentId: 0 }),
1389
+ "include": [
1390
+ {
1391
+ "json": [
1392
+ {
1393
+ "_set": {
1394
+ "self": buildResponse({ id: 14, parentId: 3 }),
1395
+ "include": [
1396
+ {
1397
+ "json": [],
1398
+ "parameters": {"parent_id": 14},
1399
+ "className": "ComplexModel"
1400
+ }
1401
+ ]
1402
+ }
1403
+ }
1404
+ ],
1405
+ "parameters": {"parent_id": 3},
1406
+ "className": "ComplexModel"
1407
+ }
1408
+ ]
1409
+ }
1410
+ }
1411
+ ]
1412
+ });
1413
+ }.bind(this).shouldBeCalled().once());
1414
+
1415
+ model.findAll({
1416
+ parameters: { parentId: 0 }
1417
+ })[0].getParentId().shouldEqual(0);
1418
+ },
1419
+
1420
+ "that does not double add entities that update themselves in their initializer": function() {
1421
+ var model = rio.Model.create("ComplexModel", {
1422
+ resource: "/complex_url",
1423
+ attrAccessors: ["id", "parentId", "thing"],
1424
+ clientOnlyAttrs: ["thing"],
1425
+ methods: {
1426
+ initialize: function() {
1427
+ this.setThing(1);
1428
+ }
1429
+ }
1430
+ });
1431
+ stub(rio.models, "ComplexModel").withValue(model);
1432
+
1433
+ var buildResponse = function(json) {
1434
+ return rio.environment.includeRootInJson ? { resource: json } : json;
1435
+ };
1436
+ stub(Ajax, "Request").andDo(function(url, options) {
1437
+ options.onSuccess({
1438
+ responseJSON: [
1439
+ {
1440
+ "_set": {
1441
+ "self": buildResponse({ id: 3, parentId: 0 }),
1442
+ "include": []
1443
+ }
1444
+ }
1445
+ ]
1446
+ });
1447
+ }.bind(this).shouldBeCalled());
1448
+
1449
+ model.findAll({
1450
+ parameters: { parentId: 0 }
1451
+ }).length.shouldEqual(1);
1452
+ },
1453
+
1454
+ "with a custom url": {
1455
+ beforeEach: function() {
1456
+ this.model.findAll({
1457
+ onSuccess: function(results) {
1458
+ this.findAllResults = results;
1459
+ }.bind(this),
1460
+ parameters: { projectId: 1 },
1461
+ url: "/custom_url"
1462
+ });
1463
+ },
1464
+
1465
+ "that is used for querying": function() {
1466
+ var buildResponse = function(json) {
1467
+ return rio.environment.includeRootInJson ? { resource: json } : json;
1468
+ };
1469
+
1470
+ stub(Ajax, "Request").andDo(function(url, options) {
1471
+ url.shouldEqual("/custom_url");
1472
+ }.bind(this).shouldBeCalled());
1473
+
1474
+ this.model.findAll({
1475
+ parameters: { projectId: 3 },
1476
+ url: "/custom_url"
1477
+ });
1478
+ },
1479
+
1480
+ "that is updated as new matching entities are added": function() {
1481
+ var collectionEntity = this.model.findAll({
1482
+ parameters: { projectId: 1 },
1483
+ url: "/custom_url"
1484
+ });
1485
+
1486
+ collectionEntity.shouldEqual(this.allInstances);
1487
+
1488
+ var newEntity = new this.model({ id: 3, projectId: 1 });
1489
+ collectionEntity.shouldInclude(newEntity);
1490
+ },
1491
+
1492
+ "that is not updated as non-matching entities are added": function() {
1493
+ var collectionEntity = this.model.findAll({
1494
+ parameters: { projectId: 1 },
1495
+ url: "/custom_url"
1496
+ });
1497
+
1498
+ var newEntity = new this.model({ id: 4, projectId: 2 });
1499
+ collectionEntity.shouldNotInclude(newEntity);
1500
+ },
1501
+
1502
+ "that returns the cached collection entity on subsequent searches": function() {
1503
+ var findAll1 = this.model.findAll({
1504
+ parameters: { projectId: 1 },
1505
+ url: "/custom_url"
1506
+ });
1507
+ stub(Ajax, "Request").andDo(function() {}.shouldNotBeCalled());
1508
+ var findAll2 = this.model.findAll({
1509
+ parameters: { projectId: 1 },
1510
+ url: "/custom_url"
1511
+ });
1512
+ (findAll1 === findAll2).shouldBeTrue();
1513
+ }
1514
+ }
1515
+ },
1516
+
1517
+ "should support synchronous findAll": {
1518
+ beforeEach: function() {
1519
+ this.model = rio.Model.create("SomeModel", {
1520
+ resource: "/resource_url",
1521
+ attrAccessors: ["id", "projectId"]
1522
+ });
1523
+
1524
+ this.allInstances = [
1525
+ new this.model({ id: 1, projectId: 1 }),
1526
+ new this.model({ id: 2, projectId: 1 })
1527
+ ];
1528
+ var buildResponse = function(json) {
1529
+ return rio.environment.includeRootInJson ? { resource: json } : json;
1530
+ };
1531
+ stub(Ajax, "Request").andDo(function(url, options) {
1532
+ options.onSuccess({
1533
+ responseJSON: [
1534
+ buildResponse({ id: 1, projectId: 1 }),
1535
+ buildResponse({ id: 2, projectId: 1 })
1536
+ ]
1537
+ });
1538
+ }.bind(this));
1539
+ },
1540
+
1541
+ "when asynchronous is false": function() {
1542
+ var results = this.model.findAll({
1543
+ asynchronous: false,
1544
+ parameters: { projectId: 1 },
1545
+ onSuccess: function(values) { values.shouldEqual(this.allInstances); }.bind(this).shouldBeCalled()
1546
+ });
1547
+
1548
+ results.shouldEqual(this.allInstances);
1549
+
1550
+ },
1551
+
1552
+ "when asynchronous is false and the collection entities list has a cache hit": function() {
1553
+ this.model.findAll({
1554
+ asynchronous: false,
1555
+ parameters: { projectId: 1 },
1556
+ onSuccess: function(values) { values.shouldEqual(this.allInstances); }.bind(this).shouldBeCalled()
1557
+ });
1558
+
1559
+ var results = this.model.findAll({
1560
+ asynchronous: false,
1561
+ parameters: { projectId: 1 },
1562
+ onSuccess: function(values) { values.shouldEqual(this.allInstances); }.bind(this).shouldBeCalled()
1563
+ });
1564
+ results.shouldEqual(this.allInstances);
1565
+ },
1566
+
1567
+ "when no onSuccess function is provided": function() {
1568
+ var results = this.model.findAll({
1569
+ parameters: { projectId: 1 }
1570
+ });
1571
+ results.shouldEqual(this.allInstances);
1572
+ }
1573
+ },
1574
+
1575
+ "should support find": {
1576
+ beforeEach: function() {
1577
+ this.model = rio.Model.create("SomeModel", {
1578
+ resource: "/resource_url",
1579
+ attrAccessors: ["id", "projectId"]
1580
+ });
1581
+ },
1582
+
1583
+ "by using the passed in ID to fetch the entity from the server": function() {
1584
+ stub(Ajax, "Request").andDo(function(url, options) {
1585
+ url.shouldEqual("/resource_url/28");
1586
+ }.shouldBeCalled());
1587
+
1588
+ this.model.find(28);
1589
+ },
1590
+
1591
+ "by getting the entity from the server asynchronously by default and evaluating JSON": function() {
1592
+ stub(Ajax, "Request").andDo(function(url, options) {
1593
+ options.method.shouldEqual("get");
1594
+ options.asynchronous.shouldBeTrue();
1595
+ options.evalJSON.shouldBeTrue();
1596
+ }.shouldBeCalled());
1597
+
1598
+ this.model.find(28, { onSuccess: function() {} });
1599
+ },
1600
+
1601
+ "by calling the onSuccess function with the found entity": function() {
1602
+ var buildResponse = function(json) {
1603
+ return rio.environment.includeRootInJson ? { resource: json } : json;
1604
+ };
1605
+ stub(Ajax, "Request").andDo(function(url, options) {
1606
+ options.onSuccess({
1607
+ responseJSON: buildResponse({ id: 28, projectId: 15 })
1608
+ });
1609
+ }.shouldBeCalled());
1610
+
1611
+ this.model.find(28, {
1612
+ onSuccess: function(entity) {
1613
+ entity.getId().shouldEqual(28);
1614
+ entity.getProjectId().shouldEqual(15);
1615
+ }.shouldBeCalled()
1616
+ });
1617
+ },
1618
+
1619
+ "by getting the entity synchronously and returning it if no onSuccess method is provided": function() {
1620
+ var buildResponse = function(json) {
1621
+ return rio.environment.includeRootInJson ? { resource: json } : json;
1622
+ };
1623
+ stub(Ajax, "Request").andDo(function(url, options) {
1624
+ options.onSuccess({
1625
+ responseJSON: buildResponse({ id: 28, projectId: 15 })
1626
+ });
1627
+ }.shouldBeCalled());
1628
+
1629
+ var entity = this.model.find(28);
1630
+
1631
+ (entity.constructor == this.model).shouldBeTrue();
1632
+ entity.getId().shouldEqual(28);
1633
+ entity.getProjectId().shouldEqual(15);
1634
+ },
1635
+
1636
+ "by calling onSuccess with an existing entity if found in the cache": function() {
1637
+ var existing = new this.model({ id: 28 });
1638
+
1639
+ this.model.find(28, {
1640
+ onSuccess: function(entity) {
1641
+ (entity == existing).shouldBeTrue();
1642
+ }.shouldBeCalled()
1643
+ });
1644
+ },
1645
+
1646
+ "by returning an existing entity if asynchronous is false": function() {
1647
+ var existing = new this.model({ id: 28 });
1648
+
1649
+ (this.model.find(28, { asynchronous: false }) == existing).shouldBeTrue();
1650
+ (this.model.find(28) == existing).shouldBeTrue();
1651
+ },
1652
+
1653
+ "by returning undefined if id is undefined": function() {
1654
+ shouldBeUndefined(this.model.find());
1655
+ },
1656
+
1657
+ "by not calling onSuccess if id is undefined": function() {
1658
+ this.model.find(undefined, {
1659
+ onSuccess: function(entity) {}.shouldNotBeCalled()
1660
+ });
1661
+ },
1662
+
1663
+ "by skipping Ajax request if id is undefined": function() {
1664
+ stub(Ajax, "Request").shouldNotBeCalled();
1665
+ this.model.find();
1666
+ }
1667
+
1668
+ /* "by waiting for find requests to execute afterActiveQueries": pending */
1669
+ },
1670
+
1671
+ "should support hasMany association": {
1672
+ beforeEach: function() {
1673
+ this.model = rio.Model.create("Project", {
1674
+ resource: "/projects",
1675
+ attrAccessors: ["id"],
1676
+ hasMany: [
1677
+ "tasks",
1678
+ ["comments", { parameters: { other: 7 } }]
1679
+ ]
1680
+ });
1681
+ this.taskModel = rio.Model.create("Task", {
1682
+ resource: "/tasks",
1683
+ attrAccessors: ["id", "projectId"]
1684
+ });
1685
+ this.commentModel = rio.Model.create("Comment", {
1686
+ resource: "/comments",
1687
+ attrAccessors: ["id", "projectId", "other"]
1688
+ });
1689
+
1690
+ stub(rio.models, "Project").withValue(this.model);
1691
+ stub(rio.models, "Task").withValue(this.taskModel);
1692
+ stub(rio.models, "Comment").withValue(this.commentModel);
1693
+
1694
+ instrumentModel(this.model);
1695
+ instrumentModel(this.taskModel);
1696
+ instrumentModel(this.commentModel);
1697
+
1698
+ this.modelInstance = new this.model({ id: 192 });
1699
+ },
1700
+
1701
+ "by provide an attr accessor": function() {
1702
+ (this.modelInstance.getTasks != undefined).shouldBeTrue();
1703
+ (this.modelInstance.setTasks != undefined).shouldBeTrue();
1704
+ this.modelInstance.tasks.constructor.shouldEqual(rio.Binding);
1705
+ },
1706
+
1707
+ "by adding the new attribute to the clientOnlyAttr list": function() {
1708
+ this.model._clientOnlyAttrs.shouldInclude("tasks");
1709
+ this.model._clientOnlyAttrs.shouldInclude("comments");
1710
+ },
1711
+
1712
+ "by lazily executing a synchronous AJAX request to populate it's value": function() {
1713
+
1714
+ stub(this.taskModel, "findAll").andDo(function(options) {
1715
+ options.asynchronous.shouldBeFalse();
1716
+ }.shouldBeCalled());
1717
+
1718
+ this.modelInstance.getTasks();
1719
+ },
1720
+
1721
+ "by lazily executing an AJAX request with it's id as the properly named association id": function() {
1722
+
1723
+ stub(this.taskModel, "findAll").andDo(function(options) {
1724
+ options.parameters.projectId.shouldEqual(192);
1725
+ }.shouldBeCalled());
1726
+
1727
+ this.modelInstance.getTasks();
1728
+ },
1729
+
1730
+ "by lazily executing an AJAX and return it's value": function() {
1731
+ var tasks = [1,2,3];
1732
+ stub(this.taskModel, "findAll").andDo(function(options) {
1733
+ options.onSuccess(tasks);
1734
+ }.shouldBeCalled());
1735
+
1736
+ this.modelInstance.getTasks().shouldEqual(tasks);
1737
+ },
1738
+
1739
+ "with more complicated definitions": function() {
1740
+ (this.modelInstance.getComments != undefined).shouldBeTrue();
1741
+ (this.modelInstance.setComments != undefined).shouldBeTrue();
1742
+ this.modelInstance.comments.constructor.shouldEqual(rio.Binding);
1743
+ },
1744
+
1745
+ "with parameters that limit the set beyond the association id": function() {
1746
+ stub(this.commentModel, "findAll").andDo(function(options) {
1747
+ options.parameters.projectId.shouldEqual(192);
1748
+ options.parameters.other.shouldEqual(7);
1749
+ }.shouldBeCalled());
1750
+
1751
+ this.modelInstance.getComments();
1752
+ },
1753
+
1754
+ "with a create method": {
1755
+ "that creates an object with the proper association id set": function() {
1756
+ var comment = this.modelInstance.comments.create();
1757
+ comment.getProjectId().shouldEqual(this.modelInstance.getId());
1758
+ },
1759
+
1760
+ "that creates an object with extra parameters as well": function() {
1761
+ var comment = this.modelInstance.comments.create();
1762
+ comment.getOther().shouldEqual(7);
1763
+ },
1764
+
1765
+ "that adds the created entity to the association": function() {
1766
+ this.modelInstance.getComments().length.shouldEqual(0);
1767
+ this.modelInstance.comments.create({ id: this.commentModel.id() });
1768
+ this.modelInstance.getComments().length.shouldEqual(1);
1769
+ }
1770
+ },
1771
+
1772
+ "for collection entities with unreified id's": {
1773
+ "by adding existing entities": function() {
1774
+ var project = new this.model({ id: this.model.id() });
1775
+ rio.models.Task.create({ id: rio.models.Task.id(), projectId: project.getId() });
1776
+ project.getTasks().length.shouldEqual(1);
1777
+ },
1778
+
1779
+ "by adding new entities": function() {
1780
+ var project = this.model.create({ id: this.model.id() });
1781
+ var tasks = project.getTasks();
1782
+ tasks.length.shouldEqual(0);
1783
+ rio.models.Task.create({ id: rio.models.Task.id(), projectId: project.getId() });
1784
+ tasks.length.shouldEqual(1);
1785
+ },
1786
+
1787
+ "by adding new after the id is reified": function() {
1788
+ var id = this.model.id();
1789
+ var project = new this.model({ id: id });
1790
+ var tasks = project.getTasks();
1791
+ tasks.length.shouldEqual(0);
1792
+ id.reify(12);
1793
+ rio.models.Task.create({ id: rio.models.Task.id(), projectId: 12 });
1794
+ tasks.length.shouldEqual(1);
1795
+ }
1796
+ }
1797
+ },
1798
+
1799
+ "should support belongsTo association": {
1800
+ beforeEach: function() {
1801
+ this.model = rio.Model.create("Project", {
1802
+ resource: "/projects",
1803
+ attrAccessors: [
1804
+ "id", "userId", "personId", "employerId", "customerId"
1805
+ ],
1806
+ belongsTo: [
1807
+ "user",
1808
+ ["person", { className: "User" }],
1809
+ ["company", { foreignKey: "employerId" }],
1810
+ ["client", { className: "Company", foreignKey: "customerId" }]
1811
+ ]
1812
+ });
1813
+ this.model.prepareTransaction = function() {
1814
+ this.executeTransaction(rio.Undo.isProcessingUndo(), rio.Undo.isProcessingRedo());
1815
+ };
1816
+ stub(rio.models, "Project").withValue(this.model);
1817
+
1818
+ this.modelInstance = new this.model({ id: 101, userId: 14, personId: 91, employerId: 45, customerId: 32 });
1819
+
1820
+ this.userModel = rio.Model.create("User", {
1821
+ resource: "/users",
1822
+ attrAccessors: ["id"]
1823
+ });
1824
+ stub(rio.models, "User").withValue(this.userModel);
1825
+
1826
+ this.companyModel = rio.Model.create("Company", {
1827
+ resource: "/companies",
1828
+ attrAccessors: ["id"]
1829
+ });
1830
+ stub(rio.models, "Company").withValue(this.companyModel);
1831
+ },
1832
+
1833
+ "by providing an attrAccessor": function() {
1834
+ (this.modelInstance.getUser != undefined).shouldBeTrue();
1835
+ (this.modelInstance.setUser != undefined).shouldBeTrue();
1836
+ this.modelInstance.user.constructor.shouldEqual(rio.Binding);
1837
+ },
1838
+
1839
+ "by adding the new attribute to the clientOnlyAttr list": function() {
1840
+ this.model._clientOnlyAttrs.shouldInclude("user");
1841
+ },
1842
+
1843
+ "by lazily executing a synchronous AJAX request to populate its value": function() {
1844
+ stub(this.userModel, "find").andDo(function(id, options) {
1845
+ options.asynchronous.shouldBeFalse();
1846
+ }.shouldBeCalled());
1847
+
1848
+ this.modelInstance.getUser();
1849
+ },
1850
+
1851
+ "by lazily executing an AJAX request with the proper id to populate its value": function() {
1852
+ stub(this.userModel, "find").andDo(function(id, options) {
1853
+ id.shouldEqual(14);
1854
+ }.shouldBeCalled());
1855
+
1856
+ this.modelInstance.getUser();
1857
+ },
1858
+
1859
+ "by lazily executing an AJAX request and returning the value": function() {
1860
+ var user14 = new rio.models.User({ id: 14 });
1861
+ (this.modelInstance.getUser() == user14).shouldBeTrue();
1862
+ },
1863
+
1864
+ "by updating it's value and firing bindings when the association id is changed": function() {
1865
+ var user14 = new rio.models.User({ id: 14 });
1866
+ var user22 = new rio.models.User({ id: 22 });
1867
+
1868
+ (this.modelInstance.getUser() == user14).shouldBeTrue();
1869
+
1870
+ this.modelInstance.user.bind(function(u) {
1871
+ (u == user22).shouldBeTrue();
1872
+ }.shouldBeCalled(), true);
1873
+
1874
+ this.modelInstance.setUserId(22);
1875
+ },
1876
+
1877
+ "by allowing explicitly defined class names": function() {
1878
+ var user91 = new rio.models.User({ id: 91 });
1879
+
1880
+ (this.modelInstance.getPerson() == user91).shouldBeTrue();
1881
+ },
1882
+
1883
+ "by allowing explicitly defined foreign keys": function() {
1884
+ var company45 = new rio.models.Company({ id: 45 });
1885
+
1886
+ (this.modelInstance.getCompany() == company45).shouldBeTrue();
1887
+ },
1888
+
1889
+ "by allowing explicitly defined class names and foreign keys": function() {
1890
+ var company32 = new rio.models.Company({ id: 32 });
1891
+
1892
+ (this.modelInstance.getClient() == company32).shouldBeTrue();
1893
+ }
1894
+ }
1895
+ },
1896
+
1897
+
1898
+ "should handle push": {
1899
+ beforeEach: function() {
1900
+ var someTestModel = rio.Model.create("SomeTestModel", {
1901
+ resource: "/some_test_model",
1902
+ attrAccessors: ["id", "polarBear"],
1903
+ undoEnabled: true,
1904
+ methods: {
1905
+ parameters: function() { return {}; }
1906
+ }
1907
+ });
1908
+ stub(rio.models, "SomeTestModel").withValue(someTestModel);
1909
+
1910
+ stub(rio.Undo, "_queue").withValue(new rio.UndoQueue());
1911
+ stub(Ajax, "Request");
1912
+ rio.models.SomeTestModel.prepareTransaction = function() {
1913
+ this.executeTransaction(rio.Undo.isProcessingUndo(), rio.Undo.isProcessingRedo());
1914
+ };
1915
+ },
1916
+
1917
+ "remoteCreate": function() {
1918
+ rio.Model.remoteCreate({
1919
+ name: "SomeTestModel",
1920
+ id: 1,
1921
+ json: this.buildResponse({ id: 1, polar_bear: 72 })
1922
+ });
1923
+ var modelInstance = rio.models.SomeTestModel.getFromCache(rio.models.SomeTestModel.id(1));
1924
+ modelInstance.getPolarBear().shouldEqual(72);
1925
+ },
1926
+
1927
+ "remoteCreate, doing nothing if the transactionKey matches mine": function() {
1928
+ rio.Model.remoteCreate({
1929
+ name: "SomeTestModel",
1930
+ id: 1,
1931
+ json: { some_test_model: { id: 1, polar_bear: 72 }},
1932
+ transactionKey: rio.environment.transactionKey
1933
+ });
1934
+
1935
+ shouldBeUndefined(rio.models.SomeTestModel.getFromCache(rio.models.SomeTestModel.id(1)));
1936
+ },
1937
+
1938
+ "remoteCreate, doing nothing if the entity is found in the cache": function() {
1939
+ new rio.models.SomeTestModel({ id: 1, polar_bear: 19 });
1940
+
1941
+ rio.Model.remoteCreate({
1942
+ name: "SomeTestModel",
1943
+ id: 1,
1944
+ json: { some_test_model: { id: 1, polar_bear: 72 }}
1945
+ });
1946
+
1947
+ var modelInstance = rio.models.SomeTestModel.getFromCache(rio.models.SomeTestModel.id(1));
1948
+ modelInstance.getPolarBear().shouldEqual(19);
1949
+ },
1950
+
1951
+ "remoteCreate and register an undo": function() {
1952
+ rio.Model.remoteCreate({
1953
+ name: "SomeTestModel",
1954
+ id: 1,
1955
+ json: this.buildResponse({ id: 1, polar_bear: 72 })
1956
+ });
1957
+ var modelInstance = rio.models.SomeTestModel.getFromCache(rio.models.SomeTestModel.id(1));
1958
+ stub(modelInstance, "destroy").shouldBeCalled();
1959
+ rio.Undo.undo();
1960
+ },
1961
+
1962
+ "remoteCreate should not register an undo if the entity is found in the cache": function() {
1963
+ new rio.models.SomeTestModel({ id: 1, polar_bear: 19 });
1964
+
1965
+ rio.Model.remoteCreate({
1966
+ name: "SomeTestModel",
1967
+ id: 1,
1968
+ json: { some_test_model: { id: 1, polar_bear: 72 }}
1969
+ });
1970
+
1971
+ var modelInstance = rio.models.SomeTestModel.getFromCache(rio.models.SomeTestModel.id(1));
1972
+ modelInstance.getPolarBear().shouldEqual(19);
1973
+ stub(modelInstance, "destroy").shouldNotBeCalled();
1974
+ rio.Undo.undo();
1975
+ },
1976
+
1977
+ "remoteUpdate": function() {
1978
+ var modelInstance = new rio.models.SomeTestModel({ id: 1, polar_bear: 19 });
1979
+
1980
+ rio.Model.remoteUpdate({
1981
+ name: "SomeTestModel",
1982
+ id: 1,
1983
+ json: this.buildResponse({id: 1, polar_bear: 72 })
1984
+ });
1985
+
1986
+ modelInstance.getPolarBear().shouldEqual(72);
1987
+ },
1988
+
1989
+ "remoteUpdate, doing nothing if the transactionKey key matches mine": function() {
1990
+ var modelInstance = new rio.models.SomeTestModel({ id: 1, polar_bear: 19 });
1991
+
1992
+ rio.Model.remoteUpdate({
1993
+ name: "SomeTestModel",
1994
+ id: 1,
1995
+ json: { some_test_model: { id: 1, polar_bear: 72 }},
1996
+ transactionKey: rio.environment.transactionKey
1997
+ });
1998
+
1999
+ modelInstance.getPolarBear().shouldEqual(19);
2000
+ },
2001
+
2002
+ "remoteUpdate and not resave the entity": function() {
2003
+ var modelInstance = new rio.models.SomeTestModel({ id: 1, polar_bear: 19 });
2004
+
2005
+ stub(modelInstance, "save").andDo(function() {}.shouldNotBeCalled());
2006
+ rio.Model.remoteUpdate({
2007
+ name: "SomeTestModel",
2008
+ id: 1,
2009
+ json: this.buildResponse({id: 1, polar_bear: 72})
2010
+ });
2011
+ },
2012
+
2013
+ "remoteUpdate and register an undo": function() {
2014
+ var modelInstance = new rio.models.SomeTestModel({ id: 1, polar_bear: 19 });
2015
+
2016
+ rio.Model.remoteUpdate({
2017
+ name: "SomeTestModel",
2018
+ id: 1,
2019
+ json: this.buildResponse({ id: 1, polar_bear: 72 })
2020
+ });
2021
+ modelInstance.getPolarBear().shouldEqual(72);
2022
+
2023
+ rio.Undo.undo();
2024
+ modelInstance.getPolarBear().shouldEqual(19);
2025
+ },
2026
+
2027
+ "remoteUpdate and update the entities _lastSavedState": function() {
2028
+ var modelInstance = new rio.models.SomeTestModel({ id: 1, polar_bear: 19 });
2029
+
2030
+ rio.Model.remoteUpdate({
2031
+ name: "SomeTestModel",
2032
+ id: 1,
2033
+ json: this.buildResponse({ id: 1, polar_bear: 72 })
2034
+ });
2035
+
2036
+ modelInstance._lastSavedState.polarBear.shouldEqual(72);
2037
+ },
2038
+
2039
+ "remoteUpdate and add to the proper collection entities": function() {
2040
+ var modelInstance = new rio.models.SomeTestModel({ id: 1, polar_bear: 19 });
2041
+ var collectionEntity = rio.CollectionEntity.create({
2042
+ model: rio.models.SomeTestModel,
2043
+ values: [],
2044
+ condition: function(e) { return e.getPolarBear() == 72; }
2045
+ });
2046
+
2047
+ rio.models.SomeTestModel.putCollectionEntity(rio.models.SomeTestModel.url() + "#{polar_bear: 72}", collectionEntity);
2048
+
2049
+ rio.Model.remoteUpdate({
2050
+ name: "SomeTestModel",
2051
+ id: 1,
2052
+ json: this.buildResponse({ id: 1, polar_bear: 72 })
2053
+ });
2054
+
2055
+ (collectionEntity.first() == modelInstance).shouldBeTrue();
2056
+ },
2057
+
2058
+ /*
2059
+
2060
+ This requirement doesn't make much sense. We have to have confidence in our push server.
2061
+ If something is created the client should know about it. Delegating to create does not
2062
+ work now that we send updates as a diff, rather than the whole entity.
2063
+
2064
+ "remoteUpdate and delegate to remoteCreate if there is a cache miss": function() {
2065
+ rio.Model.remoteUpdate({
2066
+ name: "SomeTestModel",
2067
+ id: 1,
2068
+ json: this.buildResponse({ id: 7, polar_bear: 145 })
2069
+ });
2070
+
2071
+ var modelInstance = rio.models.SomeTestModel.getFromCache(rio.models.SomeTestModel.id(7));
2072
+ modelInstance.getPolarBear().shouldEqual(145);
2073
+ },
2074
+ */
2075
+
2076
+ "remoteDestroy and remove the entity from the collection entities": function() {
2077
+ var collectionEntity = rio.CollectionEntity.create({
2078
+ model: rio.models.SomeTestModel,
2079
+ values: [],
2080
+ condition: function(e) { return e.getPolarBear() == 72; }
2081
+ });
2082
+ rio.models.SomeTestModel.putCollectionEntity(rio.models.SomeTestModel.url() + "#{polar_bear: 72}", collectionEntity);
2083
+
2084
+ var modelInstance = new rio.models.SomeTestModel({ id: 1, polar_bear: 72 });
2085
+ (collectionEntity.first() == modelInstance).shouldBeTrue();
2086
+
2087
+ rio.Model.remoteDestroy({
2088
+ name: "SomeTestModel",
2089
+ id: 1
2090
+ });
2091
+
2092
+ collectionEntity.shouldBeEmpty();
2093
+ },
2094
+
2095
+ "remoteDestroy, doing nothing if the transactionKey matches mine": function() {
2096
+ var collectionEntity = rio.CollectionEntity.create({
2097
+ model: rio.models.SomeTestModel,
2098
+ values: [],
2099
+ condition: function(e) { return e.getPolarBear() == 72; }
2100
+ });
2101
+ rio.models.SomeTestModel.putCollectionEntity(rio.models.SomeTestModel.url() + "#{polar_bear: 72}", collectionEntity);
2102
+
2103
+ var modelInstance = new rio.models.SomeTestModel({ id: 1, polar_bear: 72 });
2104
+ (collectionEntity.first() == modelInstance).shouldBeTrue();
2105
+
2106
+ rio.Model.remoteDestroy({
2107
+ name: "SomeTestModel",
2108
+ id: 1,
2109
+ transactionKey: rio.environment.transactionKey
2110
+ });
2111
+
2112
+ collectionEntity.shouldNotBeEmpty();
2113
+ },
2114
+
2115
+ "remoteDestroy and remove the entity from identity cache": function() {
2116
+ var modelInstance = new rio.models.SomeTestModel({ id: 1, polar_bear: 72 });
2117
+
2118
+ (rio.models.SomeTestModel.getFromCache(1) == modelInstance).shouldBeTrue();
2119
+
2120
+ rio.Model.remoteDestroy({
2121
+ name: "SomeTestModel",
2122
+ id: 1
2123
+ });
2124
+
2125
+ shouldBeUndefined(rio.models.SomeTestModel.getFromCache(1));
2126
+ },
2127
+
2128
+ "remoteDestroy and fire the destroy event": function() {
2129
+ var modelInstance = new rio.models.SomeTestModel({ id: 1, polar_bear: 72 });
2130
+
2131
+ (rio.models.SomeTestModel.getFromCache(1) == modelInstance).shouldBeTrue();
2132
+
2133
+ modelInstance.observe("destroy", function() {}.shouldBeCalled());
2134
+
2135
+ rio.Model.remoteDestroy({
2136
+ name: "SomeTestModel",
2137
+ id: 1
2138
+ });
2139
+ },
2140
+
2141
+ "remoteDestroy and register an undo": function() {
2142
+ var modelInstance = new rio.models.SomeTestModel({ id: 1, polar_bear: 72 });
2143
+
2144
+ (rio.models.SomeTestModel.getFromCache(1) == modelInstance).shouldBeTrue();
2145
+
2146
+ modelInstance.observe("destroy", function() {}.shouldBeCalled());
2147
+
2148
+ rio.Model.remoteDestroy({
2149
+ name: "SomeTestModel",
2150
+ id: 1
2151
+ });
2152
+
2153
+ rio.Undo.undo();
2154
+
2155
+ var newInstance = rio.models.SomeTestModel.getFromCache(1);
2156
+
2157
+ newInstance.getPolarBear().shouldEqual(72);
2158
+ shouldBeUndefined(newInstance.getPolarBear()._lastSavedState);
2159
+ },
2160
+
2161
+ "remoteTransaction by delegating to the proper remote functions": function() {
2162
+ stub(rio.Model, "doRemoteCreate").andDo(function(options) {
2163
+ options.id.shouldEqual(1);
2164
+ options.name.shouldEqual("SomeTestModel");
2165
+ options.json.hello.shouldEqual("world");
2166
+ return {};
2167
+ }.shouldBeCalled());
2168
+ stub(rio.Model, "doRemoteUpdate").andDo(function(options) {
2169
+ options.id.shouldEqual(2);
2170
+ options.name.shouldEqual("SomeTestModel");
2171
+ options.json.hello.shouldEqual("somebody");
2172
+ return {};
2173
+ }.shouldBeCalled());
2174
+ stub(rio.Model, "doRemoteDestroy").andDo(function(options) {
2175
+ options.id.shouldEqual(3);
2176
+ options.name.shouldEqual("SomeTestModel");
2177
+ return {};
2178
+ }.shouldBeCalled());
2179
+ rio.Model.remoteTransaction({
2180
+ transaction: [
2181
+ { action: "create", id: 1, name: "SomeTestModel", json: { hello: "world"} },
2182
+ { action: "update", id: 2, name: "SomeTestModel", json: { hello: "somebody"} },
2183
+ { action: "destroy", id: 3, name: "SomeTestModel" }
2184
+ ]
2185
+ });
2186
+ },
2187
+
2188
+ "remoteTransaction, doing nothing if the transactionKey matches mine": function() {
2189
+ stub(rio.Model, "doRemoteCreate").shouldNotBeCalled();
2190
+ stub(rio.Model, "doRemoteUpdate").shouldNotBeCalled();
2191
+ stub(rio.Model, "doRemoteDestroy").shouldNotBeCalled();
2192
+ rio.Model.remoteTransaction({
2193
+ transactionKey: rio.environment.transactionKey,
2194
+ transaction: [
2195
+ { action: "create", id: 1, name: "SomeTestModel", json: { hello: "world"} },
2196
+ { action: "update", id: 2, name: "SomeTestModel", json: { hello: "somebody"} },
2197
+ { action: "destroy", id: 3, name: "SomeTestModel" }
2198
+ ]
2199
+ });
2200
+ },
2201
+
2202
+ "remoteTransaction and register an undo": function() {
2203
+ var instance2 = new rio.models.SomeTestModel({ id: 2, polar_bear: 72 });
2204
+ var instance3 = new rio.models.SomeTestModel({ id: 3, polar_bear: 12 });
2205
+
2206
+ var buildResponse = function(json) {
2207
+ return rio.environment.includeRootInJson ? { some_test_model: json } : json;
2208
+ };
2209
+ rio.Model.remoteTransaction({
2210
+ transaction: [
2211
+ { action: "create", id: 1, name: "SomeTestModel", json: buildResponse({ id: 1, polar_bear: "world"}) },
2212
+ { action: "update", id: 2, name: "SomeTestModel", json: buildResponse({ id: 2, polar_bear: "somebody"}) },
2213
+ { action: "destroy", id: 3, name: "SomeTestModel" }
2214
+ ]
2215
+ });
2216
+
2217
+ rio.Undo.undo();
2218
+
2219
+ shouldBeUndefined(rio.models.SomeTestModel.getFromCache(1));
2220
+ instance2.getPolarBear().shouldEqual(72);
2221
+ rio.models.SomeTestModel.getFromCache(3).getPolarBear().shouldEqual(12);
2222
+ }
2223
+ },
2224
+
2225
+ "should support concise syntax": {
2226
+ beforeEach: function() {
2227
+ this.model = rio.Model.create("SomeModel", {
2228
+ attrReaders: ["name"],
2229
+ attrAccessors: [["age", 25]],
2230
+ methods: {
2231
+ hello: function() {
2232
+ return "hello world";
2233
+ }
2234
+ },
2235
+ classMethods: {
2236
+ hello: function() {
2237
+ return "HELLO WORLD";
2238
+ }
2239
+ }
2240
+ });
2241
+
2242
+ this.modelInstance = new this.model({ name: "Jason" });
2243
+ },
2244
+
2245
+ "for NAME": function() {
2246
+ this.model.toString().shouldEqual("SomeModel");
2247
+ },
2248
+
2249
+ "for attrReader": function() {
2250
+ this.modelInstance.getName().shouldEqual("Jason");
2251
+ },
2252
+
2253
+ "for attrAccessor with default value": function() {
2254
+ this.modelInstance.getAge().shouldEqual(25);
2255
+ this.modelInstance.setAge(26);
2256
+ this.modelInstance.getAge().shouldEqual(26);
2257
+ },
2258
+
2259
+ "for instance methods": function() {
2260
+ this.modelInstance.hello().shouldEqual("hello world");
2261
+ },
2262
+
2263
+ "for class methods": function() {
2264
+ this.model.hello().shouldEqual("HELLO WORLD");
2265
+ }
2266
+ }
2267
+
2268
+ });