rails-uploader 0.2.8 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/MIT-LICENSE +1 -1
- data/README.md +162 -86
- data/app/assets/javascripts/uploader/application.js +6 -4
- data/app/assets/javascripts/uploader/jquery.uploader.js.coffee +58 -0
- data/app/controllers/uploader/attachments_controller.rb +63 -39
- data/app/views/uploader/default/_container.html.erb +19 -45
- data/app/views/uploader/default/_download.html.erb +4 -5
- data/app/views/uploader/default/_upload.html.erb +0 -1
- data/config/locales/en.yml +2 -0
- data/config/locales/ru.yml +3 -0
- data/config/locales/uk.yml +3 -0
- data/config/routes.rb +1 -1
- data/lib/uploader/asset.rb +63 -84
- data/lib/uploader/authorization.rb +52 -0
- data/lib/uploader/authorization_adapter.rb +24 -0
- data/lib/uploader/chunked_uploads.rb +15 -0
- data/lib/uploader/engine.rb +6 -0
- data/lib/uploader/file_part.rb +18 -0
- data/lib/uploader/fileuploads.rb +42 -65
- data/lib/uploader/helpers/field_tag.rb +10 -5
- data/lib/uploader/hooks/formtastic.rb +0 -13
- data/lib/uploader/upload_request.rb +72 -0
- data/lib/uploader/version.rb +1 -1
- data/lib/uploader.rb +41 -8
- data/spec/dummy/app/models/asset.rb +12 -0
- data/spec/dummy/app/models/picture.rb +6 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/test.log +325 -0
- data/spec/dummy/public/uploads/picture/data/1/thumb_rails.png +0 -0
- data/spec/dummy/public/uploads/picture/data/3/thumb_rails.png +0 -0
- data/spec/fileuploads_spec.rb +4 -4
- data/spec/requests/attachments_controller_spec.rb +11 -12
- data/vendor/assets/javascripts/uploader/jquery.fileupload-process.js +175 -0
- data/vendor/assets/javascripts/uploader/jquery.fileupload-ui.js +164 -261
- data/vendor/assets/javascripts/uploader/jquery.fileupload-validate.js +122 -0
- data/vendor/assets/javascripts/uploader/jquery.fileupload.js +335 -101
- data/vendor/assets/javascripts/uploader/jquery.iframe-transport.js +47 -15
- data/vendor/assets/javascripts/uploader/vendor/jquery.ui.widget.js +572 -0
- data/vendor/assets/javascripts/uploader/vendor/tmpl.min.js +1 -0
- data/vendor/assets/stylesheets/uploader/default.css +26 -19
- metadata +12 -9
- data/vendor/assets/javascripts/uploader/jquery.fileupload-fp.js +0 -227
- data/vendor/assets/javascripts/uploader/jquery.ui.widget.js +0 -530
- data/vendor/assets/javascripts/uploader/load-image.min.js +0 -1
- data/vendor/assets/javascripts/uploader/locales/en.js +0 -27
- data/vendor/assets/javascripts/uploader/locales/ru.js +0 -27
- data/vendor/assets/javascripts/uploader/locales/uk.js +0 -27
- data/vendor/assets/javascripts/uploader/tmpl.min.js +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 78c6ab34d67ec73bf7b3d802e186d3a179d7a25e
|
4
|
+
data.tar.gz: e76947d90bd1f5c0ea024a3939a0565ab7088477
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 34b3d48312fd023264a053d7c7c669dd6da137701b33c288c8e18f90bc70218d1e467f48c84673f60738f84aaec8f50a3490ff5d665587428a781ea23a73c327
|
7
|
+
data.tar.gz: 25737d84ebc664c361195c97b380a694297a96c9c7d5d299187cf898404ceaf8f74080f6c03cb93b77e711755b43352c5d6b9b79b66cf4a0d9127e7825d932dc
|
data/MIT-LICENSE
CHANGED
data/README.md
CHANGED
@@ -2,16 +2,12 @@
|
|
2
2
|
|
3
3
|
This gem use https://github.com/blueimp/jQuery-File-Upload for upload files.
|
4
4
|
|
5
|
-
Preview:
|
6
|
-
|
7
|
-
![Uploader in use](http://img39.imageshack.us/img39/2206/railsuploader.png)
|
8
|
-
|
9
5
|
## Install
|
10
6
|
|
11
7
|
In Gemfile:
|
12
8
|
|
13
9
|
```
|
14
|
-
gem
|
10
|
+
gem 'rails-uploader'
|
15
11
|
```
|
16
12
|
|
17
13
|
In routes:
|
@@ -33,44 +29,14 @@ Architecture to store uploaded files (cancan integration):
|
|
33
29
|
``` ruby
|
34
30
|
class Asset < ActiveRecord::Base
|
35
31
|
include Uploader::Asset
|
36
|
-
|
37
|
-
def uploader_create(params, request = nil)
|
38
|
-
ability = Ability.new(request.env['warden'].user)
|
39
|
-
|
40
|
-
if ability.can? :create, self
|
41
|
-
self.user = request.env['warden'].user
|
42
|
-
super
|
43
|
-
else
|
44
|
-
errors.add(:id, :access_denied)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def uploader_destroy(params, request = nil)
|
49
|
-
ability = Ability.new(request.env['warden'].user)
|
50
|
-
|
51
|
-
if ability.can? :delete, self
|
52
|
-
super
|
53
|
-
else
|
54
|
-
errors.add(:id, :access_denied)
|
55
|
-
end
|
56
|
-
end
|
57
32
|
end
|
58
33
|
|
59
34
|
class Picture < Asset
|
60
|
-
mount_uploader :data, PictureUploader
|
61
|
-
|
62
|
-
validates_integrity_of :data
|
63
|
-
validates_filesize_of :data, :maximum => 2.megabytes.to_i
|
35
|
+
mount_uploader :data, PictureUploader, mount_on: :data_file_name
|
36
|
+
validates :data, file_size: { maximum: 5.megabytes.to_i }
|
64
37
|
|
65
|
-
|
66
|
-
|
67
|
-
{
|
68
|
-
"id" => id.to_s,
|
69
|
-
"filename" => File.basename(data.path),
|
70
|
-
"url" => data.url,
|
71
|
-
"thumb_url" => data.url(:thumb),
|
72
|
-
"public_token" => public_token
|
73
|
-
}
|
38
|
+
def thumb_url
|
39
|
+
url(:thumb)
|
74
40
|
end
|
75
41
|
end
|
76
42
|
```
|
@@ -79,12 +45,9 @@ For example user has one picture:
|
|
79
45
|
|
80
46
|
``` ruby
|
81
47
|
class User < ActiveRecord::Base
|
82
|
-
has_one :picture, :
|
48
|
+
has_one :picture, as: :assetable, dependent: :destroy
|
83
49
|
|
84
50
|
fileuploads :picture
|
85
|
-
|
86
|
-
# If your don't use strong_parameters, uncomment next line
|
87
|
-
# attr_accessible :fileupload_guid
|
88
51
|
end
|
89
52
|
```
|
90
53
|
|
@@ -94,45 +57,6 @@ Find asset by foreign key or guid:
|
|
94
57
|
@user.fileupload_asset(:picture)
|
95
58
|
```
|
96
59
|
|
97
|
-
### Mongoid
|
98
|
-
|
99
|
-
No parent asset model is required, one only has to `include Uploader::Asset::Mongoid` into the
|
100
|
-
model that should act like an asset:
|
101
|
-
|
102
|
-
``` ruby
|
103
|
-
class Picture
|
104
|
-
include Mongoid::Document
|
105
|
-
include Uploader::Asset::Mongoid
|
106
|
-
|
107
|
-
belongs_to :user
|
108
|
-
end
|
109
|
-
|
110
|
-
class User
|
111
|
-
include Mongoid::Document
|
112
|
-
include Uploader::Fileuploads
|
113
|
-
|
114
|
-
has_one :picture, :as => :assetable
|
115
|
-
|
116
|
-
fileuploads :picture
|
117
|
-
end
|
118
|
-
```
|
119
|
-
|
120
|
-
### Notice
|
121
|
-
|
122
|
-
User method fileuploads only once pre model. So if you have many attached files, use this:
|
123
|
-
|
124
|
-
``` ruby
|
125
|
-
class User
|
126
|
-
include Uploader::Fileuploads
|
127
|
-
|
128
|
-
has_one :picture, :as => :assetable
|
129
|
-
has_one :avatar, :as => :assetable
|
130
|
-
|
131
|
-
fileuploads :picture, :avatar
|
132
|
-
end
|
133
|
-
```
|
134
|
-
|
135
|
-
|
136
60
|
### Include assets
|
137
61
|
|
138
62
|
Javascripts:
|
@@ -156,19 +80,19 @@ Stylesheets:
|
|
156
80
|
or FormBuilder:
|
157
81
|
|
158
82
|
```erb
|
159
|
-
<%= form.uploader_field :photo, :
|
83
|
+
<%= form.uploader_field :photo, sortable: true %>
|
160
84
|
```
|
161
85
|
|
162
86
|
### Formtastic
|
163
87
|
|
164
88
|
```erb
|
165
|
-
<%= f.input :pictures, :
|
89
|
+
<%= f.input :pictures, as: :uploader %>
|
166
90
|
```
|
167
91
|
|
168
92
|
### SimpleForm
|
169
93
|
|
170
94
|
```erb
|
171
|
-
<%= f.input :pictures, :
|
95
|
+
<%= f.input :pictures, as: :uploader, input_html: { sortable: true } %>
|
172
96
|
```
|
173
97
|
|
174
98
|
#### Confirming deletions
|
@@ -181,6 +105,158 @@ This is only working in Formtastic and FormBuilder:
|
|
181
105
|
# the i18n lookup key would be en.formtastic.delete_confirmations.picture
|
182
106
|
```
|
183
107
|
|
108
|
+
## Authorization
|
109
|
+
|
110
|
+
Setup custom authorization adapter and current user:
|
111
|
+
|
112
|
+
``` ruby
|
113
|
+
# config/initializers/uploader.rb
|
114
|
+
Uploader.setup do |config|
|
115
|
+
config.authorization_adapter = CanCanUploaderAdapter
|
116
|
+
config.current_user_proc = -> (request) { request.env['warden'].user }
|
117
|
+
end
|
118
|
+
```
|
119
|
+
|
120
|
+
CanCanUploaderAdapter class just create cancan ability object and call can? method with same args:
|
121
|
+
|
122
|
+
``` ruby
|
123
|
+
class CanCanUploaderAdapter < Uploader::AuthorizationAdapter
|
124
|
+
def authorized?(action, subject = nil)
|
125
|
+
cancan_ability.can?(action, subject)
|
126
|
+
end
|
127
|
+
|
128
|
+
def scope_collection(collection, action = :index)
|
129
|
+
collection.accessible_by(cancan_ability, action)
|
130
|
+
end
|
131
|
+
|
132
|
+
protected
|
133
|
+
|
134
|
+
def cancan_ability
|
135
|
+
@cancan_ability ||= Ability.new(user)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
```
|
139
|
+
|
140
|
+
## JSON Response
|
141
|
+
|
142
|
+
https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#using-jquery-file-upload-ui-version-with-a-custom-server-side-upload-handler
|
143
|
+
|
144
|
+
Extend your custom server-side upload handler to return a JSON response akin to the following output:
|
145
|
+
|
146
|
+
``` json
|
147
|
+
{"files": [
|
148
|
+
{
|
149
|
+
"name": "picture1.jpg",
|
150
|
+
"size": 902604,
|
151
|
+
"url": "http:\/\/example.org\/files\/picture1.jpg",
|
152
|
+
"thumb_url": "http:\/\/example.org\/files\/thumbnail\/picture1.jpg",
|
153
|
+
"id": 1,
|
154
|
+
"content_type": "image/jpg"
|
155
|
+
},
|
156
|
+
{
|
157
|
+
"name": "picture2.jpg",
|
158
|
+
"size": 841946,
|
159
|
+
"url": "http:\/\/example.org\/files\/picture2.jpg",
|
160
|
+
"thumb_url": "http:\/\/example.org\/files\/thumbnail\/picture2.jpg",
|
161
|
+
"id": 2,
|
162
|
+
"content_type": "image/jpg"
|
163
|
+
}
|
164
|
+
]}
|
165
|
+
```
|
166
|
+
|
167
|
+
To return errors to the UI, just add an error property to the individual file objects:
|
168
|
+
|
169
|
+
``` json
|
170
|
+
{"files": [
|
171
|
+
{
|
172
|
+
"name": "picture1.jpg",
|
173
|
+
"size": 902604,
|
174
|
+
"error": "Filetype not allowed"
|
175
|
+
},
|
176
|
+
{
|
177
|
+
"name": "picture2.jpg",
|
178
|
+
"size": 841946,
|
179
|
+
"error": "Filetype not allowed"
|
180
|
+
}
|
181
|
+
]}
|
182
|
+
```
|
183
|
+
|
184
|
+
When removing files using the delete button, the response should be like this:
|
185
|
+
|
186
|
+
``` json
|
187
|
+
{"files": [
|
188
|
+
{
|
189
|
+
"picture1.jpg": true
|
190
|
+
},
|
191
|
+
{
|
192
|
+
"picture2.jpg": true
|
193
|
+
}
|
194
|
+
]}
|
195
|
+
```
|
196
|
+
|
197
|
+
Note that the response should always be a JSON object containing a files array even if only one file is uploaded.
|
198
|
+
|
199
|
+
### Customize JSON response and views
|
200
|
+
|
201
|
+
To customize JSON response just overwrite to_fileupload method:
|
202
|
+
|
203
|
+
``` ruby
|
204
|
+
class Asset
|
205
|
+
include Uploader::Asset
|
206
|
+
|
207
|
+
def to_fileupload
|
208
|
+
{
|
209
|
+
id: id,
|
210
|
+
name: filename,
|
211
|
+
content_type: content_type,
|
212
|
+
size: size,
|
213
|
+
url: url,
|
214
|
+
thumb_url: thumb_url
|
215
|
+
}
|
216
|
+
end
|
217
|
+
end
|
218
|
+
```
|
219
|
+
|
220
|
+
For exsample let's overwrite id method to public_token method:
|
221
|
+
|
222
|
+
``` ruby
|
223
|
+
class Asset
|
224
|
+
include Uploader::Asset
|
225
|
+
|
226
|
+
def to_fileupload
|
227
|
+
super.merge(id: public_token)
|
228
|
+
end
|
229
|
+
|
230
|
+
def self.fileupload_find_asset(params)
|
231
|
+
where(public_token: params[:id]).first
|
232
|
+
end
|
233
|
+
end
|
234
|
+
```
|
235
|
+
|
236
|
+
To customize views just create new theme. For example create avatar theme:
|
237
|
+
|
238
|
+
app/views/uploader/avatar/_container.html.erb
|
239
|
+
app/views/uploader/avatar/_download.html.erb
|
240
|
+
app/views/uploader/avatar/_upload.html.erb
|
241
|
+
|
242
|
+
And pass theme to input field:
|
243
|
+
|
244
|
+
``` slim
|
245
|
+
= form.uploader_field :photo, theme: 'avatar'
|
246
|
+
```
|
247
|
+
|
248
|
+
## Chunked file uploads
|
249
|
+
|
250
|
+
Chunked file uploads are only supported by browsers with support for XHR file uploads and the Blob API, which includes Google Chrome and Mozilla Firefox 4+.
|
251
|
+
|
252
|
+
To upload large files in smaller chunks, set the max_chunk_size option to a preferred maximum chunk size in Bytes:
|
253
|
+
|
254
|
+
``` slim
|
255
|
+
= f.uploader_field :video, class: 'button', theme: 'media', data: { max_chunk_size: 10_000_000 }
|
256
|
+
```
|
257
|
+
|
258
|
+
That's it!
|
259
|
+
|
184
260
|
## Contributing
|
185
261
|
|
186
262
|
1. Fork it
|
@@ -189,4 +265,4 @@ This is only working in Formtastic and FormBuilder:
|
|
189
265
|
4. Push to the branch (`git push origin my-new-feature`)
|
190
266
|
5. Create new Pull Request
|
191
267
|
|
192
|
-
Copyright (c)
|
268
|
+
Copyright (c) 2016 Fodojo LLC, released under the MIT license
|
@@ -1,8 +1,10 @@
|
|
1
|
-
//= require uploader/jquery.ui.widget
|
2
|
-
//= require uploader/
|
3
|
-
|
4
|
-
//= require uploader/load-image.min
|
1
|
+
//= require uploader/vendor/jquery.ui.widget
|
2
|
+
//= require uploader/vendor/tmpl.min
|
3
|
+
|
5
4
|
//= require uploader/jquery.iframe-transport
|
6
5
|
//= require uploader/jquery.fileupload
|
6
|
+
//= require uploader/jquery.fileupload-process
|
7
|
+
//= require uploader/jquery.fileupload-validate
|
7
8
|
//= require uploader/jquery.fileupload-ui
|
8
9
|
|
10
|
+
//= require uploader/jquery.uploader
|
@@ -0,0 +1,58 @@
|
|
1
|
+
$ = jQuery
|
2
|
+
|
3
|
+
$.fn.uploaderWidget = (options = {}) ->
|
4
|
+
@each ->
|
5
|
+
$this = $(this)
|
6
|
+
data = $this.data('uploaderWidget')
|
7
|
+
if (!data)
|
8
|
+
$this.data('uploaderWidget', new UploaderWidget(this, options))
|
9
|
+
if (typeof options is 'string')
|
10
|
+
data[options]()
|
11
|
+
|
12
|
+
class UploaderWidget
|
13
|
+
constructor: (@dom_id, options = {}) ->
|
14
|
+
defaults =
|
15
|
+
dataType: 'json'
|
16
|
+
autoUpload: true
|
17
|
+
paramName: 'asset[data]'
|
18
|
+
formData: (form) -> return []
|
19
|
+
namespace: 'uploader'
|
20
|
+
uploadTemplateId: 'template-upload-'
|
21
|
+
downloadTemplateId: 'template-download-'
|
22
|
+
|
23
|
+
@options = $.extend defaults, options
|
24
|
+
|
25
|
+
this._setup()
|
26
|
+
|
27
|
+
_setup: ->
|
28
|
+
@element = $(@dom_id)
|
29
|
+
@container = @element.find('div.uploader-files')
|
30
|
+
@template = @element.data('tpml')
|
31
|
+
@input = @element.find('input[type="file"]:eq(0)')
|
32
|
+
|
33
|
+
@options['dropZone'] = @element
|
34
|
+
@options['filesContainer'] = @container
|
35
|
+
@options['uploadTemplateId'] += @template
|
36
|
+
@options['downloadTemplateId'] += @template
|
37
|
+
|
38
|
+
this._initFileupload()
|
39
|
+
|
40
|
+
_initFileupload: ->
|
41
|
+
@input.fileupload(@options)
|
42
|
+
|
43
|
+
@uploader = (@input.data('blueimp-fileupload') || @input.data('fileupload'))
|
44
|
+
|
45
|
+
this._load() if @element.data('exists')
|
46
|
+
|
47
|
+
_load: ->
|
48
|
+
$.ajax(
|
49
|
+
url: @input.data('url')
|
50
|
+
dataType: 'json'
|
51
|
+
method: 'GET'
|
52
|
+
success: (data) =>
|
53
|
+
if data['files']?
|
54
|
+
this.render(data['files'])
|
55
|
+
)
|
56
|
+
|
57
|
+
render: (files) ->
|
58
|
+
@uploader._renderDownload(files).appendTo(@container)
|
@@ -1,56 +1,80 @@
|
|
1
1
|
module Uploader
|
2
2
|
class AttachmentsController < ActionController::Metal
|
3
3
|
include AbstractController::Callbacks
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
include Uploader::Authorization
|
5
|
+
include Uploader::ChunkedUploads
|
6
|
+
|
7
|
+
before_action :find_klass
|
8
|
+
before_action :build_asset, only: [:create]
|
9
|
+
before_action :find_asset, only: [:destroy]
|
10
|
+
|
11
|
+
def index
|
12
|
+
authorize!(:index, @klass)
|
13
|
+
|
14
|
+
@assets = @klass.fileupload_find_assets(params)
|
15
|
+
@assets = uploader_authorization.scope_collection(@assets)
|
16
|
+
|
17
|
+
render_json(files: @assets.map(&:to_fileupload))
|
18
|
+
end
|
19
|
+
|
8
20
|
def create
|
9
|
-
|
10
|
-
|
21
|
+
authorize!(:create, @asset)
|
22
|
+
|
23
|
+
with_chunked_upload(asset_params[:data]) do |file|
|
24
|
+
@asset.data = file
|
25
|
+
@asset.fileupload_create(params, request)
|
26
|
+
end
|
27
|
+
|
11
28
|
render_resourse(@asset, 201)
|
12
29
|
end
|
13
|
-
|
14
|
-
def update
|
15
|
-
@assets = Array.wrap(params[:assets] || [])
|
16
30
|
|
17
|
-
|
18
|
-
|
19
|
-
end
|
31
|
+
def update
|
32
|
+
authorize!(:update, @klass)
|
20
33
|
|
21
|
-
|
34
|
+
@klass.fileupload_update_ordering(params)
|
35
|
+
render_json(files: [])
|
22
36
|
end
|
23
37
|
|
24
38
|
def destroy
|
25
|
-
|
26
|
-
|
39
|
+
authorize!(:destroy, @asset)
|
40
|
+
|
41
|
+
@asset.fileupload_destroy(params, request)
|
42
|
+
render_resourse(@asset)
|
27
43
|
end
|
28
|
-
|
44
|
+
|
29
45
|
protected
|
30
|
-
|
31
|
-
def find_klass
|
32
|
-
@klass = Uploader.constantize(params[:klass])
|
33
|
-
raise ActionController::RoutingError.new("Class not found #{params[:klass]}") if @klass.nil?
|
34
|
-
end
|
35
46
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
47
|
+
def find_klass
|
48
|
+
@klass = Uploader.constantize(params[:klass])
|
49
|
+
raise ActionController::RoutingError, "Class not found #{params[:klass]}" if @klass.nil?
|
50
|
+
end
|
51
|
+
|
52
|
+
def build_asset
|
53
|
+
@asset = @klass.new(asset_params)
|
54
|
+
end
|
55
|
+
|
56
|
+
def find_asset
|
57
|
+
@asset = @klass.fileupload_find_asset(params)
|
58
|
+
raise ActionController::RoutingError, "Asset not found by #{params[:id]}" if @asset.nil?
|
59
|
+
end
|
60
|
+
|
61
|
+
def render_resourse(record, status = 200)
|
62
|
+
if record.errors.empty?
|
63
|
+
render_json({ files: [record.to_fileupload] }, status)
|
64
|
+
else
|
65
|
+
hash = { name: record.filename, error: record.errors.full_messages.first }
|
66
|
+
render_json({ files: [hash] }, 422)
|
53
67
|
end
|
54
|
-
|
68
|
+
end
|
69
|
+
|
70
|
+
def render_json(hash_or_object, status = 200)
|
71
|
+
self.status = status
|
72
|
+
self.content_type = request.format
|
73
|
+
self.response_body = hash_or_object.to_json(root: false)
|
74
|
+
end
|
75
|
+
|
76
|
+
def asset_params
|
77
|
+
ActionController::Parameters.new(params).require(:asset).permit(:data)
|
78
|
+
end
|
55
79
|
end
|
56
80
|
end
|