populate-me 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/Gemfile +3 -0
  4. data/LICENSE +20 -0
  5. data/README.md +655 -0
  6. data/Rakefile +14 -0
  7. data/example/config.ru +100 -0
  8. data/lib/populate_me.rb +2 -0
  9. data/lib/populate_me/admin.rb +157 -0
  10. data/lib/populate_me/admin/__assets__/css/asmselect.css +63 -0
  11. data/lib/populate_me/admin/__assets__/css/jquery-ui.min.css +6 -0
  12. data/lib/populate_me/admin/__assets__/css/main.css +244 -0
  13. data/lib/populate_me/admin/__assets__/img/help/children.png +0 -0
  14. data/lib/populate_me/admin/__assets__/img/help/create.png +0 -0
  15. data/lib/populate_me/admin/__assets__/img/help/delete.png +0 -0
  16. data/lib/populate_me/admin/__assets__/img/help/edit.png +0 -0
  17. data/lib/populate_me/admin/__assets__/img/help/form.png +0 -0
  18. data/lib/populate_me/admin/__assets__/img/help/list.png +0 -0
  19. data/lib/populate_me/admin/__assets__/img/help/login.png +0 -0
  20. data/lib/populate_me/admin/__assets__/img/help/logout.png +0 -0
  21. data/lib/populate_me/admin/__assets__/img/help/menu.png +0 -0
  22. data/lib/populate_me/admin/__assets__/img/help/overview.png +0 -0
  23. data/lib/populate_me/admin/__assets__/img/help/save.png +0 -0
  24. data/lib/populate_me/admin/__assets__/img/help/sort.png +0 -0
  25. data/lib/populate_me/admin/__assets__/img/help/sublist.png +0 -0
  26. data/lib/populate_me/admin/__assets__/js/asmselect.js +412 -0
  27. data/lib/populate_me/admin/__assets__/js/columnav.js +87 -0
  28. data/lib/populate_me/admin/__assets__/js/jquery-ui.min.js +7 -0
  29. data/lib/populate_me/admin/__assets__/js/main.js +388 -0
  30. data/lib/populate_me/admin/__assets__/js/mustache.js +578 -0
  31. data/lib/populate_me/admin/__assets__/js/sortable.js +2 -0
  32. data/lib/populate_me/admin/views/help.erb +94 -0
  33. data/lib/populate_me/admin/views/page.erb +189 -0
  34. data/lib/populate_me/api.rb +124 -0
  35. data/lib/populate_me/attachment.rb +186 -0
  36. data/lib/populate_me/document.rb +192 -0
  37. data/lib/populate_me/document_mixins/admin_adapter.rb +149 -0
  38. data/lib/populate_me/document_mixins/callbacks.rb +125 -0
  39. data/lib/populate_me/document_mixins/outcasting.rb +83 -0
  40. data/lib/populate_me/document_mixins/persistence.rb +95 -0
  41. data/lib/populate_me/document_mixins/schema.rb +198 -0
  42. data/lib/populate_me/document_mixins/typecasting.rb +70 -0
  43. data/lib/populate_me/document_mixins/validation.rb +44 -0
  44. data/lib/populate_me/file_system_attachment.rb +40 -0
  45. data/lib/populate_me/grid_fs_attachment.rb +103 -0
  46. data/lib/populate_me/mongo.rb +160 -0
  47. data/lib/populate_me/s3_attachment.rb +120 -0
  48. data/lib/populate_me/variation.rb +38 -0
  49. data/lib/populate_me/version.rb +4 -0
  50. data/populate-me.gemspec +34 -0
  51. data/test/helper.rb +37 -0
  52. data/test/test_admin.rb +183 -0
  53. data/test/test_api.rb +246 -0
  54. data/test/test_attachment.rb +167 -0
  55. data/test/test_document.rb +128 -0
  56. data/test/test_document_admin_adapter.rb +221 -0
  57. data/test/test_document_callbacks.rb +151 -0
  58. data/test/test_document_outcasting.rb +247 -0
  59. data/test/test_document_persistence.rb +83 -0
  60. data/test/test_document_schema.rb +280 -0
  61. data/test/test_document_typecasting.rb +128 -0
  62. data/test/test_grid_fs_attachment.rb +239 -0
  63. data/test/test_mongo.rb +324 -0
  64. data/test/test_s3_attachment.rb +281 -0
  65. data/test/test_variation.rb +91 -0
  66. data/test/test_version.rb +11 -0
  67. metadata +294 -0
@@ -0,0 +1,280 @@
1
+ require 'helper'
2
+ require 'populate_me/document'
3
+
4
+ describe PopulateMe::Document, 'Schema' do
5
+
6
+ parallelize_me!
7
+
8
+ describe "Relationships" do
9
+
10
+ class Relative < PopulateMe::Document
11
+ relationship :siblings
12
+ relationship :home_remedies
13
+ relationship :friends, label: 'Budies', class_name: 'Budy', foreign_key: :budy_id, dependent: false
14
+ end
15
+
16
+ class Relative::Sibling < PopulateMe::Document
17
+ field :size
18
+ field :relative_id, type: :parent
19
+ end
20
+
21
+ before do
22
+ Relative.documents = []
23
+ Relative::Sibling.documents = []
24
+ end
25
+
26
+ it "Defaults class name" do
27
+ assert_equal "Relative::Sibling", Relative.relationships[:siblings][:class_name]
28
+ assert_equal "Relative::HomeRemedy", Relative.relationships[:home_remedies][:class_name]
29
+ end
30
+
31
+ it "Defaults label" do
32
+ assert_equal "Siblings", Relative.relationships[:siblings][:label]
33
+ assert_equal "Home remedies", Relative.relationships[:home_remedies][:label]
34
+ end
35
+
36
+ it "Defaults foreign key" do
37
+ assert_equal :relative_id, Relative.relationships[:siblings][:foreign_key]
38
+ assert_equal :relative_id, Relative.relationships[:home_remedies][:foreign_key]
39
+ end
40
+
41
+ it "Defaults :dependent to true" do
42
+ assert Relative.relationships[:siblings][:dependent]
43
+ end
44
+
45
+ it "Has everything editable" do
46
+ assert_equal "Budies", Relative.relationships[:friends][:label]
47
+ assert_equal "Budy", Relative.relationships[:friends][:class_name]
48
+ assert_equal :budy_id, Relative.relationships[:friends][:foreign_key]
49
+ refute Relative.relationships[:friends][:dependent]
50
+ end
51
+
52
+ it "Creates a getter for cached items" do
53
+ relative = Relative.new(id: 10)
54
+ relative.save
55
+ Relative::Sibling.new(relative_id: 10, size: 'S').save
56
+ Relative::Sibling.new(relative_id: 10, size: 'M').save
57
+ Relative::Sibling.new(relative_id: 10, size: 'L').save
58
+ assert relative.respond_to? :siblings
59
+ assert_nil relative.instance_variable_get('@cached_siblings')
60
+ siblings = relative.siblings
61
+ assert_equal 3, siblings.size
62
+ assert_equal siblings, relative.instance_variable_get('@cached_siblings')
63
+ end
64
+
65
+ it "Creates a getter for cached first item" do
66
+ relative = Relative.new(id: 10)
67
+ relative.save
68
+ Relative::Sibling.new(relative_id: 10, size: 'S').save
69
+ Relative::Sibling.new(relative_id: 10, size: 'M').save
70
+ Relative::Sibling.new(relative_id: 10, size: 'L').save
71
+ assert relative.respond_to? :siblings_first
72
+ assert_nil relative.instance_variable_get('@cached_siblings_first')
73
+ sibling = relative.siblings_first
74
+ assert_equal 'S', sibling.size
75
+ assert_equal sibling, relative.instance_variable_get('@cached_siblings_first')
76
+ end
77
+
78
+ end
79
+
80
+ describe '::to_select_options' do
81
+
82
+ class Selectoptionable < PopulateMe::Document
83
+ field :name
84
+ field :slug
85
+ label :slug
86
+ end
87
+
88
+ class Selectpreviewable < PopulateMe::Document
89
+ set :default_attachment_class, PopulateMe::Attachment
90
+ field :name
91
+ field :img, type: :attachment, variations: [
92
+ PopulateMe::Variation.default
93
+ ]
94
+ end
95
+
96
+ before do
97
+ Selectoptionable.documents = []
98
+ Selectoptionable.new(id: '1', name: 'Joe', slug: 'joe').save
99
+ Selectoptionable.new(id: '2', name: 'William', slug: 'william').save
100
+ Selectoptionable.new(id: '3', name: 'Jack', slug: 'jack').save
101
+ Selectoptionable.new(id: '4', name: 'Averell', slug: 'averell').save
102
+ Selectpreviewable.documents = []
103
+ Selectpreviewable.new(id: '1', name: 'Project', img: 'project.jpg').save
104
+ end
105
+
106
+ after do
107
+ Selectoptionable.documents = []
108
+ end
109
+
110
+ it 'Formats all items for a select_options' do
111
+ output_proc = Selectoptionable.to_select_options
112
+ assert output_proc.is_a?(Proc)
113
+ output = output_proc.call
114
+ assert_equal 4, output.size
115
+ assert output.all?{|o| o.is_a?(Hash) and o.size==2}
116
+ assert_equal '1', output.find{|o|o.fetch(:description)=='joe'}.fetch(:value)
117
+ end
118
+
119
+ it 'Puts items in alphabetical order of their label' do
120
+ output= Selectoptionable.to_select_options.call
121
+ assert_equal({description: 'averell', value: '4'}, output[0])
122
+ end
123
+
124
+ it 'Has an option for prepending empty choice' do
125
+ output= Selectoptionable.to_select_options(allow_empty: true).call
126
+ assert_equal({description: '?', value: ''}, output[0])
127
+ assert_equal({description: 'averell', value: '4'}, output[1])
128
+ end
129
+
130
+ it 'Adds a :preview_uri when there is a thumbnail' do
131
+ output= Selectpreviewable.to_select_options.call
132
+ assert output[0].key?(:preview_uri)
133
+ assert_equal 'Project', output[0][:description]
134
+ assert_equal '1', output[0][:value]
135
+ assert_equal '/attachment/selectpreviewable/project.populate_me_thumb.jpg', output[0][:preview_uri]
136
+ end
137
+
138
+ end
139
+
140
+ describe 'Polymorphism' do
141
+
142
+ class PolyBox < PopulateMe::Document
143
+ field :first_name
144
+ field :last_name
145
+ field :middle_name, only_for: ['Middle', 'Long name']
146
+ field :nick_name, only_for: 'Funny'
147
+ field :third_name, only_for: 'Long name'
148
+ end
149
+
150
+ class NotPoly < PopulateMe::Document
151
+ field :name
152
+ relationship :images
153
+ end
154
+
155
+ class JustPoly < PopulateMe::Document
156
+ polymorphic
157
+ end
158
+
159
+ class PolyCustom < PopulateMe::Document
160
+ polymorphic type: :text, custom_option: 'Custom'
161
+ end
162
+
163
+ class PolyApplicable < PopulateMe::Document
164
+ field :name_1, only_for: 'Shape 1'
165
+ field :name_2, only_for: 'Shape 2'
166
+ field :position
167
+ end
168
+
169
+ class PolyLabel < PopulateMe::Document
170
+ polymorphic
171
+ field :name
172
+ end
173
+
174
+ class PolyRelationship < PopulateMe::Document
175
+ field :name
176
+ field :short_description, only_for: 'Chapter'
177
+ relationship :images, only_for: 'Slider'
178
+ relationship :paragraphs, only_for: 'Chapter'
179
+ end
180
+
181
+ class PolyGroup < PopulateMe::Document
182
+ only_for 'Slider' do
183
+ field :name
184
+ relationship :images
185
+ end
186
+ field :no_only_for
187
+ only_for ['Try','This'] do
188
+ field :crazy
189
+ end
190
+ field :position
191
+ end
192
+
193
+ it 'Creates a field for polymorphic type if it does not exist yet' do
194
+ assert_equal :polymorphic_type, PolyBox.fields[:polymorphic_type][:type]
195
+ assert_equal :polymorphic_type, JustPoly.fields[:polymorphic_type][:type]
196
+ assert_equal :polymorphic_type, PolyRelationship.fields[:polymorphic_type][:type]
197
+ end
198
+
199
+ it 'By default polymorphic_type is hidden and not wrapped' do
200
+ assert_equal :hidden, PolyBox.fields[:polymorphic_type][:input_attributes][:type]
201
+ refute PolyBox.fields[:polymorphic_type][:wrap]
202
+ end
203
+
204
+ it 'Does not create polymorphic type field if not required' do
205
+ assert_nil NotPoly.fields[:polymorphic_type]
206
+ end
207
+
208
+ it 'Gathers all polymorphic type unique values' do
209
+ assert_equal ['Middle', 'Long name', 'Funny'], PolyBox.fields[:polymorphic_type][:values]
210
+ assert_equal [], JustPoly.fields[:polymorphic_type][:values]
211
+ assert_equal ['Chapter', 'Slider'], PolyRelationship.fields[:polymorphic_type][:values]
212
+ end
213
+
214
+ it 'Adds or update field options for polymorphic type passed as arguments' do
215
+ assert_equal :text, PolyCustom.fields[:polymorphic_type][:type]
216
+ assert_equal 'Custom', PolyCustom.fields[:polymorphic_type][:custom_option]
217
+ end
218
+
219
+ it 'Forces only_for field option to be an Array if String' do
220
+ assert_nil PolyBox.fields[:first_name][:only_for]
221
+ assert_equal ['Middle', 'Long name'], PolyBox.fields[:middle_name][:only_for]
222
+ assert_equal ['Funny'], PolyBox.fields[:nick_name][:only_for]
223
+ assert_equal ['Slider'], PolyRelationship.relationships[:images][:only_for]
224
+ end
225
+
226
+ it 'Has a polymorphic? predicate' do
227
+ assert PolyBox.polymorphic?
228
+ refute NotPoly.polymorphic?
229
+ end
230
+
231
+ it 'Knows when a field is applicable to a polymorphic_type' do
232
+ assert NotPoly.field_applicable?(:name, nil) # Not polymorphic
233
+ assert NotPoly.field_applicable?(:name, 'Fake') # Not polymorphic
234
+ refute NotPoly.field_applicable?(:non_existing_field, nil) # Not polymorphic
235
+ assert PolyApplicable.field_applicable?(:polymorphic_type, 'Shape 1') # no only_for
236
+ assert PolyApplicable.field_applicable?(:position, 'Shape 1') # no only_for
237
+ refute PolyApplicable.field_applicable?(:name_2, 'Shape 1') # not included
238
+ assert PolyApplicable.field_applicable?(:name_2, nil) # no type set yet
239
+
240
+ assert NotPoly.new.field_applicable?(:name) # Not polymorphic
241
+ refute NotPoly.new.field_applicable?(:non_existing_field) # Not polymorphic
242
+ assert PolyApplicable.new(polymorphic_type: 'Shape 2').field_applicable?(:name_2)
243
+ refute PolyApplicable.new(polymorphic_type: 'Shape 2').field_applicable?(:name_1)
244
+ assert PolyApplicable.new.field_applicable?(:name_2) # No type set yet
245
+ assert PolyApplicable.new.field_applicable?(:name_1) # No type set yes
246
+ end
247
+
248
+ it 'Knows when a relationship is applicable to a polymorphic_type' do
249
+ assert PolyRelationship.relationship_applicable?(:images, 'Slider')
250
+ refute PolyRelationship.relationship_applicable?(:images, 'Chapter')
251
+ assert PolyRelationship.new(polymorphic_type: 'Slider').relationship_applicable?(:images)
252
+ refute PolyRelationship.new(polymorphic_type: 'Chapter').relationship_applicable?(:images)
253
+ end
254
+
255
+ it 'Ignores polymorphic_type when picking the default label_field' do
256
+ assert_equal :name, PolyLabel.label_field
257
+ end
258
+
259
+ it 'Uses groups to define only_for option for all included fields and relationships' do
260
+ assert_equal :polymorphic_type, PolyGroup.fields[:polymorphic_type][:type]
261
+ assert_equal ['Slider', 'Try', 'This'], PolyGroup.fields[:polymorphic_type][:values]
262
+ assert_equal ['Slider'], PolyGroup.fields[:name][:only_for]
263
+ assert_equal ['Slider'], PolyGroup.relationships[:images][:only_for]
264
+ assert_equal ['Try','This'], PolyGroup.fields[:crazy][:only_for]
265
+ end
266
+
267
+ it 'Group only_for option does not leak on special fields' do
268
+ refute PolyGroup.fields[:id].key?(:only_for)
269
+ refute PolyGroup.fields[:polymorphic_type].key?(:only_for)
270
+ end
271
+
272
+ it 'Group only_for option does not leak outside of their scope' do
273
+ refute PolyGroup.fields[:no_only_for].key?(:only_for)
274
+ refute PolyGroup.fields[:position].key?(:only_for)
275
+ end
276
+
277
+ end
278
+
279
+ end
280
+
@@ -0,0 +1,128 @@
1
+ require 'helper'
2
+ require 'populate_me/document'
3
+
4
+ class Person < PopulateMe::Document
5
+ field :name
6
+ field :shared, type: :boolean
7
+ field :age, type: :integer
8
+ field :salary, type: :price
9
+ field :size, type: :select, select_option: ['S','M','L']
10
+ field :categories, type: :select, select_option: ['A','B','C'], multiple: true
11
+ field :dob, type: :date
12
+ field :when, type: :datetime
13
+ end
14
+
15
+ describe PopulateMe::Document, 'Typecasting' do
16
+
17
+ parallelize_me!
18
+
19
+ let(:subject_class) { Person }
20
+ subject { subject_class.new }
21
+
22
+ describe "Value is blank or nil" do
23
+ it "Returns nil" do
24
+ assert_nil subject.typecast(:name,nil)
25
+ assert_nil subject.typecast(:name,'')
26
+ assert_nil subject.typecast(:salary,'')
27
+ assert_nil subject.typecast(:dob,'')
28
+ assert_nil subject.typecast(:when,'')
29
+ end
30
+ end
31
+
32
+ describe "Field has type :string" do
33
+ it "Returns it as-is" do
34
+ assert_equal 'Bob', subject.typecast(:name,'Bob')
35
+ assert_equal 'true', subject.typecast(:name,'true')
36
+ assert_equal '5', subject.typecast(:name,'5')
37
+ end
38
+ end
39
+
40
+ describe "Field has type :boolean" do
41
+ it "Casts as a boolean" do
42
+ assert_equal true, subject.typecast(:shared,'true')
43
+ assert_equal false, subject.typecast(:shared,'false')
44
+ end
45
+ end
46
+
47
+ describe "Field has type :integer" do
48
+ describe "Value is just an integer" do
49
+ it "Casts it as integer" do
50
+ assert_equal 42, subject.typecast(:age,'42')
51
+ end
52
+ end
53
+ describe "Value has something written at the end" do
54
+ it "Ignores it" do
55
+ assert_equal 42, subject.typecast(:age,'42 yo')
56
+ end
57
+ end
58
+ describe "Value is a float" do
59
+ it "Rounds it" do
60
+ assert_equal 42, subject.typecast(:age,'42.50')
61
+ end
62
+ end
63
+ end
64
+
65
+ describe "Field has type :price" do
66
+ describe "Value is an integer" do
67
+ it "Casts it in cents/pence (x100)" do
68
+ assert_equal 4200, subject.typecast(:salary,'42')
69
+ end
70
+ end
71
+ describe "Value is a float with 2 decimals" do
72
+ it "Casts it in cents/pence" do
73
+ assert_equal 4250, subject.typecast(:salary,'42.50')
74
+ end
75
+ end
76
+ describe "Value is a float with irregular decimals for a price" do
77
+ it "Assumes 2 digits after comma" do
78
+ assert_equal 4250, subject.typecast(:salary,'42.5')
79
+ end
80
+ end
81
+ describe "Value is prefixed or suffixed" do
82
+ it "Ignores what is not part of the price" do
83
+ assert_equal 4250, subject.typecast(:salary,'$42.5')
84
+ assert_equal 4250, subject.typecast(:salary,'42.5 Dollars')
85
+ end
86
+ end
87
+ end
88
+
89
+ describe "Field has type :select" do
90
+ it "Removes 'nil' string if multiple/array" do
91
+ assert_equal 'M', subject.typecast(:size, 'M')
92
+ assert_equal ['A','B'], subject.typecast(:categories, ['A','B'])
93
+ assert_equal ['A','B'], subject.typecast(:categories, ['nil','A','B'])
94
+ end
95
+ end
96
+
97
+ describe "Field has type :date" do
98
+ it "Parses the date with Date.parse" do
99
+ assert_equal Date.parse('10/11/1979'), subject.typecast(:dob,'10/11/1979')
100
+ end
101
+ it "Works with yyyy-mm-dd date html input format" do
102
+ assert_equal Date.parse('10/11/1979'), subject.typecast(:dob,'1979-11-10')
103
+ end
104
+ describe "Value is malformed" do
105
+ it "Returns nil" do
106
+ assert_nil subject.typecast(:dob,'10/11')
107
+ end
108
+ end
109
+ end
110
+
111
+ describe "Field has type :datetime" do
112
+ it "Splits the numbers and feed them to Time.utc" do
113
+ assert_equal Time.utc(1979,11,10,12,30,4), subject.typecast(:when,'10/11/1979 12:30:4')
114
+ end
115
+ describe "Delimiter is a dash" do
116
+ it "Replaces them with forward slash before parsing" do
117
+ assert_equal Time.utc(1979,11,10,12,30,4), subject.typecast(:when,'10-11-1979 12:30:4')
118
+ end
119
+ end
120
+ describe "Value is malformed" do
121
+ it "Returns nil" do
122
+ assert_nil subject.typecast(:when,'10/11')
123
+ assert_nil subject.typecast(:when,'10/11/1979')
124
+ end
125
+ end
126
+ end
127
+
128
+ end
@@ -0,0 +1,239 @@
1
+ require 'helper'
2
+ require 'populate_me/mongo'
3
+ require 'populate_me/grid_fs_attachment'
4
+
5
+ Mongo::Logger.logger.level = Logger::ERROR
6
+
7
+ GRIDMONGO = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'populate-me-grid-test')
8
+ GRIDDB = GRIDMONGO.database
9
+ GRIDDB.drop
10
+ PopulateMe::Mongo.set :db, GRIDDB
11
+ PopulateMe::Mongo.set :default_attachment_class, PopulateMe::GridFS
12
+ PopulateMe::GridFS.set :db, GRIDDB
13
+
14
+ describe 'PopulateMe::GridFS' do
15
+ # parallelize_me!
16
+
17
+ class GridBook < PopulateMe::Mongo
18
+ field :cover, type: :attachment, variations: [
19
+ PopulateMe::Variation.new_image_magick_job(:thumb, :gif, "-resize '300x'")
20
+ ]
21
+ field :content, type: :attachment, variations: [
22
+ PopulateMe::Variation.new(:upcase, :txt, lambda{ |src,dst|
23
+ Kernel.system "cat \"#{src}\" | tr 'a-z' 'A-Z' > \"#{dst}\""
24
+ })
25
+ ]
26
+ end
27
+
28
+ before do
29
+ GRIDDB.drop
30
+ end
31
+
32
+ # Utils
33
+
34
+ it 'Returns URL with url_prefix' do
35
+ book = GridBook.new cover: "candy.jpg"
36
+ assert_equal '/attachment/candy.jpg', book.attachment(:cover).url
37
+ assert_equal '/attachment/candy.thumb.gif', book.attachment(:cover).url(:thumb)
38
+ end
39
+
40
+ it 'Has nil URL when field is blank' do
41
+ book = GridBook.new
42
+ assert_nil book.attachment(:cover).url
43
+ end
44
+
45
+ it 'Has location root without attachee prefix' do
46
+ book = GridBook.new
47
+ refute_match book.attachment(:cover).attachee_prefix, book.attachment(:cover).location_root
48
+ end
49
+
50
+ # Create
51
+
52
+ it "Saves attachments on create with variations" do
53
+ book = GridBook.new
54
+
55
+ file = Tempfile.new('foo')
56
+ file.write('hello')
57
+ file.rewind
58
+
59
+ assert_equal 0, GRIDDB['fs.files'].count
60
+ field_value = book.attachment(:content).create({
61
+ tempfile: file,
62
+ filename: 'story.txt',
63
+ type: 'text/plain'
64
+ })
65
+ assert_equal 2, GRIDDB['fs.files'].count
66
+ assert_equal 'grid-book/story.txt', field_value
67
+
68
+ gridfile = GRIDDB.fs.find(filename: 'grid-book/story.txt').first
69
+ assert_equal 'text/plain', gridfile['contentType']
70
+ assert_equal 'grid-book', gridfile['metadata']['parent_collection']
71
+ GRIDDB.fs.open_download_stream(gridfile['_id']) do |stream|
72
+ assert_equal 'hello', stream.read
73
+ end
74
+
75
+ vargridfile = GRIDDB.fs.find(filename: 'grid-book/story.upcase.txt').first
76
+ assert_equal 'text/plain', vargridfile['contentType']
77
+ assert_equal 'grid-book', vargridfile['metadata']['parent_collection']
78
+ GRIDDB.fs.open_download_stream(vargridfile['_id']) do |stream|
79
+ assert_equal 'HELLO', stream.read
80
+ end
81
+
82
+ file.close
83
+ file.unlink
84
+ end
85
+
86
+ it 'Does not create 2 files with the same name' do
87
+ file = Tempfile.new('foo')
88
+
89
+ book = GridBook.new
90
+
91
+ field_value = book.attachment(:content).create({
92
+ tempfile: file,
93
+ filename: 'story.txt',
94
+ type: 'text/plain'
95
+ })
96
+
97
+ assert_equal 'grid-book/story.txt', field_value
98
+
99
+ field_value = book.attachment(:content).create({
100
+ tempfile: file,
101
+ filename: 'story.txt',
102
+ type: 'text/plain'
103
+ })
104
+
105
+ assert_equal 'grid-book/story-1.txt', field_value
106
+
107
+ field_value = book.attachment(:content).create({
108
+ tempfile: file,
109
+ filename: 'story.txt',
110
+ type: 'text/plain'
111
+ })
112
+
113
+ assert_equal 'grid-book/story-2.txt', field_value
114
+
115
+ file.close
116
+ file.unlink
117
+ end
118
+
119
+ # Delete
120
+
121
+ it 'Is deletable when field is not blank' do
122
+ book = GridBook.new cover: "candy.jpg"
123
+ assert book.attachment(:cover).deletable?
124
+ end
125
+
126
+ it 'Is not deletable when field is blank' do
127
+ book = GridBook.new
128
+ refute book.attachment(:cover).deletable?
129
+ end
130
+
131
+ it 'Deletes all attachments' do
132
+ file = Tempfile.new('foo')
133
+
134
+ book = GridBook.new
135
+
136
+ field_value = book.attachment(:content).create({
137
+ tempfile: file,
138
+ filename: 'story.txt',
139
+ type: 'text/plain'
140
+ })
141
+ book.content = field_value
142
+
143
+ refute_nil GRIDDB.fs.find(filename: 'grid-book/story.txt').first
144
+ refute_nil GRIDDB.fs.find(filename: 'grid-book/story.upcase.txt').first
145
+
146
+ book.attachment(:content).delete_all
147
+
148
+ assert_nil GRIDDB.fs.find(filename: 'grid-book/story.txt').first
149
+ assert_nil GRIDDB.fs.find(filename: 'grid-book/story.upcase.txt').first
150
+
151
+ file.close
152
+ file.unlink
153
+ end
154
+
155
+ it 'Deletes one attachment at a time' do
156
+ file = Tempfile.new('foo')
157
+
158
+ book = GridBook.new
159
+
160
+ field_value = book.attachment(:content).create({
161
+ tempfile: file,
162
+ filename: 'story.txt',
163
+ type: 'text/plain'
164
+ })
165
+ book.content = field_value
166
+
167
+ refute_nil GRIDDB.fs.find(filename: 'grid-book/story.txt').first
168
+ refute_nil GRIDDB.fs.find(filename: 'grid-book/story.upcase.txt').first
169
+
170
+ book.attachment(:content).delete
171
+
172
+ assert_nil GRIDDB.fs.find(filename: 'grid-book/story.txt').first
173
+ refute_nil GRIDDB.fs.find(filename: 'grid-book/story.upcase.txt').first
174
+
175
+ book.attachment(:content).delete :upcase
176
+
177
+ assert_nil GRIDDB.fs.find(filename: 'grid-book/story.upcase.txt').first
178
+
179
+ file.close
180
+ file.unlink
181
+ end
182
+
183
+ # Update
184
+
185
+ it 'Deletes previous attachment when saving a new one' do
186
+ file = Tempfile.new('foo')
187
+ file.write('hello')
188
+ file.rewind
189
+
190
+ book = GridBook.new
191
+
192
+ field_value = book.attachment(:content).create({
193
+ tempfile: file,
194
+ filename: 'story.txt',
195
+ type: 'text/plain'
196
+ })
197
+ book.content = field_value
198
+
199
+ refute_nil GRIDDB.fs.find(filename: 'grid-book/story.txt').first
200
+ refute_nil GRIDDB.fs.find(filename: 'grid-book/story.upcase.txt').first
201
+
202
+ file.rewind
203
+ file.write('world')
204
+ file.rewind
205
+
206
+ field_value = book.attachment(:content).create({
207
+ tempfile: file,
208
+ filename: 'history.md',
209
+ type: 'text/markdown'
210
+ })
211
+ book.content = field_value
212
+
213
+ assert_nil GRIDDB.fs.find(filename: 'grid-book/story.txt').first
214
+ assert_nil GRIDDB.fs.find(filename: 'grid-book/story.upcase.txt').first
215
+ refute_nil GRIDDB.fs.find(filename: 'grid-book/history.md').first
216
+ refute_nil GRIDDB.fs.find(filename: 'grid-book/history.upcase.txt').first
217
+
218
+ gridfile = GRIDDB.fs.find(filename: 'grid-book/history.md').first
219
+ assert_equal 'text/markdown', gridfile['contentType']
220
+ assert_equal 'grid-book', gridfile['metadata']['parent_collection']
221
+ GRIDDB.fs.open_download_stream(gridfile['_id']) do |stream|
222
+ assert_equal 'world', stream.read
223
+ end
224
+
225
+ gridfile = GRIDDB.fs.find(filename: 'grid-book/history.upcase.txt').first
226
+ assert_equal 'text/plain', gridfile['contentType']
227
+ assert_equal 'grid-book', gridfile['metadata']['parent_collection']
228
+ GRIDDB.fs.open_download_stream(gridfile['_id']) do |stream|
229
+ assert_equal 'WORLD', stream.read
230
+ end
231
+
232
+ file.close
233
+ file.unlink
234
+ end
235
+
236
+ end
237
+
238
+ GRIDDB.drop
239
+