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,178 @@
1
+ require 'web_utils'
2
+ require 'ostruct'
3
+
4
+ require 'populate_me/document_mixins/typecasting'
5
+ require 'populate_me/document_mixins/outcasting'
6
+ require 'populate_me/document_mixins/schema'
7
+ require 'populate_me/document_mixins/admin_adapter'
8
+ require 'populate_me/document_mixins/callbacks'
9
+ require 'populate_me/document_mixins/validation'
10
+ require 'populate_me/document_mixins/persistence'
11
+
12
+ module PopulateMe
13
+
14
+ class MissingDocumentError < StandardError; end
15
+ class MissingAttachmentClassError < StandardError; end
16
+
17
+ class Document
18
+
19
+ # PopulateMe::Document is the base for any document
20
+ # the Backend is supposed to deal with.
21
+ #
22
+ # Any module for a specific ORM or ODM should
23
+ # subclass it.
24
+ # It contains what is not specific to a particular kind
25
+ # of database and it provides defaults.
26
+ #
27
+ # It can be used on its own but it keeps everything
28
+ # in memory. Which means it is only for tests and conceptual
29
+ # understanding.
30
+
31
+ include DocumentMixins::Typecasting
32
+ include DocumentMixins::Outcasting
33
+ include DocumentMixins::Schema
34
+ include DocumentMixins::AdminAdapter
35
+ include DocumentMixins::Callbacks
36
+ include DocumentMixins::Validation
37
+ include DocumentMixins::Persistence
38
+
39
+ class << self
40
+
41
+ def inherited sub
42
+ super
43
+ sub.callbacks = WebUtils.deep_copy callbacks
44
+ sub.settings = settings.dup # no deep copy because of Mongo.settings.db
45
+ end
46
+
47
+ def to_s
48
+ super.gsub(/[A-Z]/, ' \&')[1..-1].gsub('::','')
49
+ end
50
+
51
+ def to_s_short
52
+ self.name[/[^:]+$/].gsub(/[A-Z]/, ' \&')[1..-1]
53
+ end
54
+
55
+ def to_s_plural; WebUtils.pluralize(self.to_s); end
56
+ def to_s_short_plural; WebUtils.pluralize(self.to_s_short); end
57
+
58
+ def from_hash hash, o={}
59
+ self.new(_is_new: false).set_from_hash(hash, o).snapshot
60
+ end
61
+
62
+ def cast o={}, &block
63
+ target = block.arity==0 ? instance_eval(&block) : block.call(self)
64
+ return nil if target.nil?
65
+ return from_hash(target, o) if target.is_a?(Hash)
66
+ return target.map{|t| from_hash(t,o)} if target.respond_to?(:map)
67
+ raise(TypeError, "The block passed to #{self.name}::cast is meant to return a Hash or a list of Hash which respond to `map`")
68
+ end
69
+
70
+ # inheritable settings
71
+ attr_accessor :settings
72
+ def set name, value
73
+ self.settings[name] = value
74
+ end
75
+
76
+ end
77
+
78
+ attr_accessor :id, :_is_new, :_old
79
+
80
+ def initialize attributes=nil
81
+ self._is_new = true
82
+ set attributes if attributes
83
+ self._errors = {}
84
+ end
85
+
86
+ def inspect
87
+ "#<#{self.class.name}:#{to_h.inspect}>"
88
+ end
89
+
90
+ def to_s
91
+ return inspect if self.class.label_field.nil?
92
+ me = self.__send__(self.class.label_field)
93
+ WebUtils.blank?(me) ? inspect : me
94
+ end
95
+
96
+ def new?; self._is_new; end
97
+
98
+ def to_h
99
+ persistent_instance_variables.inject({'_class'=>self.class.name}) do |h,var|
100
+ k = var.to_s[1..-1]
101
+ v = instance_variable_get var
102
+ if v.is_a? Array
103
+ h[k] = v.map(&:to_h)
104
+ else
105
+ h[k] = v
106
+ end
107
+ h
108
+ end
109
+ end
110
+ alias_method :to_hash, :to_h
111
+
112
+ def snapshot
113
+ self._old = self.to_h
114
+ self
115
+ end
116
+
117
+ def nested_docs
118
+ persistent_instance_variables.map do |var|
119
+ instance_variable_get var
120
+ end.find_all do |val|
121
+ val.is_a? Array
122
+ end.flatten
123
+ end
124
+
125
+ def == other
126
+ return false unless other.respond_to?(:to_h)
127
+ other.to_h==to_h
128
+ end
129
+
130
+ def set attributes
131
+ attributes.dup.each do |k,v|
132
+ setter = "#{k}="
133
+ if respond_to? setter
134
+ __send__ setter, v
135
+ end
136
+ end
137
+ self
138
+ end
139
+
140
+ def set_defaults o={}
141
+ self.class.fields.each do |k,v|
142
+ if v.key?(:default)&&(__send__(k).nil?||o[:force])
143
+ set k.to_sym => WebUtils.get_value(v[:default],self)
144
+ end
145
+ end
146
+ self
147
+ end
148
+
149
+ def set_from_hash hash, o={}
150
+ raise(TypeError, "#{hash} is not a Hash") unless hash.is_a? Hash
151
+ hash = hash.dup # Leave original untouched
152
+ hash.delete('_class')
153
+ hash.each do |k,v|
154
+ getter = k.to_sym
155
+ if v.is_a? Array
156
+ break unless respond_to?(getter)
157
+ __send__(getter).clear
158
+ v.each do |d|
159
+ obj = WebUtils.resolve_class_name(d['_class']).new.set_from_hash(d,o)
160
+ __send__(getter) << obj
161
+ end
162
+ else
163
+ v = typecast(getter,v) if o[:typecast]
164
+ set getter => v
165
+ end
166
+ end
167
+ self
168
+ end
169
+
170
+ # class settings
171
+ def settings
172
+ self.class.settings
173
+ end
174
+ self.settings = OpenStruct.new
175
+
176
+ end
177
+ end
178
+
@@ -0,0 +1,131 @@
1
+ module PopulateMe
2
+ module DocumentMixins
3
+ module AdminAdapter
4
+
5
+ def to_admin_url
6
+ "#{Utils.dasherize_class_name(self.class.name)}/#{id}".sub(/\/$/,'')
7
+ end
8
+
9
+ def admin_image_url
10
+ thefield = self.class.admin_image_field
11
+ return nil if thefield.nil?
12
+ self.attachment(thefield).url(:populate_me_thumb)
13
+ end
14
+
15
+ def to_admin_list_item o={}
16
+ {
17
+ class_name: self.class.name,
18
+ id: self.id,
19
+ admin_url: to_admin_url,
20
+ title: to_s,
21
+ image_url: admin_image_url,
22
+ local_menu: self.class.relationships.inject([]) do |out,(k,v)|
23
+ unless v[:hidden]
24
+ out << {
25
+ title: "#{v[:label]}",
26
+ href: "#{o[:request].script_name}/list/#{Utils.dasherize_class_name(v[:class_name])}?filter[#{v[:foreign_key]}]=#{self.id}"
27
+ }
28
+ out
29
+ end
30
+ end
31
+ }
32
+ end
33
+
34
+ def to_admin_form o={}
35
+ o[:input_name_prefix] ||= 'data'
36
+ class_item = {
37
+ type: :hidden,
38
+ input_name: "#{o[:input_name_prefix]}[_class]",
39
+ input_value: self.class.name,
40
+ }
41
+ self.class.complete_field_options :_class, class_item
42
+ items = self.class.fields.inject([class_item]) do |out,(k,item)|
43
+ item = item.dup
44
+ if item[:form_field]
45
+ outcast k, item, o
46
+ out << item
47
+ end
48
+ out
49
+ end
50
+ {
51
+ template: "template#{'_nested' if o[:nested]}_form",
52
+ page_title: self.new? ? "New #{self.class.to_s_short}" : self.to_s,
53
+ admin_url: self.to_admin_url,
54
+ is_new: self.new?,
55
+ fields: items
56
+ }
57
+ end
58
+
59
+ def self.included(base)
60
+ base.extend(ClassMethods)
61
+ end
62
+
63
+ module ClassMethods
64
+
65
+ def admin_image_field
66
+ res = self.fields.find do |k,v|
67
+ if v[:type]==:attachment and !v[:variations].nil?
68
+ v[:variations].any?{|var|var.name==:populate_me_thumb}
69
+ else
70
+ false
71
+ end
72
+ end
73
+ res.nil? ? nil : res[0]
74
+ end
75
+
76
+ def admin_get id
77
+ self.cast do
78
+ documents.find{|doc| doc[id_string_key]==id }
79
+ end
80
+ end
81
+
82
+ def admin_find o={}
83
+ o[:query] ||= {}
84
+ docs = self.cast{documents}.find_all do |d|
85
+ o[:query].inject(true) do |out,(k,v)|
86
+ out && (d.__send__(k)==v)
87
+ end
88
+ end
89
+ docs.sort!(&@sort_proc) if @sort_proc.is_a?(Proc)
90
+ docs
91
+ end
92
+
93
+ def sort_field_for o={}
94
+ filter = o[:params][:filter]
95
+ return nil if !filter.nil?&&filter.size>1
96
+ expected_scope = filter.nil? ? nil : filter.keys[0].to_sym
97
+ f = self.fields.find do |k,v|
98
+ v[:type]==:position&&v[:scope]==expected_scope
99
+ end
100
+ f.nil? ? nil : f[0]
101
+ end
102
+
103
+ def to_admin_list o={}
104
+ o[:params] ||= {}
105
+ unless o[:params][:filter].nil?
106
+ query = o[:params][:filter].inject({}) do |q, (k,v)|
107
+ q[k.to_sym] = self.new.typecast(k,v)
108
+ q
109
+ end
110
+ new_data = Rack::Utils.build_nested_query(data: o[:params][:filter])
111
+ end
112
+ {
113
+ template: 'template_list',
114
+ page_title: self.to_s_short_plural,
115
+ dasherized_class_name: PopulateMe::Utils.dasherize_class_name(self.name),
116
+ new_data: new_data,
117
+ sort_field: self.sort_field_for(o),
118
+ # 'command_plus'=> !self.populate_config[:no_plus],
119
+ # 'command_search'=> !self.populate_config[:no_search],
120
+ items: self.admin_find(query: query).map do |d|
121
+ d.to_admin_list_item(o)
122
+ end
123
+ }
124
+ end
125
+
126
+ end
127
+
128
+ end
129
+ end
130
+ end
131
+
@@ -0,0 +1,104 @@
1
+ module PopulateMe
2
+ module DocumentMixins
3
+ module Callbacks
4
+
5
+ def exec_callback name
6
+ name = name.to_sym
7
+ return self if self.class.callbacks[name].nil?
8
+ self.class.callbacks[name].each do |job|
9
+ if job.respond_to?(:call)
10
+ self.instance_exec name, &job
11
+ else
12
+ meth = self.method(job)
13
+ meth.arity==1 ? meth.call(name) : meth.call
14
+ end
15
+ end
16
+ self
17
+ end
18
+
19
+ def recurse_callback name
20
+ nested_docs.each do |d|
21
+ d.exec_callback name
22
+ end
23
+ end
24
+
25
+ def ensure_id
26
+ if self.id.nil?
27
+ self.id = WebUtils::generate_random_id
28
+ end
29
+ self
30
+ end
31
+
32
+ def ensure_not_new; self._is_new = false; end
33
+
34
+ def ensure_delete_related
35
+ self.class.relationships.each do |k,v|
36
+ if v[:dependent]
37
+ klass = WebUtils.resolve_class_name v[:class_name]
38
+ next if klass.nil?
39
+ klass.admin_find(query: {v[:foreign_key]=>self.id}).each do |d|
40
+ d.delete
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ def ensure_delete_attachments
47
+ self.class.fields.each do |k,v|
48
+ if v[:type]==:attachment
49
+ self.attachment(k).delete_all
50
+ end
51
+ end
52
+ end
53
+
54
+ def ensure_new; self._is_new = true; end
55
+
56
+ def self.included(base)
57
+ base.extend(ClassMethods)
58
+ base.class_eval do
59
+ [:save,:create,:update,:delete].each do |cb|
60
+ before cb, :recurse_callback
61
+ after cb, :recurse_callback
62
+ end
63
+ before :create, :ensure_id
64
+ after :create, :ensure_not_new
65
+ after :save, :snapshot
66
+ before :delete, :ensure_delete_related
67
+ before :delete, :ensure_delete_attachments
68
+ after :delete, :ensure_new
69
+ end
70
+ end
71
+
72
+ module ClassMethods
73
+
74
+ attr_accessor :callbacks
75
+
76
+ def register_callback name, item=nil, options={}, &block
77
+ name = name.to_sym
78
+ if block_given?
79
+ options = item || {}
80
+ item = block
81
+ end
82
+ self.callbacks ||= {}
83
+ self.callbacks[name] ||= []
84
+ if options[:prepend]
85
+ self.callbacks[name].unshift item
86
+ else
87
+ self.callbacks[name] << item
88
+ end
89
+ end
90
+
91
+ def before name, item=nil, options={}, &block
92
+ register_callback "before_#{name}", item, options, &block
93
+ end
94
+
95
+ def after name, item=nil, options={}, &block
96
+ register_callback "after_#{name}", item, options, &block
97
+ end
98
+
99
+ end
100
+
101
+ end
102
+ end
103
+ end
104
+
@@ -0,0 +1,49 @@
1
+ module PopulateMe
2
+ module DocumentMixins
3
+ module Outcasting
4
+
5
+ # This module prepares the field for being send to the Admin API
6
+ # and build the form.
7
+ # It compiles the value and all other info in a hash.
8
+ # Therefore, it is a complement to the AdminAdapter module.
9
+
10
+ def outcast field, item, o={}
11
+ item[:input_name] = "#{o[:input_name_prefix]}[#{item[:field_name]}]"
12
+ unless item[:type]==:list
13
+ WebUtils.ensure_key! item, :input_value, self.__send__(field)
14
+ end
15
+ meth = "outcast_#{item[:type]}".to_sym
16
+ __send__(meth, field, item, o) if respond_to?(meth)
17
+ end
18
+
19
+ def outcast_list field, item, o={}
20
+ item[:items] = self.__send__(field).map do |nested|
21
+ nested.to_admin_form(o.merge(input_name_prefix: item[:input_name]+'[]'))
22
+ end
23
+ end
24
+
25
+ def outcast_select field, item, o={}
26
+ unless item[:select_options].nil?
27
+ opts = WebUtils.get_value(item[:select_options],self).dup
28
+ opts.map! do |opt|
29
+ if opt.is_a?(String)||opt.is_a?(Symbol)
30
+ opt = [opt.to_s.capitalize,opt]
31
+ end
32
+ if opt.is_a?(Array)
33
+ opt = {description: opt[0].to_s, value: opt[1].to_s}
34
+ end
35
+ opt[:selected] = true if item[:input_value]==opt[:value]
36
+ opt
37
+ end
38
+ item[:select_options] = opts
39
+ end
40
+ end
41
+
42
+ def outcast_attachment field, item, o={}
43
+ item[:url] = self.attachment(field).url
44
+ end
45
+
46
+ end
47
+ end
48
+ end
49
+