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
data/example/config.ru ADDED
@@ -0,0 +1,67 @@
1
+ $:.unshift File.expand_path('../../lib', __FILE__)
2
+
3
+ # Models ##########
4
+
5
+ require 'populate_me/document'
6
+ require 'mongo'
7
+
8
+ MONGO = Mongo::Connection.new
9
+ DB = MONGO['blog-populate-me-test']
10
+
11
+ # require 'populate_me/attachment'
12
+ # PopulateMe::Document.set :default_attachment_class, PopulateMe::Attachment
13
+ # require 'populate_me/file_system_attachment'
14
+ # PopulateMe::Document.set :default_attachment_class, PopulateMe::FileSystemAttachment
15
+ require 'populate_me/grid_fs_attachment'
16
+ PopulateMe::Document.set :default_attachment_class, PopulateMe::GridFS
17
+ PopulateMe::GridFS.set :db, DB
18
+
19
+ class BlogPost < PopulateMe::Document
20
+ field :title, required: true
21
+ field :thumbnail, type: :attachment, variations: [
22
+ PopulateMe::Variation.new_image_magick_job(:populate_me_thumb, :jpg, "-resize '400x225^' -gravity center -extent 400x225")
23
+ ]
24
+ field :content, type: :text
25
+ field :authors, type: :list
26
+ field :published, type: :boolean
27
+ relationship :comments
28
+ def validate
29
+ error_on(:content,'Cannot be blank') if PopulateMe::Utils.blank?(self.content)
30
+ end
31
+ end
32
+ class BlogPost::Author < PopulateMe::Document
33
+ # nested
34
+ field :name
35
+ def validate
36
+ error_on(:name, 'Cannot be shit') if self.name=='shit'
37
+ end
38
+ end
39
+ class BlogPost::Comment < PopulateMe::Document
40
+ # not nested
41
+ field :author, default: 'Anonymous'
42
+ field :content, type: :text
43
+ field :blog_post_id, type: :hidden
44
+ position_field scope: :blog_post_id
45
+ end
46
+
47
+ class Article < PopulateMe::Document
48
+ field :title
49
+ field :content, type: :text
50
+ position_field
51
+ end
52
+
53
+ # Admin ##########
54
+
55
+ require "populate_me/admin"
56
+ class Admin < PopulateMe::Admin
57
+ set :menu, [
58
+ ['Blog Posts', '/list/blog-post'],
59
+ ['Articles', '/list/article'],
60
+ ]
61
+ end
62
+
63
+ # use PopulateMe::Attachment::Middleware
64
+ # use PopulateMe::FileSystemAttachment::Middleware
65
+ use PopulateMe::GridFS::Middleware
66
+ run Admin
67
+
@@ -0,0 +1,2 @@
1
+ require 'populate_me/api'
2
+
@@ -0,0 +1,143 @@
1
+ require 'populate_me/api'
2
+ require 'json'
3
+ require 'tilt/erb'
4
+ begin
5
+ require "rack/cerberus"
6
+ rescue LoadError
7
+ puts "Cerberus not loaded."
8
+ end
9
+
10
+ class PopulateMe::Admin < Sinatra::Base
11
+
12
+ # Settings
13
+ set :app_file, nil # Need to be set when subclassed
14
+ set :show_exceptions, false
15
+ set :meta_title, 'Populate Me'
16
+ set :index_path, '/menu'
17
+ enable :cerberus
18
+ set :cerberus_active, Proc.new{
19
+ cerberus_available? &&
20
+ !!cerberus_pass &&
21
+ settings.cerberus?
22
+ }
23
+ set :logout_path, Proc.new{ settings.cerberus_active ? '/logout' : false }
24
+
25
+ # Load API helpers
26
+ helpers PopulateMe::API::Helpers
27
+ helpers do
28
+ def user_name
29
+ return 'Anonymous' if session.nil?||session[:populate_me_user].nil?
30
+ session[:populate_me_user]
31
+ end
32
+ end
33
+
34
+ # Make all templates in admin/views accessible with their basename
35
+ Dir["#{File.expand_path('../admin/views',__FILE__)}/*.erb"].each do |f|
36
+ template File.basename(f,'.erb').to_sym do
37
+ File.read(f)
38
+ end
39
+ end
40
+
41
+ before do
42
+ content_type :json
43
+ end
44
+
45
+ get '/?' do
46
+ content_type :html
47
+ erb :page, layout: false
48
+ end
49
+
50
+ get '/menu/?*?' do
51
+ page_title = 'Menu'
52
+ current_level = settings.menu.dup
53
+ levels = params[:splat][0].split('/')
54
+ levels.each do |l|
55
+ page_title, current_level = current_level.find{|item| slugify(item[0])==l}
56
+ end
57
+ items = current_level.map do |l|
58
+ href = l[1].is_a?(String) ? l[1] : "#{request.script_name}/menu#{levels.map{|level|'/'+level}.join}/#{slugify(l[0])}"
59
+ { title: l[0], href: href }
60
+ end
61
+ {
62
+ template: 'template_menu',
63
+ page_title: page_title,
64
+ items: items
65
+ }.to_json
66
+ end
67
+
68
+ get '/list/:class_name' do
69
+ @model_class = resolve_model_class params[:class_name]
70
+ @model_class.to_admin_list(request: request, params: params).to_json
71
+ end
72
+
73
+ get '/form/?:class_name?/?:id?' do
74
+ @model_class = resolve_model_class params[:class_name]
75
+ if params[:id].nil?
76
+ @model_instance = @model_class.new.set_defaults
77
+ @model_instance.set_from_hash(params[:data], typecast: true) unless params[:data].nil?
78
+ else
79
+ @model_instance = resolve_model_instance @model_class, params[:id]
80
+ end
81
+ @model_instance.to_admin_form(
82
+ request: request,
83
+ params: params,
84
+ nested: params[:nested]=='true',
85
+ input_name_prefix: params[:input_name_prefix]
86
+ ).to_json
87
+ end
88
+
89
+ not_found do
90
+ response.headers['X-Cascade'] = 'pass'
91
+ {'success'=>false,'message'=>'Not Found'}.to_json
92
+ end
93
+
94
+ error do
95
+ puts
96
+ puts env['sinatra.error'].inspect
97
+ puts
98
+ {'success'=>false,'message'=>env['sinatra.error'].message}.to_json
99
+ end
100
+
101
+ class << self
102
+
103
+ def cerberus_pass
104
+ # Method = overridable = testable
105
+ ENV['CERBERUS_PASS']
106
+ end
107
+ def cerberus_available?
108
+ # Method = overridable = testable
109
+ Rack.const_defined?(:Cerberus)
110
+ end
111
+
112
+ private
113
+
114
+ def setup_default_middleware builder
115
+ # Override the Sinatra method
116
+ super builder
117
+ setup_populate_me_middleware builder
118
+ end
119
+
120
+ def setup_populate_me_middleware builder
121
+ # Authentication
122
+ setup_cerberus builder
123
+ # Mount assets on /__assets__
124
+ builder.use Rack::Static, :urls=>['/__assets__'], :root=>File.expand_path('../admin',__FILE__)
125
+ # Mount the API on /api
126
+ builder.use Rack::Builder do
127
+ map('/api'){ run PopulateMe::API }
128
+ end
129
+ end
130
+
131
+ def setup_cerberus builder
132
+ return unless settings.cerberus_active
133
+ cerberus_settings = settings.cerberus==true ? {} : settings.cerberus
134
+ cerberus_settings[:session_key] = 'populate_me_user'
135
+ builder.use Rack::Cerberus, cerberus_settings do |user,pass,req|
136
+ pass==cerberus_pass
137
+ end
138
+ end
139
+
140
+ end
141
+
142
+ end
143
+
@@ -0,0 +1,174 @@
1
+ /* General */
2
+
3
+ html, body {
4
+ margin: 0;
5
+ padding: 0;
6
+ color: #002b36;
7
+ background-color: #eee8d5;
8
+ height: 100%;
9
+ font-family: 'Avenir', sans-serif;
10
+ }
11
+
12
+ body {
13
+ line-height: 1.5em;
14
+ }
15
+
16
+ #finder {
17
+ height: 100%;
18
+ }
19
+
20
+ .column {
21
+ background: #fdf6e3;
22
+ height: 100%;
23
+ min-width: 250px;
24
+ overflow: auto;
25
+ box-shadow: 0px 0px 10px #586e75;
26
+ }
27
+
28
+ .column > h1, .column > ol,
29
+ .column > form, .column > p { /* Column padding */
30
+ margin-left: 20px;
31
+ margin-right: 20px;
32
+ }
33
+
34
+ .column > h1 {
35
+ font-size: 1.5em;
36
+ font-weight: normal;
37
+ margin-top: 1em;
38
+ margin-bottom: 1.5em;
39
+ }
40
+
41
+ ol {
42
+ list-style: none;
43
+ margin: 0;
44
+ padding: 0;
45
+ }
46
+
47
+ a {
48
+ color: #002b36;
49
+ text-decoration: none;
50
+ /* border-bottom: 1px solid #002b36; */
51
+ }
52
+ a:hover, a.selected {
53
+ /* Blue */
54
+ color: #268bd2;
55
+ /* Green/Blue */
56
+ /* color: #2aa198; */
57
+ }
58
+
59
+ button { cursor: pointer; }
60
+
61
+ /* Lists */
62
+
63
+ .new-document-btn, .new-nested-document-btn {
64
+ border: 0;
65
+ font-size: 1.615em;
66
+ margin-bottom: 1em;
67
+ }
68
+
69
+ button.admin-delete, button.admin-delete-nested, .handle {
70
+ display: inline-block; vertical-align: middle;
71
+ font-size: 1em; line-height: 1em;
72
+ width: auto; height: auto;
73
+ margin: 0;
74
+ padding: 0.5em 0.5em 0.5em 0;
75
+ border: 0px;
76
+ }
77
+ button.admin-delete, button.admin-delete-nested {
78
+ color: #dc322f;
79
+ background-color: transparent;
80
+ }
81
+ button.admin-delete:focus, button.admin-delete-nested:focus, .handle:focus {
82
+ outline: 0;
83
+ }
84
+ .handle {
85
+ cursor: move;
86
+ color: #b58900;
87
+ }
88
+ .handle:hover {
89
+ }
90
+
91
+ [type=submit] {
92
+ border: 0px;
93
+ cursor: pointer;
94
+ color: #fdf6e3;
95
+ background-color: #859900;
96
+ }
97
+ [type=submit]:hover {
98
+ background-color: #268bd2;
99
+ }
100
+
101
+ .documents, .nested-documents {
102
+ -webkit-touch-callout: none;
103
+ -webkit-user-select: none;
104
+ -khtml-user-select: none;
105
+ -moz-user-select: none;
106
+ -ms-user-select: none;
107
+ user-select: none;
108
+ }
109
+ .documents > li, .nested-documents > li {
110
+ display: block;
111
+ margin-bottom: 1em;
112
+ }
113
+ .documents > li:hover, .nested-documents > li:hover {
114
+ background-color: #eee8d5;
115
+ outline: 0.5em solid #eee8d5;
116
+ }
117
+ .nested-documents > li > header {
118
+ background: #002b36;
119
+ height: 1.5em;
120
+ margin-bottom: 1em;
121
+ }
122
+ .documents .sortable-placeholder, .nested-documents .sortable-placeholder {
123
+ border: 1px dashed #839496;
124
+ background-color: #eee8d5;
125
+ }
126
+ .local-menu { font-size: 0.7em; }
127
+
128
+ /* Forms */
129
+
130
+ form {
131
+ margin-bottom: 2em;
132
+ }
133
+
134
+ .field {
135
+ margin-bottom: 1em;
136
+ }
137
+
138
+ input[type=text], input[type=email],
139
+ textarea {
140
+ border: 0px;
141
+ color: #839496;
142
+ background: #ffffff;
143
+ width: 400px;
144
+ padding: 0.5em;
145
+ font-size: 1em;
146
+ }
147
+ input[type=text]:focus, input[type=email]:focus,
148
+ textarea:focus {
149
+ /* color: #002b36; */
150
+ }
151
+
152
+ textarea { height: 6em; resize: vertical; }
153
+ textarea.oneline { height: 1em; resize: none; }
154
+
155
+ fieldset {
156
+ border: 0;
157
+ padding-left: 3em; padding-right: 0;
158
+ }
159
+
160
+ .invalid > label {
161
+ color: #dc322f;
162
+ }
163
+
164
+
165
+ /\
166
+ / *\
167
+ / # \
168
+ / * \
169
+ / # \
170
+ / # * \
171
+ / * # \
172
+ /______________\
173
+
174
+
@@ -0,0 +1,82 @@
1
+ (function($){
2
+
3
+ $.fn.columnav = function(options) {
4
+
5
+ var settings = $.extend({
6
+ column_class: 'column',
7
+ link_class: 'column-push',
8
+ selected_link_class: 'selected',
9
+ on_push: function(){},
10
+ on_pop: function(){},
11
+ process_data: function(data){return data;}
12
+ }, options );
13
+
14
+ var new_column = function(el, root) {
15
+ var $el = $(el).css({
16
+ display: 'inline-block',
17
+ verticalAlign: 'top',
18
+ whiteSpace: 'normal',
19
+ listStyle: 'none'
20
+ });
21
+ $('.'+settings.link_class, $el)
22
+ .on('click.columnav', function(e,cb) {
23
+ $('.'+settings.link_class, $el).removeClass(settings.selected_link_class);
24
+ var $this = $(this).addClass(settings.selected_link_class);
25
+ var removable = $this.parents('.'+settings.column_class).nextAll();
26
+ if (removable.length === 0) {
27
+ root.trigger('getandpush.columnav',[$this.attr('href'),cb]);
28
+ } else {
29
+ root.trigger('pop.columnav',[removable,function(cb_info) {
30
+ root.trigger('getandpush.columnav',[$this.attr('href'),cb]);
31
+ }]);
32
+ }
33
+ e.preventDefault();
34
+ });
35
+ };
36
+
37
+ return this.each(function(){
38
+ var root = $(this).css({
39
+ overflow: 'scroll',
40
+ whiteSpace: 'nowrap'
41
+ });
42
+ root.on('push.columnav', function(e,data,cb) {
43
+ var $data = $("<li class='"+settings.column_class+"'>"+data+"</li>");
44
+ root.append($data);
45
+ new_column($data, root);
46
+ var cb_object = {event:e,column:$data,container:root,settings:settings};
47
+ root.animate({scrollLeft: root.width()},function(){
48
+ settings.on_push(cb_object);
49
+ if (typeof cb == 'function') cb(cb_object);
50
+ });
51
+ });
52
+ root.on('getandpush.columnav', function(e,url,cb) {
53
+ $.get(url, function(data) {
54
+ root.trigger('push.columnav',[settings.process_data(data),function(cb_object) {
55
+ cb_object.column.data('href',url);
56
+ if (typeof cb == 'function') cb(cb_object);
57
+ }]);
58
+ });
59
+ });
60
+ root.on('pop.columnav', function(e,removable,cb) {
61
+ removable = removable || root.children().last();
62
+ removable.css({visibility:'hidden'})
63
+ .animate({width:0},function(){
64
+ // Trick to make sure it runs only once
65
+ // not for each column removed
66
+ if (removable.filter(':animated').length === 0) {
67
+ removable.remove();
68
+ var cb_object = {event:e,container:root,settings:settings}
69
+ settings.on_pop(cb_object);
70
+ if (typeof cb == 'function') cb(cb_object);
71
+ }
72
+ });
73
+ });
74
+ root.children().each(function() {
75
+ new_column(this, root);
76
+ });
77
+ });
78
+
79
+ };
80
+
81
+ }(jQuery));
82
+