my_annotations 0.5.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/.gitignore +6 -0
  2. data/.rvmrc +1 -0
  3. data/AUTHORS.rdoc +5 -0
  4. data/CHANGELOG.rdoc +64 -0
  5. data/INDEX.rdoc +17 -0
  6. data/generators/annotations_migration/annotations_migration_generator.rb +32 -0
  7. data/generators/annotations_migration/templates/migration_v1.rb +60 -0
  8. data/generators/annotations_migration/templates/migration_v2.rb +9 -0
  9. data/generators/annotations_migration/templates/migration_v3.rb +74 -0
  10. data/generators/annotations_migration/templates/migration_v4.rb +13 -0
  11. data/install.rb +1 -0
  12. data/lib/annotations/acts_as_annotatable.rb +271 -0
  13. data/lib/annotations/acts_as_annotation_source.rb +117 -0
  14. data/lib/annotations/acts_as_annotation_value.rb +115 -0
  15. data/lib/annotations/config.rb +148 -0
  16. data/lib/annotations/routing.rb +8 -0
  17. data/lib/annotations/util.rb +82 -0
  18. data/lib/annotations_version_fu.rb +119 -0
  19. data/lib/app/controllers/annotations_controller.rb +162 -0
  20. data/lib/app/controllers/application_controller.rb +2 -0
  21. data/lib/app/helpers/application_helper.rb +2 -0
  22. data/lib/app/models/annotation.rb +413 -0
  23. data/lib/app/models/annotation_attribute.rb +37 -0
  24. data/lib/app/models/annotation_value_seed.rb +48 -0
  25. data/lib/app/models/number_value.rb +23 -0
  26. data/lib/app/models/text_value.rb +23 -0
  27. data/my_annotations.gemspec +4 -9
  28. data/rails/init.rb +8 -0
  29. data/test/acts_as_annotatable_test.rb +186 -0
  30. data/test/acts_as_annotation_source_test.rb +84 -0
  31. data/test/acts_as_annotation_value_test.rb +17 -0
  32. data/test/annotation_attribute_test.rb +22 -0
  33. data/test/annotation_test.rb +213 -0
  34. data/test/annotation_value_seed_test.rb +14 -0
  35. data/test/annotation_version_test.rb +39 -0
  36. data/test/annotations_controller_test.rb +27 -0
  37. data/test/app_root/app/controllers/application_controller.rb +9 -0
  38. data/test/app_root/app/models/book.rb +5 -0
  39. data/test/app_root/app/models/chapter.rb +5 -0
  40. data/test/app_root/app/models/group.rb +3 -0
  41. data/test/app_root/app/models/tag.rb +6 -0
  42. data/test/app_root/app/models/user.rb +3 -0
  43. data/test/app_root/app/views/annotations/edit.html.erb +12 -0
  44. data/test/app_root/app/views/annotations/index.html.erb +1 -0
  45. data/test/app_root/app/views/annotations/new.html.erb +11 -0
  46. data/test/app_root/app/views/annotations/show.html.erb +3 -0
  47. data/test/app_root/config/boot.rb +115 -0
  48. data/test/app_root/config/environment.rb +16 -0
  49. data/test/app_root/config/environments/mysql.rb +0 -0
  50. data/test/app_root/config/routes.rb +4 -0
  51. data/test/app_root/db/migrate/001_create_test_models.rb +38 -0
  52. data/test/app_root/db/migrate/002_annotations_migration_v1.rb +60 -0
  53. data/test/app_root/db/migrate/003_annotations_migration_v2.rb +9 -0
  54. data/test/app_root/db/migrate/004_annotations_migration_v3.rb +72 -0
  55. data/test/config_test.rb +383 -0
  56. data/test/fixtures/annotation_attributes.yml +49 -0
  57. data/test/fixtures/annotation_value_seeds.csv +16 -0
  58. data/test/fixtures/annotation_versions.yml +259 -0
  59. data/test/fixtures/annotations.yml +239 -0
  60. data/test/fixtures/books.yml +13 -0
  61. data/test/fixtures/chapters.yml +27 -0
  62. data/test/fixtures/groups.yml +7 -0
  63. data/test/fixtures/number_value_versions.csv +2 -0
  64. data/test/fixtures/number_values.csv +2 -0
  65. data/test/fixtures/text_value_versions.csv +35 -0
  66. data/test/fixtures/text_values.csv +35 -0
  67. data/test/fixtures/users.yml +8 -0
  68. data/test/number_value_version_test.rb +40 -0
  69. data/test/routing_test.rb +27 -0
  70. data/test/test_helper.rb +41 -0
  71. data/test/text_value_version_test.rb +40 -0
  72. metadata +77 -7
@@ -0,0 +1,383 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class ConfigTest < ActiveSupport::TestCase
4
+ def setup
5
+ Annotations::Config::reset
6
+
7
+ Annotations::Config::attribute_names_for_values_to_be_downcased.concat([ "downcased_thing" ])
8
+
9
+ Annotations::Config::attribute_names_for_values_to_be_upcased.concat([ "upcased_thing" ])
10
+
11
+ Annotations::Config::strip_text_rules.update({ "tag" => [ '"', ',' ], "comma_stripped" => ',', "regex_strip" => /\d/ })
12
+
13
+ Annotations::Config::limits_per_source.update({ "rating" => 1 })
14
+
15
+ Annotations::Config::attribute_names_to_allow_duplicates.concat([ "allow_duplicates_for_this" ])
16
+
17
+ Annotations::Config::content_restrictions.update({ "rating" => { :in => 1..5, :error_message => "Please provide a rating between 1 and 5" },
18
+ "category" => { :in => [ "fruit", "nut", "fibre" ], :error_message => "Please select a valid category" } })
19
+
20
+ Annotations::Config::default_attribute_identifier_template = "http://x.com/attribute#%s"
21
+
22
+ Annotations::Config::attribute_name_transform_for_identifier = Proc.new { |name|
23
+ regex = /\.|-|:/
24
+ if name.match(regex)
25
+ name.gsub(regex, ' ').titleize.gsub(' ', '').camelize(:lower)
26
+ else
27
+ name.camelize(:lower)
28
+ end
29
+ }
30
+
31
+ Annotations::Config::value_factories["tag"] = Proc.new { |v|
32
+ case v
33
+ when String, Symbol
34
+ Tag.find_or_create_by_name(v.to_s)
35
+ else
36
+ v
37
+ end
38
+ }
39
+
40
+ Annotations::Config::valid_value_types["tag"] = "Tag"
41
+
42
+ end
43
+
44
+ def teardown
45
+ Annotations::Config.reset
46
+ end
47
+
48
+ def test_values_downcased_or_upcased
49
+ source = users(:jane)
50
+
51
+ # Should downcase
52
+
53
+ ann1 = Annotation.create(:attribute_name => "downcased_thing",
54
+ :value => "UNIque",
55
+ :source_type => source.class.name,
56
+ :source_id => source.id,
57
+ :annotatable_type => "Book",
58
+ :annotatable_id => 1)
59
+
60
+ assert ann1.valid?
61
+ assert_equal "unique", ann1.value_content
62
+
63
+ # Should upcase
64
+
65
+ ann2 = Annotation.create(:attribute_name => "upcased_thing",
66
+ :value => "UNIque",
67
+ :source_type => source.class.name,
68
+ :source_id => source.id,
69
+ :annotatable_type => "Book",
70
+ :annotatable_id => 1)
71
+
72
+ assert ann2.valid?
73
+ assert_equal "UNIQUE", ann2.value_content
74
+
75
+ # Should not do anything
76
+
77
+ ann3 = Annotation.create(:attribute_name => "dont_do_anything",
78
+ :value => "UNIque",
79
+ :source_type => source.class.name,
80
+ :source_id => source.id,
81
+ :annotatable_type => "Book",
82
+ :annotatable_id => 1)
83
+
84
+ assert ann3.valid?
85
+ assert_equal "UNIque", ann3.value_content
86
+ end
87
+
88
+ def test_strip_text_rules
89
+ source = users(:john)
90
+
91
+ # Strip 'tag'
92
+
93
+ ann1 = Annotation.create(:attribute_name => "Tag",
94
+ :value => 'v,al"ue',
95
+ :source_type => source.class.name,
96
+ :source_id => source.id,
97
+ :annotatable_type => "Book",
98
+ :annotatable_id => 1)
99
+
100
+ assert ann1.valid?
101
+ assert_equal "value", ann1.value_content
102
+
103
+ # Strip 'comma_stripped'
104
+
105
+ ann2 = Annotation.create(:attribute_name => "comma_stripped",
106
+ :value => 'v,al"ue',
107
+ :source_type => source.class.name,
108
+ :source_id => source.id,
109
+ :annotatable_type => "Book",
110
+ :annotatable_id => 1)
111
+
112
+ assert ann2.valid?
113
+ assert_equal 'val"ue', ann2.value_content
114
+
115
+ # Regexp strip
116
+
117
+ ann3 = Annotation.create(:attribute_name => "regex_strip",
118
+ :value => 'v1,al"ue23x4',
119
+ :source_type => source.class.name,
120
+ :source_id => source.id,
121
+ :annotatable_type => "Book",
122
+ :annotatable_id => 1)
123
+
124
+ assert ann3.valid?
125
+ assert_equal 'v,al"uex', ann3.value_content
126
+
127
+ # Don't strip!
128
+
129
+ ann4 = Annotation.create(:attribute_name => "dont_strip",
130
+ :value => 'v,al"ue',
131
+ :source_type => source.class.name,
132
+ :source_id => source.id,
133
+ :annotatable_type => "Book",
134
+ :annotatable_id => 1)
135
+
136
+ assert ann4.valid?
137
+ assert_equal 'v,al"ue', ann4.value_content
138
+ end
139
+
140
+ def test_limits_per_source
141
+ source = users(:john)
142
+
143
+ bk1 = Book.create
144
+
145
+ anns = bk1.annotations << Annotation.new(:attribute_name => "rating",
146
+ :value => NumberValue.new(:number => 4),
147
+ :source_type => source.class.name,
148
+ :source_id => source.id)
149
+
150
+ assert anns.length > 0
151
+ assert_equal 4, bk1.annotations(true)[0].value_content
152
+ assert_equal 1, bk1.annotations(true).length
153
+
154
+ ann2 = Annotation.new(:attribute_name => "rating",
155
+ :value => 1,
156
+ :source_type => source.class.name,
157
+ :source_id => source.id,
158
+ :annotatable_type => "Book",
159
+ :annotatable_id => bk1.id)
160
+
161
+ assert ann2.invalid?
162
+ assert !ann2.save
163
+ assert_equal 1, bk1.annotations(true).length
164
+
165
+ ann3 = bk1.annotations(true)[0]
166
+ ann3.value = 3
167
+ assert ann3.valid?
168
+ assert ann3.save
169
+ assert_equal 1, bk1.annotations(true).length
170
+
171
+ # Check that two versions of the annotation now exist
172
+ assert_equal 2, bk1.annotations[0].versions.length
173
+ end
174
+
175
+ def test_attribute_names_to_allow_duplicates
176
+ source = users(:john)
177
+
178
+ # First test the default case of not allowing duplicates...
179
+
180
+ bk1 = Book.create
181
+
182
+ ann1 = bk1.annotations << Annotation.new(:attribute_name => "no_duplicates_allowed",
183
+ :value => "Hello there",
184
+ :source_type => source.class.name,
185
+ :source_id => source.id)
186
+
187
+ assert_not_nil ann1
188
+ assert_equal 1, bk1.annotations.length
189
+
190
+ ann2 = bk1.annotations << Annotation.new(:attribute_name => "no_duplicates_allowed",
191
+ :value => "Hello there again",
192
+ :source_type => source.class.name,
193
+ :source_id => source.id)
194
+
195
+ assert_not_nil ann2
196
+ assert_equal 2, bk1.annotations(true).length
197
+
198
+ ann3 = bk1.annotations << Annotation.new(:attribute_name => "no_duplicates_allowed",
199
+ :value => "Hello there",
200
+ :source_type => source.class.name,
201
+ :source_id => source.id)
202
+
203
+ assert_equal false, ann3
204
+ assert_equal 2, bk1.annotations(true).length
205
+
206
+
207
+ # Then test the configured exceptions to the default rule...
208
+
209
+ bk2 = Book.create
210
+
211
+ ann4 = bk2.annotations << Annotation.new(:attribute_name => "allow_duplicates_for_this",
212
+ :value => "Hi there",
213
+ :source_type => source.class.name,
214
+ :source_id => source.id)
215
+
216
+ assert_not_nil ann4
217
+ assert_equal 1, bk2.annotations.length
218
+
219
+ ann5 = bk2.annotations << Annotation.new(:attribute_name => "allow_duplicates_for_this",
220
+ :value => "Hi there again",
221
+ :source_type => source.class.name,
222
+ :source_id => source.id)
223
+
224
+ assert_not_nil ann5
225
+ assert_equal 2, bk2.annotations(true).length
226
+
227
+ ann6 = bk2.annotations << Annotation.new(:attribute_name => "allow_duplicates_for_this",
228
+ :value => "Hi there",
229
+ :source_type => source.class.name,
230
+ :source_id => source.id)
231
+
232
+ assert_not_nil ann6
233
+ assert_equal 3, bk2.annotations(true).length
234
+ end
235
+
236
+ def test_content_restrictions
237
+ source1 = users(:john)
238
+ source2 = users(:jane)
239
+
240
+ # First test the default case of not restricting values...
241
+
242
+ bk1 = Book.create
243
+
244
+ ann1 = Annotation.new(:attribute_name => "allow_any_value",
245
+ :value => "Hello there",
246
+ :source_type => source1.class.name,
247
+ :source_id => source1.id,
248
+ :annotatable_type => "Book",
249
+ :annotatable_id => bk1.id)
250
+
251
+ assert ann1.valid?
252
+ assert ann1.save
253
+ assert_equal 1, bk1.annotations.length
254
+
255
+
256
+ # Then test the configured exceptions to the default rule...
257
+
258
+ bk2 = Book.create
259
+
260
+ ann2 = Annotation.new(:attribute_name => "rating",
261
+ :value => "2",
262
+ :source_type => source1.class.name,
263
+ :source_id => source1.id,
264
+ :annotatable_type => "Book",
265
+ :annotatable_id => bk2.id)
266
+
267
+ assert ann2.valid?
268
+ assert ann2.save
269
+ assert_equal 1, bk2.annotations.length
270
+
271
+
272
+ ann3 = Annotation.new(:attribute_name => "rating",
273
+ :value => "10",
274
+ :source_type => source2.class.name,
275
+ :source_id => source2.id,
276
+ :annotatable_type => "Book",
277
+ :annotatable_id => bk2.id)
278
+
279
+ assert ann3.invalid?
280
+ assert !ann3.save
281
+ assert ann3.errors.full_messages.include?("Please provide a rating between 1 and 5")
282
+ assert_equal 1, bk2.annotations(true).length
283
+
284
+
285
+ ann4 = Annotation.new(:attribute_name => "category",
286
+ :value => "fibre",
287
+ :source_type => source1.class.name,
288
+ :source_id => source1.id,
289
+ :annotatable_type => "Book",
290
+ :annotatable_id => bk2.id)
291
+
292
+ assert ann4.valid?
293
+ assert ann4.save
294
+ assert_equal 2, bk2.annotations(true).length
295
+
296
+ ann5 = Annotation.new(:attribute_name => "category",
297
+ :value => "home cooking",
298
+ :source_type => source2.class.name,
299
+ :source_id => source2.id,
300
+ :annotatable_type => "Book",
301
+ :annotatable_id => bk2.id)
302
+
303
+ assert ann5.invalid?
304
+ assert !ann5.save
305
+ assert ann5.errors.full_messages.include?("Please select a valid category")
306
+ assert_equal 2, bk2.annotations(true).length
307
+ end
308
+
309
+ def test_default_attribute_identifier_template
310
+ attrib1 = AnnotationAttribute.create(:name => "myAttribute")
311
+ assert attrib1.valid?
312
+ assert_equal "http://x.com/attribute#myAttribute", attrib1.identifier
313
+
314
+ attrib2 = AnnotationAttribute.create(:name => "http://www.example.org/annotations#details")
315
+ assert attrib2.valid?
316
+ assert_equal "http://www.example.org/annotations#details", attrib2.identifier
317
+
318
+ attrib3 = AnnotationAttribute.create(:name => "<www.example.org/annotations#details>")
319
+ assert attrib3.valid?
320
+ assert_equal "www.example.org/annotations#details", attrib3.identifier
321
+
322
+ attrib4 = AnnotationAttribute.create(:name => "hello_world-attribute:zero")
323
+ assert attrib4.valid?
324
+ assert_equal "http://x.com/attribute#helloWorldAttributeZero", attrib4.identifier
325
+ end
326
+
327
+ def test_value_factories
328
+ source = users(:john)
329
+
330
+ ann1 = Annotation.create(:attribute_name => "Tag",
331
+ :value => 'alignment',
332
+ :source_type => source.class.name,
333
+ :source_id => source.id,
334
+ :annotatable_type => "Book",
335
+ :annotatable_id => 1)
336
+
337
+ assert ann1.valid?
338
+ assert_kind_of Tag, ann1.value
339
+ assert_equal "alignment", ann1.value_content
340
+
341
+ ann2 = Annotation.new(:attribute_name => "tag",
342
+ :source_type => source.class.name,
343
+ :source_id => source.id,
344
+ :annotatable_type => "Book",
345
+ :annotatable_id => 1)
346
+
347
+ ann2.value = Tag.find_or_create_by_name("hello")
348
+
349
+ assert ann2.save
350
+ assert_equal 'hello', ann2.value_content
351
+ end
352
+
353
+ def test_valid_value_types
354
+ source = users(:john)
355
+
356
+ # Test valid one
357
+
358
+ ann1 = Annotation.new(:attribute_name => "Tag",
359
+ :value => 'smashing',
360
+ :source_type => source.class.name,
361
+ :source_id => source.id,
362
+ :annotatable_type => "Book",
363
+ :annotatable_id => 1)
364
+
365
+ assert ann1.valid?
366
+ assert ann1.save
367
+ assert_kind_of Tag, ann1.value
368
+
369
+ # Test invalid one
370
+
371
+ ann2 = Annotation.new(:attribute_name => "Tag",
372
+ :value => TextValue.new(:text => 'smashing'),
373
+ :source_type => source.class.name,
374
+ :source_id => source.id,
375
+ :annotatable_type => "Book",
376
+ :annotatable_id => 1)
377
+
378
+
379
+ assert ann2.invalid?
380
+ assert !ann2.save
381
+ assert ann2.errors.full_messages.include?("Annotation value is of an invalid type for attribute name: 'tag'. Provided value is a TextValue.")
382
+ end
383
+ end
@@ -0,0 +1,49 @@
1
+ aa_tag:
2
+ id: 1
3
+ name: Tag
4
+ identifier: "http://www.example.org/attribute#Tag"
5
+
6
+ aa_summary:
7
+ id: 2
8
+ name: Summary
9
+ identifier: "http://www.example.org/attribute#Summary"
10
+
11
+ aa_length:
12
+ id: 3
13
+ name: length
14
+ identifier: "http://www.example.org/attribute#Length"
15
+
16
+ aa_title:
17
+ id: 4
18
+ name: Title
19
+ identifier: "http://www.example.org/attribute#Title"
20
+
21
+ aa_complexity:
22
+ id: 5
23
+ name: complexity
24
+ identifier: "http://www.example.org/attribute#complexity"
25
+
26
+ aa_endingtype:
27
+ id: 6
28
+ name: endingType
29
+ identifier: "http://www.example.org/attribute#endingType"
30
+
31
+ aa_note:
32
+ id: 7
33
+ name: Note
34
+ identifier: "http://www.example.org/attribute#Note"
35
+
36
+ aa_author:
37
+ id: 8
38
+ name: Author
39
+ identifier: "http://www.example.org/attribute#Author"
40
+
41
+ aa_rating:
42
+ id: 9
43
+ name: rating
44
+ identifier: "http://www.example.org/attribute#rating"
45
+
46
+ aa_contextualtag:
47
+ id: 10
48
+ name: Contextual Tag
49
+ identifier: "http://www.example.org/attribute#ContextualTag"
@@ -0,0 +1,16 @@
1
+ id,attribute_id,value_type,value_id
2
+ 1,1,TextValue,1
3
+ 2,1,TextValue,2
4
+ 3,1,TextValue,3
5
+ 4,1,TextValue,4
6
+ 5,1,TextValue,5
7
+ 6,1,TextValue,6
8
+ 7,1,TextValue,7
9
+ 8,1,TextValue,8
10
+ 9,1,TextValue,9
11
+ 10,1,TextValue,10
12
+ 11,8,TextValue,11
13
+ 12,8,TextValue,12
14
+ 13,8,TextValue,13
15
+ 14,8,TextValue,14
16
+ 15,8,TextValue,15
@@ -0,0 +1,259 @@
1
+ bh_summary_1_av1:
2
+ annotation: bh_summary_1
3
+ annotatable_type: Book
4
+ annotatable_id: 1
5
+ attribute_id: 2
6
+ value_type: TextValue
7
+ value_id: 16
8
+ source_type: User
9
+ source_id: 1
10
+ version: 1
11
+ created_at: <%= 1.minute.ago.to_s(:db) %>
12
+ updated_at: <%= 1.minute.ago.to_s(:db) %>
13
+
14
+ bh_length_1_av1:
15
+ annotation: bh_length_1
16
+ annotatable_type: Book
17
+ annotatable_id: 1
18
+ attribute_id: 3
19
+ value_type: NumberValue
20
+ value_id: 1
21
+ source_type: Group
22
+ source_id: 2
23
+ version: 1
24
+ created_at: <%= 1.minute.ago.to_s(:db) %>
25
+ updated_at: <%= 1.minute.ago.to_s(:db) %>
26
+
27
+ bh_title_1_av1:
28
+ annotation: bh_title_1
29
+ annotatable_type: Book
30
+ annotatable_id: 1
31
+ attribute_id: 4
32
+ value_type: TextValue
33
+ value_id: 17
34
+ source_type: User
35
+ source_id: 1
36
+ version: 1
37
+ created_at: <%= 1.minute.ago.to_s(:db) %>
38
+ updated_at: <%= 1.minute.ago.to_s(:db) %>
39
+
40
+ bh_rating_1_av1:
41
+ annotation: bh_rating_1
42
+ annotatable_type: Rating
43
+ annotatable_id: 1
44
+ attribute_id: 9
45
+ value_type: TextValue
46
+ value_id: 18
47
+ source_type: User
48
+ source_id: 2
49
+ version: 1
50
+ created_at: <%= 1.minute.ago.to_s(:db) %>
51
+ updated_at: <%= 1.minute.ago.to_s(:db) %>
52
+
53
+ bh_tag_1_av1:
54
+ annotation: bh_tag_1
55
+ annotatable_type: Book
56
+ annotatable_id: 1
57
+ attribute_id: 1
58
+ value_type: TextValue
59
+ value_id: 19
60
+ source_type: Group
61
+ source_id: 2
62
+ version: 1
63
+ created_at: <%= 1.minute.ago.to_s(:db) %>
64
+ updated_at: <%= 1.minute.ago.to_s(:db) %>
65
+
66
+ bh_tag_2_av1:
67
+ annotation: bh_tag_2
68
+ annotatable_type: Book
69
+ annotatable_id: 1
70
+ attribute_id: 1
71
+ value_type: TextValue
72
+ value_id: 20
73
+ source_type: User
74
+ source_id: 2
75
+ version: 1
76
+ created_at: <%= 1.minute.ago.to_s(:db) %>
77
+ updated_at: <%= 1.minute.ago.to_s(:db) %>
78
+
79
+ br_summary_1_av1:
80
+ annotation: br_summary_1
81
+ annotatable_type: Book
82
+ annotatable_id: 2
83
+ attribute_id: 2
84
+ value_type: TextValue
85
+ value_id: 21
86
+ source_type: User
87
+ source_id: 1
88
+ version: 1
89
+ created_at: <%= 1.minute.ago.to_s(:db) %>
90
+ updated_at: <%= 1.minute.ago.to_s(:db) %>
91
+
92
+ br_summary_2_av1:
93
+ annotation: br_summary_2
94
+ annotatable_type: Book
95
+ annotatable_id: 2
96
+ attribute_id: 2
97
+ value_type: TextValue
98
+ value_id: 22
99
+ source_type: Group
100
+ source_id: 1
101
+ version: 1
102
+ created_at: <%= 1.minute.ago.to_s(:db) %>
103
+ updated_at: <%= 1.minute.ago.to_s(:db) %>
104
+
105
+ br_author_1_av1:
106
+ annotation: br_author_1
107
+ annotatable_type: Book
108
+ annotatable_id: 2
109
+ attribute_id: 8
110
+ value_type: TextValue
111
+ value_id: 23
112
+ source_type: User
113
+ source_id: 1
114
+ version: 1
115
+ created_at: <%= 1.minute.ago.to_s(:db) %>
116
+ updated_at: <%= 1.minute.ago.to_s(:db) %>
117
+
118
+ br_tag_1_av1:
119
+ annotation: br_tag_1
120
+ annotatable_type: Book
121
+ annotatable_id: 2
122
+ attribute_id: 1
123
+ value_type: TextValue
124
+ value_id: 24
125
+ source_type: User
126
+ source_id: 2
127
+ version: 1
128
+ created_at: <%= 1.minute.ago.to_s(:db) %>
129
+ updated_at: <%= 1.minute.ago.to_s(:db) %>
130
+
131
+ br_note_1_av1:
132
+ annotation: br_note_1
133
+ annotatable_type: Book
134
+ annotatable_id: 2
135
+ attribute_id: 7
136
+ value_type: TextValue
137
+ value_id: 25
138
+ source_type: User
139
+ source_id: 2
140
+ version: 1
141
+ created_at: <%= 1.minute.ago.to_s(:db) %>
142
+ updated_at: <%= 1.minute.ago.to_s(:db) %>
143
+
144
+ bh_c10_endingtype_1_av1:
145
+ annotation: bh_c10_endingtype_1
146
+ annotatable_type: Chapter
147
+ annotatable_id: 2
148
+ attribute_id: 6
149
+ value_type: TextValue
150
+ value_id: 26
151
+ source_type: Group
152
+ source_id: 1
153
+ version: 1
154
+ created_at: <%= 1.minute.ago.to_s(:db) %>
155
+ updated_at: <%= 1.minute.ago.to_s(:db) %>
156
+
157
+ bh_c10_title_1_av1:
158
+ annotation: bh_c10_title_1
159
+ annotatable_type: Chapter
160
+ annotatable_id: 2
161
+ attribute_id: 4
162
+ value_type: TextValue
163
+ value_id: 27
164
+ source_type: User
165
+ source_id: 2
166
+ version: 1
167
+ created_at: <%= 1.minute.ago.to_s(:db) %>
168
+ updated_at: <%= 1.minute.ago.to_s(:db) %>
169
+
170
+ br_c2_title_1_av1:
171
+ annotation: br_c2_title_1
172
+ annotatable_type: Chapter
173
+ annotatable_id: 3
174
+ attribute_id: 4
175
+ value_type: TextValue
176
+ value_id: 28
177
+ source_type: User
178
+ source_id: 1
179
+ version: 1
180
+ created_at: <%= 1.minute.ago.to_s(:db) %>
181
+ updated_at: <%= 1.minute.ago.to_s(:db) %>
182
+
183
+ br_c2_tag_1_av1:
184
+ annotation: br_c2_tag_1
185
+ annotatable_type: Chapter
186
+ annotatable_id: 3
187
+ attribute_id: 1
188
+ value_type: TextValue
189
+ value_id: 29
190
+ source_type: Group
191
+ source_id: 2
192
+ version: 1
193
+ created_at: <%= 1.minute.ago.to_s(:db) %>
194
+ updated_at: <%= 1.minute.ago.to_s(:db) %>
195
+
196
+ br_c2_tag_2_av1:
197
+ annotation: br_c2_tag_2
198
+ annotatable_type: Chapter
199
+ annotatable_id: 3
200
+ attribute_id: 1
201
+ value_type: TextValue
202
+ value_id: 30
203
+ source_type: User
204
+ source_id: 1
205
+ version: 1
206
+ created_at: <%= 1.minute.ago.to_s(:db) %>
207
+ updated_at: <%= 1.minute.ago.to_s(:db) %>
208
+
209
+ br_c2_title_2_av1:
210
+ annotation: br_c2_title_2
211
+ annotatable_type: Chapter
212
+ annotatable_id: 3
213
+ attribute_id: 4
214
+ value_type: TextValue
215
+ value_id: 31
216
+ source_type: User
217
+ source_id: 2
218
+ version: 1
219
+ created_at: <%= 1.minute.ago.to_s(:db) %>
220
+ updated_at: <%= 1.minute.ago.to_s(:db) %>
221
+
222
+ br_c202_complexity_1_av1:
223
+ annotation: br_c202_complexity_1
224
+ annotatable_type: Chapter
225
+ annotatable_id: 4
226
+ attribute_id: 5
227
+ value_type: TextValue
228
+ value_id: 32
229
+ source_type: Group
230
+ source_id: 1
231
+ version: 1
232
+ created_at: <%= 1.minute.ago.to_s(:db) %>
233
+ updated_at: <%= 1.minute.ago.to_s(:db) %>
234
+
235
+ br_c202_title_1_av1:
236
+ annotation: br_c202_title_1
237
+ annotatable_type: Chapter
238
+ annotatable_id: 4
239
+ attribute_id: 4
240
+ value_type: TextValue
241
+ value_id: 33
242
+ source_type: User
243
+ source_id: 1
244
+ version: 1
245
+ created_at: <%= 1.minute.ago.to_s(:db) %>
246
+ updated_at: <%= 1.minute.ago.to_s(:db) %>
247
+
248
+ br_c202_tag_1_av1:
249
+ annotation: br_c202_tag_1
250
+ annotatable_type: Chapter
251
+ annotatable_id: 4
252
+ attribute_id: 1
253
+ value_type: TextValue
254
+ value_id: 34
255
+ source_type: Group
256
+ source_id: 2
257
+ version: 1
258
+ created_at: <%= 1.minute.ago.to_s(:db) %>
259
+ updated_at: <%= 1.minute.ago.to_s(:db) %>