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,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,94 @@
1
+ <div class='text-page'>
2
+
3
+ <h1>Help</h1>
4
+
5
+ <h2>Overview</h2>
6
+
7
+ <p>The screen is divided in columns and expands on the right as you go further down the hierarchy, a little bit like a file explorer. </p>
8
+
9
+ <%= help_img 'Overview', 'overview.png' %>
10
+
11
+ <p>The very first column displays your login name (e.g. admin). At the bottom you have a "Logout" button so that you can securely finish your working session.</p>
12
+
13
+ <%= help_img 'Logout', 'logout.png' %>
14
+
15
+ <p>And then the second column is your starting point which represents as close as possible the sections of your website.</p>
16
+
17
+ <%= help_img 'Menu', 'menu.png' %>
18
+
19
+ <p>The item you've selected is always blue in order to show you the path you've been through, again like a file explorer.</p>
20
+
21
+ <%= help_img 'Subist', 'sublist.png' %>
22
+
23
+ <h2>List pages</h2>
24
+
25
+ <p>When you have reached a column like the last one on this image, it is a list page.</p>
26
+
27
+ <%= help_img 'List', 'list.png' %>
28
+
29
+ <p>This can be a list of blog articles, a list of images or a list of contact details. The presentation will always be similar. You will see a relevant title, a relevant image (if there is one), and buttons for actions.If you click the title of the item you'll be able to edit the information of this item.</p>
30
+
31
+ <%= help_img 'Edit', 'edit.png' %>
32
+
33
+ <p>The "delete" button allows you to delete the item.</p>
34
+
35
+ <%= help_img 'Delete', 'delete.png' %>
36
+
37
+ <p>While being irreversible and potentially dangerous, the Bureau will warn you and give you the opportunity to cancel before proceeding.</p>
38
+
39
+ <p>When you rollover the top of the item, your cursor will turn into a hand which is a clue that you can re-order the items by drag-and-drop.</p>
40
+
41
+ <%= help_img 'Sort', 'sort.png' %>
42
+
43
+ <p>Sometimes this hand does not appear, it means that the items cannot be sorted manually, generally because they are sorted automatically, alphabetically or by date for example.</p>
44
+
45
+ <p>When you want to create a new item, you can click the "plus" button.</p>
46
+
47
+ <%= help_img 'Create', 'create.png' %>
48
+
49
+ <p>Items will sometimes have other items attached to them. For example a blog article can have a list of images or an artist can have a list of social medias. These are called relationships.</p>
50
+
51
+ <p>When this is the case, you will have links for each relationship at the bottom of the items and when you click on them, the system will bring you to another list column which follows the same structure.</p>
52
+
53
+ <%= help_img 'Children', 'children.png' %>
54
+
55
+ <h2>Form</h2>
56
+
57
+ <p>Once you get to finally edit or create something, the page will be a form like the example below.</p>
58
+
59
+ <%= help_img 'Form', 'form.png' %>
60
+
61
+ <p>This page will probably be quite familiar to you. Field names within forms are meant to be as explicit as possible. For example they also include ideal image dimensions.</p>
62
+
63
+ <p>Press "Save"/"Update" when you are finished editing the item.</p>
64
+
65
+ <%= help_img 'Save', 'save.png' %>
66
+
67
+ <h2>Image specifications</h2>
68
+
69
+ <p>All images on the website should be exported for web in one of the following formats:</p>
70
+
71
+ <p>&mdash; Jpeg<br/>&mdash; PNG (if it requires transparancy)<br/>&mdash; SVG (for logos and anything vector-based)<br/>&mdash; Gif (not recommended unless a short animation is required)</p>
72
+
73
+ <p>You will not be able to upload images above a predifined maximum, usually 1MB. Typically most images saved for web can be compressed down to a file size between 150KB and 500KB.</p>
74
+
75
+ <h2>Text formating</h2>
76
+
77
+ <p>On most text fields, you can add links or make text bold or italic with a simple markup called <a href='https://en.wikipedia.org/wiki/Markdown' target='_blank'>markdown</a>. Here is an example of what you can do:</p>
78
+
79
+ <div class='frame'>
80
+ <p>Markdown is good for *making things look italic* or **making things look bold**. Your can also create links [like this which links to Google](https://www.google.com). Although if you write a URL or an email address, you don't have to do anything because it will be turned into a link automatically anyway.</p>
81
+ </div>
82
+
83
+ <p>Which will be rendered like this:</p>
84
+
85
+ <div class='frame'>
86
+ <p>Markdown is good for <em>making things look italic</em> or <strong>making things look bold</strong>. Your can also create links <a href="https://www.google.com" target='_blank'>like this which links to Google</a>. Although if you write a URL or an email address, you don't have to do anything because it will be turned into a link automatically anyway.</p>
87
+ </div>
88
+
89
+ <p><em>*Surrounding with stars makes text italic*</em><br><strong>**Surrounding text with double stars makes it bold**</strong><br>Links look like: [<a href='javascript:void(0);'>text to link</a>](http://website.com)</p>
90
+
91
+ <p>It should be self explanatory but you can do much more with markdown. If you are interrested, we encourage you to read the <a href='https://daringfireball.net/projects/markdown/syntax' target='_blank'>full documentation</a>.</p>
92
+
93
+ </div>
94
+
@@ -0,0 +1,189 @@
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/jquery-ui.min.css" type="text/css" media='screen' />
8
+ <link rel="stylesheet" href="<%= request.script_name %>/__assets__/css/asmselect.css" type="text/css" media='screen' />
9
+ <link rel="stylesheet" href="<%= request.script_name %>/__assets__/css/main.css" type="text/css" media='screen' />
10
+ <% if settings.respond_to? :custom_css_url %>
11
+ <link rel="stylesheet" href="<%= settings.custom_css_url %>" type="text/css" media='screen' />
12
+ <% end %>
13
+ </head>
14
+ <body>
15
+
16
+ <ol id='finder'><li class='column'>
17
+ <h1>Welcome<br><%= label_for_field user_name %></h1>
18
+ <% if settings.logout_path? %>
19
+ <p><a href="<%= request.script_name %><%= settings.logout_path %>">Logout</a></p>
20
+ <% end %>
21
+ </li></ol>
22
+
23
+ <!-- Templates -->
24
+
25
+ <script id="template-menu" type="x-tmpl-mustache">
26
+ <h1>{{page_title}}</h1>
27
+ <ol class='menu'>
28
+ {{#items}}
29
+ <li><a href="{{href}}" {{#new_page}}target='_blank'{{/new_page}}{{^new_page}}class='column-push'{{/new_page}} title='Open'>{{title}}</a></li>
30
+ {{/items}}
31
+ </ol>
32
+ </script>
33
+
34
+ <script id="template-list" type="x-tmpl-mustache">
35
+ <h1>
36
+ {{page_title}}
37
+ </h1>
38
+ <p class='new-document-btn-wrap'>
39
+ {{#is_polymorphic}}
40
+ <select class='polymorphic_type_values'>
41
+ {{#polymorphic_type_values}}
42
+ <option value='{{.}}'>{{.}}</option>
43
+ {{/polymorphic_type_values}}
44
+ </select>
45
+ &nbsp;
46
+ {{/is_polymorphic}}
47
+ <a href="<%= request.script_name %>/form/{{dasherized_class_name}}{{#new_data}}?{{new_data}}{{/new_data}}" class='column-push new-document-btn' title='Create'>+</a>
48
+ </p>
49
+ <ol class='documents {{#grid_view}}grid{{/grid_view}}' data-sort-field='{{sort_field}}' data-sort-url='<%= request.script_name %>/api/{{dasherized_class_name}}'>
50
+ {{#items}}{{#custom_partial_or_default}}template_document{{/custom_partial_or_default}}{{/items}}
51
+ </ol>
52
+ </script>
53
+
54
+ <script id="template-document" type="x-tmpl-mustache">
55
+ <li class='admin-list-item' data-id='{{id}}'>
56
+ <header class='{{#sort_field}}handle{{/sort_field}}' title='{{#sort_field}}Drag and drop{{/sort_field}}'>
57
+ <button type='button' class='admin-delete' title='Delete' value='<%= request.script_name %>/api/{{admin_url}}'>&times;</button>
58
+ </header>
59
+ <a href="<%= request.script_name %>/form/{{admin_url}}" class='column-push' title='Edit'>
60
+ {{title}}
61
+ {{#image_url}}
62
+ <br />
63
+ <img src='{{image_url}}{{cache_buster}}' alt='{{title}}' width='300' />
64
+ {{/image_url}}
65
+ </a>
66
+ <ol class='local-menu'>
67
+ {{#local_menu}}
68
+ <li>&rarr; <a href="{{href}}" {{#new_page}}target='_blank'{{/new_page}}{{^new_page}}class='column-push'{{/new_page}} title='Open'>{{title}}</a></li>
69
+ {{/local_menu}}
70
+ </ol>
71
+ </li>
72
+ </script>
73
+
74
+ <script id="template-form" type="x-tmpl-mustache">
75
+ <h1>{{page_title}}</h1>
76
+ {{#polymorphic_type}}
77
+ <p>({{polymorphic_type}})</p>
78
+ {{/polymorphic_type}}
79
+ <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}}'>
80
+ {{#custom_partial_or_default}}template_form_fields{{/custom_partial_or_default}}
81
+ {{^is_new}}
82
+ <input type="hidden" name="_method" value="PUT" />
83
+ {{/is_new}}
84
+ <input type="submit" value="{{#is_new}}Create{{/is_new}}{{^is_new}}Update{{/is_new}}" />
85
+ </form>
86
+ </script>
87
+
88
+ <script id="template-nested-form" type="x-tmpl-mustache">
89
+ <li>
90
+ <header>
91
+ <span class='handle handle-button' title='Drag and drop'>&#8597;</span><button type='button' class='admin-delete-nested' title='Delete'>&times;</button>
92
+ </header>
93
+ {{#custom_partial_or_default}}template_form_fields{{/custom_partial_or_default}}
94
+ </li>
95
+ </script>
96
+
97
+ <script id="template-form-fields" type="x-tmpl-mustache">
98
+ {{#fields}}
99
+ {{>template_form_field}}
100
+ {{/fields}}
101
+ </script>
102
+
103
+ <script id="template-form-field" type="x-tmpl-mustache">
104
+ {{#wrap}}
105
+ <div class='field' data-field-name='{{field_name}}'>
106
+ <label>{{label}}</label>
107
+ <br>
108
+ {{#adapted_field}}{{/adapted_field}}
109
+ </div>
110
+ {{/wrap}}
111
+ {{^wrap}}
112
+ {{#adapted_field}}{{/adapted_field}}
113
+ {{/wrap}}
114
+ </script>
115
+
116
+ <script id="template-string-field" type="x-tmpl-mustache">
117
+ <input name='{{input_name}}' value='{{input_value}}' {{#required}}required{{/required}}{{{build_input_attributes}}} />
118
+ </script>
119
+
120
+ <script id="template-text-field" type="x-tmpl-mustache">
121
+ <textarea name='{{input_name}}' {{#required}}required{{/required}}{{{build_input_attributes}}}>{{input_value}}</textarea>
122
+ </script>
123
+
124
+ <script id="template-boolean-field" type="x-tmpl-mustache">
125
+ <select name='{{input_name}}' {{{build_input_attributes}}}>
126
+ <option value='false' {{^input_value}}selected{{/input_value}}>No</option>
127
+ <option value='true' {{#input_value}}selected{{/input_value}}>Yes</option>
128
+ </select>
129
+ </script>
130
+
131
+ <script id="template-select-field" type="x-tmpl-mustache">
132
+ {{#multiple}}
133
+ <input type='hidden' name='{{input_name}}' value='nil' />
134
+ {{/multiple}}
135
+ <select name='{{input_name}}' {{#multiple}}multiple title='?'{{/multiple}}{{{build_input_attributes}}}>
136
+ {{#select_options}}
137
+ <option value='{{value}}' {{#selected}}selected{{/selected}} {{#preview_uri}}data-preview='{{preview_uri}}'{{/preview_uri}}>{{description}}</option>
138
+ {{/select_options}}
139
+ </select>
140
+ <div class='preview-container'></div>
141
+ </script>
142
+
143
+ <script id="template-attachment-field" type="x-tmpl-mustache">
144
+ {{#url}}
145
+ <img src='{{url}}{{cache_buster}}' alt='Preview' width='150' />
146
+ <button class='attachment-deleter'>x</button>
147
+ <br />
148
+ {{/url}}
149
+ <input type='file' name='{{input_name}}' {{#max_size}}data-max-size='{{max_size}}'{{/max_size}} {{{build_input_atrributes}}} />
150
+ </script>
151
+
152
+ <script id="template-list-field" type="x-tmpl-mustache">
153
+ <fieldset class='field' data-field-name='{{field_name}}'>
154
+ <legend>{{label}}</legend>
155
+ {{#dasherized_class_name}}
156
+ <p>
157
+ <a href="<%= request.script_name %>/form/{{dasherized_class_name}}?input_name_prefix={{input_name}}[]&nested=true" class='new-nested-document-btn'>+</a>
158
+ </p>
159
+ {{/dasherized_class_name}}
160
+ <ol class='nested-documents'>
161
+ {{#items}}
162
+ {{>template_nested_form}}
163
+ {{/items}}
164
+ </ol>
165
+ </fieldset>
166
+ </script>
167
+
168
+ <% if settings.respond_to? :custom_templates_view %>
169
+ <%= erb settings.custom_templates_view, layout: false %>
170
+ <% end %>
171
+
172
+ <!-- JS -->
173
+ <script src="//code.jquery.com/jquery-1.10.2.min.js" type="text/javascript" charset="utf-8"></script>
174
+ <script src="<%= request.script_name %>/__assets__/js/jquery-ui.min.js" type="text/javascript" charset="utf-8"></script>
175
+ <script src="<%= request.script_name %>/__assets__/js/mustache.js" type="text/javascript" charset="utf-8"></script>
176
+ <script src="<%= request.script_name %>/__assets__/js/columnav.js" type="text/javascript" charset="utf-8"></script>
177
+ <%# <script src="<%= request.script_name %1>/__assets__/js/sortable.js" type="text/javascript" charset="utf-8"></script> %>
178
+ <script src="<%= request.script_name %>/__assets__/js/asmselect.js" type="text/javascript" charset="utf-8"></script>
179
+ <script type="text/javascript">
180
+ window.admin_path = "<%= request.script_name %>";
181
+ window.index_path = "<%= settings.index_path %>";
182
+ </script>
183
+ <script src="<%= request.script_name %>/__assets__/js/main.js" type="text/javascript" charset="utf-8"></script>
184
+ <% if settings.respond_to? :custom_js_url %>
185
+ <script src="<%= settings.custom_js_url %>" type="text/javascript" charset="utf-8"></script>
186
+ <% end %>
187
+ </body>
188
+ </html>
189
+
@@ -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,186 @@
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 field_options
56
+ self.document.class.fields[self.field]
57
+ end
58
+
59
+ def variations
60
+ self.field_options[:variations] || []
61
+ end
62
+
63
+ def field_filename variation_name=:original
64
+ return nil if WebUtils.blank?(self.field_value)
65
+ return self.field_value if variation_name==:original
66
+ v = self.variations.find{|var|var.name==variation_name}
67
+ WebUtils.filename_variation(self.field_value, v.name, v.ext)
68
+ end
69
+
70
+ def attachee_prefix
71
+ WebUtils.dasherize_class_name self.document.class.name
72
+ end
73
+
74
+ def url variation_name=:original
75
+ return nil if WebUtils.blank?(self.field_filename(variation_name))
76
+ "#{settings.url_prefix.sub(/\/$/,'')}/#{self.attachee_prefix}/#{self.field_filename(variation_name)}"
77
+ end
78
+
79
+ def location_root
80
+ File.join(
81
+ settings.root,
82
+ settings.url_prefix,
83
+ self.attachee_prefix
84
+ )
85
+ end
86
+
87
+ def location variation_name=:original
88
+ self.location_for_filename self.field_filename(variation_name)
89
+ end
90
+ def location_for_filename filename
91
+ File.join self.location_root, filename
92
+ end
93
+
94
+ def ensure_local_path
95
+ self.location
96
+ end
97
+
98
+ def create form_hash
99
+ self.delete
100
+ future_field_value = perform_create form_hash
101
+ create_variations form_hash.merge({future_field_value: future_field_value})
102
+ future_field_value
103
+ end
104
+
105
+ def create_variations hash
106
+ return if self.variations.nil?
107
+ tmppath = hash[:tempfile].path
108
+ path = self.location_for_filename hash[:future_field_value]
109
+ FileUtils.mkdir_p(File.dirname(path))
110
+ variations.each do |v|
111
+ self.delete v.name
112
+ v_path = WebUtils.filename_variation path, v.name, v.ext
113
+ v.job.call tmppath, v_path
114
+ self.perform_create hash.merge({variation: v, variation_path: v_path})
115
+ end
116
+ end
117
+
118
+ def perform_create hash
119
+ return File.basename(hash[:variation_path]) unless WebUtils.blank?(hash[:variation_path])
120
+ # Rack 1.6 deletes multipart files after request
121
+ # So we have to create a copy
122
+ FileUtils.mkdir_p self.location_root
123
+ tmppath = hash[:tempfile].path
124
+ unique_prefix = File.basename(tmppath, File.extname(tmppath))
125
+ unique_filename = "#{unique_prefix}-#{hash[:filename]}"
126
+ path = self.location_for_filename unique_filename
127
+ FileUtils.copy_entry(tmppath, path)
128
+ unique_filename
129
+ end
130
+
131
+ def deletable? variation_name=:original
132
+ !WebUtils.blank?(self.field_filename(variation_name)) and File.exist?(self.location(variation_name))
133
+ end
134
+
135
+ def delete variation_name=:original
136
+ if self.deletable?(variation_name)
137
+ perform_delete variation_name
138
+ end
139
+ end
140
+
141
+ def delete_all
142
+ to_delete = [:original] + variations.map(&:name)
143
+ to_delete.each do |v_name|
144
+ self.delete v_name
145
+ end
146
+ end
147
+
148
+ def perform_delete variation_name=:original
149
+ FileUtils.rm self.location(variation_name)
150
+ end
151
+
152
+ self.settings = OpenStruct.new
153
+ set :root, File.join(Dir.tmpdir, 'populate-me')
154
+ set :url_prefix, '/attachment'
155
+ FileUtils.mkdir_p self.settings.root
156
+
157
+ class Middleware
158
+
159
+ # Used for proxing a rack middleware adapted to the attachment system.
160
+ # The options are then taken from the attachment class.
161
+ # It can be used in rackup file as:
162
+ #
163
+ # use AtachmentClass::Middleware
164
+
165
+ class << self
166
+ attr_accessor :parent
167
+ end
168
+ self.parent = PopulateMe::Attachment
169
+ def parent
170
+ self.class.parent
171
+ end
172
+
173
+ def initialize app
174
+ @proxied = parent.middleware.new(app,*parent.middleware_options)
175
+ end
176
+
177
+ def call env
178
+ @proxied.call env
179
+ end
180
+
181
+ end
182
+
183
+ end
184
+
185
+ end
186
+