spontaneous 0.2.0.alpha7 → 0.2.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (247) hide show
  1. data/Gemfile +10 -4
  2. data/Readme.markdown +1 -1
  3. data/application/css/definitions.css.scss +5 -0
  4. data/application/css/dialogue.css.scss +62 -0
  5. data/application/js/content.js +1 -1
  6. data/application/js/dom.js +1 -1
  7. data/application/js/event_source.js +3 -0
  8. data/application/js/{field_types/date_field.js → field/date.js} +2 -2
  9. data/application/js/{field_types/file_field.js → field/file.js} +2 -2
  10. data/application/js/{field_types/image_field.js → field/image.js} +54 -20
  11. data/application/js/{field_types/long_string_field.js → field/long_string.js} +2 -2
  12. data/application/js/{field_types/markdown_field.js → field/markdown.js} +2 -2
  13. data/application/js/{field_types/select_field.js → field/select.js} +2 -2
  14. data/application/js/{field_types/string_field.js → field/string.js} +21 -7
  15. data/application/js/{field_types/webvideo_field.js → field/webvideo.js} +2 -2
  16. data/application/js/field.js +2 -2
  17. data/application/js/publish.js +99 -19
  18. data/application/js/spontaneous.js +8 -8
  19. data/application/js/top_bar.js +6 -4
  20. data/db/migrations/20130109125023_add_page_publish_lock.rb +17 -0
  21. data/db/migrations/20130111161934_convert_bcrypt_passwords.rb +22 -0
  22. data/db/migrations/20130114120000_create_revision_tables.rb +106 -0
  23. data/db/migrations/20130116220423_add_index_to_archive.rb +9 -0
  24. data/lib/spontaneous/box.rb +53 -18
  25. data/lib/spontaneous/box_style.rb +2 -3
  26. data/lib/spontaneous/change.rb +39 -13
  27. data/lib/spontaneous/cli/fields.rb +29 -0
  28. data/lib/spontaneous/cli/init.rb +2 -2
  29. data/lib/spontaneous/cli/migrate.rb +0 -1
  30. data/lib/spontaneous/cli/server.rb +14 -10
  31. data/lib/spontaneous/cli/site.rb +20 -9
  32. data/lib/spontaneous/cli.rb +8 -6
  33. data/lib/spontaneous/collections/box_set.rb +11 -0
  34. data/lib/spontaneous/collections/field_set.rb +24 -1
  35. data/lib/spontaneous/concern.rb +37 -0
  36. data/lib/spontaneous/config.rb +3 -4
  37. data/lib/spontaneous/crypt/version.rb +130 -0
  38. data/lib/spontaneous/crypt.rb +84 -0
  39. data/lib/spontaneous/data_mapper/content_model/associations.rb +199 -0
  40. data/lib/spontaneous/data_mapper/content_model/column_accessors.rb +52 -0
  41. data/lib/spontaneous/data_mapper/content_model/instance_hooks.rb +34 -0
  42. data/lib/spontaneous/data_mapper/content_model/serialization.rb +54 -0
  43. data/lib/spontaneous/data_mapper/content_model/timestamps.rb +39 -0
  44. data/lib/spontaneous/data_mapper/content_model.rb +343 -0
  45. data/lib/spontaneous/data_mapper/content_table.rb +103 -0
  46. data/lib/spontaneous/data_mapper/dataset.rb +194 -0
  47. data/lib/spontaneous/data_mapper/scope.rb +195 -0
  48. data/lib/spontaneous/data_mapper.rb +161 -0
  49. data/lib/spontaneous/facet.rb +2 -2
  50. data/lib/spontaneous/field/base.rb +418 -0
  51. data/lib/spontaneous/field/date.rb +54 -0
  52. data/lib/spontaneous/{field_version.rb → field/field_version.rb} +1 -1
  53. data/lib/spontaneous/field/file.rb +100 -0
  54. data/lib/spontaneous/{field_types/image_field.rb → field/image.rb} +33 -33
  55. data/lib/spontaneous/{field_types/location_field.rb → field/location.rb} +2 -2
  56. data/lib/spontaneous/{field_types/long_string_field.rb → field/long_string.rb} +3 -3
  57. data/lib/spontaneous/field/markdown.rb +36 -0
  58. data/lib/spontaneous/{field_types/select_field.rb → field/select.rb} +4 -5
  59. data/lib/spontaneous/field/string.rb +17 -0
  60. data/lib/spontaneous/field/update.rb +156 -0
  61. data/lib/spontaneous/field/webvideo.rb +310 -0
  62. data/lib/spontaneous/field.rb +80 -0
  63. data/lib/spontaneous/generators/site/Gemfile.tt +2 -2
  64. data/lib/spontaneous/generators/site/config/environments/development.rb.tt +1 -1
  65. data/lib/spontaneous/generators/site/config/environments/production.rb.tt +1 -1
  66. data/lib/spontaneous/generators/site/lib/content.rb.tt +6 -0
  67. data/lib/spontaneous/generators/site/schema/box.rb.tt +3 -2
  68. data/lib/spontaneous/generators/site/schema/page.rb.tt +3 -1
  69. data/lib/spontaneous/generators/site/schema/piece.rb.tt +3 -1
  70. data/lib/spontaneous/generators/site/templates/layouts/standard.html.cut.tt +3 -1
  71. data/lib/spontaneous/generators/site.rb +4 -3
  72. data/lib/spontaneous/image_size.rb +8 -1
  73. data/lib/spontaneous/layout.rb +5 -1
  74. data/lib/spontaneous/loader.rb +2 -5
  75. data/lib/spontaneous/media/file.rb +11 -2
  76. data/lib/spontaneous/media/temp_file.rb +23 -0
  77. data/lib/spontaneous/media.rb +20 -39
  78. data/lib/spontaneous/{plugins → model/box}/allowed_types.rb +38 -17
  79. data/lib/spontaneous/model/box.rb +18 -0
  80. data/lib/spontaneous/{plugins → model/core}/aliases.rb +10 -14
  81. data/lib/spontaneous/{plugins → model/core}/boxes.rb +2 -2
  82. data/lib/spontaneous/{plugins → model/core}/content_groups.rb +2 -2
  83. data/lib/spontaneous/model/core/editor_class.rb +4 -0
  84. data/lib/spontaneous/{plugins → model/core}/entries.rb +19 -7
  85. data/lib/spontaneous/{plugins → model/core}/entry.rb +3 -3
  86. data/lib/spontaneous/{plugins → model/core}/fields.rb +38 -5
  87. data/lib/spontaneous/{plugins → model/core}/instance_code.rb +2 -2
  88. data/lib/spontaneous/{plugins → model/core}/media.rb +2 -12
  89. data/lib/spontaneous/{plugins → model/core}/modifications.rb +7 -6
  90. data/lib/spontaneous/model/core/page_search.rb +36 -0
  91. data/lib/spontaneous/{plugins → model/core}/permissions.rb +4 -4
  92. data/lib/spontaneous/{plugins → model/core}/prototypes.rb +4 -4
  93. data/lib/spontaneous/{plugins → model/core}/publishing.rb +93 -115
  94. data/lib/spontaneous/{plugins → model/core}/render.rb +2 -2
  95. data/lib/spontaneous/{plugins → model/core}/schema_hierarchy.rb +7 -11
  96. data/lib/spontaneous/model/core/schema_id.rb +65 -0
  97. data/lib/spontaneous/{plugins → model/core}/schema_title.rb +2 -2
  98. data/lib/spontaneous/{plugins → model/core}/serialisation.rb +2 -2
  99. data/lib/spontaneous/{plugins → model/core}/styles.rb +2 -2
  100. data/lib/spontaneous/{plugins → model/core}/supertype.rb +2 -2
  101. data/lib/spontaneous/{plugins → model/core}/visibility.rb +7 -48
  102. data/lib/spontaneous/model/core.rb +143 -0
  103. data/lib/spontaneous/{plugins → model/page}/controllers.rb +3 -3
  104. data/lib/spontaneous/{plugins → model}/page/formats.rb +2 -2
  105. data/lib/spontaneous/{plugins → model/page}/layouts.rb +2 -2
  106. data/lib/spontaneous/model/page/locks.rb +14 -0
  107. data/lib/spontaneous/{plugins → model/page}/page_tree.rb +3 -3
  108. data/lib/spontaneous/{plugins → model/page}/paths.rb +30 -12
  109. data/lib/spontaneous/{plugins → model}/page/request.rb +2 -2
  110. data/lib/spontaneous/{plugins → model/page}/site_map.rb +2 -2
  111. data/lib/spontaneous/model/page/site_timestamps.rb +44 -0
  112. data/lib/spontaneous/{page.rb → model/page.rb} +49 -28
  113. data/lib/spontaneous/{piece.rb → model/piece.rb} +7 -6
  114. data/lib/spontaneous/model.rb +97 -0
  115. data/lib/spontaneous/output/context.rb +1 -1
  116. data/lib/spontaneous/output/format.rb +4 -0
  117. data/lib/spontaneous/output/template/renderer.rb +2 -2
  118. data/lib/spontaneous/output.rb +2 -2
  119. data/lib/spontaneous/page_lock.rb +62 -0
  120. data/lib/spontaneous/page_piece.rb +1 -1
  121. data/lib/spontaneous/permissions/access_key.rb +9 -4
  122. data/lib/spontaneous/permissions/user.rb +19 -9
  123. data/lib/spontaneous/permissions.rb +2 -5
  124. data/lib/spontaneous/plugins/application/facets.rb +1 -2
  125. data/lib/spontaneous/plugins/application/features.rb +1 -1
  126. data/lib/spontaneous/plugins/application/paths.rb +1 -1
  127. data/lib/spontaneous/plugins/application/render.rb +1 -1
  128. data/lib/spontaneous/plugins/application/serialisation.rb +1 -1
  129. data/lib/spontaneous/plugins/application/state.rb +30 -1
  130. data/lib/spontaneous/plugins/application/system.rb +12 -12
  131. data/lib/spontaneous/prototypes/box_prototype.rb +1 -1
  132. data/lib/spontaneous/prototypes/field_prototype.rb +3 -6
  133. data/lib/spontaneous/prototypes/style_prototype.rb +1 -1
  134. data/lib/spontaneous/publishing/immediate.rb +77 -49
  135. data/lib/spontaneous/publishing/revision.rb +355 -0
  136. data/lib/spontaneous/publishing/simultaneous.rb +10 -49
  137. data/lib/spontaneous/publishing.rb +1 -0
  138. data/lib/spontaneous/rack/around_back.rb +1 -1
  139. data/lib/spontaneous/rack/around_front.rb +2 -4
  140. data/lib/spontaneous/rack/around_preview.rb +1 -1
  141. data/lib/spontaneous/rack/back.rb +80 -63
  142. data/lib/spontaneous/rack/cacheable_file.rb +2 -2
  143. data/lib/spontaneous/rack/cookie_authentication.rb +1 -1
  144. data/lib/spontaneous/rack/front.rb +1 -1
  145. data/lib/spontaneous/rack/helpers.rb +8 -9
  146. data/lib/spontaneous/{page_controller.rb → rack/page_controller.rb} +1 -1
  147. data/lib/spontaneous/rack/public.rb +3 -3
  148. data/lib/spontaneous/rack.rb +15 -15
  149. data/lib/spontaneous/schema/uid.rb +4 -1
  150. data/lib/spontaneous/schema.rb +57 -24
  151. data/lib/spontaneous/search/database.rb +12 -1
  152. data/lib/spontaneous/search/index.rb +34 -6
  153. data/lib/spontaneous/search/results.rb +1 -1
  154. data/lib/spontaneous/server.rb +3 -3
  155. data/lib/spontaneous/simultaneous.rb +53 -0
  156. data/lib/spontaneous/{plugins/site → site}/features.rb +2 -2
  157. data/lib/spontaneous/{plugins/site → site}/helpers.rb +2 -3
  158. data/lib/spontaneous/{plugins/site → site}/hooks.rb +2 -2
  159. data/lib/spontaneous/{plugins/site → site}/instance.rb +4 -6
  160. data/lib/spontaneous/{plugins/site → site}/level.rb +2 -2
  161. data/lib/spontaneous/{plugins/site → site}/map.rb +4 -4
  162. data/lib/spontaneous/{plugins/site → site}/paths.rb +2 -2
  163. data/lib/spontaneous/site/publishing.rb +89 -0
  164. data/lib/spontaneous/{plugins/site → site}/schema.rb +4 -4
  165. data/lib/spontaneous/{plugins/site → site}/search.rb +2 -2
  166. data/lib/spontaneous/{plugins/site → site}/selectors.rb +15 -7
  167. data/lib/spontaneous/{plugins/site → site}/state.rb +2 -2
  168. data/lib/spontaneous/{plugins/site → site}/storage.rb +2 -2
  169. data/lib/spontaneous/{plugins/site → site}/url.rb +2 -2
  170. data/lib/spontaneous/site.rb +31 -14
  171. data/lib/spontaneous/state.rb +5 -6
  172. data/lib/spontaneous/style.rb +3 -2
  173. data/lib/spontaneous/utils/database/mysql_dumper.rb +13 -0
  174. data/lib/spontaneous/utils/database/postgres_dumper.rb +5 -0
  175. data/lib/spontaneous/version.rb +1 -1
  176. data/lib/spontaneous.rb +34 -89
  177. data/spontaneous.gemspec +112 -114
  178. data/test/experimental/test_crypt.rb +158 -0
  179. data/test/experimental/test_features.rb +3 -3
  180. data/test/fixtures/example_application/config/environments/development.rb +1 -1
  181. data/test/fixtures/example_application/lib/content.rb +5 -0
  182. data/test/fixtures/example_application/schema/page.rb +2 -1
  183. data/test/fixtures/example_application/schema/piece.rb +3 -2
  184. data/test/fixtures/serialisation/class_hash.yaml.erb +5 -5
  185. data/test/fixtures/serialisation/root_hash.yaml.erb +8 -0
  186. data/test/functional/test_application.rb +12 -1
  187. data/test/functional/test_back.rb +80 -48
  188. data/test/functional/test_front.rb +39 -46
  189. data/test/functional/test_user_manager.rb +3 -9
  190. data/test/javascript/test_markdown.rb +2 -2
  191. data/test/test_helper.rb +78 -23
  192. data/test/unit/test_alias.rb +21 -15
  193. data/test/unit/test_asset_bundler.rb +3 -3
  194. data/test/unit/test_assets.rb +2 -2
  195. data/test/unit/test_async.rb +7 -6
  196. data/test/unit/test_authentication.rb +43 -37
  197. data/test/unit/test_boxes.rb +46 -21
  198. data/test/unit/test_changesets.rb +65 -20
  199. data/test/unit/test_config.rb +9 -9
  200. data/test/unit/test_content.rb +50 -51
  201. data/test/unit/test_content_inheritance.rb +6 -20
  202. data/test/unit/test_datamapper.rb +1330 -0
  203. data/test/unit/test_datamapper_content.rb +214 -0
  204. data/test/unit/test_fields.rb +543 -54
  205. data/test/unit/test_formats.rb +2 -3
  206. data/test/unit/test_generators.rb +6 -6
  207. data/test/unit/test_helpers.rb +1 -1
  208. data/test/unit/test_image_size.rb +10 -5
  209. data/test/unit/test_images.rb +17 -18
  210. data/test/unit/test_layouts.rb +18 -3
  211. data/test/unit/test_media.rb +74 -49
  212. data/test/unit/test_modifications.rb +43 -43
  213. data/test/unit/test_page.rb +7 -10
  214. data/test/unit/test_permissions.rb +3 -10
  215. data/test/unit/test_piece.rb +5 -6
  216. data/test/unit/test_plugins.rb +7 -14
  217. data/test/unit/test_prototypes.rb +3 -3
  218. data/test/unit/test_publishing.rb +49 -27
  219. data/test/unit/test_render.rb +46 -15
  220. data/test/unit/test_revisions.rb +124 -127
  221. data/test/unit/test_schema.rb +53 -58
  222. data/test/unit/test_search.rb +64 -16
  223. data/test/unit/test_serialisation.rb +4 -11
  224. data/test/unit/test_site.rb +11 -12
  225. data/test/unit/test_structure.rb +2 -5
  226. data/test/unit/test_styles.rb +22 -24
  227. data/test/unit/test_type_hierarchy.rb +7 -5
  228. data/test/unit/test_visibility.rb +79 -55
  229. metadata +128 -102
  230. data/lib/sequel/plugins/content_table_inheritance.rb +0 -203
  231. data/lib/sequel/plugins/scoped_table_name.rb +0 -54
  232. data/lib/spontaneous/content.rb +0 -129
  233. data/lib/spontaneous/field_types/date_field.rb +0 -56
  234. data/lib/spontaneous/field_types/field.rb +0 -302
  235. data/lib/spontaneous/field_types/file_field.rb +0 -68
  236. data/lib/spontaneous/field_types/markdown_field.rb +0 -38
  237. data/lib/spontaneous/field_types/string_field.rb +0 -19
  238. data/lib/spontaneous/field_types/webvideo_field.rb +0 -313
  239. data/lib/spontaneous/field_types.rb +0 -38
  240. data/lib/spontaneous/generators/site/lib/site.rb.tt +0 -4
  241. data/lib/spontaneous/plugins/field/editor_class.rb +0 -13
  242. data/lib/spontaneous/plugins/page/site_timestamps.rb +0 -28
  243. data/lib/spontaneous/plugins/page_search.rb +0 -63
  244. data/lib/spontaneous/plugins/schema_id.rb +0 -68
  245. data/lib/spontaneous/plugins/site/publishing.rb +0 -75
  246. data/lib/spontaneous/rack/fiber_pool.rb +0 -26
  247. data/test/unit/test_table_scoping.rb +0 -80
@@ -0,0 +1,1330 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path('../../test_helper', __FILE__)
4
+ require 'sequel'
5
+
6
+ class DataMapperTest < MiniTest::Spec
7
+ def setup
8
+ @site = setup_site
9
+ end
10
+
11
+ def teardown
12
+ teardown_site
13
+ end
14
+
15
+ class NameMap
16
+ def initialize(*args)
17
+ end
18
+
19
+ def to_id(klass)
20
+ klass.to_s
21
+ end
22
+
23
+ def to_class(sid)
24
+ sid.to_s.constantize
25
+ end
26
+ end
27
+
28
+ context "datamapper" do
29
+ setup do
30
+ @now = Spontaneous::DataMapper.timestamp
31
+ @expected_columns = [:id, :type_sid, :label, :object1, :object2]
32
+ @database = ::Sequel.mock(autoid: 1)
33
+ @table = Spontaneous::DataMapper::ContentTable.new(:content, @database)
34
+ @schema = Spontaneous::Schema.new(Dir.pwd, NameMap)
35
+ @mapper = Spontaneous::DataMapper.new(@table, @schema)
36
+ @database.columns = @expected_columns
37
+ Spontaneous::DataMapper.stubs(:timestamp).returns(@now)
38
+ MockContent = Spontaneous::DataMapper::Model(:content, @database, @schema) do
39
+ serialize_columns :object1, :object2
40
+ end
41
+ # having timestamps on makes testing the sql very difficult/tedious
42
+ MockContent2 = Class.new(MockContent)
43
+ @database.sqls # clear sql log -- column introspection makes a query to the db
44
+ end
45
+
46
+ teardown do
47
+ DataMapperTest.send :remove_const, :MockContent rescue nil
48
+ DataMapperTest.send :remove_const, :MockContent2 rescue nil
49
+ end
50
+
51
+ should "be creatable from any table" do
52
+ table = Spontaneous::DataMapper::ContentTable.new(:content, @database)
53
+ mapper = Spontaneous::DataMapper.new(table, @schema)
54
+ mapper.must_be_instance_of Spontaneous::DataMapper::ScopingMapper
55
+ end
56
+
57
+ context "instances" do
58
+ should "insert model data when saving a new model instance" do
59
+ @database.fetch = { id:1, label:"column1", type_sid:"DataMapperTest::MockContent" }
60
+ instance = MockContent.new(:label => "column1")
61
+ instance.new?.should be_true
62
+ @mapper.create(instance)
63
+ @database.sqls.should == [
64
+ "INSERT INTO content (label, type_sid) VALUES ('column1', 'DataMapperTest::MockContent')",
65
+ "SELECT * FROM content WHERE (id = 1) LIMIT 1"
66
+ ]
67
+ instance.new?.should be_false
68
+ instance.id.should == 1
69
+ end
70
+
71
+ should "insert models using the DataMapper.create method" do
72
+ @database.fetch = { id:1, label:"column1", type_sid:"DataMapperTest::MockContent" }
73
+ instance = @mapper.instance MockContent, :label => "column1"
74
+ @database.sqls.should == [
75
+ "INSERT INTO content (label, type_sid) VALUES ('column1', 'DataMapperTest::MockContent')",
76
+ "SELECT * FROM content WHERE (id = 1) LIMIT 1"
77
+ ]
78
+ instance.new?.should be_false
79
+ instance.id.should == 1
80
+ end
81
+
82
+ should "update an existing model" do
83
+ @database.fetch = { id:1, label:"column1", type_sid:"DataMapperTest::MockContent" }
84
+ instance = @mapper.instance MockContent, :label => "column1"
85
+ instance.set label: "changed"
86
+ @mapper.save(instance)
87
+ @database.sqls.should == [
88
+ "INSERT INTO content (label, type_sid) VALUES ('column1', 'DataMapperTest::MockContent')",
89
+ "SELECT * FROM content WHERE (id = 1) LIMIT 1",
90
+ "UPDATE content SET label = 'changed' WHERE (id = 1)"
91
+ ]
92
+ end
93
+
94
+ should "update model rows directly" do
95
+ @mapper.update([MockContent], label: "changed")
96
+ @database.sqls.should == [
97
+ "UPDATE content SET label = 'changed' WHERE (type_sid IN ('DataMapperTest::MockContent'))"
98
+ ]
99
+ end
100
+
101
+ should "find an existing model" do
102
+ instance = @mapper.instance MockContent, :label => "column1"
103
+ @database.sqls # clear the sql log
104
+ @database.fetch = { id:1, label:"column1", type_sid:"DataMapperTest::MockContent" }
105
+ instance = @mapper.get(1)
106
+ @database.sqls.should == [
107
+ "SELECT * FROM content WHERE (id = 1) LIMIT 1"
108
+ ]
109
+ instance.must_be_instance_of MockContent
110
+ instance.id.should == 1
111
+ instance.attributes[:label].should == "column1"
112
+ end
113
+
114
+ should "retirieve a list of objects in the specified order" do
115
+ instance = @mapper.instance MockContent, :label => "column1"
116
+ @database.sqls # clear the sql log
117
+ @database.fetch = [
118
+ { id:1, type_sid:"DataMapperTest::MockContent" },
119
+ { id:2, type_sid:"DataMapperTest::MockContent" },
120
+ { id:3, type_sid:"DataMapperTest::MockContent" },
121
+ { id:4, type_sid:"DataMapperTest::MockContent" }
122
+ ]
123
+ results = @mapper.get([2, 3, 4, 1])
124
+ results.map(&:id).should == [2, 3, 4, 1]
125
+ end
126
+
127
+ should "allow for finding the first instance of a model" do
128
+ @database.fetch = { id:1, label:"column1", type_sid:"DataMapperTest::MockContent" }
129
+ instance = @mapper.first([MockContent], id: 1)
130
+ @database.sqls.should == [
131
+ "SELECT * FROM content WHERE ((type_sid IN ('DataMapperTest::MockContent')) AND (id = 1)) LIMIT 1"
132
+ ]
133
+ end
134
+
135
+ should "be scopable to a revision" do
136
+ @database.fetch = { id:1, label:"column1", type_sid:"DataMapperTest::MockContent" }
137
+ @mapper.revision(10) do
138
+ instance = @mapper.instance MockContent, :label => "column1"
139
+ instance.set label: "changed"
140
+ @mapper.save(instance)
141
+ @database.sqls.should == [
142
+ "INSERT INTO __r00010_content (label, type_sid) VALUES ('column1', 'DataMapperTest::MockContent')",
143
+ "SELECT * FROM __r00010_content WHERE (id = 1) LIMIT 1",
144
+ "UPDATE __r00010_content SET label = 'changed' WHERE (id = 1)"
145
+ ]
146
+ end
147
+ end
148
+
149
+ should "return the correct table name" do
150
+ @mapper.table_name.should == :"content"
151
+ @mapper.revision(10) do
152
+ @mapper.table_name.should == :"__r00010_content"
153
+ end
154
+ end
155
+
156
+ should "allow for retrieval of rows from a specific revision" do
157
+ @database.fetch = { id:1, label:"column1", type_sid:"DataMapperTest::MockContent" }
158
+ instance = @mapper.revision(20).get(1)
159
+ @database.sqls.should == ["SELECT * FROM __r00020_content WHERE (id = 1) LIMIT 1"]
160
+ instance.must_be_instance_of MockContent
161
+ instance.id.should == 1
162
+ instance.attributes[:label].should == "column1"
163
+ end
164
+
165
+ should "support nested revision scopes" do
166
+ @database.fetch = { id:1, label:"column1", type_sid:"DataMapperTest::MockContent" }
167
+ @mapper.revision(10) do
168
+ instance = @mapper.get(1)
169
+ instance.label = "changed1"
170
+ @mapper.save(instance)
171
+ @mapper.revision(20) do
172
+ instance = @mapper.get(1)
173
+ instance.label = "changed2"
174
+ @mapper.save(instance)
175
+ instance = @mapper.editable.get(3)
176
+ end
177
+ end
178
+ @database.sqls.should == [
179
+ "SELECT * FROM __r00010_content WHERE (id = 1) LIMIT 1",
180
+ "UPDATE __r00010_content SET label = 'changed1' WHERE (id = 1)",
181
+ "SELECT * FROM __r00020_content WHERE (id = 1) LIMIT 1",
182
+ "UPDATE __r00020_content SET label = 'changed2' WHERE (id = 1)",
183
+ "SELECT * FROM content WHERE (id = 3) LIMIT 1"
184
+ ]
185
+ end
186
+
187
+ should "allow for finding all instances of a class with DataMapper#all" do
188
+ @database.fetch = [
189
+ { id:1, label:"column1", type_sid:"DataMapperTest::MockContent" },
190
+ { id:2, label:"column2", type_sid:"DataMapperTest::MockContent2" }
191
+ ]
192
+ results = @mapper.all([MockContent, MockContent2])
193
+ @database.sqls.should == [
194
+ "SELECT * FROM content WHERE (type_sid IN ('DataMapperTest::MockContent', 'DataMapperTest::MockContent2'))"
195
+ ]
196
+ results.map(&:class).should == [MockContent, MockContent2]
197
+ results.map(&:id).should == [1, 2]
198
+ end
199
+
200
+ should "allow for finding all instances of a class with DataMapper#types" do
201
+ @database.fetch = [
202
+ { id:1, label:"column1", type_sid:"DataMapperTest::MockContent" },
203
+ { id:2, label:"column2", type_sid:"DataMapperTest::MockContent2" }
204
+ ]
205
+ results = @mapper.all([MockContent, MockContent2])
206
+ @database.sqls.should == [
207
+ "SELECT * FROM content WHERE (type_sid IN ('DataMapperTest::MockContent', 'DataMapperTest::MockContent2'))"
208
+ ]
209
+ results.map(&:class).should == [MockContent, MockContent2]
210
+ results.map(&:id).should == [1, 2]
211
+ end
212
+
213
+ should "allow for counting type rows" do
214
+ @mapper.count([MockContent, MockContent2])
215
+ @database.sqls.should == [
216
+ "SELECT COUNT(*) AS count FROM content WHERE (type_sid IN ('DataMapperTest::MockContent', 'DataMapperTest::MockContent2')) LIMIT 1"
217
+ ]
218
+ end
219
+
220
+ should "allow for use of block iterator when loading all model instances" do
221
+ ids = []
222
+ @database.fetch = [
223
+ { id:1, label:"column1", type_sid:"DataMapperTest::MockContent" },
224
+ { id:2, label:"column2", type_sid:"DataMapperTest::MockContent2" }
225
+ ]
226
+ results = @mapper.all([MockContent, MockContent2]) do |i|
227
+ ids << i.id
228
+ end
229
+ ids.should == [1, 2]
230
+ results.map(&:class).should == [MockContent, MockContent2]
231
+ end
232
+
233
+ should "allow for defining an order" do
234
+ ds = @mapper.order([MockContent], "column1").all
235
+ @database.sqls.should == [
236
+ "SELECT * FROM content WHERE (type_sid IN ('DataMapperTest::MockContent')) ORDER BY 'column1'"
237
+ ]
238
+ end
239
+
240
+ should "allow for defining a limit" do
241
+ ds = @mapper.limit([MockContent], 10...20).all
242
+ ds = @mapper.filter([], label: "this").limit(10).all
243
+ @database.sqls.should == [
244
+ "SELECT * FROM content WHERE (type_sid IN ('DataMapperTest::MockContent')) LIMIT 10 OFFSET 10",
245
+ "SELECT * FROM content WHERE (label = 'this') LIMIT 10"
246
+ ]
247
+ end
248
+
249
+ should "support chained filters" do
250
+ @database.fetch = [
251
+ { id:1, label:"column1", type_sid:"DataMapperTest::MockContent" }
252
+ ]
253
+ ds = @mapper.filter([MockContent, MockContent2], id:1)
254
+ results = ds.all
255
+ results.map(&:class).should == [MockContent]
256
+ results.map(&:id).should == [1]
257
+ @database.sqls.should == [
258
+ "SELECT * FROM content WHERE ((type_sid IN ('DataMapperTest::MockContent', 'DataMapperTest::MockContent2')) AND (id = 1))"
259
+ ]
260
+ end
261
+
262
+ should "support filtering using virtual rows" do
263
+ @database.fetch = [
264
+ { id:1, label:"column1", type_sid:"DataMapperTest::MockContent" },
265
+ { id:2, label:"column2", type_sid:"DataMapperTest::MockContent2" }
266
+ ]
267
+ ds = @mapper.filter([MockContent, MockContent2]) { id > 0 }
268
+ results = ds.all
269
+ results.map(&:class).should == [MockContent, MockContent2]
270
+ results.map(&:id).should == [1, 2]
271
+ @database.sqls.should == [
272
+ "SELECT * FROM content WHERE ((type_sid IN ('DataMapperTest::MockContent', 'DataMapperTest::MockContent2')) AND (id > 0))"
273
+ ]
274
+ end
275
+
276
+ should "support multiple concurrent filters" do
277
+ # want to be sure that each dataset is independent
278
+ ds1 = @mapper.filter([MockContent], id: 1)
279
+ ds2 = @mapper.filter([MockContent2])
280
+
281
+ @database.fetch = { id:1, label:"column1", type_sid:"DataMapperTest::MockContent" }
282
+ ds1.first([]).must_be_instance_of MockContent
283
+
284
+ @database.fetch = { id:2, label:"column2", type_sid:"DataMapperTest::MockContent2" }
285
+ ds2.first([]).must_be_instance_of MockContent2
286
+
287
+ @database.sqls.should == [
288
+ "SELECT * FROM content WHERE ((type_sid IN ('DataMapperTest::MockContent')) AND (id = 1)) LIMIT 1",
289
+ "SELECT * FROM content WHERE (type_sid IN ('DataMapperTest::MockContent2')) LIMIT 1"
290
+ ]
291
+ end
292
+
293
+ should "allow you to delete datasets" do
294
+ @mapper.delete([MockContent])
295
+ @database.sqls.should == [
296
+ "DELETE FROM content WHERE (type_sid IN ('DataMapperTest::MockContent'))"
297
+ ]
298
+ end
299
+
300
+ should "allow you to delete instances" do
301
+ @database.fetch = { id:1, label:"label", type_sid:"DataMapperTest::MockContent" }
302
+ instance = @mapper.instance MockContent, label: "label"
303
+ @database.sqls
304
+ @mapper.delete_instance instance
305
+ @database.sqls.should == [
306
+ "DELETE FROM content WHERE (id = 1)"
307
+ ]
308
+ end
309
+
310
+ should "allow you to delete instances within a revision" do
311
+ @database.fetch = { id:1, label:"label", type_sid:"DataMapperTest::MockContent" }
312
+ instance = @mapper.instance MockContent, label: "label"
313
+ @database.sqls
314
+ @mapper.revision(20) do
315
+ @mapper.delete_instance instance
316
+ end
317
+ @database.sqls.should == [
318
+ "DELETE FROM __r00020_content WHERE (id = 1)"
319
+ ]
320
+ end
321
+
322
+ should "allow you to destroy model instances" do
323
+ @database.fetch = { id:1, label:"column1", type_sid:"DataMapperTest::MockContent" }
324
+ instance = @mapper.instance MockContent, :label => "column1"
325
+ @database.sqls # clear sql log
326
+ @mapper.delete_instance instance
327
+ instance.id.should == 1
328
+ @database.sqls.should == [
329
+ "DELETE FROM content WHERE (id = 1)"
330
+ ]
331
+ end
332
+
333
+ should "support visibility contexts" do
334
+ @database.fetch = { id:1, label:"column1", type_sid:"DataMapperTest::MockContent" }
335
+ @mapper.visible do
336
+ @mapper.get(1)
337
+ @mapper.visible(false) do
338
+ @mapper.get(1)
339
+ @mapper.visible.get(1)
340
+ end
341
+ end
342
+ @database.sqls.should == [
343
+ "SELECT * FROM content WHERE ((hidden IS FALSE) AND (id = 1)) LIMIT 1",
344
+ "SELECT * FROM content WHERE (id = 1) LIMIT 1",
345
+ "SELECT * FROM content WHERE ((hidden IS FALSE) AND (id = 1)) LIMIT 1",
346
+ ]
347
+ end
348
+
349
+ should "support mixed revision & visibility states" do
350
+ @database.fetch = { id:1, label:"column1", type_sid:"DataMapperTest::MockContent" }
351
+ @mapper.revision(25) do
352
+ @mapper.visible do
353
+ @mapper.get(1)
354
+ end
355
+ end
356
+ @database.sqls.should == [
357
+ "SELECT * FROM __r00025_content WHERE ((hidden IS FALSE) AND (id = 1)) LIMIT 1",
358
+ ]
359
+ end
360
+
361
+ should "ignore visibility filter for deletes" do
362
+ @database.fetch = { id:1, label:"label", type_sid:"DataMapperTest::MockContent" }
363
+ instance = @mapper.instance MockContent, label: "label"
364
+ @database.sqls
365
+ @mapper.visible do
366
+ @mapper.delete_instance(instance)
367
+ end
368
+ @database.sqls.should == [
369
+ "DELETE FROM content WHERE (id = 1)",
370
+ ]
371
+ end
372
+
373
+ should "ignore visibility setting for creates" do
374
+ @mapper.visible do
375
+ @mapper.revision(99) do
376
+ instance = @mapper.instance MockContent, :label => "column1"
377
+ end
378
+ end
379
+ @database.sqls.should == [
380
+ "INSERT INTO __r00099_content (label, type_sid) VALUES ('column1', 'DataMapperTest::MockContent')",
381
+ "SELECT * FROM __r00099_content WHERE (id = 1) LIMIT 1"
382
+ ]
383
+ end
384
+
385
+ should "allow for inserting raw attributes" do
386
+ @mapper.insert type_sid: "MockContent", label: "label"
387
+ @database.sqls.should == [
388
+ "INSERT INTO content (type_sid, label) VALUES ('MockContent', 'label')"
389
+ ]
390
+ end
391
+ end
392
+
393
+ context "models" do
394
+ should "be deletable" do
395
+ MockContent.delete
396
+ @database.sqls.should == [
397
+ "DELETE FROM content WHERE (type_sid IN ('DataMapperTest::MockContent'))"
398
+ ]
399
+ end
400
+
401
+ should "be creatable using Model.create" do
402
+ @database.fetch = { id:1, label:"value", type_sid:"DataMapperTest::MockContent" }
403
+ instance = MockContent.create(label: "value")
404
+ @database.sqls.should == [
405
+ "INSERT INTO content (label, type_sid) VALUES ('value', 'DataMapperTest::MockContent')",
406
+ "SELECT * FROM content WHERE (id = 1) LIMIT 1"
407
+ ]
408
+ instance.new?.should be_false
409
+ instance.id.should == 1
410
+ end
411
+
412
+ should "be instantiable using Model.new" do
413
+ instance = MockContent.new(label: "value")
414
+ instance.new?.should be_true
415
+ end
416
+
417
+ should "be creatable using Model.new" do
418
+ @database.fetch = { id:1, label:"value", type_sid:"DataMapperTest::MockContent" }
419
+ instance = MockContent.new(label: "value")
420
+ instance.save
421
+ instance.new?.should be_false
422
+ @database.sqls.should == [
423
+ "INSERT INTO content (label, type_sid) VALUES ('value', 'DataMapperTest::MockContent')",
424
+ "SELECT * FROM content WHERE (id = 1) LIMIT 1"
425
+ ]
426
+ instance.id.should == 1
427
+ end
428
+
429
+ should "be updatable" do
430
+ @database.fetch = { id:1, label:"value", type_sid:"DataMapperTest::MockContent" }
431
+ instance = MockContent.create(label: "value")
432
+ instance.update(label: "changed")
433
+ @database.sqls.should == [
434
+ "INSERT INTO content (label, type_sid) VALUES ('value', 'DataMapperTest::MockContent')",
435
+ "SELECT * FROM content WHERE (id = 1) LIMIT 1",
436
+ "UPDATE content SET label = 'changed' WHERE (id = 1)"
437
+ ]
438
+ end
439
+
440
+ should "exclude id column from updates" do
441
+ @database.fetch = { id:1, label:"value", type_sid:"DataMapperTest::MockContent" }
442
+ instance = MockContent.create(id: 103, label: "value")
443
+ instance.id.should == 1
444
+ instance.update(id: 99, label: "changed")
445
+ @database.sqls.should == [
446
+ "INSERT INTO content (label, type_sid) VALUES ('value', 'DataMapperTest::MockContent')",
447
+ "SELECT * FROM content WHERE (id = 1) LIMIT 1",
448
+ "UPDATE content SET label = 'changed' WHERE (id = 1)"
449
+ ]
450
+ instance.id.should == 1
451
+ end
452
+
453
+ should "exclude type_sid column from updates" do
454
+ @database.fetch = { id:1, label:"value", type_sid:"DataMapperTest::MockContent" }
455
+ instance = MockContent.create(type_sid: "Nothing", label: "value")
456
+ instance.update(type_sid: "Invalid", label: "changed")
457
+ @database.sqls.should == [
458
+ "INSERT INTO content (label, type_sid) VALUES ('value', 'DataMapperTest::MockContent')",
459
+ "SELECT * FROM content WHERE (id = 1) LIMIT 1",
460
+ "UPDATE content SET label = 'changed' WHERE (id = 1)"
461
+ ]
462
+ instance.id.should == 1
463
+ end
464
+
465
+ should "only update changed columns" do
466
+ @database.fetch = { id:1, label:"value", type_sid:"DataMapperTest::MockContent" }
467
+ instance = MockContent.create(label: "value")
468
+ @database.sqls
469
+ instance.changed_columns.should == []
470
+ instance.label = "changed"
471
+ instance.changed_columns.should == [:label]
472
+ instance.object1 = "updated"
473
+ instance.changed_columns.should == [:label, :object1]
474
+ instance.save
475
+ @database.sqls.should == [
476
+ "UPDATE content SET label = 'changed', object1 = '\"updated\"' WHERE (id = 1)"
477
+ ]
478
+ end
479
+
480
+ should "mark new instances as modified" do
481
+ instance = MockContent.new(label: "value")
482
+ instance.modified?.should be_true
483
+ end
484
+
485
+ should "updated modified flag after save" do
486
+ instance = MockContent.new(label: "value")
487
+ instance.save
488
+ instance.modified?.should be_false
489
+ end
490
+
491
+ should "have a modified flag if columns changed" do
492
+ @database.fetch = { id:1, label:"value", type_sid:"DataMapperTest::MockContent" }
493
+ instance = MockContent.create(label: "value")
494
+ instance.modified?.should be_false
495
+ instance.label = "changed"
496
+ instance.modified?.should be_true
497
+ end
498
+
499
+ should "not make a db call if no values have been modified" do
500
+ @database.fetch = { id:1, label:"value", type_sid:"DataMapperTest::MockContent" }
501
+ instance = MockContent.create(label: "value")
502
+ @database.sqls
503
+ instance.save
504
+ @database.sqls.should == []
505
+ end
506
+
507
+ should "allow you to force a save" do
508
+ @database.fetch = { id:1, label:"value", type_sid:"DataMapperTest::MockContent" }
509
+ instance = MockContent.create(label: "value")
510
+ @database.sqls
511
+ instance.mark_modified!
512
+ instance.save
513
+ @database.sqls.should == [
514
+ "UPDATE content SET label = 'value', type_sid = 'DataMapperTest::MockContent' WHERE (id = 1)"
515
+ ]
516
+ end
517
+
518
+ should "allow you to force an update to a specific column" do
519
+ @database.fetch = { id:1, label:"value", type_sid:"DataMapperTest::MockContent" }
520
+ instance = MockContent.create(label: "value")
521
+ @database.sqls
522
+ instance.mark_modified!(:label)
523
+ instance.save
524
+ @database.sqls.should == [
525
+ "UPDATE content SET label = 'value' WHERE (id = 1)"
526
+ ]
527
+ end
528
+
529
+ should "be destroyable" do
530
+ @database.fetch = { id:1, label:"value", type_sid:"DataMapperTest::MockContent" }
531
+ instance = MockContent.create(label: "value")
532
+ instance.id.should == 1
533
+ instance.destroy
534
+ @database.sqls.should == [
535
+ "INSERT INTO content (label, type_sid) VALUES ('value', 'DataMapperTest::MockContent')",
536
+ "SELECT * FROM content WHERE (id = 1) LIMIT 1",
537
+ "DELETE FROM content WHERE (id = 1)"
538
+ ]
539
+ end
540
+
541
+ should "allow for searching for all instances of a class" do
542
+ @database.fetch = [
543
+ { id:1, label:"column1", type_sid:"DataMapperTest::MockContent" },
544
+ { id:2, label:"column2", type_sid:"DataMapperTest::MockContent" }
545
+ ]
546
+ results = MockContent.all
547
+ @database.sqls.should == [
548
+ "SELECT * FROM content WHERE (type_sid IN ('DataMapperTest::MockContent'))"
549
+ ]
550
+ results.length.should == 2
551
+ results.map(&:class).should == [MockContent, MockContent]
552
+ results.map(&:id).should == [1, 2]
553
+ end
554
+
555
+ should "allow for finding first instance of a type" do
556
+ @database.fetch = [
557
+ { id:1, label:"column1", type_sid:"DataMapperTest::MockContent" }
558
+ ]
559
+ instance = MockContent.first
560
+ MockContent.first(id: 1)
561
+ MockContent.first { id > 0}
562
+ @database.sqls.should == [
563
+ "SELECT * FROM content WHERE (type_sid IN ('DataMapperTest::MockContent')) LIMIT 1",
564
+ "SELECT * FROM content WHERE ((type_sid IN ('DataMapperTest::MockContent')) AND (id = 1)) LIMIT 1",
565
+ "SELECT * FROM content WHERE ((type_sid IN ('DataMapperTest::MockContent')) AND (id > 0)) LIMIT 1"
566
+ ]
567
+ instance.must_be_instance_of MockContent
568
+ instance.id.should == 1
569
+ end
570
+
571
+ should "return nil if no instance matching filter is found" do
572
+ @database.fetch = []
573
+ instance = MockContent.first(id: 1)
574
+ instance.should be_nil
575
+ end
576
+
577
+ should "retrieve by primary key using []" do
578
+ instance = MockContent[1]
579
+ @database.sqls.should == [
580
+ "SELECT * FROM content WHERE (id = 1) LIMIT 1",
581
+ ]
582
+ end
583
+
584
+ should "have correct equality test" do
585
+ @database.fetch = [
586
+ { id:1, label:"column1", type_sid:"DataMapperTest::MockContent" }
587
+ ]
588
+ a = MockContent[1]
589
+ b = MockContent[1]
590
+ a.should == b
591
+
592
+ a.label = "changed"
593
+ a.should_not == b
594
+ end
595
+
596
+ should "allow for filtering model instances" do
597
+ @database.fetch = [
598
+ { id:100, label:"column1", type_sid:"DataMapperTest::MockContent" }
599
+ ]
600
+ results = MockContent.filter(hidden: false).all
601
+ @database.sqls.should == [
602
+ "SELECT * FROM content WHERE ((type_sid IN ('DataMapperTest::MockContent')) AND (hidden IS FALSE))"
603
+ ]
604
+ results.length.should == 1
605
+ results.map(&:class).should == [MockContent]
606
+ results.map(&:id).should == [100]
607
+ end
608
+
609
+ should "use the current mapper revision to save" do
610
+ @database.fetch = [
611
+ { id:100, label:"column1", type_sid:"DataMapperTest::MockContent" }
612
+ ]
613
+ instance = nil
614
+ @mapper.revision(99) do
615
+ instance = MockContent.first
616
+ end
617
+ instance.update(label: "changed")
618
+ @mapper.revision(99) do
619
+ instance.update(label: "changed2")
620
+ end
621
+ @database.sqls.should == [
622
+ "SELECT * FROM __r00099_content WHERE (type_sid IN ('DataMapperTest::MockContent')) LIMIT 1",
623
+ "UPDATE content SET label = 'changed' WHERE (id = 100)",
624
+ "UPDATE __r00099_content SET label = 'changed2' WHERE (id = 100)"
625
+ ]
626
+ end
627
+
628
+ should "allow for reloading values from the db" do
629
+ @database.fetch = { id:100, label:"column1", type_sid:"DataMapperTest::MockContent" }
630
+ instance = MockContent.first
631
+ instance.set(label:"changed")
632
+ instance.attributes[:label].should == "changed"
633
+ instance.changed_columns.should == [:label]
634
+ instance.reload
635
+ instance.attributes[:label].should == "column1"
636
+ instance.changed_columns.should == []
637
+ @database.sqls.should == [
638
+ "SELECT * FROM content WHERE (type_sid IN ('DataMapperTest::MockContent')) LIMIT 1",
639
+ "SELECT * FROM content WHERE (id = 100) LIMIT 1"
640
+ ]
641
+ end
642
+
643
+
644
+ should "update model rows directly" do
645
+ MockContent.update(label: "changed")
646
+ @database.sqls.should == [
647
+ "UPDATE content SET label = 'changed' WHERE (type_sid IN ('DataMapperTest::MockContent'))"
648
+ ]
649
+ end
650
+
651
+ should "introspect columns" do
652
+ MockContent.columns.should == @expected_columns
653
+ end
654
+
655
+ should "create getters & setters for all columns except id & type_sid" do
656
+ columns = (@expected_columns - [:id, :type_sid])
657
+ attrs = Hash[columns.map { |c| [c, "#{c}_value"] } ]
658
+ c = MockContent.new attrs
659
+
660
+ columns.each do |column|
661
+ assert c.respond_to?(column), "Instance should respond to ##{column}"
662
+ assert c.respond_to?("#{column}="), "Instance should respond to ##{column}="
663
+ c.send(column).should == attrs[column]
664
+ c.send("#{column}=", "changed")
665
+ c.send(column).should == "changed"
666
+ end
667
+ end
668
+
669
+ should "set values using the setter methods" do
670
+ model = Class.new(MockContent) do
671
+ def label=(value); super(value + "!"); end
672
+ end
673
+ instance = model.new label: "label1"
674
+ instance.set(label: "label2")
675
+ instance.label.should == "label2!"
676
+ end
677
+
678
+ should "support after_initialize hooks" do
679
+ model = Class.new(MockContent) do
680
+ attr_accessor :param
681
+ def after_initialize
682
+ self.param = true
683
+ end
684
+ end
685
+ instance = model.new
686
+ instance.param.should be_true
687
+ end
688
+
689
+ should "support before create triggers" do
690
+ model = Class.new(MockContent) do
691
+ attr_accessor :param
692
+ def before_create
693
+ self.param = true
694
+ end
695
+ end
696
+ instance = model.create
697
+ instance.param.should be_true
698
+ end
699
+
700
+ should "support after create triggers" do
701
+ model = Class.new(MockContent) do
702
+ attr_accessor :param
703
+ def after_create
704
+ self.param = true
705
+ end
706
+ end
707
+ instance = model.create
708
+ instance.param.should be_true
709
+ end
710
+
711
+ should "not insert instance & return nil if before_create throws :halt" do
712
+ model = Class.new(MockContent) do
713
+ attr_accessor :param
714
+ def before_create
715
+ throw :halt
716
+ end
717
+ end
718
+ instance = model.create
719
+ instance.should be_nil
720
+ @database.sqls.should == []
721
+ end
722
+
723
+ should "call before save triggers on model create" do
724
+ model = Class.new(MockContent) do
725
+ attr_accessor :param
726
+ def before_save
727
+ self.param = true
728
+ end
729
+ end
730
+ instance = model.create
731
+ instance.param.should be_true
732
+ end
733
+
734
+ should "call before save triggers on existing instances" do
735
+ @database.fetch = { id:1, label:"label", type_sid:"DataMapperTest::MockContent" }
736
+ model = Class.new(MockContent) do
737
+ attr_accessor :param
738
+ def before_save
739
+ self.param = true
740
+ end
741
+ end
742
+ instance = model.create
743
+ instance.param.should be_true
744
+ instance.set label: "hello"
745
+ instance.param = false
746
+ instance.save
747
+ instance.param.should be_true
748
+ end
749
+
750
+ should "call after save triggers after create" do
751
+ model = Class.new(MockContent) do
752
+ attr_accessor :param
753
+ def after_save
754
+ self.param = true
755
+ end
756
+ end
757
+ instance = model.create
758
+ instance.param.should be_true
759
+ end
760
+
761
+ should "call after save triggers on existing instances" do
762
+ @database.fetch = { id:1, label:"label", type_sid:"DataMapperTest::MockContent" }
763
+ model = Class.new(MockContent) do
764
+ attr_accessor :param
765
+ def after_save
766
+ self.param = true
767
+ end
768
+ end
769
+ instance = model.create
770
+ instance.param.should be_true
771
+ instance.set label: "hello"
772
+ instance.param = false
773
+ instance.save
774
+ instance.param.should be_true
775
+ end
776
+
777
+ should "support before_update triggers" do
778
+ @database.fetch = { id:1, label:"label", type_sid:"DataMapperTest::MockContent" }
779
+ model = Class.new(MockContent) do
780
+ attr_accessor :param
781
+ def before_update
782
+ self.param = true
783
+ end
784
+ end
785
+ instance = model.create
786
+ instance.param.should be_nil
787
+ instance.set label: "hello"
788
+ instance.save
789
+ instance.param.should be_true
790
+ end
791
+
792
+ should "fail to save instance if before_update throws halt" do
793
+ @database.fetch = { id:1, label:"label", type_sid:"DataMapperTest::MockContent" }
794
+ model = Class.new(MockContent) do
795
+ attr_accessor :param
796
+ def before_update
797
+ throw :halt
798
+ end
799
+ end
800
+ instance = model.create
801
+ @database.sqls
802
+ instance.set label: "hello"
803
+ result = instance.save
804
+ result.should be_nil
805
+ @database.sqls.should == []
806
+ end
807
+
808
+ should "support after update triggers" do
809
+ @database.fetch = { id:1, label:"label", type_sid:"DataMapperTest::MockContent" }
810
+ model = Class.new(MockContent) do
811
+ attr_accessor :param
812
+ def after_update
813
+ self.param = true
814
+ end
815
+ end
816
+ instance = model.create
817
+ instance.param.should be_nil
818
+ instance.set label: "hello"
819
+ instance.save
820
+ instance.param.should be_true
821
+ end
822
+
823
+ should "support before destroy triggers" do
824
+ @database.fetch = { id:1, label:"label", type_sid:"DataMapperTest::MockContent" }
825
+ model = Class.new(MockContent) do
826
+ attr_accessor :param
827
+ def before_destroy
828
+ self.param = true
829
+ end
830
+ end
831
+ instance = model.create
832
+ @database.sqls
833
+ instance.destroy
834
+ instance.param.should be_true
835
+ @database.sqls.should == [
836
+ "DELETE FROM content WHERE (id = 1)"
837
+ ]
838
+ end
839
+
840
+ should "not delete an instance if before_destroy throws halt" do
841
+ @database.fetch = { id:1, label:"label", type_sid:"DataMapperTest::MockContent" }
842
+ model = Class.new(MockContent) do
843
+ attr_accessor :param
844
+ def before_destroy
845
+ throw :halt
846
+ end
847
+ end
848
+ instance = model.create
849
+ @database.sqls
850
+ result = instance.destroy
851
+ @database.sqls.should == []
852
+ result.should be_nil
853
+ end
854
+
855
+ should "support after destroy triggers" do
856
+ @database.fetch = { id:1, label:"label", type_sid:"DataMapperTest::MockContent" }
857
+ model = Class.new(MockContent) do
858
+ attr_accessor :param
859
+ def after_destroy
860
+ self.param = true
861
+ end
862
+ end
863
+ instance = model.create
864
+ instance.param.should be_nil
865
+ instance.destroy
866
+ instance.param.should be_true
867
+ end
868
+
869
+ should "not trigger before destroy hooks when calling #delete" do
870
+ @database.fetch = { id:1, label:"label", type_sid:"DataMapperTest::MockContent" }
871
+ model = Class.new(MockContent) do
872
+ attr_accessor :param
873
+ def before_destroy
874
+ throw :halt
875
+ end
876
+ end
877
+ instance = model.create
878
+ @database.sqls
879
+ instance.delete
880
+ @database.sqls.should == ["DELETE FROM content WHERE (id = 1)"]
881
+ end
882
+
883
+ should "serialize column to JSON" do
884
+ row = { id: 1, type_sid:"DataMapperTest::MockContent" }
885
+ object = {name:"value"}
886
+ serialized = Spontaneous::JSON.encode(object)
887
+ MockContent.serialized_columns.each do |column|
888
+ @database.fetch = row
889
+ instance = MockContent.create({column => object})
890
+ @database.sqls.first.should == "INSERT INTO content (#{column}, type_sid) VALUES ('#{serialized}', 'DataMapperTest::MockContent')"
891
+ end
892
+ end
893
+
894
+ should "deserialize objects stored in the db" do
895
+ row = { id: 1, type_sid:"DataMapperTest::MockContent" }
896
+ object = {name:"value"}
897
+ serialized = Spontaneous::JSON.encode(object)
898
+ MockContent.serialized_columns.each do |column|
899
+ @database.fetch = row.merge(column => serialized)
900
+ instance = MockContent.first
901
+ instance.send(column).should == object
902
+ end
903
+ end
904
+ should "save updates to serialized columns" do
905
+ row = { id: 1, type_sid:"DataMapperTest::MockContent" }
906
+ object = {name:"value"}
907
+ serialized = Spontaneous::JSON.encode(object)
908
+ MockContent.serialized_columns.each do |column|
909
+ @database.fetch = row.merge(column => serialized)
910
+ instance = MockContent.first
911
+ @database.sqls
912
+ instance.send(column).should == object
913
+ changed = {name:"it's different", value:[99, 100]}
914
+ instance.send "#{column}=", changed
915
+ instance.send(column).should == changed
916
+ instance.save
917
+ @database.sqls.first.should == "UPDATE content " +
918
+ "SET #{column} = '{\"name\":\"it''s different\",\"value\":[99,100]}' " +
919
+ "WHERE (id = 1)"
920
+ end
921
+ end
922
+
923
+ context "timestamps" do
924
+ setup do
925
+ @time = @table.dataset.send :format_timestamp, @now
926
+ @database.columns = @expected_columns + [:created_at, :modified_at]
927
+ TimestampedContent = Spontaneous::DataMapper::Model(:content, @database, @schema)
928
+ @database.sqls
929
+ end
930
+
931
+ teardown do
932
+ DataMapperTest.send :remove_const, :TimestampedContent rescue nil
933
+ end
934
+
935
+ should "set created_at timestamp on creation" do
936
+ instance = TimestampedContent.create label: "something"
937
+ @database.sqls.first.should == "INSERT INTO content (label, created_at, type_sid) VALUES ('something', #{@time}, 'DataMapperTest::TimestampedContent')"
938
+ end
939
+
940
+ # should "update the modified_at value on update" do
941
+ # @database.fetch = { id: 1, type_sid:"DataMapperTest::TimestampedContent" }
942
+ # instance = TimestampedContent.create label: "something"
943
+ # @database.sqls
944
+ # instance.set label: "changed"
945
+ # instance.save
946
+ # @database.sqls.first.should == "UPDATE content SET label = 'changed', modified_at = #{@time} WHERE (id = 1)"
947
+ # end
948
+ end
949
+
950
+ context "schema" do
951
+ setup do
952
+ class A1 < MockContent; end
953
+ class A2 < MockContent; end
954
+ class B1 < A1; end
955
+ class B2 < A2; end
956
+ class C1 < B1; end
957
+ end
958
+
959
+ teardown do
960
+ %w(A1 A2 B1 B2 C1).each do |klass|
961
+ DataMapperTest.send :remove_const, klass rescue nil
962
+ end
963
+ end
964
+
965
+ should "track subclasses" do
966
+ MockContent2.subclasses.should == []
967
+ Set.new(A1.subclasses).should == Set.new([B1, C1])
968
+ A2.subclasses.should == [B2]
969
+ C1.subclasses.should == []
970
+ Set.new(MockContent.subclasses).should == Set.new([MockContent2, A1, A2, B1, B2, C1])
971
+ end
972
+ end
973
+ end
974
+
975
+ should "allow for the creation of instance after save hooks" do
976
+ @database.fetch = { id: 1, type_sid:"DataMapperTest::MockContent" }
977
+ instance = MockContent.create label: "something"
978
+ test = false
979
+ instance.after_save_hook do
980
+ test = true
981
+ end
982
+ instance.save
983
+ test.should == true
984
+ end
985
+
986
+ should "let you count available instances" do
987
+ result = MockContent.count
988
+ @database.sqls.should == [
989
+ "SELECT COUNT(*) AS count FROM content WHERE (type_sid IN ('DataMapperTest::MockContent')) LIMIT 1"
990
+ ]
991
+ end
992
+
993
+ context "has_many associations" do
994
+ setup do
995
+ @database.columns = @expected_columns + [:parent_id, :source_id]
996
+ AssocContent = Spontaneous::DataMapper::Model(:content, @database, @schema)
997
+ AssocContent.has_many :children, key: :parent_id, model: AssocContent
998
+ @database.fetch = { id: 7, type_sid:"DataMapperTest::AssocContent" }
999
+ @parent = AssocContent.first
1000
+ @database.sqls
1001
+ end
1002
+
1003
+ teardown do
1004
+ DataMapperTest.send :remove_const, :AssocContent rescue nil
1005
+ end
1006
+
1007
+ should "use the correct dataset" do
1008
+ @database.fetch = { id: 7, type_sid:"DataMapperTest::AssocContent" }
1009
+ parent = AssocContent.first
1010
+ @database.sqls
1011
+ @database.fetch = [
1012
+ { id: 8, type_sid:"DataMapperTest::MockContent" },
1013
+ { id: 9, type_sid:"DataMapperTest::AssocContent" }
1014
+ ]
1015
+ children = parent.children
1016
+ @database.sqls.should == [
1017
+ "SELECT * FROM content WHERE (content.parent_id = 7)"
1018
+ ]
1019
+ children.map(&:id).should == [8, 9]
1020
+ children.map(&:class).should == [MockContent, AssocContent]
1021
+ end
1022
+
1023
+ should "cache the result" do
1024
+ children = @parent.children
1025
+ @database.sqls.should == [
1026
+ "SELECT * FROM content WHERE (content.parent_id = 7)"
1027
+ ]
1028
+ children = @parent.children
1029
+ @database.sqls.should == [ ]
1030
+ end
1031
+
1032
+ should "reload the result if forced" do
1033
+ children = @parent.children
1034
+ @database.sqls.should == [
1035
+ "SELECT * FROM content WHERE (content.parent_id = 7)"
1036
+ ]
1037
+ children = @parent.children(reload: true)
1038
+ @database.sqls.should == [
1039
+ "SELECT * FROM content WHERE (content.parent_id = 7)"
1040
+ ]
1041
+ end
1042
+
1043
+ should "allow access to the relation dataset" do
1044
+ ds = @parent.children_dataset
1045
+ ds.filter { id > 3}.all
1046
+ @database.sqls.should == [
1047
+ "SELECT * FROM content WHERE ((content.parent_id = 7) AND (id > 3))"
1048
+ ]
1049
+ end
1050
+
1051
+ should "return correctly typed results" do
1052
+ @database.fetch = [
1053
+ { id: 8, type_sid:"DataMapperTest::MockContent" },
1054
+ { id: 9, type_sid:"DataMapperTest::AssocContent" }
1055
+ ]
1056
+ children = @parent.children
1057
+ children.map(&:id).should == [8, 9]
1058
+ children.map(&:class).should == [MockContent, AssocContent]
1059
+ end
1060
+
1061
+ should "correctly set the relation key when adding members" do
1062
+ instance = AssocContent.new
1063
+ @database.sqls
1064
+ @parent.add_child(instance)
1065
+ @database.sqls.first.should == \
1066
+ "INSERT INTO content (parent_id, type_sid) VALUES (7, 'DataMapperTest::AssocContent')"
1067
+ end
1068
+
1069
+ should "use versioned dataset" do
1070
+ parent = nil
1071
+ @mapper.revision(99) do
1072
+ @database.fetch = { id: 7, type_sid:"DataMapperTest::AssocContent" }
1073
+ parent = AssocContent.first
1074
+ @database.sqls
1075
+ children = parent.children
1076
+ end
1077
+ @database.sqls.should == [
1078
+ "SELECT * FROM __r00099_content WHERE (__r00099_content.parent_id = 7)"
1079
+ ]
1080
+ end
1081
+
1082
+ should "use global dataset version" do
1083
+ parent = nil
1084
+ @mapper.revision(99) do
1085
+ @database.fetch = { id: 7, type_sid:"DataMapperTest::AssocContent" }
1086
+ parent = AssocContent.first
1087
+ @database.sqls
1088
+ @mapper.revision(11) do
1089
+ children = parent.children
1090
+ end
1091
+ end
1092
+ @database.sqls.should == [
1093
+ "SELECT * FROM __r00011_content WHERE (__r00011_content.parent_id = 7)"
1094
+ ]
1095
+ end
1096
+
1097
+ should "destroy dependents if configured" do
1098
+ AssocContent.has_many :destinations, key: :source_id, model: AssocContent, dependent: :destroy
1099
+ @database.fetch = [
1100
+ [{ id: 8, type_sid:"DataMapperTest::AssocContent", source_id:7 }],
1101
+ [{ id: 9, type_sid:"DataMapperTest::AssocContent", source_id:7 }]
1102
+ ]
1103
+ @parent.destroy
1104
+ @database.sqls.should == [
1105
+ "SELECT * FROM content WHERE (content.source_id = 7)",
1106
+ "SELECT * FROM content WHERE (content.source_id = 8)",
1107
+ "SELECT * FROM content WHERE (content.source_id = 9)",
1108
+ "DELETE FROM content WHERE (id = 9)",
1109
+ "DELETE FROM content WHERE (id = 8)",
1110
+ "DELETE FROM content WHERE (id = 7)"
1111
+ ]
1112
+ end
1113
+
1114
+ should "delete dependents if configured" do
1115
+ AssocContent.has_many :destinations, key: :source_id, model: AssocContent, dependent: :delete
1116
+ @database.fetch = [
1117
+ [{ id: 8, type_sid:"DataMapperTest::AssocContent", source_id:7 }],
1118
+ [{ id: 9, type_sid:"DataMapperTest::AssocContent", source_id:7 }]
1119
+ ]
1120
+ @parent.destroy
1121
+ @database.sqls.should == [
1122
+ "DELETE FROM content WHERE (content.source_id = 7)",
1123
+ "DELETE FROM content WHERE (id = 7)"
1124
+ ]
1125
+ end
1126
+
1127
+ should "work with non-mapped models" do
1128
+ model = Class.new(Sequel::Model(:other)) do ; end
1129
+ model.db = @database
1130
+ AssocContent.has_many :others, model: model, key: :user_id
1131
+ @database.fetch = { id: 7, type_sid:"DataMapperTest::AssocContent", parent_id: nil }
1132
+ instance = AssocContent.first
1133
+ @database.sqls
1134
+ instance.others
1135
+ @database.sqls.should == [
1136
+ "SELECT * FROM other WHERE (user_id = 7)"
1137
+ ]
1138
+ end
1139
+ end
1140
+
1141
+ context "belongs_to associations" do
1142
+ setup do
1143
+ @database.columns = @expected_columns + [:parent_id]
1144
+ AssocContent = Spontaneous::DataMapper::Model(:content, @database, @schema)
1145
+ AssocContent.has_many :children, key: :parent_id, model: AssocContent, reciprocal: :parent
1146
+ AssocContent.belongs_to :parent, key: :parent_id, model: AssocContent, reciprocal: :children
1147
+ @database.fetch = { id: 8, type_sid:"DataMapperTest::AssocContent", parent_id: 7 }
1148
+
1149
+ @child = AssocContent.first
1150
+ @database.sqls
1151
+ end
1152
+
1153
+ teardown do
1154
+ DataMapperTest.send :remove_const, :AssocContent rescue nil
1155
+ end
1156
+
1157
+ should "load the owner" do
1158
+ @database.fetch = { id: 7, type_sid:"DataMapperTest::AssocContent", parent_id: nil }
1159
+ parent = @child.parent
1160
+ @database.sqls.should == ["SELECT * FROM content WHERE (id = 7) LIMIT 1"]
1161
+ parent.must_be_instance_of AssocContent
1162
+ parent.id.should == 7
1163
+ end
1164
+
1165
+ should "cache the result" do
1166
+ parent = @child.parent
1167
+ @database.sqls.should == ["SELECT * FROM content WHERE (id = 7) LIMIT 1"]
1168
+ parent = @child.parent
1169
+ @database.sqls.should == [ ]
1170
+ end
1171
+
1172
+ should "reload the result if asked" do
1173
+ parent = @child.parent
1174
+ @database.sqls.should == ["SELECT * FROM content WHERE (id = 7) LIMIT 1"]
1175
+ parent = @child.parent(reload: true)
1176
+ @database.sqls.should == ["SELECT * FROM content WHERE (id = 7) LIMIT 1"]
1177
+ end
1178
+
1179
+ should "allow access to the relation dataset" do
1180
+ results = @child.parent_dataset.filter { id > 3 }.first
1181
+ @database.sqls.should == ["SELECT * FROM content WHERE ((content.id = 7) AND (id > 3)) LIMIT 1"]
1182
+ end
1183
+
1184
+ should "allow setting of owner for instance" do
1185
+ instance = AssocContent.new
1186
+ @database.sqls
1187
+ instance.parent = @child
1188
+ instance.parent_id.should == 8
1189
+ instance.save
1190
+ @database.sqls.first.should == \
1191
+ "INSERT INTO content (parent_id, type_sid) VALUES (8, 'DataMapperTest::AssocContent')"
1192
+ end
1193
+
1194
+ should "set the reciprocal relation" do
1195
+ @database.fetch = { id: 7, type_sid:"DataMapperTest::AssocContent" }
1196
+ parent = AssocContent.first
1197
+ @database.sqls
1198
+ @database.fetch = [
1199
+ { id: 8, type_sid:"DataMapperTest::AssocContent", parent_id: 7 },
1200
+ { id: 9, type_sid:"DataMapperTest::AssocContent", parent_id: 7 }
1201
+ ]
1202
+ children = parent.children
1203
+ children.map { |c| c.parent.object_id }.uniq.should == [parent.object_id]
1204
+ @database.sqls.should == [
1205
+ "SELECT * FROM content WHERE (content.parent_id = 7)"
1206
+ ]
1207
+ end
1208
+ end
1209
+
1210
+ context "performance" do
1211
+ should "use a cached version within revision blocks" do
1212
+ @mapper.revision(20) do
1213
+ assert @mapper.dataset.equal?(@mapper.dataset), "Dataset should be the same object"
1214
+ end
1215
+ end
1216
+
1217
+ should "use an identity map within revision scopes" do
1218
+ @database.fetch = [
1219
+ { id: 7, type_sid:"DataMapperTest::MockContent", parent_id: 7 }
1220
+ ]
1221
+ @mapper.editable do
1222
+ a = @mapper.first! :id => 7
1223
+ b = @mapper.first! :id => 7
1224
+ assert a.object_id == b.object_id, "a and b should be the same object"
1225
+ end
1226
+ end
1227
+
1228
+ should "use an object cache for #get calls" do
1229
+ @database.fetch = [
1230
+ [{ id: 8, type_sid:"DataMapperTest::MockContent", parent_id: 7 }],
1231
+ [{ id: 9, type_sid:"DataMapperTest::MockContent", parent_id: 7 }]
1232
+ ]
1233
+ @mapper.revision(20) do
1234
+ a = @mapper.get(8)
1235
+ b = @mapper.get(9)
1236
+ @database.sqls
1237
+ a = @mapper.get(8)
1238
+ b = @mapper.get(9)
1239
+ @database.sqls.should == []
1240
+ end
1241
+ end
1242
+
1243
+ should "not create new scope if revisions are the same" do
1244
+ a = b = nil
1245
+ @mapper.revision(20) do
1246
+ a = @mapper.dataset
1247
+ @mapper.revision(20) do
1248
+ b = @mapper.dataset
1249
+ end
1250
+ end
1251
+ assert a.object_id == b.object_id, "Mappers should be same object"
1252
+ end
1253
+
1254
+ should "not create new scope if visibility are the same" do
1255
+ a = b = nil
1256
+ @mapper.scope(20, true) do
1257
+ a = @mapper.dataset
1258
+ @mapper.visible do
1259
+ b = @mapper.dataset
1260
+ end
1261
+ end
1262
+ assert a.object_id == b.object_id, "Mappers should be same object"
1263
+ end
1264
+
1265
+ should "not create new scope if parameters are the same" do
1266
+ a = b = nil
1267
+ @mapper.scope(20, true) do
1268
+ a = @mapper.dataset
1269
+ @mapper.scope(20, true) do
1270
+ b = @mapper.dataset
1271
+ end
1272
+ end
1273
+ assert a.object_id == b.object_id, "Mappers should be same object"
1274
+ end
1275
+
1276
+ should "allow for using a custom cache key" do
1277
+ @database.fetch = [
1278
+ { id: 20, type_sid:"DataMapperTest::MockContent", parent_id: 7 }
1279
+ ]
1280
+ a = b = nil
1281
+ @mapper.scope(20, false) do
1282
+ a = @mapper.with_cache("key") { @mapper.filter(nil, label: "frog").first }
1283
+ b = @mapper.with_cache("key") { @mapper.filter(nil, label: "frog").first }
1284
+ end
1285
+ @database.sqls.should == [
1286
+ "SELECT * FROM __r00020_content WHERE (label = 'frog') LIMIT 1"
1287
+ ]
1288
+ end
1289
+
1290
+ should "allow for forcing the creation of a new scope to bypass the cache" do
1291
+ @database.fetch = [
1292
+ { id: 7, type_sid:"DataMapperTest::MockContent", parent_id: 7 }
1293
+ ]
1294
+ a = b = c = nil
1295
+
1296
+ @mapper.scope(nil, false) do
1297
+ a = @mapper.first! :id => 7
1298
+ @mapper.scope(nil, false) do
1299
+ b = @mapper.first! :id => 7
1300
+ @mapper.scope!(nil, false) do
1301
+ c = @mapper.first! :id => 7
1302
+ end
1303
+ end
1304
+ end
1305
+ assert a.object_id == b.object_id, "a and b should be the same object"
1306
+ assert a.object_id != c.object_id
1307
+ end
1308
+
1309
+ should "update the instance cache with updated values after a reload" do
1310
+ @database.fetch = [
1311
+ [{ id: 7, type_sid:"DataMapperTest::MockContent", parent_id: 7, label: "a" }],
1312
+ [{ id: 7, type_sid:"DataMapperTest::MockContent", parent_id: 7, label: "b" }],
1313
+ [{ id: 7, type_sid:"DataMapperTest::MockContent", parent_id: 7, label: "b" }]
1314
+ ]
1315
+ a = b = c = nil
1316
+ la = lb = lc = nil
1317
+
1318
+ @mapper.scope(nil, false) do
1319
+ a = @mapper.first! :id => 7
1320
+ la = a.label
1321
+ b = a.reload
1322
+ lb = b.label
1323
+ c = @mapper.get 7
1324
+ lc = c.label
1325
+ end
1326
+ assert [la, lb, lc] == ["a", "b", "b"], "Incorrect labels #{[la, lb, lc].inspect}"
1327
+ end
1328
+ end
1329
+ end
1330
+ end