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.
- checksums.yaml +5 -5
- data/.gitignore +1 -0
- data/LICENSE +1 -1
- data/README.md +399 -4
- data/Rakefile +14 -0
- data/example/config.ru +67 -0
- data/lib/populate_me.rb +2 -0
- data/lib/populate_me/admin.rb +143 -0
- data/lib/populate_me/admin/__assets__/css/main.css +174 -0
- data/lib/populate_me/admin/__assets__/js/columnav.js +82 -0
- data/lib/populate_me/admin/__assets__/js/main.js +251 -0
- data/lib/populate_me/admin/__assets__/js/mustache.js +578 -0
- data/lib/populate_me/admin/__assets__/js/sortable.js +2 -0
- data/lib/populate_me/admin/views/page.erb +163 -0
- data/lib/populate_me/api.rb +124 -0
- data/lib/populate_me/attachment.rb +182 -0
- data/lib/populate_me/document.rb +178 -0
- data/lib/populate_me/document_mixins/admin_adapter.rb +131 -0
- data/lib/populate_me/document_mixins/callbacks.rb +104 -0
- data/lib/populate_me/document_mixins/outcasting.rb +49 -0
- data/lib/populate_me/document_mixins/persistence.rb +92 -0
- data/lib/populate_me/document_mixins/schema.rb +99 -0
- data/lib/populate_me/document_mixins/typecasting.rb +60 -0
- data/lib/populate_me/document_mixins/validation.rb +44 -0
- data/lib/populate_me/file_system_attachment.rb +40 -0
- data/lib/populate_me/grid_fs_attachment.rb +127 -0
- data/lib/populate_me/mongo.rb +98 -3
- data/lib/populate_me/variation.rb +34 -0
- data/lib/populate_me/version.rb +4 -0
- data/populate-me.gemspec +20 -12
- data/test/helper.rb +37 -0
- data/test/test_admin.rb +161 -0
- data/test/test_api.rb +246 -0
- data/test/test_attachment.rb +155 -0
- data/test/test_document.rb +120 -0
- data/test/test_document_admin_adapter.rb +43 -0
- data/test/test_document_callbacks.rb +107 -0
- data/test/test_document_persistence.rb +56 -0
- data/test/test_document_typecasting.rb +121 -0
- data/test/test_mongo.rb +217 -0
- data/test/test_variation.rb +91 -0
- data/test/test_version.rb +11 -0
- metadata +115 -66
- data/lib/populate_me/control.rb +0 -196
- data/lib/populate_me/control/_public/css/main.css +0 -207
- data/lib/populate_me/control/_public/css/plugin.asmselect.css +0 -63
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_flat_30_cccccc_40x100.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_flat_50_5c5c5c_40x100.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_glass_20_555555_1x400.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_glass_40_0078a3_1x400.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_glass_40_ffc73d_1x400.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_gloss-wave_25_333333_500x100.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_highlight-soft_80_eeeeee_1x100.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_inset-soft_25_000000_1x100.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_inset-soft_30_f58400_1x100.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_222222_256x240.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_4b8e0b_256x240.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_a83300_256x240.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_cccccc_256x240.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_ffffff_256x240.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/jquery-ui-1.8.17.custom.css +0 -430
- data/lib/populate_me/control/_public/img/grip.png +0 -0
- data/lib/populate_me/control/_public/img/icons-cms-solarized.png +0 -0
- data/lib/populate_me/control/_public/img/icons-cms.png +0 -0
- data/lib/populate_me/control/_public/img/placeholder.png +0 -0
- data/lib/populate_me/control/_public/img/placeholder.stash_thumb.gif +0 -0
- data/lib/populate_me/control/_public/img/placeholder.stash_thumb.png +0 -0
- data/lib/populate_me/control/_public/img/small-loader.gif +0 -0
- data/lib/populate_me/control/_public/js/addon.timepicker.js +0 -20
- data/lib/populate_me/control/_public/js/jquery-ui.js +0 -114
- data/lib/populate_me/control/_public/js/jquery.js +0 -167
- data/lib/populate_me/control/_public/js/jquery.mustache.js +0 -559
- data/lib/populate_me/control/_public/js/main.js +0 -144
- data/lib/populate_me/control/_public/js/plugin.asmselect.js +0 -407
- data/lib/populate_me/control/_public/js/plugin.form.js +0 -11
- data/lib/populate_me/control/_public/js/plugin.quicksearch.js +0 -1
- data/lib/populate_me/control/_public/js/plugin.underwood.js +0 -4
- data/lib/populate_me/control/views/populate_me_layout.erb +0 -75
- data/lib/populate_me/ext.rb +0 -16
- data/lib/populate_me/mongo/backend_api_plug.rb +0 -78
- data/lib/populate_me/mongo/crushyform.rb +0 -243
- data/lib/populate_me/mongo/mutation.rb +0 -324
- data/lib/populate_me/mongo/plug.rb +0 -134
- data/lib/populate_me/mongo/stash.rb +0 -171
- data/test/spec_ext.rb +0 -29
- data/test/spec_mongo_mutation.rb +0 -279
@@ -0,0 +1,2 @@
|
|
1
|
+
"use strict";!function(a){var b,c,d=a();a.fn.sortable=function(e){var f=String(e);return e=a.extend({connectWith:!1,placeholder:null,dragImage:null},e),this.each(function(){if("reload"===f&&a(this).children(e.items).off("dragstart.h5s dragend.h5s selectstart.h5s dragover.h5s dragenter.h5s drop.h5s"),/^enable|disable|destroy$/.test(f)){var g=a(this).children(a(this).data("items")).attr("draggable","enable"===f);return void("destroy"===f&&(a(this).off("sortupdate"),a(this).removeData("opts"),g.add(this).removeData("connectWith items").off("dragstart.h5s dragend.h5s selectstart.h5s dragover.h5s dragenter.h5s drop.h5s").off("sortupdate")))}var h=a(this).data("opts");"undefined"==typeof h?a(this).data("opts",e):e=h;var i,j,k,l,m=a(this).children(e.items),n=null===e.placeholder?a("<"+(/^ul|ol$/i.test(this.tagName)?"li":"div")+' class="sortable-placeholder"/>'):a(e.placeholder).addClass("sortable-placeholder");m.find(e.handle).mousedown(function(){i=!0}).mouseup(function(){i=!1}),a(this).data("items",e.items),d=d.add(n),e.connectWith&&a(e.connectWith).add(this).data("connectWith",e.connectWith),m.attr("role","option"),m.attr("aria-grabbed","false"),m.attr("draggable","true").on("dragstart.h5s",function(d){if(d.stopImmediatePropagation(),e.handle&&!i)return!1;i=!1;var f=d.originalEvent.dataTransfer;f.effectAllowed="move",f.setData("text",""),e.dragImage&&f.setDragImage&&f.setDragImage(e.dragImage,0,0),j=(b=a(this)).addClass("sortable-dragging").attr("aria-grabbed","true").index(),c=b.outerHeight(),k=a(this).parent()}).on("dragend.h5s",function(){b&&(b.removeClass("sortable-dragging").attr("aria-grabbed","false").show(),d.detach(),l=a(this).parent(),(j!==b.index()||k.get(0)!==l.get(0))&&b.parent().triggerHandler("sortupdate",{item:b,oldindex:j,startparent:k,endparent:l}),b=null,c=null)}).not("a[href], img").on("selectstart.h5s",function(){return e.handle&&!i?!0:(this.dragDrop&&this.dragDrop(),!1)}).end().add([this,n]).on("dragover.h5s dragenter.h5s drop.h5s",function(f){if(!m.is(b)&&e.connectWith!==a(b).parent().data("connectWith"))return!0;if("drop"===f.type)return f.stopPropagation(),d.filter(":visible").after(b),b.trigger("dragend.h5s"),!1;if(f.preventDefault(),f.originalEvent.dataTransfer.dropEffect="move",m.is(this)){var g=a(this).outerHeight();if(e.forcePlaceholderSize&&n.height(c),g>c){var h=g-c,i=a(this).offset().top;if(n.index()<a(this).index()&&f.originalEvent.pageY<i+h)return!1;if(n.index()>a(this).index()&&f.originalEvent.pageY>i+g-h)return!1}b.hide(),a(this)[n.index()<a(this).index()?"after":"before"](n),d.not(n).detach()}else d.is(this)||a(this).children(e.items).length||(d.detach(),a(this).append(n));return!1})})}}(jQuery);
|
2
|
+
//# sourceMappingURL=html.sortable.min.js.map
|
@@ -0,0 +1,163 @@
|
|
1
|
+
<%# encoding: utf-8 %>
|
2
|
+
<!DOCTYPE HTML>
|
3
|
+
<html>
|
4
|
+
<head>
|
5
|
+
<meta charset="utf-8" />
|
6
|
+
<title><%= settings.meta_title %></title>
|
7
|
+
<link rel="stylesheet" href="<%= request.script_name %>/__assets__/css/main.css" type="text/css" media='screen' />
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
|
11
|
+
<ol id='finder'><li class='column'>
|
12
|
+
<h1>Welcome<br><%= label_for_field user_name %></h1>
|
13
|
+
<% if settings.logout_path? %>
|
14
|
+
<p><a href="<%= request.script_name %><%= settings.logout_path %>">Logout</a></p>
|
15
|
+
<% end %>
|
16
|
+
</li></ol>
|
17
|
+
|
18
|
+
<!-- Templates -->
|
19
|
+
|
20
|
+
<script id="template-menu" type="x-tmpl-mustache">
|
21
|
+
<h1>{{page_title}}</h1>
|
22
|
+
<ol class='menu'>
|
23
|
+
{{#items}}
|
24
|
+
<li><a href="{{href}}" class='column-push' title='Open'>{{title}}</a></li>
|
25
|
+
{{/items}}
|
26
|
+
</ol>
|
27
|
+
</script>
|
28
|
+
|
29
|
+
<script id="template-list" type="x-tmpl-mustache">
|
30
|
+
<h1>
|
31
|
+
{{page_title}}
|
32
|
+
</h1>
|
33
|
+
<p class='new-document-btn-wrap'>
|
34
|
+
<a href="<%= request.script_name %>/form/{{dasherized_class_name}}{{#new_data}}?{{new_data}}{{/new_data}}" class='column-push new-document-btn' title='Create'>+</a>
|
35
|
+
</p>
|
36
|
+
<ol class='documents' data-sort-field='{{sort_field}}' data-sort-url='<%= request.script_name %>/api/{{dasherized_class_name}}'>
|
37
|
+
{{#items}}{{#custom_partial_or_default}}template_document{{/custom_partial_or_default}}{{/items}}
|
38
|
+
</ol>
|
39
|
+
</script>
|
40
|
+
|
41
|
+
<script id="template-document" type="x-tmpl-mustache">
|
42
|
+
<li class='admin-list-item' data-id='{{id}}'>
|
43
|
+
<header>
|
44
|
+
{{#sort_field}}
|
45
|
+
<span class='handle' title='Drag and drop'>↕</span>
|
46
|
+
{{/sort_field}}
|
47
|
+
<button type='button' class='admin-delete' title='Delete' value='<%= request.script_name %>/api/{{admin_url}}'>×</button>
|
48
|
+
</header>
|
49
|
+
<a href="<%= request.script_name %>/form/{{admin_url}}" class='column-push' title='Edit'>
|
50
|
+
{{title}}
|
51
|
+
{{#image_url}}
|
52
|
+
<br />
|
53
|
+
<img src='{{image_url}}{{cache_buster}}' alt='{{title}}' width='300' />
|
54
|
+
{{/image_url}}
|
55
|
+
</a>
|
56
|
+
<ol class='local-menu'>
|
57
|
+
{{#local_menu}}
|
58
|
+
<li>→ <a href="{{href}}" class='column-push' title='Open'>{{title}}</a></li>
|
59
|
+
{{/local_menu}}
|
60
|
+
</ol>
|
61
|
+
</li>
|
62
|
+
</script>
|
63
|
+
|
64
|
+
<script id="template-form" type="x-tmpl-mustache">
|
65
|
+
<h1>{{page_title}}</h1>
|
66
|
+
<form action="<%= request.script_name %>/api/{{admin_url}}" method="POST" accept-charset="utf-8" class='admin-{{#is_new}}post{{/is_new}}{{^is_new}}put{{/is_new}}'>
|
67
|
+
{{#custom_partial_or_default}}template_form_fields{{/custom_partial_or_default}}
|
68
|
+
{{^is_new}}
|
69
|
+
<input type="hidden" name="_method" value="PUT" />
|
70
|
+
{{/is_new}}
|
71
|
+
<input type="submit" value="{{#is_new}}Create{{/is_new}}{{^is_new}}Update{{/is_new}}" />
|
72
|
+
</form>
|
73
|
+
</script>
|
74
|
+
|
75
|
+
<script id="template-nested-form" type="x-tmpl-mustache">
|
76
|
+
<li>
|
77
|
+
<header>
|
78
|
+
<span class='handle' title='Drag and drop'>↕</span><button type='button' class='admin-delete-nested' title='Delete'>×</button>
|
79
|
+
</header>
|
80
|
+
{{#custom_partial_or_default}}template_form_fields{{/custom_partial_or_default}}
|
81
|
+
</li>
|
82
|
+
</script>
|
83
|
+
|
84
|
+
<script id="template-form-fields" type="x-tmpl-mustache">
|
85
|
+
{{#fields}}
|
86
|
+
{{>template_form_field}}
|
87
|
+
{{/fields}}
|
88
|
+
</script>
|
89
|
+
|
90
|
+
<script id="template-form-field" type="x-tmpl-mustache">
|
91
|
+
{{#wrap}}
|
92
|
+
<div class='field' data-field-name='{{field_name}}'>
|
93
|
+
<label>{{label}}</label>
|
94
|
+
<br>
|
95
|
+
{{#adapted_field}}{{/adapted_field}}
|
96
|
+
</div>
|
97
|
+
{{/wrap}}
|
98
|
+
{{^wrap}}
|
99
|
+
{{#adapted_field}}{{/adapted_field}}
|
100
|
+
{{/wrap}}
|
101
|
+
</script>
|
102
|
+
|
103
|
+
<script id="template-string-field" type="x-tmpl-mustache">
|
104
|
+
<input name='{{input_name}}' value='{{input_value}}' {{#required}}required{{/required}}{{{build_input_attributes}}} />
|
105
|
+
</script>
|
106
|
+
|
107
|
+
<script id="template-text-field" type="x-tmpl-mustache">
|
108
|
+
<textarea name='{{input_name}}' {{#required}}required{{/required}}{{{build_input_attributes}}}>{{input_value}}</textarea>
|
109
|
+
</script>
|
110
|
+
|
111
|
+
<script id="template-boolean-field" type="x-tmpl-mustache">
|
112
|
+
<select name='{{input_name}}' {{{build_input_attributes}}}>
|
113
|
+
<option value='false' {{^input_value}}selected{{/input_value}}>No</option>
|
114
|
+
<option value='true' {{#input_value}}selected{{/input_value}}>Yes</option>
|
115
|
+
</select>
|
116
|
+
</script>
|
117
|
+
|
118
|
+
<script id="template-select-field" type="x-tmpl-mustache">
|
119
|
+
<select name='{{input_name}}' {{#multiple}}multiple{{/multiple}}{{{build_input_attributes}}}>
|
120
|
+
{{#select_options}}
|
121
|
+
<option value='{{value}}' {{#selected}}selected{{/selected}}>{{description}}</option>
|
122
|
+
{{/select_options}}
|
123
|
+
</select>
|
124
|
+
</script>
|
125
|
+
|
126
|
+
<script id="template-attachment-field" type="x-tmpl-mustache">
|
127
|
+
{{#url}}
|
128
|
+
<img src='{{url}}{{cache_buster}}' alt='Preview' width='150' />
|
129
|
+
<button class='attachment-deleter'>x</button>
|
130
|
+
<br />
|
131
|
+
{{/url}}
|
132
|
+
<input type='file' name='{{input_name}}' {{{build_input_atrributes}}} />
|
133
|
+
</script>
|
134
|
+
|
135
|
+
<script id="template-list-field" type="x-tmpl-mustache">
|
136
|
+
<fieldset class='field' data-field-name='{{field_name}}'>
|
137
|
+
<legend>{{label}}</legend>
|
138
|
+
{{#dasherized_class_name}}
|
139
|
+
<p>
|
140
|
+
<a href="<%= request.script_name %>/form/{{dasherized_class_name}}?input_name_prefix={{input_name}}[]&nested=true" class='new-nested-document-btn'>+</a>
|
141
|
+
</p>
|
142
|
+
{{/dasherized_class_name}}
|
143
|
+
<ol class='nested-documents'>
|
144
|
+
{{#items}}
|
145
|
+
{{>template_nested_form}}
|
146
|
+
{{/items}}
|
147
|
+
</ol>
|
148
|
+
</fieldset>
|
149
|
+
</script>
|
150
|
+
|
151
|
+
<!-- JS -->
|
152
|
+
<script src="//code.jquery.com/jquery-1.10.2.min.js" type="text/javascript" charset="utf-8"></script>
|
153
|
+
<script src="<%= request.script_name %>/__assets__/js/mustache.js" type="text/javascript" charset="utf-8"></script>
|
154
|
+
<script src="<%= request.script_name %>/__assets__/js/columnav.js" type="text/javascript" charset="utf-8"></script>
|
155
|
+
<script src="<%= request.script_name %>/__assets__/js/sortable.js" type="text/javascript" charset="utf-8"></script>
|
156
|
+
<script type="text/javascript">
|
157
|
+
window.admin_path = "<%= request.script_name %>";
|
158
|
+
window.index_path = "<%= settings.index_path %>";
|
159
|
+
</script>
|
160
|
+
<script src="<%= request.script_name %>/__assets__/js/main.js" type="text/javascript" charset="utf-8"></script>
|
161
|
+
</body>
|
162
|
+
</html>
|
163
|
+
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'web_utils'
|
3
|
+
require 'populate_me/version'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
class PopulateMe::API < Sinatra::Base
|
7
|
+
|
8
|
+
use Rack::MethodOverride
|
9
|
+
|
10
|
+
set :show_exceptions, false
|
11
|
+
|
12
|
+
before do
|
13
|
+
content_type :json
|
14
|
+
end
|
15
|
+
|
16
|
+
after do
|
17
|
+
redirect(params['_destination']) unless params['_destination'].nil?
|
18
|
+
end
|
19
|
+
|
20
|
+
get '/' do
|
21
|
+
status 200
|
22
|
+
{'success'=>true}.to_json
|
23
|
+
end
|
24
|
+
|
25
|
+
get '/version' do
|
26
|
+
status 200
|
27
|
+
{'success'=>true, 'version'=>PopulateMe::VERSION}.to_json
|
28
|
+
end
|
29
|
+
|
30
|
+
get '/:model' do
|
31
|
+
end
|
32
|
+
|
33
|
+
post '/:model' do
|
34
|
+
model_class = resolve_model_class params[:model]
|
35
|
+
model_instance = model_class.new.set_from_hash((params[:data]||{}), typecast: true)
|
36
|
+
if model_instance.valid?
|
37
|
+
model_instance.save
|
38
|
+
status 201
|
39
|
+
{
|
40
|
+
'success'=>true,'message'=>'Created Successfully',
|
41
|
+
'data'=>model_instance.to_h
|
42
|
+
}.to_json
|
43
|
+
else
|
44
|
+
status 400
|
45
|
+
{
|
46
|
+
'success'=>false,'message'=>'Invalid Document',
|
47
|
+
'data'=>model_instance.error_report
|
48
|
+
}.to_json
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
put '/:model' do
|
53
|
+
pass unless params[:action]=='sort'
|
54
|
+
model_class = resolve_model_class params[:model]
|
55
|
+
model_class.set_indexes(params[:field].to_sym,params[:ids])
|
56
|
+
{'success'=>true,'message'=>'Sorted Successfully'}.to_json
|
57
|
+
end
|
58
|
+
|
59
|
+
get '/:model/:id' do
|
60
|
+
model_class = resolve_model_class params[:model]
|
61
|
+
model_instance = resolve_model_instance model_class, params[:id]
|
62
|
+
{'success'=>true,'data'=>model_instance.to_h}.to_json
|
63
|
+
end
|
64
|
+
|
65
|
+
put '/:model/:id' do
|
66
|
+
model_class = resolve_model_class params[:model]
|
67
|
+
model_instance = resolve_model_instance model_class, params[:id]
|
68
|
+
model_instance.set_from_hash params[:data], typecast: true
|
69
|
+
if model_instance.valid?
|
70
|
+
model_instance.save
|
71
|
+
{
|
72
|
+
'success'=>true,'message'=>'Updated Successfully',
|
73
|
+
'data'=>model_instance.to_h
|
74
|
+
}.to_json
|
75
|
+
else
|
76
|
+
status 400
|
77
|
+
{
|
78
|
+
'success'=>false,'message'=>'Invalid Document',
|
79
|
+
'data'=>model_instance.error_report
|
80
|
+
}.to_json
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
delete '/:model/:id' do
|
85
|
+
model_class = resolve_model_class params[:model]
|
86
|
+
model_instance = resolve_model_instance model_class, params[:id]
|
87
|
+
model_instance.delete
|
88
|
+
{'success'=>true,'message'=>'Deleted Successfully','data'=>model_instance.to_h}.to_json
|
89
|
+
end
|
90
|
+
|
91
|
+
not_found do
|
92
|
+
response.headers['X-Cascade'] = 'pass'
|
93
|
+
{'success'=>false,'message'=>'Not Found'}.to_json
|
94
|
+
end
|
95
|
+
|
96
|
+
error do
|
97
|
+
puts
|
98
|
+
puts env['sinatra.error'].inspect
|
99
|
+
puts
|
100
|
+
{'success'=>false,'message'=>env['sinatra.error'].message}.to_json
|
101
|
+
end
|
102
|
+
|
103
|
+
module Helpers
|
104
|
+
|
105
|
+
include WebUtils
|
106
|
+
|
107
|
+
def resolve_model_class name
|
108
|
+
model_class = resolve_dasherized_class_name(name) rescue nil
|
109
|
+
halt(404) unless model_class.respond_to?(:admin_get)
|
110
|
+
model_class
|
111
|
+
end
|
112
|
+
|
113
|
+
def resolve_model_instance model_class, id
|
114
|
+
instance = model_class.admin_get id
|
115
|
+
halt(404) if instance.nil?
|
116
|
+
instance
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
helpers Helpers
|
122
|
+
|
123
|
+
end
|
124
|
+
|
@@ -0,0 +1,182 @@
|
|
1
|
+
require 'web_utils'
|
2
|
+
require 'populate_me/variation'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'ostruct'
|
5
|
+
|
6
|
+
module PopulateMe
|
7
|
+
|
8
|
+
class Attachment
|
9
|
+
|
10
|
+
class << self
|
11
|
+
|
12
|
+
attr_accessor :settings
|
13
|
+
|
14
|
+
# inheritable settings
|
15
|
+
def set name, value
|
16
|
+
self.settings[name] = value
|
17
|
+
end
|
18
|
+
|
19
|
+
def inherited sub
|
20
|
+
super
|
21
|
+
sub.settings = settings.dup
|
22
|
+
sub::Middleware.parent = sub
|
23
|
+
end
|
24
|
+
|
25
|
+
def middleware
|
26
|
+
Rack::Static
|
27
|
+
end
|
28
|
+
|
29
|
+
def middleware_options
|
30
|
+
[
|
31
|
+
{
|
32
|
+
urls: [settings.url_prefix],
|
33
|
+
root: settings.root
|
34
|
+
}
|
35
|
+
]
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
attr_accessor :document, :field
|
41
|
+
|
42
|
+
def initialize doc, field
|
43
|
+
@document = doc
|
44
|
+
@field = field
|
45
|
+
end
|
46
|
+
|
47
|
+
def settings
|
48
|
+
self.class.settings
|
49
|
+
end
|
50
|
+
|
51
|
+
def field_value
|
52
|
+
self.document.__send__(field)
|
53
|
+
end
|
54
|
+
|
55
|
+
def variations
|
56
|
+
self.document.class.fields[self.field][:variations]
|
57
|
+
end
|
58
|
+
|
59
|
+
def field_filename variation_name=:original
|
60
|
+
return nil if WebUtils.blank?(self.field_value)
|
61
|
+
return self.field_value if variation_name==:original
|
62
|
+
v = self.variations.find{|var|var.name==variation_name}
|
63
|
+
WebUtils.filename_variation(self.field_value, v.name, v.ext)
|
64
|
+
end
|
65
|
+
|
66
|
+
def attachee_prefix
|
67
|
+
WebUtils.dasherize_class_name self.document.class.name
|
68
|
+
end
|
69
|
+
|
70
|
+
def url variation_name=:original
|
71
|
+
return nil if WebUtils.blank?(self.field_filename(variation_name))
|
72
|
+
"#{settings.url_prefix.sub(/\/$/,'')}/#{self.attachee_prefix}/#{self.field_filename(variation_name)}"
|
73
|
+
end
|
74
|
+
|
75
|
+
def location_root
|
76
|
+
File.join(
|
77
|
+
settings.root,
|
78
|
+
settings.url_prefix,
|
79
|
+
self.attachee_prefix
|
80
|
+
)
|
81
|
+
end
|
82
|
+
|
83
|
+
def location variation_name=:original
|
84
|
+
self.location_for_filename self.field_filename(variation_name)
|
85
|
+
end
|
86
|
+
def location_for_filename filename
|
87
|
+
File.join self.location_root, filename
|
88
|
+
end
|
89
|
+
|
90
|
+
def ensure_local_path
|
91
|
+
self.location
|
92
|
+
end
|
93
|
+
|
94
|
+
def create form_hash
|
95
|
+
self.delete
|
96
|
+
future_field_value = perform_create form_hash
|
97
|
+
create_variations form_hash.merge({future_field_value: future_field_value})
|
98
|
+
future_field_value
|
99
|
+
end
|
100
|
+
|
101
|
+
def create_variations hash
|
102
|
+
return if self.variations.nil?
|
103
|
+
tmppath = hash[:tempfile].path
|
104
|
+
path = self.location_for_filename hash[:future_field_value]
|
105
|
+
FileUtils.mkdir_p(File.dirname(path))
|
106
|
+
variations.each do |v|
|
107
|
+
self.delete v.name
|
108
|
+
v_path = WebUtils.filename_variation path, v.name, v.ext
|
109
|
+
v.job.call tmppath, v_path
|
110
|
+
self.perform_create hash.merge({variation: v, variation_path: v_path})
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def perform_create hash
|
115
|
+
return File.basename(hash[:variation_path]) unless WebUtils.blank?(hash[:variation_path])
|
116
|
+
# Rack 1.6 deletes multipart files after request
|
117
|
+
# So we have to create a copy
|
118
|
+
FileUtils.mkdir_p self.location_root
|
119
|
+
tmppath = hash[:tempfile].path
|
120
|
+
unique_prefix = File.basename(tmppath, File.extname(tmppath))
|
121
|
+
unique_filename = "#{unique_prefix}-#{hash[:filename]}"
|
122
|
+
path = self.location_for_filename unique_filename
|
123
|
+
FileUtils.copy_entry(tmppath, path)
|
124
|
+
unique_filename
|
125
|
+
end
|
126
|
+
|
127
|
+
def deletable? variation_name=:original
|
128
|
+
!WebUtils.blank?(self.field_filename(variation_name)) and File.exist?(self.location(variation_name))
|
129
|
+
end
|
130
|
+
|
131
|
+
def delete variation_name=:original
|
132
|
+
if self.deletable?(variation_name)
|
133
|
+
perform_delete variation_name
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def delete_all
|
138
|
+
to_delete = [:original] + variations.map(&:name)
|
139
|
+
to_delete.each do |v_name|
|
140
|
+
self.delete v_name
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def perform_delete variation_name=:original
|
145
|
+
FileUtils.rm self.location(variation_name)
|
146
|
+
end
|
147
|
+
|
148
|
+
self.settings = OpenStruct.new
|
149
|
+
set :root, File.join(Dir.tmpdir, 'populate-me')
|
150
|
+
set :url_prefix, '/attachment'
|
151
|
+
FileUtils.mkdir_p self.settings.root
|
152
|
+
|
153
|
+
class Middleware
|
154
|
+
|
155
|
+
# Used for proxing a rack middleware adapted to the attachment system.
|
156
|
+
# The options are then taken from the attachment class.
|
157
|
+
# It can be used in rackup file as:
|
158
|
+
#
|
159
|
+
# use AtachmentClass::Middleware
|
160
|
+
|
161
|
+
class << self
|
162
|
+
attr_accessor :parent
|
163
|
+
end
|
164
|
+
self.parent = PopulateMe::Attachment
|
165
|
+
def parent
|
166
|
+
self.class.parent
|
167
|
+
end
|
168
|
+
|
169
|
+
def initialize app
|
170
|
+
@proxied = parent.middleware.new(app,*parent.middleware_options)
|
171
|
+
end
|
172
|
+
|
173
|
+
def call env
|
174
|
+
@proxied.call env
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
178
|
+
|
179
|
+
end
|
180
|
+
|
181
|
+
end
|
182
|
+
|