rails_uploads 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +87 -0
  3. data/Rakefile +40 -0
  4. data/app/controllers/rails_uploads/application_controller.rb +4 -0
  5. data/app/controllers/rails_uploads/presets_controller.rb +14 -0
  6. data/config/routes.rb +5 -0
  7. data/lib/rails_uploads/active_record/base.rb +122 -0
  8. data/lib/rails_uploads/engine.rb +7 -0
  9. data/lib/rails_uploads/magick/image.rb +83 -0
  10. data/lib/rails_uploads/railtie.rb +14 -0
  11. data/lib/rails_uploads/types/file.rb +120 -0
  12. data/lib/rails_uploads/types/image.rb +61 -0
  13. data/lib/rails_uploads/validators/attachment_content_type_validator.rb +13 -0
  14. data/lib/rails_uploads/validators/attachment_presence_validator.rb +11 -0
  15. data/lib/rails_uploads/validators/attachment_size_validator.rb +28 -0
  16. data/lib/rails_uploads/validators/base.rb +18 -0
  17. data/lib/rails_uploads/version.rb +5 -0
  18. data/lib/rails_uploads.rb +14 -0
  19. data/lib/tasks/rails_uploads_tasks.rake +30 -0
  20. data/test/controller_test.rb +27 -0
  21. data/test/dummy/README.rdoc +261 -0
  22. data/test/dummy/Rakefile +7 -0
  23. data/test/dummy/app/assets/images/image.jpg +0 -0
  24. data/test/dummy/app/assets/javascripts/application.js +15 -0
  25. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  26. data/test/dummy/app/controllers/application_controller.rb +3 -0
  27. data/test/dummy/app/helpers/application_helper.rb +2 -0
  28. data/test/dummy/app/models/file_upload.rb +4 -0
  29. data/test/dummy/app/models/image_upload.rb +4 -0
  30. data/test/dummy/app/models/validation_upload.rb +17 -0
  31. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  32. data/test/dummy/config/application.rb +65 -0
  33. data/test/dummy/config/boot.rb +10 -0
  34. data/test/dummy/config/database.yml +25 -0
  35. data/test/dummy/config/environment.rb +5 -0
  36. data/test/dummy/config/environments/development.rb +37 -0
  37. data/test/dummy/config/environments/production.rb +67 -0
  38. data/test/dummy/config/environments/test.rb +37 -0
  39. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  40. data/test/dummy/config/initializers/inflections.rb +15 -0
  41. data/test/dummy/config/initializers/mime_types.rb +5 -0
  42. data/test/dummy/config/initializers/secret_token.rb +7 -0
  43. data/test/dummy/config/initializers/session_store.rb +8 -0
  44. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  45. data/test/dummy/config/locales/en.yml +12 -0
  46. data/test/dummy/config/routes.rb +5 -0
  47. data/test/dummy/config.ru +4 -0
  48. data/test/dummy/db/development.sqlite3 +0 -0
  49. data/test/dummy/db/migrate/20130108201834_create_file_uploads.rb +10 -0
  50. data/test/dummy/db/migrate/20130110194721_create_validation_uploads.rb +19 -0
  51. data/test/dummy/db/migrate/20130124013431_create_image_uploads.rb +10 -0
  52. data/test/dummy/db/schema.rb +43 -0
  53. data/test/dummy/db/test.sqlite3 +0 -0
  54. data/test/dummy/log/development.log +21 -0
  55. data/test/dummy/log/test.log +6846 -0
  56. data/test/dummy/public/404.html +26 -0
  57. data/test/dummy/public/422.html +26 -0
  58. data/test/dummy/public/500.html +25 -0
  59. data/test/dummy/public/favicon.ico +0 -0
  60. data/test/dummy/script/rails +6 -0
  61. data/test/dummy/test/fixtures/file.txt +1 -0
  62. data/test/dummy/test/fixtures/file_big.txt +1 -0
  63. data/test/dummy/test/fixtures/file_empty.txt +1 -0
  64. data/test/dummy/test/fixtures/image.jpg +0 -0
  65. data/test/dummy/test/fixtures/image_big.jpg +0 -0
  66. data/test/dummy/test/fixtures/image_empty.jpg +0 -0
  67. data/test/file_string_test.rb +34 -0
  68. data/test/file_upload_test.rb +41 -0
  69. data/test/fixtures/file.txt +1 -0
  70. data/test/fixtures/file_big.txt +1 -0
  71. data/test/fixtures/file_empty.txt +1 -0
  72. data/test/fixtures/image.jpg +0 -0
  73. data/test/fixtures/image_big.jpg +0 -0
  74. data/test/fixtures/image_empty.jpg +0 -0
  75. data/test/image_presets_test.rb +32 -0
  76. data/test/image_string_test.rb +36 -0
  77. data/test/image_upload_test.rb +34 -0
  78. data/test/rails_uploads_test.rb +9 -0
  79. data/test/records_test.rb +55 -0
  80. data/test/test_helper.rb +21 -0
  81. data/test/validators_test.rb +83 -0
  82. metadata +220 -0
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2013 Mattways
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,87 @@
1
+ === NOTE: From version 0.1.3 and above RailsUploads::Engine renamed Rails::Uploads::Engine
2
+
3
+ ---
4
+
5
+ {<img src="https://codeclimate.com/github/mattways/rails_uploads.png" />}[https://codeclimate.com/github/mattways/rails_uploads] {<img src="https://travis-ci.org/mattways/rails_uploads.png?branch=master" alt="Build Status" />}[https://travis-ci.org/mattways/rails_uploads] {<img src="https://gemnasium.com/mattways/rails_uploads.png" alt="Dependency Status" />}[https://gemnasium.com/mattways/rails_uploads]
6
+
7
+ = Rails Uploads
8
+
9
+ Minimalistic toolkit to handle file and images uploads using ActiveRecord.
10
+
11
+ = Install
12
+
13
+ Put this line in your Gemfile:
14
+ gem 'rails_uploads'
15
+
16
+ Then bundle:
17
+ $ bundle
18
+
19
+ = Usage
20
+
21
+ Mount the engine at the end of you routes.rb:
22
+ mount Rails::Uploads::Engine => '/' # Will be use to generate on the fly missing image presets
23
+
24
+ Add the column to your table (just a string):
25
+ create_table :models do |t|
26
+ t.string :prop
27
+ end
28
+
29
+ If you need a file:
30
+ class Model < ActiveRecord::Base
31
+ attr_accessible :prop
32
+ attached_file :prop, :default => 'file.txt' # It's optional set a default file, the path it's relative to the public folder (used to generate the url)
33
+ end
34
+
35
+ If you need a image:
36
+ class Model < ActiveRecord::Base
37
+ attr_accessible :prop
38
+ attached_image :prop, :presets => [:small, :big], :default => 'assets/image.jpg' # It's optional set a default image, the path it's relative to the publica folder (used to generate the url)
39
+ end
40
+
41
+ Define presets in your application.rb
42
+ config.uploads.presets = {
43
+ :big => { :method => :fit, :width => 1024, :height => 768 }, # Fit will scale the image until it fit in the space without cropping
44
+ :small => { :method => :fill, :width => 120, :height => :120 }, # Fill will scale the image to fill all the space and then crop
45
+ :custom => proc { |image| image.convert :resize => '100x100' } # ImageMagick wrapper to do whatever you want
46
+ }
47
+ config.uploads.default_presets = [:small] # Define the default presets for all models with attached images
48
+
49
+ The validation works very similar to paperclip:
50
+ class Model < ActiveRecord::Base
51
+ attr_accessible :prop
52
+ attached_file :prop
53
+ validates :prop, :attachment_presence => true, :attachment_size => { :in => 0..4.megabytes }, :attachment_content_type => { :in => ['txt'] }
54
+ end
55
+
56
+ If you want to translate the errores the keys are:
57
+ errors.messages.attachment_presence
58
+ errors.messages.attachment_size_in # :less_than and :greater_than
59
+ errors.messages.attachment_size_less_than # :less_than
60
+ errors.messages.attachment_size_greater_than # :greater_than
61
+ errors.messages.attachment_content_type # :types
62
+
63
+ In your views:
64
+ a{ :href => record.file.path } # To get the file
65
+ a{ :href => record.image.path } # To get the original image
66
+ a{ :href => record.image.path(:big) } # To get the thumb
67
+ a{ :href => record.image.url(:big) } # If you want to use a base url
68
+
69
+ In your forms:
70
+ = f.file_field :prop
71
+
72
+ = FAQ
73
+
74
+ == How can I use a cdn with this plugin?
75
+
76
+ Just define a base url in your application.rb and use url method instead of path:
77
+ config.uploads.base_url = 'http://example.com'
78
+
79
+ == How can I clean a preset?
80
+
81
+ Just remove the corresponding folder in uploads/images manually or with rake task:
82
+ rake uploads:preset:clean[preset]
83
+
84
+ == How to migrate from versions before 0.1.0?
85
+
86
+ To migrate from versions before 0.1.0 you need to reorganize uploads with this task after define your presets in your application.rb:
87
+ rake uploads:migrate
data/Rakefile ADDED
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'RailsUploads'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ APP_RAKEFILE = File.expand_path('../test/dummy/Rakefile', __FILE__)
24
+ load 'rails/tasks/engine.rake'
25
+
26
+
27
+
28
+ Bundler::GemHelper.install_tasks
29
+
30
+ require 'rake/testtask'
31
+
32
+ Rake::TestTask.new(:test) do |t|
33
+ t.libs << 'lib'
34
+ t.libs << 'test'
35
+ t.pattern = 'test/**/*_test.rb'
36
+ t.verbose = false
37
+ end
38
+
39
+
40
+ task :default => :test
@@ -0,0 +1,4 @@
1
+ module RailsUploads
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,14 @@
1
+ module RailsUploads
2
+ class PresetsController < ApplicationController
3
+
4
+ def generate
5
+ filename = "#{params[:image]}.#{params[:format]}"
6
+ preset = params[:preset].gsub('-', '_').to_sym
7
+ if ::File.exists?(Rails.root.join('public', 'uploads', 'images', 'original', filename)) and Rails.application.config.uploads.presets.has_key?(preset)
8
+ RailsUploads::Types::Image.new(filename).send :generate_preset, preset
9
+ redirect_to request.url, :cb => Random.rand(100000)
10
+ end
11
+ end
12
+
13
+ end
14
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,5 @@
1
+ RailsUploads::Engine.routes.draw do
2
+
3
+ match 'uploads/images/:preset/:image' => 'presets#generate', :status => 404
4
+
5
+ end
@@ -0,0 +1,122 @@
1
+ module RailsUploads
2
+ module ActiveRecord
3
+ module Base
4
+ module NonAttachable
5
+
6
+ def self.extended(base)
7
+ [:image].each do |type|
8
+ base.send(:define_singleton_method, "attached_#{type}") do |*args|
9
+ options = args.extract_options!
10
+ options[:type] = type
11
+ attached_file *args.append(options)
12
+ end
13
+ end
14
+ end
15
+
16
+ def attached_file(*args)
17
+ options = args.extract_options!
18
+ options.reverse_merge! :type => :file
19
+ define_attachment *args.append(options)
20
+ end
21
+
22
+ def is_attachable?
23
+ not defined?(@attachments).nil?
24
+ end
25
+
26
+ protected
27
+
28
+ def define_attachment(*args)
29
+ options = args.extract_options!
30
+ make_attachable unless is_attachable?
31
+ args.each do |attr|
32
+ define_attachable_attribute_methods attr.to_sym, options
33
+ @attachments[attr.to_sym] = options
34
+ end
35
+ end
36
+
37
+ def make_attachable
38
+ send :include, RailsUploads::ActiveRecord::Base::Attachable
39
+ before_save :store_attachments, :check_changed_attachments
40
+ after_save :remove_deleted_attachments
41
+ before_destroy :delete_attachments
42
+ after_rollback :remove_stored_attachments
43
+ @attachments = {}
44
+ end
45
+
46
+ def define_attachable_attribute_methods(attr, options)
47
+ ['set', 'get'].each do |method|
48
+ send "define_attachable_attribute_method_#{method}", attr, options
49
+ end
50
+ end
51
+
52
+ def define_attachable_attribute_method_set(attr, options)
53
+ define_method "#{attr}=" do |value|
54
+ @attachments = {} if defined?(@attachments).nil?
55
+ if value.is_a? ActionDispatch::Http::UploadedFile or value.is_a? Rack::Test::UploadedFile
56
+ @attachments[attr] = get_attachment_instance(value, options)
57
+ super(@attachments[attr].filename)
58
+ end
59
+ end
60
+ end
61
+
62
+ def define_attachable_attribute_method_get(attr, options)
63
+ define_method attr do
64
+ @attachments = {} if defined?(@attachments).nil?
65
+ return @attachments[attr] if @attachments.has_key? attr
66
+ return nil if super().nil? and not options.has_key? :default
67
+ @attachments[attr] = get_attachment_instance(super(), options)
68
+ end
69
+ end
70
+
71
+ end
72
+ module Attachable
73
+
74
+ protected
75
+
76
+ def get_attachment_instance(source, options)
77
+ klass = options.has_key?(:type) ? options[:type].to_s.classify : 'File'
78
+ RailsUploads::Types.const_get(klass).new(source, options)
79
+ end
80
+
81
+ def check_changed_attachments
82
+ @stored_attachments = []
83
+ @deleted_attachments = []
84
+ self.class.instance_variable_get('@attachments').each do |attr, options|
85
+ if changed_attributes.has_key? attr.to_s
86
+ add_changed_attachment attributes[attr.to_s], options, :stored
87
+ add_changed_attachment changed_attributes[attr.to_s], options, :deleted
88
+ end
89
+ end
90
+ end
91
+
92
+ def iterate_attachments
93
+ self.class.instance_variable_get('@attachments').each do |attr, options|
94
+ next unless value = send(attr)
95
+ yield value
96
+ end
97
+ end
98
+
99
+ def add_changed_attachment(value, options, type)
100
+ (type == :stored ? @stored_attachments : @deleted_attachments) << get_attachment_instance(value, options) unless value.nil?
101
+ end
102
+
103
+ def store_attachments
104
+ iterate_attachments { |a| a.store }
105
+ end
106
+
107
+ def delete_attachments
108
+ iterate_attachments { |a| a.delete }
109
+ end
110
+
111
+ def remove_stored_attachments
112
+ @stored_attachments.each { |a| a.delete }
113
+ end
114
+
115
+ def remove_deleted_attachments
116
+ @deleted_attachments.each { |a| a.delete }
117
+ end
118
+
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,7 @@
1
+ module RailsUploads
2
+ class Engine < ::Rails::Engine
3
+
4
+ isolate_namespace RailsUploads
5
+
6
+ end
7
+ end
@@ -0,0 +1,83 @@
1
+ module RailsUploads
2
+ module Magick
3
+ class Image
4
+
5
+ def initialize(source, output=nil)
6
+ @source = source
7
+ @output = output
8
+ end
9
+
10
+ def convert(args)
11
+ tokens = [convert? ? 'convert' : 'mogrify']
12
+ file_to_tokens tokens
13
+ args_to_tokens args, tokens
14
+ tokens << "\"#{@output}\"" if convert?
15
+ success, output = run(tokens)
16
+ if block_given?
17
+ yield success, output
18
+ else
19
+ success
20
+ end
21
+ end
22
+
23
+ def identify(args)
24
+ tokens = ['identify']
25
+ args_to_tokens args, tokens
26
+ file_to_tokens tokens
27
+ success, output = run(tokens)
28
+ if block_given?
29
+ yield success, output
30
+ else
31
+ success
32
+ end
33
+ end
34
+
35
+ def dimensions
36
+ identify(:format => '%wx%h') do |success, output|
37
+ success ? output.chomp.split('x').map(&:to_i) : []
38
+ end
39
+ end
40
+
41
+ def width
42
+ dimensions[0]
43
+ end
44
+
45
+ def height
46
+ dimensions[1]
47
+ end
48
+
49
+ def resize_to_fill(max_width, max_height)
50
+ width, height = dimensions
51
+ scale = [max_width/width.to_f, max_height/height.to_f].max
52
+ convert :resize => "#{(scale*width).to_i}x#{(scale*height).to_i}", :gravity => 'center', :crop => "#{max_width}x#{max_height}+0+0"
53
+ end
54
+
55
+ def resize_to_fit(max_width, max_height)
56
+ width, height = dimensions
57
+ scale = [max_width/width.to_f, max_height/height.to_f].min
58
+ convert :resize => "#{(scale*width).to_i}x#{(scale*height).to_i}", :gravity => 'center'
59
+ end
60
+
61
+ protected
62
+
63
+ def convert?
64
+ not (@output.nil? and ::File.exists?(@output))
65
+ end
66
+
67
+ def file_to_tokens(tokens)
68
+ tokens << (convert? ? "\"#{@source}\"" : @output)
69
+ end
70
+
71
+ def args_to_tokens(args, tokens)
72
+ args.each { |k, v| tokens << "-#{k} #{v}" }
73
+ end
74
+
75
+ def run(tokens)
76
+ output = `#{tokens.join(' ')}`
77
+ success = $?.exitstatus == 0
78
+ [success, output]
79
+ end
80
+
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,14 @@
1
+ module RailsUploads
2
+ class Railtie < Rails::Railtie
3
+
4
+ config.uploads = ActiveSupport::OrderedOptions.new
5
+ config.uploads.presets = {}
6
+ config.uploads.default_presets = []
7
+ config.uploads.base_url = ''
8
+
9
+ initializer 'rails_uploads' do
10
+ ::ActiveRecord::Base.send :extend, RailsUploads::ActiveRecord::Base::NonAttachable
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,120 @@
1
+ module RailsUploads
2
+ module Types
3
+ class File
4
+
5
+ def initialize(source, options={})
6
+ if source.is_a? ActionDispatch::Http::UploadedFile or source.is_a? Rack::Test::UploadedFile
7
+ @upload = source
8
+ @stored = false
9
+ @default = false
10
+ elsif source.is_a? String
11
+ @upload = false
12
+ @filename = source
13
+ @stored = true
14
+ @default = false
15
+ elsif options.has_key? :default
16
+ @upload = false
17
+ @filename = options[:default]
18
+ @stored = true
19
+ @default = true
20
+ end
21
+ @deleted = false
22
+ @options = options
23
+ end
24
+
25
+ def has_default?
26
+ @default
27
+ end
28
+
29
+ def is_upload?
30
+ @upload != false
31
+ end
32
+
33
+ def is_stored?
34
+ @stored
35
+ end
36
+
37
+ def is_deleted?
38
+ @deleted
39
+ end
40
+
41
+ def default_path(*args)
42
+ @options[:default]
43
+ end
44
+
45
+ def exists?(*args)
46
+ is_deleted? ? false : ::File.exists?(realpath(*args))
47
+ end
48
+
49
+ def size(*args)
50
+ return 0 if is_deleted?
51
+ (exists? ? ::File.size(realpath(*args)) : 0)
52
+ end
53
+
54
+ def extname
55
+ return nil if is_deleted?
56
+ @extname ||= ::File.extname(filename)
57
+ end
58
+
59
+ def filename
60
+ return nil if is_deleted?
61
+ @filename ||= "#{(Time.now.to_f * 10000000).to_i}#{::File.extname @upload.original_filename}".downcase
62
+ end
63
+
64
+ def path(*args)
65
+ return nil if is_deleted?
66
+ ::File.join('', public_path(*args))
67
+ end
68
+
69
+ def url(*args)
70
+ return nil if is_deleted?
71
+ ::File.join(Rails.application.config.uploads.base_url, path(*args))
72
+ end
73
+
74
+ def realpath(*args)
75
+ return nil if is_deleted?
76
+ ::File.expand_path(is_stored? ? destination_path(*args) : @upload.path)
77
+ end
78
+
79
+ def store
80
+ if not is_stored? and is_upload?
81
+ check_store_dir
82
+ ::File.open(destination_path, 'wb') do |file|
83
+ file.write(@upload.read)
84
+ end
85
+ @stored = true
86
+ yield if block_given?
87
+ end
88
+ end
89
+
90
+ def delete
91
+ if not has_default? and is_stored? and exists?
92
+ ::File.delete realpath
93
+ yield if block_given?
94
+ @stored = false
95
+ @deleted = true
96
+ end
97
+ end
98
+
99
+ protected
100
+
101
+ def check_store_dir(*args)
102
+ dir = Rails.root.join('public', 'uploads', store_path(*args))
103
+ FileUtils.mkdir_p dir unless ::File.directory? dir
104
+ end
105
+
106
+ def destination_path(*args)
107
+ Rails.root.join('public', public_path(*args))
108
+ end
109
+
110
+ def public_path(*args)
111
+ has_default? ? default_path(*args) : ::File.join('uploads', store_path(*args), filename)
112
+ end
113
+
114
+ def store_path(*args)
115
+ 'files'
116
+ end
117
+
118
+ end
119
+ end
120
+ end