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,151 @@
1
+ require 'helper'
2
+ require 'populate_me/document'
3
+
4
+ class Hamburger < PopulateMe::Document
5
+ attr_accessor :taste
6
+
7
+ register_callback :layers, :bread
8
+ register_callback :layers, :cheese
9
+ register_callback :layers do
10
+ self.taste
11
+ end
12
+
13
+ register_callback :after_cook, :add_salad
14
+ register_callback :after_cook do
15
+ taste << ' with more cheese'
16
+ end
17
+ def add_salad
18
+ taste << ' with salad'
19
+ end
20
+
21
+ register_callback :after_eat do
22
+ taste << ' stomach'
23
+ end
24
+ register_callback :after_eat, prepend: true do
25
+ taste << ' my'
26
+ end
27
+ register_callback :after_eat, :prepend_for, prepend: true
28
+ def prepend_for
29
+ taste << ' for'
30
+ end
31
+
32
+ before :digest do
33
+ taste << ' taste'
34
+ end
35
+ before :digest, :add_dot
36
+ before :digest, prepend: true do
37
+ taste << ' the'
38
+ end
39
+ before :digest, :prepend_was, prepend: true
40
+ after :digest do
41
+ taste << ' taste'
42
+ end
43
+ after :digest, :add_dot
44
+ after :digest, prepend: true do
45
+ taste << ' the'
46
+ end
47
+ after :digest, :prepend_was, prepend: true
48
+ def add_dot; taste << '.'; end
49
+ def prepend_was; taste << ' was'; end
50
+
51
+ before :argument do |name|
52
+ taste << " #{name}"
53
+ end
54
+ after :argument, :after_with_arg
55
+ def after_with_arg(name)
56
+ taste << " #{name}"
57
+ end
58
+ end
59
+
60
+ describe PopulateMe::Document, 'Callbacks' do
61
+
62
+ parallelize_me!
63
+
64
+ let(:subject_class) { Hamburger }
65
+ subject { subject_class.new taste: 'good' }
66
+ let(:layers_callbacks) { subject_class.callbacks[:layers] }
67
+
68
+ it 'Registers callbacks as symbols or blocks' do
69
+ assert_equal 3, layers_callbacks.size
70
+ assert_equal :bread, layers_callbacks[0]
71
+ assert_equal :cheese, layers_callbacks[1]
72
+ assert_equal 'good', subject.instance_eval(&layers_callbacks[2])
73
+ end
74
+
75
+ it 'Executes symbol or block callbacks' do
76
+ subject.exec_callback('after_cook')
77
+ assert_equal 'good with salad with more cheese', subject.taste
78
+ end
79
+
80
+ it 'Does not raise if executing a callback which does not exist' do
81
+ subject.exec_callback(:after_burn)
82
+ assert_equal 'good', subject.taste
83
+ end
84
+
85
+ it 'Has an option to prepend when registering callbacks' do
86
+ subject.exec_callback(:after_eat)
87
+ assert_equal 'good for my stomach', subject.taste
88
+ end
89
+
90
+ it 'Has callbacks shortcut for before_ prefix' do
91
+ subject.exec_callback(:before_digest)
92
+ assert_equal 'good was the taste.', subject.taste
93
+ end
94
+ it 'Has callbacks shortcut for after_ prefix' do
95
+ subject.exec_callback(:after_digest)
96
+ assert_equal 'good was the taste.', subject.taste
97
+ end
98
+
99
+ it 'Can pass the callback name as an argument to the callback method' do
100
+ subject.exec_callback(:before_argument)
101
+ assert_equal 'good before_argument', subject.taste
102
+ subject.exec_callback(:after_argument)
103
+ assert_equal 'good before_argument after_argument', subject.taste
104
+ end
105
+
106
+ describe '#ensure_position' do
107
+
108
+ class RaceCar < PopulateMe::Document
109
+ field :name
110
+ field :team
111
+ field :position_by_team, type: :position, scope: :team
112
+ position_field
113
+ end
114
+
115
+ before do
116
+ RaceCar.documents = []
117
+ end
118
+
119
+ it 'Does not change position if already set' do
120
+ RaceCar.new(name: 'racer3', position: 3, team: 'A', position_by_team: 14).save
121
+ car = RaceCar.new(name: 'racer1', position: 12, team: 'A', position_by_team: 41)
122
+ car.ensure_position
123
+ assert_equal 12, car.position
124
+ assert_equal 41, car.position_by_team
125
+ end
126
+
127
+ it 'Sets position fields to last position' do
128
+ RaceCar.new(name: 'racer1', position: 1, team: 'A', position_by_team: 41).save
129
+ RaceCar.new(name: 'racer2', position: 2, team: 'B', position_by_team: 12).save
130
+ RaceCar.new(name: 'racer3', position: 3, team: 'B', position_by_team: 14).save
131
+ car = RaceCar.new(team: 'B').ensure_position
132
+ assert_equal 4, car.position
133
+ assert_equal 15, car.position_by_team
134
+ end
135
+
136
+ it 'Returns 0 when first document in scope' do
137
+ assert_equal 0, RaceCar.new.ensure_position.position
138
+ RaceCar.new(name: 'racer1', position: 1, team: 'A', position_by_team: 41).save
139
+ assert_equal 0, RaceCar.new(team: 'B').ensure_position.position_by_team
140
+ end
141
+
142
+ it "Ensures position before creating new document" do
143
+ car = RaceCar.new
144
+ car.save
145
+ assert_equal 0, car.position
146
+ end
147
+
148
+ end
149
+
150
+ end
151
+
@@ -0,0 +1,247 @@
1
+ require 'helper'
2
+ require 'populate_me/document'
3
+ require 'populate_me/attachment'
4
+
5
+ class Outcasted < PopulateMe::Document
6
+ set :default_attachment_class, PopulateMe::Attachment
7
+
8
+ field :name
9
+ field :size, type: :select, select_options: [
10
+ {description: 'small', value: 's'},
11
+ {description: 'medium', value: 'm'},
12
+ {description: 'large', value: 'l'}
13
+ ]
14
+ field :availability, type: :select, select_options: [
15
+ {description: 'Available', value: 'yes', preview_uri: 'http://www.example.org/yes.jpg' },
16
+ {description: 'On offer', value: 'almost', preview_uri: 'http://www.example.org/almost.jpg' },
17
+ {description: 'Sold', value: 'no', preview_uri: 'http://www.example.org/no.jpg' }
18
+ ]
19
+ field :tags, type: :select, select_options: ['art','sport','science'], multiple: true
20
+ field :related_properties, type: :select, select_options: ['prop1','prop2','prop3'], multiple: true
21
+ field :pdf, type: :attachment
22
+ field :authors, type: :list
23
+ field :weirdo, type: :strange
24
+ field :price, type: :price
25
+
26
+ def get_size_options
27
+ [
28
+ [:small, :s],
29
+ [:medium, :m],
30
+ [:large, :l]
31
+ ]
32
+ end
33
+
34
+ end
35
+
36
+ class Outcasted::Author < PopulateMe::Document
37
+ field :name
38
+ end
39
+
40
+ describe PopulateMe::Document, 'Outcasting' do
41
+
42
+ parallelize_me!
43
+
44
+ describe '#outcast' do
45
+
46
+ it 'Keeps the original info unchanged' do
47
+ original = Outcasted.fields[:name]
48
+ output = Outcasted.new.outcast(:name, original, {input_name_prefix: 'data'})
49
+ refute original.equal?(output)
50
+ end
51
+
52
+ it 'Adds input_name_prefix to input_name' do
53
+ original = Outcasted.fields[:name]
54
+ output = Outcasted.new.outcast(:name, original, {input_name_prefix: 'data'})
55
+ assert_equal 'data[name]', output[:input_name]
56
+ end
57
+
58
+ it 'Sets input_value to the field value' do
59
+ original = Outcasted.fields[:name]
60
+ outcasted = Outcasted.new
61
+ outcasted.name = 'Thomas'
62
+ output = outcasted.outcast(:name, original, {input_name_prefix: 'data'})
63
+ assert_equal 'Thomas', output[:input_value]
64
+ end
65
+
66
+ end
67
+
68
+ describe '#outcast_list' do
69
+
70
+ it 'Has no value and an empty list of items when list is empty' do
71
+ original = Outcasted.fields[:authors]
72
+ output = Outcasted.new.outcast(:authors, original, {input_name_prefix: 'data'})
73
+ assert_nil output[:input_value]
74
+ assert_equal [], output[:items]
75
+ end
76
+
77
+ it 'Nests the items in the list with their own nested prefix' do
78
+ original = Outcasted.fields[:authors]
79
+ outcasted = Outcasted.new
80
+ outcasted.authors.push(Outcasted::Author.new(name: 'Bob'))
81
+ outcasted.authors.push(Outcasted::Author.new(name: 'Mould'))
82
+ output = outcasted.outcast(:authors, original, {input_name_prefix: 'data'})
83
+ assert_nil output[:input_value]
84
+ assert_equal 2, output[:items].size
85
+ first_item = output[:items][0][:fields]
86
+ assert_equal 'data[authors][][name]', first_item[1][:input_name]
87
+ assert_equal 'Bob', first_item[1][:input_value]
88
+ end
89
+
90
+ end
91
+
92
+ describe '#outcast_select' do
93
+
94
+ def formated_options? original, output
95
+ assert_equal [ {description: 'small', value: 's'}, {description: 'medium', value: 'm'}, {description: 'large', value: 'l'} ], output[:select_options]
96
+ refute original[:select_options].equal?(output[:select_options])
97
+ assert output[:select_options].all?{|o|!o[:selected]}
98
+ end
99
+
100
+ it 'Leaves the options as they are when they are already formated' do
101
+ original = Outcasted.fields[:size]
102
+ output = Outcasted.new.outcast(:size, original, {input_name_prefix: 'data'})
103
+ formated_options?(original, output)
104
+ end
105
+
106
+ it 'Can have more fields when they are already formated' do
107
+ # Mainly for adding a preview_uri but also future tweaks
108
+ original = Outcasted.fields[:availability]
109
+ output = Outcasted.new.outcast(:availability, original, {input_name_prefix: 'data'})
110
+ assert(output[:select_options].all?{|o| o.key?(:preview_uri)})
111
+ last = output[:select_options].last
112
+ assert_equal 'Sold', last[:description]
113
+ assert_equal 'no', last[:value]
114
+ assert_equal 'http://www.example.org/no.jpg', last[:preview_uri]
115
+ end
116
+
117
+ it 'Formats the options when it is a 2 strings array' do
118
+ original = Outcasted.fields[:size].dup
119
+ original[:select_options] = [
120
+ [:small, :s],
121
+ [:medium, :m],
122
+ [:large, :l]
123
+ ]
124
+ output = Outcasted.new.outcast(:size, original, {input_name_prefix: 'data'})
125
+ formated_options?(original, output)
126
+ end
127
+
128
+ it 'Formats the options when they come from a proc' do
129
+ original = Outcasted.fields[:size].dup
130
+ original[:select_options] = proc{[
131
+ [:small, :s],
132
+ [:medium, :m],
133
+ [:large, :l]
134
+ ]}
135
+ output = Outcasted.new.outcast(:size, original, {input_name_prefix: 'data'})
136
+ formated_options?(original, output)
137
+ end
138
+
139
+ it 'Formats the options when they come from a symbol' do
140
+ original = Outcasted.fields[:size].dup
141
+ original[:select_options] = :get_size_options
142
+ output = Outcasted.new.outcast(:size, original, {input_name_prefix: 'data'})
143
+ formated_options?(original, output)
144
+ end
145
+
146
+ it 'Formats options when it comes from a simple string' do
147
+ original = Outcasted.fields[:size].dup
148
+ original[:select_options] = [:small,:medium,:large]
149
+ output = Outcasted.new.outcast(:size, original, {input_name_prefix: 'data'})
150
+ assert_equal [ {description: 'Small', value: 'small'}, {description: 'Medium', value: 'medium'}, {description: 'Large', value: 'large'} ], output[:select_options]
151
+ end
152
+
153
+ it 'Selects the input value' do
154
+ original = Outcasted.fields[:size]
155
+ outcasted = Outcasted.new size: 'm'
156
+ output = outcasted.outcast(:size, original, {input_name_prefix: 'data'})
157
+ assert_equal 1, output[:select_options].select{|o|o[:selected]}.size
158
+ assert output[:select_options].find{|o|o[:value]=='m'}[:selected]
159
+ end
160
+
161
+ it 'Adds [] at the end of input_name if multiple is true' do
162
+ original = Outcasted.fields[:tags]
163
+ output = Outcasted.new.outcast(:tags, original, {input_name_prefix: 'data'})
164
+ assert_equal 'data[tags][]', output[:input_name]
165
+ end
166
+
167
+ it 'Selects multiple options when input_value is an array' do
168
+ original = Outcasted.fields[:tags]
169
+ outcasted = Outcasted.new tags: ['art','science']
170
+ output = outcasted.outcast(:tags, original, {input_name_prefix: 'data'})
171
+ assert output[:select_options].find{|o|o[:value]=='art'}[:selected]
172
+ assert output[:select_options].find{|o|o[:value]=='science'}[:selected]
173
+ refute output[:select_options].find{|o|o[:value]=='sport'}[:selected]
174
+ end
175
+
176
+ it 'Orders input values at the begining when multiple options' do
177
+ original = Outcasted.fields[:related_properties]
178
+
179
+ # Normal
180
+ outcasted = Outcasted.new related_properties: ['prop3','prop1']
181
+ output = outcasted.outcast(:related_properties, original, {input_name_prefix: 'data'})
182
+ assert_equal 'prop3', output[:select_options][0][:value]
183
+ assert output[:select_options][0][:selected]
184
+ assert_equal 'prop1', output[:select_options][1][:value]
185
+ assert output[:select_options][1][:selected]
186
+ assert_equal 'prop2', output[:select_options][2][:value]
187
+ refute output[:select_options][2][:selected]
188
+
189
+ # When input_value is nil
190
+ outcasted = Outcasted.new
191
+ output = outcasted.outcast(:related_properties, original, {input_name_prefix: 'data'})
192
+ assert_equal 'prop1', output[:select_options][0][:value]
193
+ refute output[:select_options][0][:selected]
194
+ assert_equal 'prop2', output[:select_options][1][:value]
195
+ refute output[:select_options][1][:selected]
196
+ assert_equal 'prop3', output[:select_options][2][:value]
197
+ refute output[:select_options][2][:selected]
198
+
199
+ # When input_value has a non existing value
200
+ outcasted = Outcasted.new related_properties: ['stale','prop2']
201
+ output = outcasted.outcast(:related_properties, original, {input_name_prefix: 'data'})
202
+ assert_equal 'prop2', output[:select_options][0][:value]
203
+ assert output[:select_options][0][:selected]
204
+ assert_equal 'prop1', output[:select_options][1][:value]
205
+ refute output[:select_options][1][:selected]
206
+ assert_equal 'prop3', output[:select_options][2][:value]
207
+ refute output[:select_options][2][:selected]
208
+ end
209
+
210
+ end
211
+
212
+ describe '#outcast_price' do
213
+
214
+ it 'Displays the price in dollars/pounds' do
215
+ original = Outcasted.fields[:price]
216
+ outcasted = Outcasted.new price: 2999
217
+ output = outcasted.outcast(:price, original, {input_name_prefix: 'data'})
218
+ assert_equal '29.99', output[:input_value]
219
+ end
220
+
221
+ it 'Leaves the field alone when value not an integer' do
222
+ original = Outcasted.fields[:price]
223
+ outcasted = Outcasted.new
224
+ output = outcasted.outcast(:price, original, {input_name_prefix: 'data'})
225
+ assert_nil outcasted.price
226
+ assert_nil output[:input_value]
227
+ end
228
+
229
+ end
230
+
231
+ describe '#outcast_attachment' do
232
+
233
+ it 'Sets url' do
234
+ original = Outcasted.fields[:pdf]
235
+ outcasted = Outcasted.new
236
+ output = outcasted.outcast(:pdf, original, {input_name_prefix: 'data'})
237
+ assert_nil output[:url]
238
+
239
+ outcasted.pdf = 'guidelines.pdf'
240
+ output = outcasted.outcast(:pdf, original, {input_name_prefix: 'data'})
241
+ assert_equal outcasted.attachment(:pdf).url, output[:url]
242
+ end
243
+
244
+ end
245
+
246
+ end
247
+
@@ -0,0 +1,83 @@
1
+ require 'helper'
2
+ require 'populate_me/document'
3
+
4
+ class Stuborn < PopulateMe::Document
5
+ attr_accessor :age
6
+ field :position
7
+ field :reversed, direction: :desc
8
+ end
9
+
10
+ describe PopulateMe::Document, 'Persistence' do
11
+
12
+ parallelize_me!
13
+
14
+ let(:subject_class) { Stuborn }
15
+ subject { subject_class.new }
16
+
17
+ describe '::is_unique' do
18
+
19
+ before do
20
+ Stuborn.documents = []
21
+ end
22
+
23
+ # def doc_doesnt_exist
24
+ # subject_class.stub(:admin_get, nil) do
25
+ # yield
26
+ # end
27
+ # end
28
+ # def doc_exists
29
+ # subject_class.stub(:admin_get, subject) do
30
+ # yield
31
+ # end
32
+ # end
33
+
34
+ describe 'The document does not exist yet' do
35
+ it 'Saves an entry with the ID `unique` by default' do
36
+ Stuborn.is_unique
37
+ assert_equal 1, Stuborn.documents.count
38
+ assert_equal 'unique', Stuborn.documents[0]['id']
39
+ end
40
+ it 'Saves an entry with a provided ID' do
41
+ Stuborn.is_unique('provided_id')
42
+ assert_equal 1, Stuborn.documents.count
43
+ assert_equal 'provided_id', Stuborn.documents[0]['id']
44
+ end
45
+ end
46
+ describe 'The document already exist' do
47
+ it 'Does not create a new document and preserve the current one' do
48
+ doc = Stuborn.new({'id'=>'unique', 'age'=>4})
49
+ doc.save
50
+ Stuborn.is_unique
51
+ assert_equal 1, Stuborn.documents.count
52
+ assert_equal doc.to_h, Stuborn.documents[0]
53
+ end
54
+ end
55
+ end
56
+
57
+ describe '::set_indexes' do
58
+
59
+ before do
60
+ Stuborn.documents = []
61
+ Stuborn.new('id' => 'a').save
62
+ Stuborn.new('id' => 'b').save
63
+ Stuborn.new('id' => 'c').save
64
+ end
65
+
66
+ it 'Sets the indexes on the provided field' do
67
+ Stuborn.set_indexes(:position,['b','a','c'])
68
+ assert_equal 1, Stuborn.admin_get('a').position
69
+ assert_equal 0, Stuborn.admin_get('b').position
70
+ assert_equal 2, Stuborn.admin_get('c').position
71
+ end
72
+
73
+ it 'Sets the indexes taking direction into account' do
74
+ Stuborn.set_indexes(:reversed,['b','a','c'])
75
+ assert_equal 1, Stuborn.admin_get('a').reversed
76
+ assert_equal 2, Stuborn.admin_get('b').reversed
77
+ assert_equal 0, Stuborn.admin_get('c').reversed
78
+ end
79
+
80
+ end
81
+
82
+ end
83
+