attachinary 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +68 -24
- data/config/routes.rb +0 -1
- data/lib/assets/javascripts/attachinary.js.coffee +40 -22
- data/lib/attachinary/orm/active_record.rb +1 -0
- data/lib/attachinary/orm/active_record/extension.rb +18 -75
- data/lib/attachinary/orm/base_extension.rb +63 -0
- data/lib/attachinary/orm/mongoid.rb +1 -0
- data/lib/attachinary/orm/mongoid/extension.rb +20 -70
- data/lib/attachinary/utils.rb +54 -0
- data/lib/attachinary/version.rb +1 -1
- data/lib/attachinary/view_helpers.rb +2 -0
- data/lib/tasks/attachinary.rake +28 -0
- metadata +39 -26
- data/app/controllers/attachinary/files_controller.rb +0 -21
data/README.md
CHANGED
@@ -14,6 +14,7 @@ Why is Attachinary different:
|
|
14
14
|
* **Files are uploaded directly to Cloudinary** completely bypassing your app (without affecting its performance).
|
15
15
|
* **Very easy to use**. Once set up, 1 line is enough to add attachment support to your model. **No migrations, no Uploaders**.
|
16
16
|
* **Lightweight form submission**. Attachinary handles file upload asynchronously and the only thing that is passed to your server is metadata. That makes form postbacks fast and reliable.
|
17
|
+
* Benefits of [jQuery File Upload](https://github.com/blueimp/jQuery-File-Upload/) (**drag'n'drop**, **selecting multiple files**, **progress indicators**.. etc)
|
17
18
|
* All the [benefits of Cloudinary](http://cloudinary.com/documentation/image_transformations) (resizing, cropping, rotating, rounding corners, **face detection**...).
|
18
19
|
|
19
20
|
Attachinary uses [Cloudinary](http://cloudinary.com) service. Gem is structured as mountable rails engine.
|
@@ -52,35 +53,52 @@ Finally, make sure that you have following line in head section of your applicat
|
|
52
53
|
|
53
54
|
Lets say that we want all of our **users** to have single **avatar** and many **photos** in their gallery. We also want *avatar* to be required. We also want to limit the number of photos user can upload to 10. We can declare it like this:
|
54
55
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
56
|
+
```ruby
|
57
|
+
class User < ActiveRecord::Base
|
58
|
+
...
|
59
|
+
has_attachment :avatar, accept: [:jpg, :png, :gif]
|
60
|
+
has_attachments :photos, maximum: 10
|
59
61
|
|
60
|
-
|
61
|
-
|
62
|
-
|
62
|
+
validates :avatar, presence: true
|
63
|
+
# ...
|
64
|
+
end
|
65
|
+
```
|
63
66
|
|
64
67
|
In our `_form.html.erb` template, we need to add only this:
|
65
68
|
|
66
|
-
|
67
|
-
|
69
|
+
```erb
|
70
|
+
<%= f.attachinary_file_field :avatar %>
|
71
|
+
<%= f.attachinary_file_field :photos %>
|
72
|
+
```
|
68
73
|
|
69
74
|
If you're using [SimpleForm](https://github.com/plataformatec/simple_form), you can even shorten this to:
|
70
75
|
|
71
|
-
|
72
|
-
|
76
|
+
```erb
|
77
|
+
<%= f.input :avatar, as: :attachinary %>
|
78
|
+
<%= f.input :photos, as: :attachinary %>
|
79
|
+
```
|
73
80
|
|
74
|
-
Finally, you have to include both
|
81
|
+
Finally, you have to include both required javascript files. In your `application.js`, add following lines:
|
75
82
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
83
|
+
```javascript
|
84
|
+
//= require jquery.ui.widget
|
85
|
+
//= require jquery.iframe-transport
|
86
|
+
//= require jquery.fileupload
|
87
|
+
//= require cloudinary/jquery.cloudinary
|
88
|
+
//= require attachinary
|
89
|
+
```
|
90
|
+
|
91
|
+
If you don't have the jQuery File Upload files, you can use following rake task to fetch (or update) them:
|
92
|
+
|
93
|
+
```
|
94
|
+
rake attachinary:fetch_fileupload
|
95
|
+
```
|
80
96
|
|
81
97
|
And, add this code on document ready:
|
82
98
|
|
83
|
-
|
99
|
+
```javascript
|
100
|
+
$('.attachinary-input').attachinary()
|
101
|
+
```
|
84
102
|
|
85
103
|
Attachinary jquery plugin is based upon [jQuery File Upload plugin](https://github.com/blueimp/jQuery-File-Upload) but without any fancy UI (it leaves it up to you to decorate it).
|
86
104
|
|
@@ -91,24 +109,50 @@ Plugin is fully customizable. It uses John Resig's micro templating in the backg
|
|
91
109
|
|
92
110
|
Here comes the good part. There is no need to transform images on your server. Instead, you can request image transformations directly from Cloudinary. First time you request image, it is created and cached on the Cloudinary server for later use. Here is sample code that you can use in your `_user.html.erb` partial:
|
93
111
|
|
94
|
-
|
95
|
-
|
96
|
-
|
112
|
+
```erb
|
113
|
+
<% if @user.avatar? %>
|
114
|
+
<%= cl_image_tag(@user.avatar.path, { size: '50x50', crop: :fit, gravity: :face }) %>
|
115
|
+
<% end %>
|
97
116
|
|
98
|
-
|
99
|
-
|
100
|
-
|
117
|
+
<% @user.photos.each do |photo| %>
|
118
|
+
<%= cl_image_tag(photo.path, { size: '125x125', crop: :fit }) %>
|
119
|
+
<% end %>
|
120
|
+
```
|
101
121
|
|
102
122
|
Avatar will be automatically cropped to 50x50px to show only user face. You read it right: **face detection** :) All other user photos are just cropped to fit within 125x125.
|
103
123
|
|
104
124
|
Whenever you feel like changing image sizes, you don't need to set rake task that will take forever to re-process thousands of photos. You just change the dimension in your partial and thats it.
|
105
125
|
|
106
126
|
|
127
|
+
### Additional methods
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
# uploading avatar by passing url
|
131
|
+
user.avatar_url = "http://path/to/avatar.jpg"
|
132
|
+
|
133
|
+
# uploading photos by passing multiple urls
|
134
|
+
user.photo_urls = %w[ http://path/to/photo1.jpg http://path/to/photo2.jpg]
|
135
|
+
|
136
|
+
# uploading by passing IO object (e.g. direct file upload)
|
137
|
+
user.avatar = File.open("/path/to/file", 'r')
|
138
|
+
```
|
139
|
+
|
140
|
+
|
141
|
+
### No-JS usage
|
142
|
+
|
143
|
+
If you don't want fancy JS features, all you have to do is just switch to `:input` file field:
|
144
|
+
|
145
|
+
```erb
|
146
|
+
<%= f.input :photo, as: :file %>
|
147
|
+
<%= f.input :images, as: :file, input_html: { multiple: true } %>
|
148
|
+
```
|
149
|
+
|
150
|
+
|
107
151
|
## Conventions
|
108
152
|
|
109
153
|
* always use singular identifier after `has_attachment` (e.g. `has_attachment :photo`)
|
110
154
|
* always use plural identifier after `has_attachments` (e.g. `has_attachments :images`)
|
111
|
-
*
|
155
|
+
* do not use colliding identifiers (e.g. `has_attachment :photo` and `has_attachments :photos`) on same model.
|
112
156
|
|
113
157
|
|
114
158
|
## Requirements and Compatibility
|
data/config/routes.rb
CHANGED
@@ -4,6 +4,7 @@
|
|
4
4
|
index: 0
|
5
5
|
config:
|
6
6
|
disableWith: 'Uploading...'
|
7
|
+
indicateProgress: true
|
7
8
|
invalidFormatMessage: 'Invalid file format'
|
8
9
|
template: """
|
9
10
|
<ul>
|
@@ -37,6 +38,9 @@
|
|
37
38
|
@options = @$input.data('attachinary')
|
38
39
|
@files = @options.files
|
39
40
|
|
41
|
+
@$form = @$input.closest('form')
|
42
|
+
@$submit = @$form.find('input[type=submit]')
|
43
|
+
|
40
44
|
@initFileUpload()
|
41
45
|
@addFilesContainer()
|
42
46
|
@bindEventHandlers()
|
@@ -47,46 +51,55 @@
|
|
47
51
|
@options.field_name = @$input.attr('name')
|
48
52
|
|
49
53
|
options =
|
50
|
-
maxFileSize: 10000000
|
51
54
|
dataType: 'json'
|
52
55
|
paramName: 'file'
|
53
56
|
headers: {"X-Requested-With": "XMLHttpRequest"}
|
57
|
+
dropZone: @$input
|
58
|
+
sequentialUploads: true
|
54
59
|
|
55
60
|
if @$input.attr('accept')
|
56
61
|
options.acceptFileTypes = new RegExp("^#{@$input.attr('accept').split(",").join("|")}$", "i")
|
57
62
|
|
58
63
|
@$input.fileupload(options)
|
59
64
|
|
60
|
-
|
61
65
|
bindEventHandlers: ->
|
62
|
-
@$input.bind '
|
63
|
-
setTimeout (=> @addFile(data.result)), 0 # let 'fileuploadalways' finish
|
64
|
-
|
65
|
-
@$input.bind 'fileuploadstart', (event) =>
|
66
|
-
@$input = $(event.target) # important! changed on every file upload
|
67
|
-
$form = @$input.closest('form')
|
68
|
-
$submit = $form.find('input[type=submit]')
|
69
|
-
|
66
|
+
@$input.bind 'fileuploadsend', (event, data) =>
|
70
67
|
@$input.addClass 'uploading'
|
71
|
-
|
68
|
+
@$form.addClass 'uploading'
|
72
69
|
|
73
70
|
@$input.prop 'disabled', true
|
74
71
|
if @config.disableWith
|
75
|
-
|
76
|
-
|
77
|
-
|
72
|
+
@$submit.data 'old-val', @$submit.val()
|
73
|
+
@$submit.val @config.disableWith
|
74
|
+
@$submit.prop 'disabled', true
|
75
|
+
|
76
|
+
!@maximumReached()
|
77
|
+
|
78
|
+
|
79
|
+
@$input.bind 'fileuploaddone', (event, data) =>
|
80
|
+
@addFile(data.result)
|
78
81
|
|
79
|
-
@$input.bind 'fileuploadalways', (event) =>
|
80
|
-
$form = @$input.closest('form')
|
81
|
-
$submit = $form.find('input[type=submit]')
|
82
82
|
|
83
|
+
@$input.bind 'fileuploadstart', (event) =>
|
84
|
+
# important! changed on every file upload
|
85
|
+
@$input = $(event.target)
|
86
|
+
|
87
|
+
|
88
|
+
@$input.bind 'fileuploadalways', (event) =>
|
83
89
|
@$input.removeClass 'uploading'
|
84
|
-
|
90
|
+
@$form.removeClass 'uploading'
|
85
91
|
|
86
|
-
|
92
|
+
@checkMaximum()
|
87
93
|
if @config.disableWith
|
88
|
-
|
89
|
-
|
94
|
+
@$submit.val @$submit.data('old-val')
|
95
|
+
@$submit.prop 'disabled', false
|
96
|
+
|
97
|
+
|
98
|
+
@$input.bind 'fileuploadprogressall', (e, data) =>
|
99
|
+
progress = parseInt(data.loaded / data.total * 100, 10)
|
100
|
+
if @config.disableWith && @config.indicateProgress
|
101
|
+
@$submit.val "[#{progress}%] #{@config.disableWith}"
|
102
|
+
|
90
103
|
|
91
104
|
addFile: (file) ->
|
92
105
|
if !@options.accept || $.inArray(file.format, @options.accept) != -1
|
@@ -102,11 +115,16 @@
|
|
102
115
|
@checkMaximum()
|
103
116
|
|
104
117
|
checkMaximum: ->
|
105
|
-
if @
|
118
|
+
if @maximumReached()
|
106
119
|
@$input.prop('disabled', true)
|
107
120
|
else
|
108
121
|
@$input.prop('disabled', false)
|
109
122
|
|
123
|
+
maximumReached: ->
|
124
|
+
@options.maximum && @files.length >= @options.maximum
|
125
|
+
|
126
|
+
|
127
|
+
|
110
128
|
addFilesContainer: ->
|
111
129
|
@$filesContainer = $('<div class="attachinary_container">')
|
112
130
|
@$input.after @$filesContainer
|
@@ -1,94 +1,37 @@
|
|
1
|
+
require 'attachinary/utils'
|
2
|
+
|
1
3
|
module Attachinary
|
2
4
|
module Extension
|
5
|
+
include Base
|
3
6
|
|
4
|
-
def
|
5
|
-
options[:
|
6
|
-
attachinary scope, options
|
7
|
-
end
|
8
|
-
|
9
|
-
def has_attachments(scope, options={})
|
10
|
-
options[:single] = false
|
11
|
-
attachinary scope, options
|
12
|
-
end
|
13
|
-
|
14
|
-
def attachinary(scope, options)
|
15
|
-
options.reverse_merge!({
|
16
|
-
accessible: true
|
17
|
-
})
|
18
|
-
|
19
|
-
if options[:single]
|
20
|
-
singular = scope.to_s
|
21
|
-
plural = scope.to_s.pluralize
|
22
|
-
else
|
23
|
-
plural = scope.to_s
|
24
|
-
singular = scope.to_s.singularize
|
25
|
-
end
|
26
|
-
|
27
|
-
relation = "#{singular}_files"
|
7
|
+
def attachinary_orm_definition(options)
|
8
|
+
relation = "#{options[:singular]}_files"
|
28
9
|
|
29
10
|
# has_many :photo_files, ...
|
30
11
|
# has_many :image_files, ...
|
31
12
|
has_many :"#{relation}",
|
32
13
|
as: :attachinariable,
|
33
14
|
class_name: '::Attachinary::File',
|
34
|
-
conditions: { scope: scope.to_s },
|
15
|
+
conditions: { scope: options[:scope].to_s },
|
35
16
|
dependent: :destroy
|
36
17
|
|
37
|
-
# attr_accessible :photo
|
38
|
-
# attr_accessible :images
|
39
|
-
attr_accessible :"#{scope}" if options[:accessible]
|
40
|
-
|
41
|
-
|
42
|
-
# def photo?
|
43
|
-
# photo.present?
|
44
|
-
# end
|
45
|
-
# def images?
|
46
|
-
# images.present?
|
47
|
-
# end
|
48
|
-
define_method :"#{scope}?" do
|
49
|
-
send(:"#{scope}").present?
|
50
|
-
end
|
51
|
-
|
52
|
-
# def photo_metadata
|
53
|
-
# options[:scope] = 'photo'
|
54
|
-
# options[:maximum] = 1 if options[:single]
|
55
|
-
# options
|
56
|
-
# end
|
57
|
-
define_method :"#{scope}_metadata" do
|
58
|
-
options[:scope] = scope
|
59
|
-
options[:maximum] = 1 if options[:single]
|
60
|
-
options
|
61
|
-
end
|
62
18
|
|
63
19
|
# def photo=(file)
|
64
|
-
#
|
65
|
-
#
|
20
|
+
# input = Attachinary::Utils.process_input(input)
|
21
|
+
# if input.blank?
|
22
|
+
# photo_files.clear
|
66
23
|
# else
|
67
|
-
#
|
68
|
-
#
|
69
|
-
# files = ... parse JSON and MAP to array of Attachinary::File ..
|
70
|
-
# self.photo_files = files
|
71
|
-
# else
|
72
|
-
# self.photo_files = [file].flatten
|
73
|
-
# end
|
24
|
+
# files = [input].flatten
|
25
|
+
# self.photo_files = files
|
74
26
|
# end
|
75
27
|
# end
|
76
|
-
define_method "#{scope}=" do |
|
77
|
-
|
28
|
+
define_method "#{options[:scope]}=" do |input|
|
29
|
+
input = Attachinary::Utils.process_input(input, options[:scope])
|
30
|
+
if input.nil?
|
78
31
|
send("#{relation}").clear
|
79
32
|
else
|
80
|
-
|
81
|
-
|
82
|
-
files = [JSON.parse(file)].flatten.map do |data|
|
83
|
-
data = data.slice(*Attachinary::File.attr_accessible[:default].to_a)
|
84
|
-
Attachinary::File.new(data) do |f|
|
85
|
-
f.scope = scope.to_s
|
86
|
-
end
|
87
|
-
end
|
88
|
-
send("#{relation}=", files)
|
89
|
-
else
|
90
|
-
send("#{relation}=", [file].flatten)
|
91
|
-
end
|
33
|
+
files = [input].flatten
|
34
|
+
send("#{relation}=", files)
|
92
35
|
end
|
93
36
|
end
|
94
37
|
|
@@ -97,7 +40,7 @@ module Attachinary
|
|
97
40
|
# def photo
|
98
41
|
# photo_files.first
|
99
42
|
# end
|
100
|
-
define_method "#{scope}" do
|
43
|
+
define_method "#{options[:scope]}" do
|
101
44
|
send("#{relation}").first
|
102
45
|
end
|
103
46
|
|
@@ -105,7 +48,7 @@ module Attachinary
|
|
105
48
|
# def images
|
106
49
|
# image_files
|
107
50
|
# end
|
108
|
-
define_method "#{scope}" do
|
51
|
+
define_method "#{options[:scope]}" do
|
109
52
|
send("#{relation}")
|
110
53
|
end
|
111
54
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'attachinary/utils'
|
2
|
+
|
3
|
+
module Attachinary
|
4
|
+
module Extension
|
5
|
+
module Base
|
6
|
+
|
7
|
+
def has_attachment(scope, options={})
|
8
|
+
attachinary options.merge(single: true, scope: scope)
|
9
|
+
end
|
10
|
+
|
11
|
+
def has_attachments(scope, options={})
|
12
|
+
attachinary options.merge(single: false, scope: scope)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
def attachinary(options)
|
17
|
+
options = Attachinary::Utils.process_options(options)
|
18
|
+
|
19
|
+
attachinary_orm_definition(options)
|
20
|
+
|
21
|
+
# attr_accessible :photo
|
22
|
+
# attr_accessible :images
|
23
|
+
attr_accessible :"#{options[:scope]}" if options[:accessible]
|
24
|
+
|
25
|
+
# def photo?
|
26
|
+
# photo.present?
|
27
|
+
# end
|
28
|
+
# def images?
|
29
|
+
# images.present?
|
30
|
+
# end
|
31
|
+
define_method :"#{options[:scope]}?" do
|
32
|
+
send(:"#{options[:scope]}").present?
|
33
|
+
end
|
34
|
+
|
35
|
+
# def photo_metadata
|
36
|
+
# options
|
37
|
+
# end
|
38
|
+
define_method :"#{options[:scope]}_metadata" do
|
39
|
+
options
|
40
|
+
end
|
41
|
+
|
42
|
+
if options[:single]
|
43
|
+
# def photo_url=(url)
|
44
|
+
# ...
|
45
|
+
# end
|
46
|
+
define_method :"#{options[:scope]}_url=" do |url|
|
47
|
+
send(:"#{options[:scope]}=", Cloudinary::Uploader.upload(url))
|
48
|
+
end
|
49
|
+
|
50
|
+
else
|
51
|
+
# def image_urls=(urls)
|
52
|
+
# ...
|
53
|
+
# end
|
54
|
+
define_method :"#{options[:singular]}_urls=" do |urls|
|
55
|
+
send(:"#{options[:scope]}=", urls.map { |url| Cloudinary::Uploader.upload(url) })
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -1,93 +1,43 @@
|
|
1
|
+
require 'attachinary/utils'
|
2
|
+
|
1
3
|
module Attachinary
|
2
4
|
module Extension
|
5
|
+
include Base
|
3
6
|
|
4
|
-
def
|
5
|
-
options[:single] = true
|
6
|
-
attachinary scope, options
|
7
|
-
end
|
8
|
-
|
9
|
-
def has_attachments(scope, options={})
|
10
|
-
options[:single] = false
|
11
|
-
attachinary scope, options
|
12
|
-
end
|
13
|
-
|
14
|
-
def attachinary(scope, options)
|
15
|
-
options.reverse_merge!({
|
16
|
-
accessible: true
|
17
|
-
})
|
18
|
-
|
19
|
-
if options[:single]
|
20
|
-
singular = scope.to_s
|
21
|
-
plural = scope.to_s.pluralize
|
22
|
-
else
|
23
|
-
plural = scope.to_s
|
24
|
-
singular = scope.to_s.singularize
|
25
|
-
end
|
26
|
-
|
7
|
+
def attachinary_orm_definition(options)
|
27
8
|
if options[:single]
|
28
9
|
# embeds_on :photo, ...
|
29
|
-
embeds_one :"#{scope}",
|
10
|
+
embeds_one :"#{options[:scope]}",
|
30
11
|
as: :attachinariable,
|
31
12
|
class_name: '::Attachinary::File',
|
32
13
|
cascade_callbacks: true
|
33
14
|
else
|
34
15
|
# embeds_many :images, ...
|
35
|
-
embeds_many :"#{scope}",
|
16
|
+
embeds_many :"#{options[:scope]}",
|
36
17
|
as: :attachinariable,
|
37
18
|
class_name: '::Attachinary::File',
|
38
19
|
cascade_callbacks: true
|
39
20
|
end
|
40
21
|
|
41
|
-
|
42
|
-
# attr_accessible :photo
|
43
|
-
# attr_accessible :images
|
44
|
-
attr_accessible :"#{scope}" if options[:accessible]
|
45
|
-
|
46
|
-
# def photo?
|
47
|
-
# photo.present?
|
48
|
-
# end
|
49
|
-
# def images?
|
50
|
-
# images.present?
|
51
|
-
# end
|
52
|
-
define_method :"#{scope}?" do
|
53
|
-
send(:"#{scope}").present?
|
54
|
-
end
|
55
|
-
|
56
|
-
# def photo_metadata
|
57
|
-
# options[:scope] = 'photo'
|
58
|
-
# options[:maximum] = 1 if options[:single]
|
59
|
-
# options
|
60
|
-
# end
|
61
|
-
define_method :"#{scope}_metadata" do
|
62
|
-
options[:scope] = scope
|
63
|
-
options[:maximum] = 1 if options[:single]
|
64
|
-
options
|
65
|
-
end
|
66
|
-
|
67
|
-
|
68
22
|
# alias_method :orig_photo=, :photo=
|
69
|
-
# def photo=(
|
70
|
-
#
|
71
|
-
# if
|
72
|
-
#
|
73
|
-
# files = files[0] if options[:singular]
|
74
|
-
# super files
|
23
|
+
# def photo=(input)
|
24
|
+
# input = Attachinary::Utils.process_input(input)
|
25
|
+
# if input.nil?
|
26
|
+
# self.orig_photo = nil
|
75
27
|
# else
|
76
|
-
#
|
28
|
+
# files = [input].flatten
|
29
|
+
# self.orig_photo = options[:single] ? files[0] : files
|
77
30
|
# end
|
78
31
|
# end
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
if
|
84
|
-
|
85
|
-
data = data.slice(*Attachinary::File.attr_accessible[:default].to_a)
|
86
|
-
Attachinary::File.new(data)
|
87
|
-
end
|
88
|
-
send("orig_#{scope}=", options[:single] ? files[0] : files)
|
32
|
+
# end
|
33
|
+
alias_method "orig_#{options[:scope]}=", "#{options[:scope]}="
|
34
|
+
define_method "#{options[:scope]}=" do |input|
|
35
|
+
input = Attachinary::Utils.process_input(input)
|
36
|
+
if input.nil?
|
37
|
+
send("orig_#{options[:scope]}=", nil)
|
89
38
|
else
|
90
|
-
|
39
|
+
files = [input].flatten
|
40
|
+
send("orig_#{options[:scope]}=", options[:single] ? files[0] : files)
|
91
41
|
end
|
92
42
|
end
|
93
43
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Attachinary
|
2
|
+
module Utils
|
3
|
+
|
4
|
+
def self.process_json(json, scope=nil)
|
5
|
+
[JSON.parse(json)].flatten.map do |data|
|
6
|
+
process_hash(data, scope)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.process_hash(hash, scope=nil)
|
11
|
+
file = Attachinary::File.new hash.slice(*Attachinary::File.attr_accessible[:default].to_a)
|
12
|
+
file.scope = scope.to_s if scope && file.respond_to?(:scope=)
|
13
|
+
file
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def self.process_input(input, scope=nil)
|
18
|
+
case input
|
19
|
+
when :blank?.to_proc
|
20
|
+
nil
|
21
|
+
when lambda { |e| e.respond_to?(:read) }
|
22
|
+
process_hash Cloudinary::Uploader.upload(input), scope
|
23
|
+
when String
|
24
|
+
process_json(input, scope)
|
25
|
+
when Hash
|
26
|
+
process_hash(input, scope)
|
27
|
+
when Array
|
28
|
+
input = input.map{ |el| process_input(el, scope) }.flatten.compact
|
29
|
+
input = nil if input.empty?
|
30
|
+
input
|
31
|
+
else
|
32
|
+
input
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.process_options(options)
|
37
|
+
options = options.reverse_merge({
|
38
|
+
accessible: true
|
39
|
+
})
|
40
|
+
options[:maximum] = 1 if options[:single]
|
41
|
+
|
42
|
+
if options[:single]
|
43
|
+
options[:singular] = options[:scope].to_s
|
44
|
+
options[:plural] = options[:scope].to_s.pluralize
|
45
|
+
else
|
46
|
+
options[:plural] = options[:scope].to_s
|
47
|
+
options[:singular] = options[:scope].to_s.singularize
|
48
|
+
end
|
49
|
+
|
50
|
+
options
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
data/lib/attachinary/version.rb
CHANGED
@@ -42,6 +42,8 @@ module Attachinary
|
|
42
42
|
options[:html][:accept] = accept.join(',') unless accept.empty?
|
43
43
|
end
|
44
44
|
|
45
|
+
options[:html][:multiple] = true unless options[:attachinary][:single]
|
46
|
+
|
45
47
|
options[:html][:data] ||= {}
|
46
48
|
options[:html][:data][:attachinary] = options[:attachinary] || {}
|
47
49
|
options[:html][:data][:attachinary][:files] = [model.send(relation)].compact.flatten
|
@@ -0,0 +1,28 @@
|
|
1
|
+
namespace :attachinary do
|
2
|
+
|
3
|
+
desc 'fetches required jQuery File Upload files from github'
|
4
|
+
task :fetch_fileupload do
|
5
|
+
require 'open-uri'
|
6
|
+
require 'uri'
|
7
|
+
|
8
|
+
urls = %w[
|
9
|
+
https://raw.github.com/blueimp/jQuery-File-Upload/master/js/vendor/jquery.ui.widget.js
|
10
|
+
https://raw.github.com/blueimp/jQuery-File-Upload/master/js/jquery.iframe-transport.js
|
11
|
+
https://raw.github.com/blueimp/jQuery-File-Upload/master/js/jquery.fileupload.js
|
12
|
+
]
|
13
|
+
|
14
|
+
dir = Rails.root.join("vendor/assets/javascripts")
|
15
|
+
FileUtils.mkdir_p dir.to_s
|
16
|
+
|
17
|
+
urls.each do |url|
|
18
|
+
uri = URI.parse(url)
|
19
|
+
filename = uri.path.split('/').last
|
20
|
+
puts "Getting #{filename}"
|
21
|
+
|
22
|
+
dest = File.open(dir.join(filename), "w")
|
23
|
+
dest.puts open(url).read
|
24
|
+
dest.close
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: attachinary
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-10-
|
12
|
+
date: 2012-10-27 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
16
|
-
requirement: &
|
16
|
+
requirement: &70366625912520 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '3.2'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70366625912520
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: cloudinary
|
27
|
-
requirement: &
|
27
|
+
requirement: &70366625911980 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: 1.0.40
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70366625911980
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: sqlite3
|
38
|
-
requirement: &
|
38
|
+
requirement: &70366625911560 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70366625911560
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: rspec-rails
|
49
|
-
requirement: &
|
49
|
+
requirement: &70366625910780 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ~>
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: '2.5'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70366625910780
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: valid_attribute
|
60
|
-
requirement: &
|
60
|
+
requirement: &70366625910340 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ! '>='
|
@@ -65,10 +65,10 @@ dependencies:
|
|
65
65
|
version: '0'
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *70366625910340
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: capybara
|
71
|
-
requirement: &
|
71
|
+
requirement: &70366625909740 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ! '>='
|
@@ -76,10 +76,21 @@ dependencies:
|
|
76
76
|
version: '0'
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *70366625909740
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: capybara-webkit
|
82
|
+
requirement: &70366625909100 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
type: :development
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: *70366625909100
|
80
91
|
- !ruby/object:Gem::Dependency
|
81
92
|
name: factory_girl_rails
|
82
|
-
requirement: &
|
93
|
+
requirement: &70366625908420 !ruby/object:Gem::Requirement
|
83
94
|
none: false
|
84
95
|
requirements:
|
85
96
|
- - ~>
|
@@ -87,10 +98,10 @@ dependencies:
|
|
87
98
|
version: '3.0'
|
88
99
|
type: :development
|
89
100
|
prerelease: false
|
90
|
-
version_requirements: *
|
101
|
+
version_requirements: *70366625908420
|
91
102
|
- !ruby/object:Gem::Dependency
|
92
103
|
name: launchy
|
93
|
-
requirement: &
|
104
|
+
requirement: &70366625907260 !ruby/object:Gem::Requirement
|
94
105
|
none: false
|
95
106
|
requirements:
|
96
107
|
- - ! '>='
|
@@ -98,10 +109,10 @@ dependencies:
|
|
98
109
|
version: '0'
|
99
110
|
type: :development
|
100
111
|
prerelease: false
|
101
|
-
version_requirements: *
|
112
|
+
version_requirements: *70366625907260
|
102
113
|
- !ruby/object:Gem::Dependency
|
103
114
|
name: database_cleaner
|
104
|
-
requirement: &
|
115
|
+
requirement: &70366625906760 !ruby/object:Gem::Requirement
|
105
116
|
none: false
|
106
117
|
requirements:
|
107
118
|
- - ! '>='
|
@@ -109,10 +120,10 @@ dependencies:
|
|
109
120
|
version: '0'
|
110
121
|
type: :development
|
111
122
|
prerelease: false
|
112
|
-
version_requirements: *
|
123
|
+
version_requirements: *70366625906760
|
113
124
|
- !ruby/object:Gem::Dependency
|
114
125
|
name: guard-rspec
|
115
|
-
requirement: &
|
126
|
+
requirement: &70366625906080 !ruby/object:Gem::Requirement
|
116
127
|
none: false
|
117
128
|
requirements:
|
118
129
|
- - ! '>='
|
@@ -120,7 +131,7 @@ dependencies:
|
|
120
131
|
version: '0'
|
121
132
|
type: :development
|
122
133
|
prerelease: false
|
123
|
-
version_requirements: *
|
134
|
+
version_requirements: *70366625906080
|
124
135
|
description: Attachments handler for Rails that uses Cloudinary for storage.
|
125
136
|
email:
|
126
137
|
- milovan.zogovic@gmail.com
|
@@ -130,7 +141,6 @@ extra_rdoc_files: []
|
|
130
141
|
files:
|
131
142
|
- app/controllers/attachinary/application_controller.rb
|
132
143
|
- app/controllers/attachinary/cors_controller.rb
|
133
|
-
- app/controllers/attachinary/files_controller.rb
|
134
144
|
- config/routes.rb
|
135
145
|
- db/migrate/20120612112526_create_attachinary_tables.rb
|
136
146
|
- lib/assets/javascripts/attachinary.js.coffee
|
@@ -139,14 +149,17 @@ files:
|
|
139
149
|
- lib/attachinary/orm/active_record/extension.rb
|
140
150
|
- lib/attachinary/orm/active_record/file.rb
|
141
151
|
- lib/attachinary/orm/active_record.rb
|
152
|
+
- lib/attachinary/orm/base_extension.rb
|
142
153
|
- lib/attachinary/orm/file_mixin.rb
|
143
154
|
- lib/attachinary/orm/mongoid/extension.rb
|
144
155
|
- lib/attachinary/orm/mongoid/file.rb
|
145
156
|
- lib/attachinary/orm/mongoid.rb
|
146
157
|
- lib/attachinary/simple_form.rb
|
158
|
+
- lib/attachinary/utils.rb
|
147
159
|
- lib/attachinary/version.rb
|
148
160
|
- lib/attachinary/view_helpers.rb
|
149
161
|
- lib/attachinary.rb
|
162
|
+
- lib/tasks/attachinary.rake
|
150
163
|
- MIT-LICENSE
|
151
164
|
- Rakefile
|
152
165
|
- README.md
|
@@ -164,7 +177,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
164
177
|
version: '0'
|
165
178
|
segments:
|
166
179
|
- 0
|
167
|
-
hash: -
|
180
|
+
hash: -3737432047722910225
|
168
181
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
169
182
|
none: false
|
170
183
|
requirements:
|
@@ -173,11 +186,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
173
186
|
version: '0'
|
174
187
|
segments:
|
175
188
|
- 0
|
176
|
-
hash: -
|
189
|
+
hash: -3737432047722910225
|
177
190
|
requirements: []
|
178
191
|
rubyforge_project:
|
179
192
|
rubygems_version: 1.8.11
|
180
193
|
signing_key:
|
181
194
|
specification_version: 3
|
182
|
-
summary: attachinary-1.
|
195
|
+
summary: attachinary-1.2.0
|
183
196
|
test_files: []
|
@@ -1,21 +0,0 @@
|
|
1
|
-
module Attachinary
|
2
|
-
class FilesController < Attachinary::ApplicationController
|
3
|
-
respond_to :json
|
4
|
-
|
5
|
-
def callback
|
6
|
-
success = valid_cloudinary_response?
|
7
|
-
if success && !params[:error].present?
|
8
|
-
@file = File.create(file_params)
|
9
|
-
respond_with @file
|
10
|
-
else
|
11
|
-
render nothing: true, status: 400
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
private
|
16
|
-
def file_params
|
17
|
-
request.query_parameters.slice(:public_id, :version, :width, :height, :format, :resource_type, :scope)
|
18
|
-
end
|
19
|
-
|
20
|
-
end
|
21
|
-
end
|