populate-me 0.0.33 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/LICENSE +1 -1
  4. data/README.md +399 -4
  5. data/Rakefile +14 -0
  6. data/example/config.ru +67 -0
  7. data/lib/populate_me.rb +2 -0
  8. data/lib/populate_me/admin.rb +143 -0
  9. data/lib/populate_me/admin/__assets__/css/main.css +174 -0
  10. data/lib/populate_me/admin/__assets__/js/columnav.js +82 -0
  11. data/lib/populate_me/admin/__assets__/js/main.js +251 -0
  12. data/lib/populate_me/admin/__assets__/js/mustache.js +578 -0
  13. data/lib/populate_me/admin/__assets__/js/sortable.js +2 -0
  14. data/lib/populate_me/admin/views/page.erb +163 -0
  15. data/lib/populate_me/api.rb +124 -0
  16. data/lib/populate_me/attachment.rb +182 -0
  17. data/lib/populate_me/document.rb +178 -0
  18. data/lib/populate_me/document_mixins/admin_adapter.rb +131 -0
  19. data/lib/populate_me/document_mixins/callbacks.rb +104 -0
  20. data/lib/populate_me/document_mixins/outcasting.rb +49 -0
  21. data/lib/populate_me/document_mixins/persistence.rb +92 -0
  22. data/lib/populate_me/document_mixins/schema.rb +99 -0
  23. data/lib/populate_me/document_mixins/typecasting.rb +60 -0
  24. data/lib/populate_me/document_mixins/validation.rb +44 -0
  25. data/lib/populate_me/file_system_attachment.rb +40 -0
  26. data/lib/populate_me/grid_fs_attachment.rb +127 -0
  27. data/lib/populate_me/mongo.rb +98 -3
  28. data/lib/populate_me/variation.rb +34 -0
  29. data/lib/populate_me/version.rb +4 -0
  30. data/populate-me.gemspec +20 -12
  31. data/test/helper.rb +37 -0
  32. data/test/test_admin.rb +161 -0
  33. data/test/test_api.rb +246 -0
  34. data/test/test_attachment.rb +155 -0
  35. data/test/test_document.rb +120 -0
  36. data/test/test_document_admin_adapter.rb +43 -0
  37. data/test/test_document_callbacks.rb +107 -0
  38. data/test/test_document_persistence.rb +56 -0
  39. data/test/test_document_typecasting.rb +121 -0
  40. data/test/test_mongo.rb +217 -0
  41. data/test/test_variation.rb +91 -0
  42. data/test/test_version.rb +11 -0
  43. metadata +115 -66
  44. data/lib/populate_me/control.rb +0 -196
  45. data/lib/populate_me/control/_public/css/main.css +0 -207
  46. data/lib/populate_me/control/_public/css/plugin.asmselect.css +0 -63
  47. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_flat_30_cccccc_40x100.png +0 -0
  48. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_flat_50_5c5c5c_40x100.png +0 -0
  49. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_glass_20_555555_1x400.png +0 -0
  50. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_glass_40_0078a3_1x400.png +0 -0
  51. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_glass_40_ffc73d_1x400.png +0 -0
  52. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_gloss-wave_25_333333_500x100.png +0 -0
  53. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_highlight-soft_80_eeeeee_1x100.png +0 -0
  54. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_inset-soft_25_000000_1x100.png +0 -0
  55. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_inset-soft_30_f58400_1x100.png +0 -0
  56. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_222222_256x240.png +0 -0
  57. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_4b8e0b_256x240.png +0 -0
  58. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_a83300_256x240.png +0 -0
  59. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_cccccc_256x240.png +0 -0
  60. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_ffffff_256x240.png +0 -0
  61. data/lib/populate_me/control/_public/css/ui-darkness/jquery-ui-1.8.17.custom.css +0 -430
  62. data/lib/populate_me/control/_public/img/grip.png +0 -0
  63. data/lib/populate_me/control/_public/img/icons-cms-solarized.png +0 -0
  64. data/lib/populate_me/control/_public/img/icons-cms.png +0 -0
  65. data/lib/populate_me/control/_public/img/placeholder.png +0 -0
  66. data/lib/populate_me/control/_public/img/placeholder.stash_thumb.gif +0 -0
  67. data/lib/populate_me/control/_public/img/placeholder.stash_thumb.png +0 -0
  68. data/lib/populate_me/control/_public/img/small-loader.gif +0 -0
  69. data/lib/populate_me/control/_public/js/addon.timepicker.js +0 -20
  70. data/lib/populate_me/control/_public/js/jquery-ui.js +0 -114
  71. data/lib/populate_me/control/_public/js/jquery.js +0 -167
  72. data/lib/populate_me/control/_public/js/jquery.mustache.js +0 -559
  73. data/lib/populate_me/control/_public/js/main.js +0 -144
  74. data/lib/populate_me/control/_public/js/plugin.asmselect.js +0 -407
  75. data/lib/populate_me/control/_public/js/plugin.form.js +0 -11
  76. data/lib/populate_me/control/_public/js/plugin.quicksearch.js +0 -1
  77. data/lib/populate_me/control/_public/js/plugin.underwood.js +0 -4
  78. data/lib/populate_me/control/views/populate_me_layout.erb +0 -75
  79. data/lib/populate_me/ext.rb +0 -16
  80. data/lib/populate_me/mongo/backend_api_plug.rb +0 -78
  81. data/lib/populate_me/mongo/crushyform.rb +0 -243
  82. data/lib/populate_me/mongo/mutation.rb +0 -324
  83. data/lib/populate_me/mongo/plug.rb +0 -134
  84. data/lib/populate_me/mongo/stash.rb +0 -171
  85. data/test/spec_ext.rb +0 -29
  86. data/test/spec_mongo_mutation.rb +0 -279
@@ -0,0 +1,92 @@
1
+ module PopulateMe
2
+ module DocumentMixins
3
+ module Persistence
4
+
5
+ def persistent_instance_variables
6
+ instance_variables.select do |k|
7
+ if self.class.fields.empty?
8
+ k !~ /^@_/
9
+ else
10
+ self.class.fields.key? k[1..-1].to_sym
11
+ end
12
+ end
13
+ end
14
+
15
+ def attachment f
16
+ attacher = Utils.resolve_class_name self.class.fields[f][:class_name]
17
+ attacher.new self, f
18
+ end
19
+
20
+ # Saving
21
+ def save
22
+ return unless valid?
23
+ exec_callback :before_save
24
+ if new?
25
+ exec_callback :before_create
26
+ id = perform_create
27
+ exec_callback :after_create
28
+ else
29
+ exec_callback :before_update
30
+ id = perform_update
31
+ exec_callback :after_update
32
+ end
33
+ exec_callback :after_save
34
+ id
35
+ end
36
+ def perform_create
37
+ self.class.documents << self.to_h
38
+ self.id
39
+ end
40
+ def perform_update
41
+ index = self.class.documents.index{|d| d['id']==self.id }
42
+ raise MissingDocumentError, "No document can be found with this ID: #{self.id}" if self.id.nil?||index.nil?
43
+ self.class.documents[index] = self.to_h
44
+ end
45
+
46
+ # Deletion
47
+ def delete o={}
48
+ exec_callback :before_delete
49
+ perform_delete
50
+ exec_callback :after_delete
51
+ end
52
+ def perform_delete
53
+ index = self.class.documents.index{|d| d['id']==self.id }
54
+ raise MissingDocumentError, "No document can be found with this ID: #{self.id}" if self.id.nil?||index.nil?
55
+ self.class.documents.delete_at(index)
56
+ end
57
+
58
+ def self.included(base)
59
+ base.extend(ClassMethods)
60
+ end
61
+
62
+ module ClassMethods
63
+
64
+ attr_writer :documents
65
+
66
+ def documents; @documents ||= []; end
67
+
68
+ def id_string_key
69
+ (self.fields.keys[0]||'id').to_s
70
+ end
71
+
72
+ def set_indexes f, ids=[]
73
+ ids.each_with_index do |id,i|
74
+ self.documents.each do |d|
75
+ d[f.to_s] = i if d[self.id_string_key]==id
76
+ end
77
+ end
78
+ self
79
+ end
80
+
81
+ def is_unique unique_id='unique'
82
+ if self.admin_get(unique_id).nil?
83
+ self.new.set_from_hash({id:unique_id}).save
84
+ end
85
+ end
86
+
87
+ end
88
+
89
+ end
90
+ end
91
+ end
92
+
@@ -0,0 +1,99 @@
1
+ module PopulateMe
2
+ module DocumentMixins
3
+ module Schema
4
+
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ module ClassMethods
10
+
11
+ attr_writer :fields
12
+ attr_accessor :label_field
13
+
14
+ def fields; @fields ||= {}; end
15
+
16
+ def field name, o={}
17
+ set_id_field if self.fields.empty?&&o[:type]!=:id
18
+ complete_field_options name, o
19
+ if o[:type]==:list
20
+ define_method(name) do
21
+ var = "@#{name}"
22
+ instance_variable_set(var, instance_variable_get(var)||[])
23
+ end
24
+ else
25
+ attr_accessor name
26
+ end
27
+ self.fields[name] = o
28
+ end
29
+
30
+ def complete_field_options name, o={}
31
+ o[:field_name] = name
32
+ WebUtils.ensure_key! o, :type, :string
33
+ WebUtils.ensure_key! o, :form_field, ![:id,:position].include?(o[:type])
34
+ o[:wrap] = false unless o[:form_field]
35
+ WebUtils.ensure_key! o, :wrap, ![:hidden,:list].include?(o[:type])
36
+ WebUtils.ensure_key! o, :label, WebUtils.label_for_field(name)
37
+ if o[:type]==:attachment
38
+ WebUtils.ensure_key! o, :class_name, settings.default_attachment_class
39
+ raise MissingAttachmentClassError, "No attachment class was provided for the #{self.name} field: #{name}" if o[:class_name].nil?
40
+ o[:class_name] = o[:class_name].name unless o[:class_name].is_a?(String)
41
+ end
42
+ if o[:type]==:list
43
+ o[:class_name] = WebUtils.guess_related_class_name(self.name,o[:class_name]||name)
44
+ o[:dasherized_class_name] = WebUtils.dasherize_class_name o[:class_name]
45
+ else
46
+ WebUtils.ensure_key! o, :input_attributes, {}
47
+ o[:input_attributes][:type] = :hidden if o[:type]==:hidden
48
+ unless o[:type]==:text
49
+ WebUtils.ensure_key! o[:input_attributes], :type, :text
50
+ end
51
+ end
52
+ end
53
+
54
+ def set_id_field
55
+ field :id, {type: :id}
56
+ end
57
+
58
+ def position_field o={}
59
+ name = o[:name]||:position
60
+ o[:type] = :position
61
+ field name, o
62
+ sort_by name
63
+ end
64
+
65
+ def label sym # sets the label_field
66
+ @label_field = sym.to_sym
67
+ end
68
+
69
+ def label_field
70
+ @label_field || self.fields.keys[1]
71
+ end
72
+
73
+ def sort_by f, direction=:asc
74
+ raise(ArgumentError) unless [:asc,:desc].include? direction
75
+ raise(ArgumentError) unless self.new.respond_to? f
76
+ @sort_proc = Proc.new do |a,b|
77
+ a,b = b,a if direction==:desc
78
+ a.__send__(f)<=>b.__send__(f)
79
+ end
80
+ self
81
+ end
82
+
83
+ def relationships; @relationships ||= {}; end
84
+
85
+ def relationship name, o={}
86
+ o[:class_name] = WebUtils.guess_related_class_name(self.name,o[:class_name]||name)
87
+ WebUtils.ensure_key! o, :label, name.to_s.capitalize
88
+ WebUtils.ensure_key! o, :foreign_key, "#{WebUtils.dasherize_class_name(self.name).gsub('-','_')}_id"
89
+ o[:foreign_key] = o[:foreign_key].to_sym
90
+ WebUtils.ensure_key! o, :dependent, true
91
+ self.relationships[name] = o
92
+ end
93
+
94
+ end
95
+
96
+ end
97
+ end
98
+ end
99
+
@@ -0,0 +1,60 @@
1
+ module PopulateMe
2
+ module DocumentMixins
3
+ module Typecasting
4
+
5
+ # This module deals with typecasting the fields
6
+ # when they are received as strings,
7
+ # generally from a form or a csv file
8
+
9
+ def typecast k, v
10
+ unless self.class.fields.key?(k)
11
+ return WebUtils.automatic_typecast(v)
12
+ end
13
+ f = self.class.fields[k].dup
14
+ meth = "typecast_#{f[:type]}".to_sym
15
+ unless respond_to? meth
16
+ return WebUtils.automatic_typecast(v, [f[:type],:nil])
17
+ end
18
+ __send__ meth, k, v
19
+ end
20
+
21
+ def typecast_integer k, v
22
+ v.to_i
23
+ end
24
+
25
+ def typecast_price k, v
26
+ return nil if WebUtils.blank?(v)
27
+ WebUtils.parse_price(v)
28
+ end
29
+
30
+ def typecast_date k, v
31
+ if v[/\d\d(\/|-)\d\d(\/|-)\d\d\d\d/]
32
+ Date.parse v
33
+ else
34
+ nil
35
+ end
36
+ end
37
+
38
+ def typecast_datetime k, v
39
+ if v[/\d\d(\/|-)\d\d(\/|-)\d\d\d\d \d\d?:\d\d?:\d\d?/]
40
+ d,m,y,h,min,s = v.split(/[-:\s\/]/)
41
+ Time.utc(y,m,d,h,min,s)
42
+ else
43
+ nil
44
+ end
45
+ end
46
+
47
+ def typecast_attachment k, v
48
+ attached = self.attachment k
49
+ if WebUtils.blank? v
50
+ attached.delete_all
51
+ return nil
52
+ elsif v.is_a?(Hash)&&v.key?(:tempfile)
53
+ return attached.create v
54
+ end
55
+ end
56
+
57
+ end
58
+ end
59
+ end
60
+
@@ -0,0 +1,44 @@
1
+ module PopulateMe
2
+ module DocumentMixins
3
+ module Validation
4
+
5
+ attr_accessor :_errors
6
+
7
+ def errors; self._errors; end
8
+
9
+ def error_on k,v
10
+ self._errors[k] = (self._errors[k]||[]) << v
11
+ self
12
+ end
13
+
14
+ def valid?
15
+ self._errors = {}
16
+ exec_callback :before_validate
17
+ validate
18
+ exec_callback :after_validate
19
+ nested_docs.reduce self._errors.empty? do |result,d|
20
+ result &= d.valid?
21
+ end
22
+ end
23
+
24
+ def validate; end
25
+
26
+ def error_report
27
+ report = self._errors.dup || {}
28
+ persistent_instance_variables.each do |var|
29
+ value = instance_variable_get var
30
+ if value.is_a? Array
31
+ k = var[1..-1].to_sym
32
+ report[k] = []
33
+ value.each do |d|
34
+ report[k] << d.error_report
35
+ end
36
+ end
37
+ end
38
+ report
39
+ end
40
+
41
+ end
42
+ end
43
+ end
44
+
@@ -0,0 +1,40 @@
1
+ require 'populate_me/attachment'
2
+
3
+ module PopulateMe
4
+
5
+ class FileSystemAttachment < Attachment
6
+
7
+ set :root, File.expand_path('public')
8
+
9
+ def next_available_filename filename
10
+ FileUtils.mkdir_p self.location_root
11
+ ext = File.extname(filename)
12
+ base = File.basename(filename,ext)
13
+ i = 0
14
+ loop do
15
+ suffix = i==0 ? '' : "-#{i}"
16
+ potential_filename = [base,suffix,ext].join
17
+ potential_location = self.location_for_filename(potential_filename)
18
+ if File.exist?(potential_location)
19
+ i += 1
20
+ else
21
+ filename = potential_filename
22
+ break
23
+ end
24
+ end
25
+ filename
26
+ end
27
+
28
+ def perform_create hash
29
+ return File.basename(hash[:variation_path]) unless WebUtils.blank?(hash[:variation_path])
30
+ source = hash[:tempfile].path
31
+ filename = self.next_available_filename hash[:filename]
32
+ destination = self.location_for_filename filename
33
+ FileUtils.cp source, destination
34
+ filename
35
+ end
36
+
37
+ end
38
+
39
+ end
40
+
@@ -0,0 +1,127 @@
1
+ require 'populate_me/attachment'
2
+ require 'mongo'
3
+ require 'rack/gridfs'
4
+
5
+ module PopulateMe
6
+
7
+ class MissingMongoDBError < StandardError; end
8
+
9
+ class GridFSAttachment < Attachment
10
+
11
+ # set :url_prefix, '/attachment'
12
+
13
+ def grid
14
+ self.class.grid
15
+ end
16
+
17
+ def gridfs
18
+ self.class.gridfs
19
+ end
20
+
21
+ # Attachee_prefix is moved on field_value for gridfs
22
+ def url variation_name=:original
23
+ return nil if WebUtils.blank?(self.field_filename(variation_name))
24
+ "#{settings.url_prefix.sub(/\/$/,'')}/#{self.field_filename(variation_name)}"
25
+ end
26
+ # Attachee_prefix is moved on field_value for gridfs
27
+ def location_root
28
+ File.join(
29
+ settings.root,
30
+ settings.url_prefix
31
+ )
32
+ end
33
+
34
+ def deletable? variation_name=:original
35
+ !WebUtils.blank? self.field_filename(variation_name)
36
+ # Fine since deleting a non-existent file does not raise an error in mongo
37
+ end
38
+
39
+ def perform_delete variation_name=:original
40
+ # gridfs works with names instead of IDs
41
+ gridfs.delete self.field_filename(variation_name)
42
+ end
43
+
44
+ def next_available_filename filename
45
+ ext = File.extname(filename)
46
+ base = "#{attachee_prefix}/#{File.basename(filename,ext)}"
47
+ i = 0
48
+ loop do
49
+ suffix = i==0 ? '' : "-#{i}"
50
+ potential_filename = [base,suffix,ext].join
51
+ if grid.exist?(filename: potential_filename)
52
+ i += 1
53
+ else
54
+ filename = potential_filename
55
+ break
56
+ end
57
+ end
58
+ filename
59
+ end
60
+
61
+ def perform_create hash
62
+ if hash[:variation].nil?
63
+ fn = next_available_filename(hash[:filename])
64
+ file = hash[:tempfile]
65
+ type = hash[:type]
66
+ else
67
+ fn = WebUtils.filename_variation hash[:future_field_value], hash[:variation].name, hash[:variation].ext
68
+ file = File.open(hash[:variation_path])
69
+ type = Rack::Mime.mime_type ".#{hash[:variation].ext}"
70
+ end
71
+ attachment_id = grid.put(
72
+ file, {
73
+ filename: fn,
74
+ content_type: type,
75
+ metadata: {
76
+ parent_collection: (self.document.class.respond_to?(:collection) ? self.document.class.collection.name : self.attachee_prefix),
77
+ }
78
+ }
79
+ )
80
+ file.close unless hash[:variation].nil?
81
+ fn
82
+ end
83
+
84
+ class << self
85
+
86
+ def ensure_db
87
+ raise MissingMongoDBError, "Attachment class #{self.name} does not have a Mongo database." if settings.db.nil?
88
+ end
89
+
90
+ def grid
91
+ ensure_db
92
+ unless settings.grid.is_a?(::Mongo::Grid)
93
+ set :grid, ::Mongo::Grid.new(settings.db)
94
+ end
95
+ settings.grid
96
+ end
97
+
98
+ def gridfs
99
+ ensure_db
100
+ unless settings.gridfs.is_a?(::Mongo::GridFileSystem)
101
+ set :gridfs, ::Mongo::GridFileSystem.new(settings.db)
102
+ end
103
+ settings.gridfs
104
+ end
105
+
106
+ def middleware
107
+ Rack::GridFS
108
+ end
109
+
110
+ def middleware_options
111
+ [
112
+ {
113
+ prefix: settings.url_prefix.dup,
114
+ db: settings.db,
115
+ lookup: :path
116
+ }
117
+ ]
118
+ end
119
+
120
+ end
121
+
122
+ end
123
+
124
+ GridFS = GridFSAttachment
125
+
126
+ end
127
+