populate-me 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
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
+