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,167 @@
1
+ require 'helper'
2
+ require 'populate_me/attachment'
3
+ require 'populate_me/document'
4
+
5
+ describe PopulateMe::Attachment do
6
+
7
+ parallelize_me!
8
+
9
+ class NiceAttachment < PopulateMe::Attachment
10
+ set :url_prefix, '/under'
11
+ end
12
+
13
+ class NiceIllustration < PopulateMe::Document
14
+ set :default_attachment_class, NiceAttachment
15
+ field :name
16
+ field :image, type: :attachment, variations: [
17
+ PopulateMe::Variation.new_image_magick_job(:negated, :jpg, '-negate'),
18
+ PopulateMe::Variation.new_image_magick_job(:negated_gif, :gif, '-negate')
19
+ ]
20
+ field :pdf, type: :attachment
21
+ end
22
+
23
+ subject { NiceAttachment.new(document, field) }
24
+ let(:described_class) { NiceAttachment }
25
+ let(:document) { NiceIllustration.new(name: 'Painting', image: 'myimage.jpg') }
26
+ let(:field) { :image }
27
+
28
+ describe "Kept attributes" do
29
+ it "Keeps document and field as attributes" do
30
+ assert_equal document, subject.document
31
+ assert_equal field, subject.field
32
+ end
33
+ end
34
+
35
+ it "Delegates settings to its class" do
36
+ assert_equal '/under', described_class.settings.url_prefix
37
+ assert_equal '/under', subject.settings.url_prefix
38
+ end
39
+
40
+ describe "#field_value" do
41
+ it "Gets the field value of its document" do
42
+ assert_equal 'myimage.jpg', subject.field_value
43
+ end
44
+ end
45
+
46
+ describe "#field_options" do
47
+ it "Get options of the field" do
48
+ assert_equal :attachment, subject.field_options[:type]
49
+ end
50
+ end
51
+
52
+ describe "#variations" do
53
+ it "Gets variations for the field" do
54
+ variations = subject.variations
55
+ assert_equal 2, variations.size
56
+ assert_equal :negated, variations[0].name
57
+ end
58
+ it "Returns an empty array when there is none (as opposed to nil)" do
59
+ variations = NiceAttachment.new(document, :pdf).variations
60
+ assert_instance_of Array, variations
61
+ assert_equal 0, variations.size
62
+ end
63
+ end
64
+
65
+ describe "#attachee_prefix" do
66
+ it "Returns the dasherized version of its document class" do
67
+ assert_equal 'nice-illustration', subject.attachee_prefix
68
+ end
69
+ end
70
+
71
+ describe "#location_root" do
72
+ it "It concatenates the root, url_prefix and class name dasherized" do
73
+ assert_equal "#{subject.settings.root}/under/nice-illustration", subject.location_root
74
+ end
75
+ end
76
+
77
+ describe "#location" do
78
+ it "Combines location root and the field value" do
79
+ assert_equal "#{subject.location_root}/myimage.jpg", subject.location
80
+ end
81
+ end
82
+
83
+ describe "#deletable?" do
84
+ let(:fake) { File.expand_path(__FILE__) }
85
+ def setup_stubs
86
+ subject.stub(:field_value, path) do
87
+ subject.stub(:location, loc) do
88
+ yield
89
+ end
90
+ end
91
+ end
92
+ let(:path) { fake }
93
+ let(:loc) { fake }
94
+ describe "When there is something to delete" do
95
+ it "is true" do
96
+ setup_stubs do
97
+ assert subject.deletable?
98
+ end
99
+ end
100
+ end
101
+ describe "When field is blank" do
102
+ let(:path) { '' }
103
+ it "is false" do
104
+ setup_stubs do
105
+ refute subject.deletable?
106
+ end
107
+ end
108
+ end
109
+ describe "When the file does not exist" do
110
+ let(:loc) { 'non-existing.path' }
111
+ it "is false" do
112
+ setup_stubs do
113
+ refute subject.deletable?
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ describe "#delete" do
120
+ it "Performs if deletable" do
121
+ subject.stub(:deletable?, true) do
122
+ assert_receive(subject, :perform_delete, nil, [:negated]) do
123
+ subject.delete(:negated)
124
+ end
125
+ end
126
+ end
127
+ it "Does not perform if not deletable" do
128
+ subject.stub(:deletable?, false) do
129
+ refute_receive(subject, :perform_delete) do
130
+ subject.delete(:thumb)
131
+ end
132
+ end
133
+ end
134
+ it "Can delete all variations in one go" do
135
+ # Most used
136
+ subject.stub(:deletable?, true) do
137
+ mocked_meth = Minitest::Mock.new
138
+ mocked_meth.expect(:call, nil, [:original])
139
+ mocked_meth.expect(:call, nil, [:negated])
140
+ mocked_meth.expect(:call, nil, [:negated_gif])
141
+ subject.stub :perform_delete, mocked_meth do
142
+ subject.delete_all
143
+ end
144
+ assert mocked_meth.verify, "Expected #{subject.inspect} to call :perform_delete for all variations."
145
+ end
146
+ end
147
+ end
148
+
149
+ describe "#perform_delete" do
150
+ let(:fake_path) { "/path/to/file" }
151
+ it "Deletes the file at self.location" do
152
+ subject.stub(:location, fake_path) do
153
+ assert_receive(FileUtils, :rm, nil, [fake_path]) do
154
+ subject.perform_delete
155
+ end
156
+ end
157
+ end
158
+ end
159
+
160
+ describe "#create" do
161
+ it "Calls delete before performing" do
162
+
163
+ end
164
+ end
165
+
166
+ end
167
+
@@ -0,0 +1,128 @@
1
+ require 'helper'
2
+ require 'populate_me/document'
3
+
4
+ module Car
5
+ class RearviewMirror < PopulateMe::Document
6
+ attr_accessor :my_label_field
7
+ end
8
+ end
9
+
10
+ describe PopulateMe::Document do
11
+
12
+ parallelize_me!
13
+
14
+ let(:subject_class) { Car::RearviewMirror }
15
+ subject { subject_class.new }
16
+
17
+ describe '#inspect' do
18
+ it 'Returns a classic ruby inspect to object with an underlying Hash' do
19
+ test_hash = { a: 1 }
20
+ subject.stub(:to_h, test_hash) do
21
+ assert_equal "#<#{subject_class.name}:#{test_hash.inspect}>", subject.inspect
22
+ end
23
+ end
24
+ end
25
+
26
+ describe '#to_s' do
27
+ it 'Defaults to class name and ID' do
28
+ obj = subject_class.new id: '42'
29
+ assert_equal "#{subject_class} 42", obj.to_s
30
+ end
31
+ describe "Has a label field" do
32
+ describe "And the field is not blank" do
33
+ it "Delegates to the label_field" do
34
+ subject_class.stub :label_field, :my_label_field do
35
+ subject.stub :my_label_field, 'my label' do
36
+ assert_equal 'my label', subject.to_s
37
+ end
38
+ end
39
+ end
40
+ it 'Does not pass a reference that can be modified' do
41
+ subject_class.stub :label_field, :my_label_field do
42
+ subject.stub :my_label_field, 'my label' do
43
+ assert_equal 'my label', subject.to_s
44
+ var = subject.to_s
45
+ var << 'BOOM'
46
+ assert_equal 'my label', subject.to_s
47
+ end
48
+ end
49
+ end
50
+ end
51
+ describe "And the field is blank" do
52
+ it 'Defaults to class name and ID' do
53
+ subject_class.stub :label_field, :my_label_field do
54
+ obj = subject_class.new id: '42'
55
+ subject.stub :my_label_field, '' do
56
+ assert_equal "#{subject_class} 42", obj.to_s
57
+ end
58
+ subject.stub :my_label_field, nil do
59
+ assert_equal "#{subject_class} 42", obj.to_s
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ describe 'Class' do
68
+
69
+ describe '::to_s' do
70
+ it 'Returns a human friendly classname including parent modules' do
71
+ assert_equal 'Car Rearview Mirror', subject_class.to_s
72
+ end
73
+ end
74
+
75
+ describe '::to_s_short' do
76
+ it 'Returns a human friendly classname' do
77
+ assert_equal 'Rearview Mirror', subject_class.to_s_short
78
+ end
79
+ end
80
+
81
+ describe '::cast' do
82
+ let(:docs) do
83
+ [{my_label_field: 'A'},{my_label_field: 'B'}]
84
+ end
85
+ it 'Can return a single object from the block' do
86
+ subject_class.stub :documents, docs do
87
+ out = subject_class.cast{ |c| c.documents[0] }
88
+ assert_equal subject_class, out.class
89
+ end
90
+ end
91
+ it 'Can return a list of objects from the block' do
92
+ subject_class.stub :documents, docs do
93
+ out = subject_class.cast{ |c| c.documents }
94
+ assert_equal subject_class, out[0].class
95
+ assert_equal subject_class, out[1].class
96
+ end
97
+ end
98
+ describe 'The block returns nil' do
99
+ it 'Returns nil as well' do
100
+ subject_class.stub :documents, docs do
101
+ assert_nil subject_class.cast{|c|nil}
102
+ end
103
+ end
104
+ end
105
+ describe 'The block does not return the right thing' do
106
+ it 'Raises type error' do
107
+ subject_class.stub :documents, docs do
108
+ assert_raises(TypeError) do
109
+ subject_class.cast{|c|3}
110
+ end
111
+ end
112
+ end
113
+ end
114
+ describe 'No argument is given to the block' do
115
+ it 'Calls the block in the class context' do
116
+ subject_class.stub :documents, docs do
117
+ out = subject_class.cast{ documents }
118
+ assert_equal subject_class, out[0].class
119
+ assert_equal subject_class, out[1].class
120
+ end
121
+ end
122
+ end
123
+ end
124
+
125
+ end
126
+
127
+ end
128
+
@@ -0,0 +1,221 @@
1
+ require 'helper'
2
+ require 'populate_me/document'
3
+ require 'populate_me/attachment'
4
+
5
+ describe PopulateMe::Document, 'AdminAdapter' do
6
+
7
+ parallelize_me!
8
+
9
+ describe '::admin_image_field' do
10
+ describe 'When there is no image' do
11
+ class AdaptedNoImage < PopulateMe::Document
12
+ field :name
13
+ end
14
+ it 'Returns nil' do
15
+ assert_nil AdaptedNoImage.admin_image_field
16
+ end
17
+ end
18
+ describe 'When there is an image but not with the right variation' do
19
+ class AdaptedImageNoVar < PopulateMe::Document
20
+ field :name
21
+ field :thumbnail1, type: :attachment, class_name: PopulateMe::Attachment
22
+ field :thumbnail2, type: :attachment, variations:[
23
+ PopulateMe::Variation.new_image_magick_job(:small,:jpg,'-negate')
24
+ ], class_name: PopulateMe::Attachment
25
+ end
26
+ it 'Returns nil' do
27
+ assert_nil AdaptedImageNoVar.admin_image_field
28
+ end
29
+ end
30
+ describe 'When there is an image and the right variation' do
31
+ class AdaptedImageVar < PopulateMe::Document
32
+ field :name
33
+ field :thumbnail1, type: :attachment, class_name: PopulateMe::Attachment
34
+ field :thumbnail2, type: :attachment, variations:[
35
+ PopulateMe::Variation.new_image_magick_job(:populate_me_thumb,:jpg,'-negate')
36
+ ], class_name: PopulateMe::Attachment
37
+ end
38
+ it 'Returns the field' do
39
+ assert_equal :thumbnail2, AdaptedImageVar.admin_image_field
40
+ end
41
+ end
42
+ end
43
+
44
+ describe '::to_admin_list' do
45
+ class PolyAdapted < PopulateMe::Document
46
+ polymorphic values: ['Shape 1', 'Shape 2']
47
+ end
48
+ class NotPolyAdapted < PopulateMe::Document
49
+ end
50
+ it 'Contains polymorphic_type values and predicate if polymorphic' do
51
+ assert PolyAdapted.to_admin_list[:is_polymorphic]
52
+ assert_equal ['Shape 1', 'Shape 2'], PolyAdapted.to_admin_list[:polymorphic_type_values]
53
+ refute NotPolyAdapted.to_admin_list[:is_polymorphic]
54
+ assert_nil NotPolyAdapted.to_admin_list[:polymorphic_type_values]
55
+ end
56
+ end
57
+
58
+ describe '#to_admin_list_item' do
59
+ class ContentTitle < PopulateMe::Document
60
+ field :content
61
+ end
62
+ class PolyListItem < PopulateMe::Document
63
+ field :name
64
+ relationship :images, only_for: 'Slider'
65
+ relationship :paragraphs, only_for: 'Chapter'
66
+ end
67
+ it 'Sets ID as a string version' do
68
+ doc = ContentTitle.new id: 3
69
+ assert_equal '3', doc.to_admin_list_item[:id]
70
+ end
71
+ describe 'When title is long' do
72
+ it 'Is truncated' do
73
+ doc = ContentTitle.new
74
+ doc.content = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
75
+ title = doc.to_admin_list_item[:title]
76
+ refute_equal doc.content, title
77
+ assert_match title.sub(/\.\.\.$/, ''), doc.content
78
+ assert_operator title.length, :<=, doc.content.length
79
+ end
80
+ end
81
+ describe 'When title is short enough' do
82
+ it 'Does not truncate' do
83
+ doc = ContentTitle.new
84
+ doc.content = 'Hello'
85
+ title = doc.to_admin_list_item[:title]
86
+ assert_equal doc.content, title
87
+ end
88
+ end
89
+ describe 'Polymorphism' do
90
+ it 'Only keeps in the local menu applicable relationships' do
91
+ doc = PolyListItem.new(polymorphic_type: 'Slider')
92
+ list_item = doc.to_admin_list_item request: Struct.new(:script_name).new('/admin')
93
+ assert_equal 1, list_item[:local_menu].size
94
+ assert_equal 'Images', list_item[:local_menu][0][:title]
95
+ end
96
+ end
97
+ end
98
+
99
+ describe '::admin_find ::admin_find_first' do
100
+
101
+ class FindablePerson < PopulateMe::Document
102
+ field :first_name
103
+ field :last_name
104
+ end
105
+
106
+ before do
107
+ FindablePerson.documents = []
108
+ FindablePerson.new(first_name: 'Bobby', last_name: 'Peru').save
109
+ FindablePerson.new(first_name: 'John', last_name: 'Doe').save
110
+ FindablePerson.new(first_name: 'John', last_name: 'Turturo').save
111
+ end
112
+
113
+ it 'Finds everything' do
114
+ people = FindablePerson.admin_find
115
+ assert_equal Array, people.class
116
+ assert_equal 3, people.size
117
+ assert_equal FindablePerson.documents, people
118
+ end
119
+
120
+ it 'Finds with query' do
121
+ people = FindablePerson.admin_find query: {first_name: 'John'}
122
+ assert_equal Array, people.class
123
+ assert_equal 2, people.size
124
+ assert_equal 'Doe', people[0].last_name
125
+ end
126
+
127
+ it 'Finds first' do
128
+ person = FindablePerson.admin_find_first
129
+ assert_equal 'Bobby', person.first_name
130
+ end
131
+
132
+ it 'Finds first with query' do
133
+ person = FindablePerson.admin_find_first query: {first_name: 'John'}
134
+ assert_equal 'Doe', person.last_name
135
+ end
136
+
137
+ end
138
+
139
+ describe '::admin_distinct' do
140
+
141
+ class Distinction < PopulateMe::Document
142
+ attr_accessor :title, :age
143
+ end
144
+
145
+ before do
146
+ Distinction.documents = []
147
+ end
148
+
149
+ it 'Can list all distinct values' do
150
+ Distinction.new(title: 'Lord').save
151
+ Distinction.new(title: 'Lord').save
152
+ Distinction.new.save
153
+ Distinction.new(title: 'Chevalier').save
154
+ Distinction.new(title: 'Baron').save
155
+ Distinction.new(title: 'Baron').save
156
+ result = Distinction.admin_distinct :title
157
+ assert_instance_of Array, result
158
+ assert_equal 3, result.size
159
+ assert_includes result, 'Lord'
160
+ assert_includes result, 'Chevalier'
161
+ assert_includes result, 'Baron'
162
+ end
163
+
164
+ it 'Can list all distinct values for a specific selector' do
165
+ Distinction.new(title: 'Chevalier', age: 33).save
166
+ Distinction.new(title: 'Chevalier', age: 34).save
167
+ Distinction.new(title: 'Baron', age: 35).save
168
+ Distinction.new(title: 'Baron', age: 36).save
169
+ result = Distinction.admin_distinct :age, query: {title: 'Baron'}
170
+ assert_instance_of Array, result
171
+ assert_equal 2, result.size
172
+ assert_includes result, 35
173
+ assert_includes result, 36
174
+ end
175
+
176
+ end
177
+
178
+ describe '#to_admin_form' do
179
+ class PolyForm < PopulateMe::Document
180
+ field :name
181
+ field :image, only_for: 'Image'
182
+ field :title, only_for: 'Article'
183
+ field :content, only_for: 'Article'
184
+ field :position
185
+ end
186
+ class NotPolyForm < PopulateMe::Document
187
+ field :name
188
+ end
189
+ it 'Sets page_title according to label_field or class name when new' do
190
+ obj = NotPolyForm.new
191
+ form = obj.to_admin_form
192
+ assert_equal 'New Not Poly Form', form[:page_title]
193
+ obj = NotPolyForm.new name: "No Poly"
194
+ obj._is_new = false
195
+ form = obj.to_admin_form
196
+ assert_equal 'No Poly', form[:page_title]
197
+ end
198
+ it 'Only has fields for the current polymorphic type' do
199
+ obj = PolyForm.new polymorphic_type: 'Article'
200
+ form = obj.to_admin_form
201
+ assert_nil form[:fields].find{|f| f[:field_name]==:image}
202
+ refute_nil form[:fields].find{|f| f[:field_name]==:title}
203
+ refute_nil form[:fields].find{|f| f[:field_name]==:content}
204
+ end
205
+ it 'Works when not polymorphic' do
206
+ obj = NotPolyForm.new
207
+ form = obj.to_admin_form
208
+ refute_nil form[:fields].find{|f| f[:field_name]==:name}
209
+ end
210
+ it 'Includes the polymorphic_type at top level' do
211
+ obj = PolyForm.new polymorphic_type: 'Article'
212
+ form = obj.to_admin_form
213
+ assert_equal 'Article', form[:polymorphic_type]
214
+ obj = PolyForm.new
215
+ form = obj.to_admin_form
216
+ assert_nil form[:polymorphic_type]
217
+ end
218
+ end
219
+
220
+ end
221
+