attachy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +3 -0
  3. data/LICENSE +21 -0
  4. data/README.md +108 -0
  5. data/lib/assets/javascripts/attachy.js +289 -0
  6. data/lib/attachy.rb +13 -0
  7. data/lib/attachy/builders/attachy/form_builder.rb +15 -0
  8. data/lib/attachy/engine.rb +16 -0
  9. data/lib/attachy/helpers/attachy/view_helper.rb +31 -0
  10. data/lib/attachy/models/attachy/extension.rb +79 -0
  11. data/lib/attachy/models/attachy/file.rb +59 -0
  12. data/lib/attachy/models/attachy/viewer.rb +170 -0
  13. data/lib/attachy/version.rb +3 -0
  14. data/lib/generators/attachy/install_generator.rb +25 -0
  15. data/lib/generators/attachy/templates/config/attachy.yml.erb +15 -0
  16. data/lib/generators/attachy/templates/db/migrate/create_attachy_files_table.rb +19 -0
  17. data/lib/generators/attachy/templates/public/cloudinary_cors.html +46 -0
  18. data/spec/builders/attachy/form_builder/attachy_content_spec.rb +35 -0
  19. data/spec/builders/attachy/form_builder/attachy_file_field_spec.rb +23 -0
  20. data/spec/builders/attachy/form_builder/attachy_spec.rb +35 -0
  21. data/spec/factories/attachy/file.rb +12 -0
  22. data/spec/factories/user.rb +5 -0
  23. data/spec/helpers/attachy/attachy_content_spec.rb +21 -0
  24. data/spec/helpers/attachy/attachy_file_field_spec.rb +21 -0
  25. data/spec/helpers/attachy/attachy_spec.rb +35 -0
  26. data/spec/models/attachy/callback/destroy_file_spec.rb +55 -0
  27. data/spec/models/attachy/callback/remove_tmp_tag_spec.rb +11 -0
  28. data/spec/models/attachy/extension/user/avatar_spec.rb +91 -0
  29. data/spec/models/attachy/extension/user/photos_spec.rb +88 -0
  30. data/spec/models/attachy/file/config_spec.rb +11 -0
  31. data/spec/models/attachy/file/default_spec.rb +26 -0
  32. data/spec/models/attachy/file/path_spec.rb +16 -0
  33. data/spec/models/attachy/file/transform_spec.rb +86 -0
  34. data/spec/models/attachy/file_spec.rb +14 -0
  35. data/spec/models/attachy/viewer/attachments_spec.rb +28 -0
  36. data/spec/models/attachy/viewer/button_label_options_spec.rb +15 -0
  37. data/spec/models/attachy/viewer/button_label_spec.rb +34 -0
  38. data/spec/models/attachy/viewer/content_options_spec.rb +29 -0
  39. data/spec/models/attachy/viewer/content_spec.rb +62 -0
  40. data/spec/models/attachy/viewer/field_options_spec.rb +18 -0
  41. data/spec/models/attachy/viewer/field_spec.rb +56 -0
  42. data/spec/models/attachy/viewer/file_button_options_spec.rb +18 -0
  43. data/spec/models/attachy/viewer/file_button_spec.rb +57 -0
  44. data/spec/models/attachy/viewer/file_field_options_spec.rb +90 -0
  45. data/spec/models/attachy/viewer/file_field_spec.rb +25 -0
  46. data/spec/models/attachy/viewer/hidden_field_spec.rb +22 -0
  47. data/spec/models/attachy/viewer/image_spec.rb +170 -0
  48. data/spec/models/attachy/viewer/link_options_spec.rb +18 -0
  49. data/spec/models/attachy/viewer/link_spec.rb +131 -0
  50. data/spec/models/attachy/viewer/node_options_spec.rb +18 -0
  51. data/spec/models/attachy/viewer/node_spec.rb +134 -0
  52. data/spec/models/attachy/viewer/nodes_spec.rb +21 -0
  53. data/spec/models/attachy/viewer/remove_button_options_spec.rb +18 -0
  54. data/spec/models/attachy/viewer/transform_spec.rb +44 -0
  55. data/spec/models/attachy/viewer/value_spec.rb +83 -0
  56. data/spec/models/user_spec.rb +9 -0
  57. data/spec/rails_helper.rb +11 -0
  58. data/spec/support/common.rb +20 -0
  59. data/spec/support/database_cleaner.rb +19 -0
  60. data/spec/support/db/migrate/create_users_table.rb +7 -0
  61. data/spec/support/factory_girl.rb +7 -0
  62. data/spec/support/html_matchers.rb +5 -0
  63. data/spec/support/migrate.rb +4 -0
  64. data/spec/support/models/user.rb +5 -0
  65. data/spec/support/shoulda.rb +8 -0
  66. metadata +365 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a405801b1f0c5c745e3723b97320273d1768ba2f
4
+ data.tar.gz: 3f2174490f3404f21a3d3f4adefe663f28789aa4
5
+ SHA512:
6
+ metadata.gz: 33f314c8a0bc058f7ea0f315296d8088a77ae1c256114e8a964cd70f7748c018fad658095bcc0dae82d7bbbeed789cfed0ff42b8dfa102756b4238768387344e
7
+ data.tar.gz: cc7264176e05f9947037cf0fe79914738dcb391276fb1395b8f3a63417a565ae76df49ee1b84ea7ae2d593c537c58695bd7861263473d54f2054ac9fc4956d50
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ # 0.1.0
2
+
3
+ - Release.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Washington Botelho
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # Attachy
2
+
3
+ Attachments handler for Rails via Cloudinary
4
+
5
+ ## Desciption
6
+
7
+ Send files from your browser directly to Cloudinary.
8
+ Attachy will generate a `has_one` or `has_many` files (photos) to your model
9
+ with no need to change your model schema!
10
+
11
+ ## Install
12
+
13
+ Extracts the necessary files including a migrate that create a table used
14
+ to keep your file metadata. You can choose a [Default Image](#default-image) via params 'version', `public_id` and 'format'.
15
+
16
+ ```bash
17
+ rails g attachy:install
18
+ ```
19
+
20
+ Then execute the migrations to create the `attachy_files` table.
21
+
22
+ ```bash
23
+ rake db:migrate
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ ### Model
29
+
30
+ Upload a single image that will be overrided on each upload:
31
+
32
+ ```ruby
33
+ class User < ApplicationRecord
34
+ has_attachment :avatar
35
+ end
36
+ ```
37
+
38
+ Upload a couple of images that will be added on each upload:
39
+
40
+ ```ruby
41
+ class User < ApplicationRecord
42
+ has_attachments :photos
43
+ end
44
+ ```
45
+
46
+ ### View
47
+
48
+ Expose your Cloudinary credentials on your layout:
49
+
50
+ ```html
51
+ <%= cloudinary_js_config %>s
52
+ ```
53
+
54
+ Into your form the upload field:
55
+
56
+ ```html
57
+ <%= f.attachy :avatar %>
58
+ ```
59
+
60
+ If you want just show your uploaded image, use:
61
+
62
+ ```html
63
+ <%= attachy_link :avatar, @object %>
64
+ ```
65
+
66
+ It will generate a link to your image with the image inside.
67
+
68
+ ### Assets
69
+
70
+ Includes the `attachy.js` on your js manifest:
71
+
72
+ ```js
73
+ //= require vendor/attachy
74
+ ```
75
+
76
+ Includes the `attachy.sass` on your css manifest:
77
+
78
+ ```js
79
+ /*
80
+ *= require vendor/attachy
81
+ */
82
+ ```
83
+
84
+ ### <a name="default-image"></a> Configurations
85
+
86
+ On your `attachy.yml` configure a default image to show when model has no one:
87
+
88
+ ```js
89
+ format: jpg
90
+ public_id: default
91
+ version: 42
92
+ ```
93
+
94
+ ## Test
95
+
96
+ Before send pull request, check if specs is passing.
97
+
98
+ ```bash
99
+ rspec spec
100
+ ```
101
+
102
+ ## Code Style
103
+
104
+ Check if the code style is good.
105
+
106
+ ```bash
107
+ rubocop --debug --display-cop-names
108
+ ```
@@ -0,0 +1,289 @@
1
+ /*!
2
+ * Attachy - Attachments handler for Rails via Cloudinary
3
+ *
4
+ * The MIT License
5
+ *
6
+ * @author : Washington Botelho
7
+ * @doc : http://wbotelhos.com/attachy
8
+ * @version : 0.1.0
9
+ *
10
+ */
11
+
12
+ ;
13
+ (function($) {
14
+ 'use strict';
15
+
16
+ $.attachy = {
17
+ crop: undefined,
18
+ disableWith: ' %',
19
+ dropZone: undefined,
20
+ fieldName: 'avatar',
21
+ height: undefined,
22
+ progress: true,
23
+ secure: true,
24
+ sequential: true,
25
+ sign_url: true,
26
+ url: '/attachy/url',
27
+ width: undefined,
28
+
29
+ link: {
30
+ crop: undefined,
31
+ height: undefined,
32
+ width: undefined
33
+ }
34
+ }
35
+
36
+ $.fn.attachy = function(options) {
37
+ var settings = $.extend({}, $.attachy, options);
38
+
39
+ return this.each(function() {
40
+ return (new $.attachy.Attachy(this, settings)).create();
41
+ });
42
+ }
43
+
44
+ $.attachy.Attachy = (function() {
45
+ var Attachy = function(field, options) {
46
+ this.field = $(field);
47
+ this.options = options;
48
+ }
49
+
50
+ Attachy.prototype = {
51
+ add: function(file) {
52
+ if (this.multiple) {
53
+ this.files.push(file);
54
+ } else {
55
+ this.files = [file];
56
+ }
57
+
58
+ this.draw(file);
59
+ },
60
+
61
+ backupStatus: function() {
62
+ this.submit.data('label', this.submit.text());
63
+ this.button.data('label', this.button.text());
64
+ },
65
+
66
+ binds: function() {
67
+ this.field.on('fileuploadsend', this.onUploading.bind(this));
68
+ this.field.on('fileuploaddone', this.onDone.bind(this));
69
+ this.field.on('fileuploadprogressall', this.onProgress.bind(this));
70
+ this.field.on('fileuploadalways', this.onAlways.bind(this));
71
+
72
+ this.content.on('click', '.attachy__remove', this.onRemove.bind(this));
73
+ },
74
+
75
+ configure: function() {
76
+ var json = JSON.parse(this.hidden.val());
77
+
78
+ if (!$.isArray(json)) {
79
+ json = [json];
80
+ }
81
+
82
+ this.files = json;
83
+ this.multiple = this.content.data('multiple');
84
+ },
85
+
86
+ create: function() {
87
+ this.scan();
88
+ this.configure();
89
+ this.init();
90
+ this.backupStatus();
91
+ this.binds();
92
+
93
+ return this;
94
+ },
95
+
96
+ changeStatus: function(progress) {
97
+ var label = progress + this.options.disableWith;
98
+
99
+ this.submit.text(label);
100
+ this.button.text(label);
101
+ },
102
+
103
+ draw: function(file) {
104
+ var that = this;
105
+
106
+ this.render(file, function(json) {
107
+ var
108
+ image = that.image(file, json),
109
+ link = that.link(file, image, json),
110
+ remove = that.removeButton();
111
+
112
+ if (that.multiple) {
113
+ var node = $('<li />', { html: link, 'class': 'attachy__node' }).append(remove);
114
+
115
+ that.content.append(node);
116
+ } else {
117
+ that.content.find('li').empty().append(link, remove);
118
+ }
119
+ });
120
+ },
121
+
122
+ image: function(file, json) {
123
+ var
124
+ config = this.imageConfig(file),
125
+ attributes = { alt: config.alt, height: config.height, width: config.width };
126
+
127
+ $.extend({}, attributes, config);
128
+
129
+ if (this.options.sign_url) {
130
+ attributes.src = json.image;
131
+ } else {
132
+ attributes.src = $.cloudinary.url(file.public_id, config);
133
+ }
134
+
135
+ return $('<img />', attributes);
136
+ },
137
+
138
+ imageConfig: function(file) {
139
+ return {
140
+ crop: this.content.data('crop') || this.options.crop,
141
+ format: file.format,
142
+ height: this.content.data('height') || this.options.height,
143
+ publicId: file.public_id,
144
+ version: file.version,
145
+ width: this.content.data('width') || this.options.width
146
+ };
147
+ },
148
+
149
+ init: function() {
150
+ this.field.fileupload({
151
+ dataType: 'json',
152
+ dropZone: this.options.dropZone,
153
+ headers: { 'X-Requested-With': 'XMLHttpRequest' },
154
+ sequentialUploads: this.options.sequential
155
+ });
156
+
157
+ this.field.cloudinary_fileupload();
158
+ },
159
+
160
+ link: function(file, image, json) {
161
+ var attributes = { html: image, 'class': 'attachy__link' };
162
+
163
+ if (this.options.sign_url) {
164
+ attributes.href = json.link;
165
+ } else {
166
+ attributes.href = $.cloudinary.url(file.public_id, this.linkConfig(file));
167
+ }
168
+
169
+ return $('<a />', attributes);
170
+ },
171
+
172
+ linkConfig: function(file) {
173
+ return {
174
+ crop: this.content.data('link-crop') || this.options.link.crop,
175
+ format: file.format,
176
+ height: this.content.data('link-height') || this.options.link.height,
177
+ version: file.version,
178
+ width: this.content.data('link-width') || this.options.link.width
179
+ }
180
+ },
181
+
182
+ onAlways: function() {
183
+ this.wrapper.removeClass('attachy__uploading');
184
+
185
+ this.restoreStatus();
186
+ },
187
+
188
+ onDone: function(_evt, data) {
189
+ var file = data.result;
190
+
191
+ this.add(file);
192
+
193
+ this.updateHidden();
194
+ },
195
+
196
+ onProgress: function(_evt, data) {
197
+ var progress = parseInt(data.loaded / data.total * 100, 10);
198
+
199
+ if (this.options.progress && this.options.disableWith) {
200
+ this.changeStatus(progress);
201
+ }
202
+ },
203
+
204
+ onRemove: function(e) {
205
+ var
206
+ file,
207
+ files = [],
208
+ node = $(e.currentTarget).closest('.attachy__node'),
209
+ image = node.find('img');
210
+
211
+ for (var i = 0; i < this.files.length; i++) {
212
+ file = this.files[i];
213
+
214
+ if (file.public_id !== image.data('publicId')) {
215
+ files.push(file);
216
+ }
217
+ }
218
+
219
+ node.remove();
220
+
221
+ this.files = files;
222
+
223
+ this.updateHidden();
224
+
225
+ // if (this.defaultAvatar) {
226
+ // this.container.html(this.defaultAvatar.clone());
227
+ // }
228
+ },
229
+
230
+ onUploading: function() {
231
+ this.wrapper.addClass('attachy__uploading');
232
+ },
233
+
234
+ removeButton: function() {
235
+ return $('<span />', { html: '&#215;', 'class': 'attachy__remove' });
236
+ },
237
+
238
+ render: function(file, callback) {
239
+ if (this.options.sign_url) {
240
+ var
241
+ linkConfig = this.linkConfig(file),
242
+
243
+ options = $.extend({}, {
244
+ format: file.format,
245
+ public_id: file.public_id,
246
+ secure: this.options.secure,
247
+ sign_url: this.options.sign_url
248
+ }, this.imageConfig(file));
249
+
250
+ for (var key in linkConfig) {
251
+ if (linkConfig.hasOwnProperty(key)) {
252
+ options['link_' + key] = linkConfig[key];
253
+ }
254
+ }
255
+
256
+ var ajax = $.ajax({ data: options, url: this.options.url });
257
+
258
+ ajax.done(callback);
259
+
260
+ ajax.fail(function(response) {
261
+ alert(response.responseText);
262
+ });
263
+ } else {
264
+ callback();
265
+ }
266
+ },
267
+
268
+ restoreStatus: function() {
269
+ this.submit.text(this.submit.data('label'));
270
+ this.button.text(this.button.data('label'));
271
+ },
272
+
273
+ scan: function() {
274
+ this.wrapper = this.field.closest('.attachy');
275
+ this.button = this.wrapper.find('.attachy__button span');
276
+ this.content = this.wrapper.find('.attachy__content');
277
+ this.hidden = this.wrapper.find('input[type="hidden"]')
278
+ this.remove = this.wrapper.find('.attachy__remove');
279
+ this.submit = this.wrapper.closest('form').find(':submit');
280
+ },
281
+
282
+ updateHidden: function() {
283
+ this.hidden.val(JSON.stringify(this.files));
284
+ }
285
+ };
286
+
287
+ return Attachy;
288
+ })();
289
+ })(jQuery);
data/lib/attachy.rb ADDED
@@ -0,0 +1,13 @@
1
+ module Attachy
2
+ ENV_TAG = "attachy_#{Rails.env}".freeze
3
+ TMP_TAG = :attachy_tmp
4
+ end
5
+
6
+ require 'cloudinary'
7
+
8
+ require 'attachy/engine'
9
+ require 'attachy/models/attachy/extension'
10
+ require 'attachy/models/attachy/file'
11
+ require 'attachy/models/attachy/viewer'
12
+
13
+ ActiveRecord::Base.include Attachy::Extension