mm-attach-it 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1 @@
1
+ source "http://rubygems.org"
data/LICENSE ADDED
@@ -0,0 +1,4 @@
1
+ Copyright (c) 2010 Adilson Chacon
2
+
3
+ Absolutely free. You can have the code, change, build another from this, whatever you want.
4
+
data/README ADDED
@@ -0,0 +1,172 @@
1
+ = Mm-attach-it
2
+
3
+ Attach files (images, videos, pdfs, txts, zips and etc) to a MongoMapper record. You can choose if you to store it on file system or GridFS.
4
+
5
+ == Install
6
+
7
+ sudo gem install mm-attach-it
8
+
9
+ == Usage
10
+
11
+ === Model
12
+
13
+ Declare the plugin and use the attachment method to make attachments.
14
+
15
+ class Foo
16
+ include MongoMapper::Document
17
+ plugin AttachIt
18
+
19
+ has_attachment :photo
20
+ end
21
+
22
+ The default storage is the file system, if you want to change for GridFS you should do:
23
+
24
+ class Bar
25
+ include MongoMapper::Document
26
+ plugin AttachIt
27
+
28
+ has_attachment :photo, { :storage => 'gridfs' }
29
+ end
30
+
31
+ If you want to resize the images (you can store resized images on both: file system or GridFS)
32
+
33
+ class Foo
34
+ include MongoMapper::Document
35
+ plugin AttachIt
36
+
37
+ has_attachment :photo, { :styles => { :small => '100x100>', :medium => '200x200>' } }
38
+ end
39
+
40
+ If you want to validate the attached file (again you can validate attaches on both: file system or GridFS)
41
+
42
+ class Foo
43
+ include MongoMapper::Document
44
+ plugin AttachIt
45
+
46
+ has_attch :photo
47
+
48
+ validates_attachment_presence :photo
49
+ validates_attachment_content_type: photo, :content_type => ['image/jpeg', 'image/gif', 'image/png']
50
+ validates_attachment_size :photo, { :less_than => 1.megabyte, :greater_than => 500.kilobytes }
51
+ end
52
+
53
+ OBS: But remember, you can attach whatever kind of file!
54
+
55
+ If you are using the file system to store the files you can specify the storage directory
56
+
57
+ class Foo
58
+ include MongoMapper::Document
59
+ plugin AttachIt
60
+
61
+ has_attachment :photo, { :path => '/:rails_root/public/images/foos/:id/:style/:filename' }
62
+ end
63
+
64
+ OBS: The default directory is '/:rails_root/public/system/:attachment/:id/:style/:filename'
65
+
66
+ Where:
67
+ * :rails_root - is the root directory of your Rails application
68
+ * :environment - can be "production", "test" or "development"
69
+ * :class - the name of the class ('foo' the example above)
70
+ * :attachment - is the name of the column's collection ('photo' the example above)
71
+ * :id - the "id" of the record on MongoDB
72
+ * :style - if you specified the styles
73
+ * :filename - the name of the file
74
+ * :extension - the extension of the file
75
+
76
+ === View
77
+
78
+ Check the model below
79
+
80
+ class Foo
81
+ include MongoMapper::Document
82
+ plugin AttachIt
83
+
84
+ # on file system
85
+ has_attachment :photo, {
86
+ :style => { :thumb => '100x100>' },
87
+ :url => '/assets/groups/:id/:style/:filename',
88
+ :path => '/:rails_root/public/image/foos/:id/:style/:filename', :storage => 'gridfs',
89
+ }
90
+
91
+ # on GridFS
92
+ has_attachment :avatar, {
93
+ :style => { :thumb => '100x100>' },
94
+ :storage => 'gridfs',
95
+ }
96
+ end
97
+
98
+ On form you must set the "multipart" option and the field as "file_field":
99
+
100
+ <%= form_for(@foo, :html => { :multipart => true }) do |f| %>
101
+
102
+ <p>
103
+ <%= f.label :photo %>
104
+ <%= f.file_field :photo %>
105
+ </p>
106
+
107
+ <p>
108
+ <%= f.label :avatar %>
109
+ <%= f.file_field :avatar %>
110
+ </p>
111
+
112
+ <div class="actions">
113
+ <%= f.submit %>
114
+ </div>
115
+ <% end %>
116
+
117
+
118
+ The safest way to show the images is using Base64, doesn't matter if you are using the file system or GridFS.
119
+
120
+ <img src="<%= foo.photo.base64 %>">
121
+ <img src="<%= foo.photo.base64('thumb') %>">
122
+
123
+ <img src="<%= foo.avatar.base64 %>">
124
+ <img src="<%= foo.avatar.base64('thumb') %>">
125
+
126
+ Also, only for file system, if you specify the 'url' option you can do:
127
+
128
+ <%= image_tag foo.photo.url %>
129
+ <%= image_tag foo.photo.url('thumb') %>
130
+
131
+ === Controller
132
+
133
+ If you are using the GridFS to store your files and youn don't want to use Base64 data to show the images you've got to create a action on a controller.
134
+
135
+ But first create a new route on the route's file:
136
+
137
+ match '/foos/avatar/:id(/:style)', :to => 'foos#avatar'
138
+
139
+ Now the controller:
140
+
141
+ class FoosController < ApplicationController
142
+
143
+ ...
144
+
145
+ def avatar
146
+ foo = Foo.where('_id' => BSON::ObjectId(params[:id])).first
147
+ grid_io_data = (params[:style].nil?) ? foo.avatar.get_from_gridfs : foo.avatar.get_from_gridfs(params[:style])
148
+ bytes = grid_io_data.read
149
+ send_data(bytes, :type => foo.avatar_content_type, :disposition => 'inline')
150
+ end
151
+
152
+ ...
153
+
154
+ end
155
+
156
+ And the view do that:
157
+
158
+ <%= image_tag "/foos/avatar/#{foo.id.to_s}/small" %>
159
+
160
+ == Note on Patches/Pull Requests
161
+
162
+ * Fork the project.
163
+ * Make your feature addition or bug fix.
164
+ * Add tests for it. This is important so I don't break it in a
165
+ future version unintentionally.
166
+ * Commit, do not mess with rakefile, version, or history.
167
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
168
+ * Send me a pull request. Bonus points for topic branches.
169
+
170
+ == Copyright
171
+
172
+ Copyright (c) 2010 Adilson Chacon. See LICENSE for details.
data/README.rdoc ADDED
@@ -0,0 +1,172 @@
1
+ = Mm-attach-it
2
+
3
+ Attach files (images, videos, pdfs, txts, zips and etc) to a MongoMapper record. You can choose if you to store it on file system or GridFS.
4
+
5
+ == Install
6
+
7
+ sudo gem install mm-attach-it
8
+
9
+ == Usage
10
+
11
+ === Model
12
+
13
+ Declare the plugin and use the attachment method to make attachments.
14
+
15
+ class Foo
16
+ include MongoMapper::Document
17
+ plugin AttachIt
18
+
19
+ has_attachment :photo
20
+ end
21
+
22
+ The default storage is the file system, if you want to change for GridFS you should do:
23
+
24
+ class Bar
25
+ include MongoMapper::Document
26
+ plugin AttachIt
27
+
28
+ has_attachment :photo, { :storage => 'gridfs' }
29
+ end
30
+
31
+ If you want to resize the images (you can store resized images on both: file system or GridFS)
32
+
33
+ class Foo
34
+ include MongoMapper::Document
35
+ plugin AttachIt
36
+
37
+ has_attachment :photo, { :styles => { :small => '100x100>', :medium => '200x200>' } }
38
+ end
39
+
40
+ If you want to validate the attached file (again you can validate attaches on both: file system or GridFS)
41
+
42
+ class Foo
43
+ include MongoMapper::Document
44
+ plugin AttachIt
45
+
46
+ has_attch :photo
47
+
48
+ validates_attachment_presence :photo
49
+ validates_attachment_content_type: photo, :content_type => ['image/jpeg', 'image/gif', 'image/png']
50
+ validates_attachment_size :photo, { :less_than => 1.megabyte, :greater_than => 500.kilobytes }
51
+ end
52
+
53
+ OBS: But remember, you can attach whatever kind of file!
54
+
55
+ If you are using the file system to store the files you can specify the storage directory
56
+
57
+ class Foo
58
+ include MongoMapper::Document
59
+ plugin AttachIt
60
+
61
+ has_attachment :photo, { :path => '/:rails_root/public/images/foos/:id/:style/:filename' }
62
+ end
63
+
64
+ OBS: The default directory is '/:rails_root/public/system/:attachment/:id/:style/:filename'
65
+
66
+ Where:
67
+ * :rails_root - is the root directory of your Rails application
68
+ * :environment - can be "production", "test" or "development"
69
+ * :class - the name of the class ('foo' the example above)
70
+ * :attachment - is the name of the column's collection ('photo' the example above)
71
+ * :id - the "id" of the record on MongoDB
72
+ * :style - if you specified the styles
73
+ * :filename - the name of the file
74
+ * :extension - the extension of the file
75
+
76
+ === View
77
+
78
+ Check the model below
79
+
80
+ class Foo
81
+ include MongoMapper::Document
82
+ plugin AttachIt
83
+
84
+ # on file system
85
+ has_attachment :photo, {
86
+ :style => { :thumb => '100x100>' },
87
+ :url => '/assets/groups/:id/:style/:filename',
88
+ :path => '/:rails_root/public/image/foos/:id/:style/:filename', :storage => 'gridfs',
89
+ }
90
+
91
+ # on GridFS
92
+ has_attachment :avatar, {
93
+ :style => { :thumb => '100x100>' },
94
+ :storage => 'gridfs',
95
+ }
96
+ end
97
+
98
+ On form you must set the "multipart" option and the field as "file_field":
99
+
100
+ <%= form_for(@foo, :html => { :multipart => true }) do |f| %>
101
+
102
+ <p>
103
+ <%= f.label :photo %>
104
+ <%= f.file_field :photo %>
105
+ </p>
106
+
107
+ <p>
108
+ <%= f.label :avatar %>
109
+ <%= f.file_field :avatar %>
110
+ </p>
111
+
112
+ <div class="actions">
113
+ <%= f.submit %>
114
+ </div>
115
+ <% end %>
116
+
117
+
118
+ The safest way to show the images is using Base64, doesn't matter if you are using the file system or GridFS.
119
+
120
+ <img src="<%= foo.photo.base64 %>">
121
+ <img src="<%= foo.photo.base64('thumb') %>">
122
+
123
+ <img src="<%= foo.avatar.base64 %>">
124
+ <img src="<%= foo.avatar.base64('thumb') %>">
125
+
126
+ Also, only for file system, if you specify the 'url' option you can do:
127
+
128
+ <%= image_tag foo.photo.url %>
129
+ <%= image_tag foo.photo.url('thumb') %>
130
+
131
+ === Controller
132
+
133
+ If you are using the GridFS to store your files and youn don't want to use Base64 data to show the images you've got to create a action on a controller.
134
+
135
+ But first create a new route on the route's file:
136
+
137
+ match '/foos/avatar/:id(/:style)', :to => 'foos#avatar'
138
+
139
+ Now the controller:
140
+
141
+ class FoosController < ApplicationController
142
+
143
+ ...
144
+
145
+ def avatar
146
+ foo = Foo.where('_id' => BSON::ObjectId(params[:id])).first
147
+ grid_io_data = (params[:style].nil?) ? foo.avatar.get_from_gridfs : foo.avatar.get_from_gridfs(params[:style])
148
+ bytes = grid_io_data.read
149
+ send_data(bytes, :type => foo.avatar_content_type, :disposition => 'inline')
150
+ end
151
+
152
+ ...
153
+
154
+ end
155
+
156
+ And the view do that:
157
+
158
+ <%= image_tag "/foos/avatar/#{foo.id.to_s}/small" %>
159
+
160
+ == Note on Patches/Pull Requests
161
+
162
+ * Fork the project.
163
+ * Make your feature addition or bug fix.
164
+ * Add tests for it. This is important so I don't break it in a
165
+ future version unintentionally.
166
+ * Commit, do not mess with rakefile, version, or history.
167
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
168
+ * Send me a pull request. Bonus points for topic branches.
169
+
170
+ == Copyright
171
+
172
+ Copyright (c) 2010 Adilson Chacon. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
@@ -0,0 +1,83 @@
1
+ module AttachIt
2
+
3
+ def self.configure(model)
4
+ model.class_eval do
5
+ end
6
+ end
7
+
8
+ module ClassMethods
9
+ def has_attachment(name, options = {})
10
+ options.symbolize_keys!
11
+ name = name.to_sym
12
+
13
+ after_save :save_attachments
14
+ before_destroy :destroy_attachments
15
+
16
+ key :"#{name}_file_name", String
17
+ key :"#{name}_file_size", Integer
18
+ key :"#{name}_content_type", String
19
+ key :"#{name}_updated_at", Date
20
+
21
+ define_method("#{name}=") do |file|
22
+ information_for(name, options).assign(file)
23
+ end
24
+
25
+ define_method("#{name}") do
26
+ information_for(name, options)
27
+ end
28
+
29
+ validates_each name, :logic => lambda { information_for(name, options).send(:flush_errors) }
30
+ end
31
+
32
+ def validates_attachment_size(name = nil, options = {})
33
+ min = options[:greater_than] || (options[:in] && options[:in].first) || 0
34
+ max = options[:less_than] || (options[:in] && options[:in].last) || (1.0/0)
35
+ range = (min..max)
36
+ message = options[:message] || "file size must be between :min and :max bytes"
37
+ message = message.gsub(/:min/, min.to_s).gsub(/:max/, max.to_s)
38
+
39
+ validates_inclusion_of :"#{name}_file_size",
40
+ :within => range,
41
+ :message => message,
42
+ :allow_nil => true
43
+ end
44
+
45
+ def validates_attachment_presence(name = nil, options = {})
46
+ message = options[:message] || "must be set"
47
+ validates_presence_of :"#{name}_file_name",
48
+ :message => message,
49
+ :if => options[:if]
50
+ end
51
+
52
+ def validates_attachment_content_type(name = nil, options = {})
53
+ validation_options = options.dup
54
+ allowed_types = [validation_options[:content_type]].flatten
55
+ message = options[:message] || "is not one of #{allowed_types.join(", ")}"
56
+
57
+ validates_inclusion_of :"#{name}_content_type",
58
+ :within => allowed_types,
59
+ :message => message,
60
+ :allow_nil => true
61
+ end
62
+ end
63
+
64
+ module InstanceMethods
65
+ def information_for(name = nil, options = nil)
66
+ @attachment_options ||= {}
67
+ @attachment_options[name] ||= AttachmentOptions.new(self, name, options)
68
+ end
69
+
70
+ def save_attachments
71
+ @attachment_options.keys.each do |name|
72
+ @attachment_options[name].save
73
+ end
74
+ end
75
+
76
+ def destroy_attachments
77
+ @attachment_options.keys.each do |name|
78
+ @attachment_options[name].delete
79
+ end
80
+ end
81
+
82
+ end
83
+ end
@@ -0,0 +1,169 @@
1
+ require 'wand'
2
+
3
+ class AttachmentOptions
4
+
5
+ attr_accessor :styles, :assigned_file, :object_id, :name
6
+
7
+ def initialize(model = nil, name = nil, options = {})
8
+ @model = model
9
+ @class_name = model.class.name.downcase
10
+ @object_id = model.id.to_s
11
+
12
+ @name = name
13
+ @attachment = name.to_s.downcase.pluralize
14
+
15
+ @url = set_url(options[:url], options[:default_url])
16
+ @path = set_path(options[:path])
17
+ @styles = set_styles(options[:styles])
18
+ @storage = set_storage(options[:storage])
19
+
20
+ if !@model.send("#{@name.to_s}_file_name").nil?
21
+ @filename = @model.send("#{@name.to_s}_file_name")
22
+ @extension = set_extension(@model.send("#{@name.to_s}_file_name"))
23
+ end
24
+
25
+ @queued_for_delete = set_queued_for_delete
26
+
27
+ @errors = Hash.new
28
+ end
29
+
30
+ def assign(file = nil)
31
+ @errors = {}
32
+
33
+ @filename = file_name(file)
34
+ @extension = set_extension(@filename)
35
+ file = file.tempfile if file.respond_to?(:tempfile)
36
+
37
+ @model.send("#{@name.to_s}_file_name=", @filename)
38
+ @model.send("#{@name.to_s}_file_size=", File.size(file))
39
+ @model.send("#{@name.to_s}_content_type=", Wand.wave(file.path))
40
+ @model.send("#{@name.to_s}_updated_at=", Time.now)
41
+
42
+ add_error('Could not resize file') unless file_is_resizale?
43
+ end
44
+
45
+ def url(style_name = 'original')
46
+ return nil unless @storage.is_a?(Filesystem)
47
+ interpolate(@url, style_name)
48
+ end
49
+
50
+ def path(style_name = 'original')
51
+ return nil unless @storage.is_a?(Filesystem)
52
+ interpolate(@path, style_name)
53
+ end
54
+
55
+ def save
56
+ unless @assigned_file.nil?
57
+ @storage.flush_delete(@queued_for_delete)
58
+ @storage.flush_write(self)
59
+ @queued_for_delete = set_queued_for_delete
60
+ end
61
+ end
62
+
63
+ def delete
64
+ @storage.flush_delete(@queued_for_delete)
65
+ end
66
+
67
+ def add_error(description = nil)
68
+ (@errors[:processing] ||= []) << description
69
+ end
70
+
71
+ def flush_errors
72
+ @errors.each do |error, message|
73
+ [message].flatten.each { |m| @model.errors.add(@name, m) }
74
+ end
75
+ end
76
+
77
+ def file_name(file = nil)
78
+ @assigned_file ||= file
79
+ @assigned_file.respond_to?(:original_filename) ? @assigned_file.original_filename : File.basename(@assigned_file.path)
80
+ end
81
+
82
+ def get_from_gridfs(style = 'original')
83
+ if @storage.is_a?(Gridfs)
84
+ @storage.read("#{@object_id}_#{@name}_#{style}")
85
+ else
86
+ nil
87
+ end
88
+ end
89
+
90
+ def base64(style = 'original')
91
+ begin
92
+ bytes = nil
93
+
94
+ if @storage.is_a?(Gridfs)
95
+ bytes = get_from_gridfs(style).read
96
+ elsif @storage.is_a?(Filesystem)
97
+ bytes = File.open(path(style), 'rb').read
98
+ end
99
+
100
+ 'data:' + @model.send("#{@name.to_s}_content_type") + ';base64,' + Base64.encode64(bytes)
101
+ rescue Exception => exception
102
+ nil
103
+ end
104
+ end
105
+
106
+ private
107
+ def interpolate(source = nil, style_name = nil)
108
+ result = source.gsub(/\:rails_root/, Rails.root)
109
+ result.gsub!(/\:environment/, Rails.env)
110
+ result.gsub!(/\:filename/, @filename.nil? ? '' : @filename)
111
+ result.gsub!(/\:extension/, @extension.nil? ? '' : @extension)
112
+ result.gsub!(/\:style/, style_name.to_s)
113
+ result.gsub!(/\:id/, @object_id)
114
+ result.gsub!(/\:class/, @class_name)
115
+ result.gsub!(/\:attachment/, @attachment)
116
+ result.gsub!(/(\/){2,}/, '/')
117
+ result
118
+ end
119
+
120
+ def set_storage(storage_option = nil)
121
+ storage_option = 'filesystem' if storage_option.nil?
122
+ (storage_option == 'filesystem') ? Filesystem.new : Gridfs.new
123
+ end
124
+
125
+ def set_styles(style_option = nil)
126
+ style_option.nil? ? {} : style_option
127
+ end
128
+
129
+ def set_url(url_option = nil, default_url = nil)
130
+ @default_url = default_url
131
+ if url_option.nil?
132
+ (@default_url.nil? ? '/system/:attachment/:id/:style/:filename' : @default_url)
133
+ else
134
+ url_option
135
+ end
136
+ end
137
+
138
+ def set_path(path_option = nil)
139
+ path_option.nil? ? ':rails_root/public/system/:attachment/:id/:style/:filename' : path_option
140
+ end
141
+
142
+ def set_extension(source_path_option = nil)
143
+ source_path_option.nil? ? nil : File.extname(source_path_option)
144
+ end
145
+
146
+ def set_queued_for_delete
147
+ if @storage.is_a?(Filesystem)
148
+ [@styles.keys, :original].flatten.map do |style_name|
149
+ path(style_name) if File.exists?(path(style_name))
150
+ end.compact
151
+ else
152
+ [@styles.keys, :original].flatten.map do |style_name|
153
+ "#{@object_id}_#{@name}_#{style_name}"
154
+ end.compact
155
+ end
156
+ end
157
+
158
+ def file_is_resizale?
159
+ if @styles.keys.size > 0
160
+ begin
161
+ system("identify -format %wx%h #{@assigned_file.path}")
162
+ rescue Exception => exception
163
+ false
164
+ end
165
+ else
166
+ true
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,30 @@
1
+ class Filesystem < Storage
2
+
3
+ def flush_write(image_options = nil)
4
+ image_options.styles.each do |style_name, style_value|
5
+ begin
6
+ FileUtils.mkdir_p(File.dirname(image_options.path(style_name)))
7
+ resize(style_value, image_options.assigned_file.path).write(image_options.path(style_name))
8
+ FileUtils.chmod(0644, image_options.path(style_name))
9
+ rescue Exception => exception
10
+ image_options.add_error(exception.to_s)
11
+ end
12
+ end
13
+
14
+ unless image_options.styles.has_key?(:original)
15
+ begin
16
+ FileUtils.mkdir_p(File.dirname(image_options.path(:original)))
17
+ FileUtils.cp(image_options.assigned_file.path, image_options.path(:original))
18
+ rescue Exception => exception
19
+ image_options.add_error(exception.to_s)
20
+ end
21
+ end
22
+ end
23
+
24
+ def flush_delete(queued_for_delete = nil)
25
+ queued_for_delete.each do |file|
26
+ FileUtils.rm(file) if File.exist?(file)
27
+ end
28
+ end
29
+
30
+ end
@@ -0,0 +1,34 @@
1
+ class Gridfs < Storage
2
+
3
+ def initialize
4
+ @grid ||= Mongo::Grid.new(MongoMapper.database)
5
+ end
6
+
7
+ def flush_write(image_options = nil)
8
+ image_options.styles.each do |style_name, style_value|
9
+ begin
10
+ gridfs_id = @grid.put(resize(style_value, image_options.assigned_file.path).to_blob, :filename => style_name.to_s + '_' + image_options.file_name, :_id => "#{image_options.object_id}_#{image_options.name}_#{style_name}")
11
+ rescue Exception => exception
12
+ image_options.add_error(exception.to_s)
13
+ end
14
+ end
15
+
16
+ begin
17
+ gridfs_id = @grid.put(image_options.assigned_file, :filename => 'original_' + image_options.file_name, :_id => "#{image_options.object_id}_#{image_options.name}_original")
18
+ rescue Exception => exception
19
+ image_options.add_error(exception.to_s)
20
+ end
21
+
22
+ end
23
+
24
+ def flush_delete(queued_for_delete = nil)
25
+ queued_for_delete.each do |id|
26
+ @grid.delete(id)
27
+ end
28
+ end
29
+
30
+ def read(id = nil)
31
+ @grid.get(id) unless id.nil?
32
+ end
33
+
34
+ end
@@ -0,0 +1,10 @@
1
+ require 'RMagick'
2
+
3
+ class Storage
4
+
5
+ def resize(style_value = nil, filename = nil)
6
+ new_image = Magick::Image.read(filename).first
7
+ new_image.change_geometry!(style_value) { |cols, rows, img| img.resize!(cols, rows) }
8
+ end
9
+
10
+ end
@@ -0,0 +1,3 @@
1
+ require File.join(File.dirname(__FILE__), 'storage/storage')
2
+ require File.join(File.dirname(__FILE__), 'storage/filesystem')
3
+ require File.join(File.dirname(__FILE__), 'storage/gridfs')
@@ -0,0 +1,3 @@
1
+ module AttachIt
2
+ Version = '0.1.2'
3
+ end
@@ -0,0 +1,5 @@
1
+ require File.join(File.dirname(__FILE__), 'attach_it', 'version')
2
+ require File.join(File.dirname(__FILE__), 'attach_it', 'attach_it')
3
+ require File.join(File.dirname(__FILE__), 'attach_it', 'attachment_options')
4
+ require File.join(File.dirname(__FILE__), 'attach_it', 'storage')
5
+
@@ -0,0 +1,134 @@
1
+ require 'rubygems'
2
+ require 'tempfile'
3
+ require 'mongo_mapper'
4
+ require 'shoulda'
5
+ require 'mocha'
6
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/mm_attach_it')
7
+
8
+ MongoMapper.database = "testing_mm_attach_it"
9
+
10
+ FakeRailsRoot = File.join(File.dirname(__FILE__) + '/tmp/')
11
+
12
+ class Test::Unit::TestCase
13
+ def setup
14
+ public_dir = FakeRailsRoot + 'public/'
15
+ FileUtils.rm_rf(public_dir) if File.exist?(public_dir)
16
+ FileUtils.mkdir_p(public_dir)
17
+ MongoMapper.database.collections.each(&:remove)
18
+ end
19
+ end
20
+
21
+ class UserOne
22
+ include MongoMapper::Document
23
+ plugin AttachIt
24
+ key :name, String
25
+ has_attachment :avatar
26
+ end
27
+
28
+ class UserTwo
29
+ include MongoMapper::Document
30
+ plugin AttachIt
31
+ key :name, String
32
+ has_attachment :avatar, { :url => '/assets/users/:id/:filename', :path => ':rails_root/public/assets/users/:id/:filename' }
33
+ end
34
+
35
+ class UserThree
36
+ include MongoMapper::Document
37
+ plugin AttachIt
38
+ key :name, String
39
+ has_attachment :avatar, { :styles => { :small => '150x150>', :medium => '300x300>' } }
40
+ end
41
+
42
+ class UserFour
43
+ include MongoMapper::Document
44
+ plugin AttachIt
45
+ key :name, String
46
+ has_attachment :avatar, { :default_url => '/images/default/avatar.jpg' }
47
+ end
48
+
49
+ class UserFive
50
+ include MongoMapper::Document
51
+ plugin AttachIt
52
+ key :name, String
53
+ has_attachment :avatar
54
+
55
+ validates_attachment_size :avatar, :less_than => 1.megabyte
56
+ end
57
+
58
+ class UserSix
59
+ include MongoMapper::Document
60
+ plugin AttachIt
61
+ key :name, String
62
+ has_attachment :avatar
63
+
64
+ validates_attachment_size :avatar, :less_than => 90.kilobytes
65
+ end
66
+
67
+ class UserSeven
68
+ include MongoMapper::Document
69
+ plugin AttachIt
70
+ key :name, String
71
+ has_attachment :avatar
72
+
73
+ validates_attachment_size :avatar, :greater_than => 90.kilobytes
74
+ end
75
+
76
+ class UserEight
77
+ include MongoMapper::Document
78
+ plugin AttachIt
79
+ key :name, String
80
+ has_attachment :avatar
81
+
82
+ validates_attachment_size :avatar, :greater_than => 1.megabyte
83
+ end
84
+
85
+ class UserNine
86
+ include MongoMapper::Document
87
+ plugin AttachIt
88
+ key :name, String
89
+ has_attachment :avatar
90
+
91
+ validates_attachment_presence :avatar
92
+ end
93
+
94
+ class UserTen
95
+ include MongoMapper::Document
96
+ plugin AttachIt
97
+ key :name, String
98
+ has_attachment :avatar
99
+
100
+ validates_attachment_content_type :avatar, :content_type => ['image/gif', 'image/png']
101
+ end
102
+
103
+ class UserEleven
104
+ include MongoMapper::Document
105
+ plugin AttachIt
106
+ key :name, String
107
+ has_attachment :avatar
108
+
109
+ validates_attachment_content_type :avatar, :content_type => ['image/jpg', 'image/jpeg']
110
+ end
111
+
112
+ class UserTwelve
113
+ include MongoMapper::Document
114
+ plugin AttachIt
115
+ key :name, String
116
+ has_attachment :avatar, { :styles => { :small => '150x150>', :medium => '300x300>' }, :storage => 'gridfs' }
117
+ end
118
+
119
+ class DocumentOne
120
+ include MongoMapper::Document
121
+ plugin AttachIt
122
+ key :name, String
123
+ has_attachment :document
124
+ end
125
+
126
+ class Rails
127
+ def self.env
128
+ 'test'
129
+ end
130
+
131
+ def self.root
132
+ File.join(File.dirname(__FILE__) + '/tmp/')
133
+ end
134
+ end
@@ -0,0 +1,289 @@
1
+ require File.join(File.dirname(__FILE__), '../test_helper')
2
+
3
+ JpgImageFile = File.join(File.dirname(__FILE__) + '/../fixtures/lake_moraine.jpg')
4
+ PdfFile = File.join(File.dirname(__FILE__) + '/../fixtures/example.pdf')
5
+ FakeImageFile = File.join(File.dirname(__FILE__) + '/../fixtures/fakeimage.jpg')
6
+
7
+ class TestAttachIt < Test::Unit::TestCase
8
+ context "#initialize" do
9
+ setup do
10
+ @user = UserOne.new
11
+ @user.name = 'Myname'
12
+ end
13
+
14
+ should "set instance of image_options" do
15
+ assert_instance_of AttachmentOptions, @user.avatar
16
+ end
17
+ end
18
+
19
+ context "Attaching an image on file system" do
20
+ setup do
21
+ @user = UserOne.new
22
+ @user.name = 'Myname'
23
+ @user.avatar = File.open(JpgImageFile, 'rb')
24
+ end
25
+
26
+ should "respond with path and url methods with default value" do
27
+ destiny_file = Rails.root + 'public/system/avatars/' + @user.id.to_s + '/original/lake_moraine.jpg'
28
+ assert_equal(destiny_file, @user.avatar.path)
29
+
30
+ destiny_url = '/system/avatars/' + @user.id.to_s + '/original/lake_moraine.jpg'
31
+ assert_equal(destiny_url, @user.avatar.url)
32
+ end
33
+
34
+ should "create file and informations columns after save" do
35
+ destiny_file = Rails.root + 'public/system/avatars/' + @user.id.to_s + '/original/lake_moraine.jpg'
36
+ @user.save
37
+ assert(File.exist?(destiny_file))
38
+ assert_equal('lake_moraine.jpg', @user.avatar_file_name)
39
+ assert_equal('image/jpeg', @user.avatar_content_type)
40
+ assert_equal(102520, @user.avatar_file_size)
41
+ assert_instance_of(Date, @user.avatar_updated_at)
42
+ end
43
+
44
+ should "retreive correct informations" do
45
+ destiny_file = Rails.root + 'public/system/avatars/' + @user.id.to_s + '/original/lake_moraine.jpg'
46
+ destiny_url = '/system/avatars/' + @user.id.to_s + '/original/lake_moraine.jpg'
47
+
48
+ @user.save
49
+
50
+ saved_user = UserOne.find(@user.id.to_s)
51
+ assert_equal(destiny_file, saved_user.avatar.path)
52
+ assert_equal(destiny_url, saved_user.avatar.url)
53
+ assert_equal('lake_moraine.jpg', saved_user.avatar_file_name)
54
+ assert_equal(102520, saved_user.avatar_file_size)
55
+ assert_equal('image/jpeg', saved_user.avatar_content_type)
56
+ assert_instance_of(Date, saved_user.avatar_updated_at)
57
+ assert_equal(@user.avatar_updated_at, saved_user.avatar_updated_at)
58
+ end
59
+
60
+ end
61
+
62
+
63
+ context "Attaching not an image on file system" do
64
+
65
+ end
66
+ setup do
67
+ @doc = DocumentOne.new
68
+ @doc.name = 'Mydocument'
69
+ @doc.document = File.open(PdfFile, 'rb')
70
+ end
71
+
72
+ should "respond with path and url methods with default value" do
73
+ destiny_file = Rails.root + 'public/system/documents/' + @doc.id.to_s + '/original/example.pdf'
74
+ assert_equal(destiny_file, @doc.document.path)
75
+
76
+ destiny_url = '/system/documents/' + @doc.id.to_s + '/original/example.pdf'
77
+ assert_equal(destiny_url, @doc.document.url)
78
+ end
79
+
80
+ should "create file and informations columns after save" do
81
+ destiny_file = Rails.root + 'public/system/documents/' + @doc.id.to_s + '/original/example.pdf'
82
+ @doc.save
83
+ assert(File.exist?(destiny_file))
84
+ assert_equal('example.pdf', @doc.document_file_name)
85
+ assert_equal('application/pdf', @doc.document_content_type)
86
+ assert_equal(9785, @doc.document_file_size)
87
+ assert_instance_of(Date, @doc.document_updated_at)
88
+ end
89
+
90
+ should "retreive correct informations from db" do
91
+ destiny_file = Rails.root + 'public/system/documents/' + @doc.id.to_s + '/original/example.pdf'
92
+ destiny_url = '/system/documents/' + @doc.id.to_s + '/original/example.pdf'
93
+
94
+ @doc.save
95
+
96
+ saved_doc = DocumentOne.find(@doc.id.to_s)
97
+ assert_equal(destiny_file, saved_doc.document.path)
98
+ assert_equal(destiny_url, saved_doc.document.url)
99
+ assert_equal('example.pdf', saved_doc.document_file_name)
100
+ assert_equal('application/pdf', saved_doc.document_content_type)
101
+ assert_equal(9785, saved_doc.document_file_size)
102
+ assert_instance_of(Date, saved_doc.document_updated_at)
103
+ assert_equal(@doc.document_updated_at, saved_doc.document_updated_at)
104
+ end
105
+ end
106
+
107
+ context "Creating differents sizes from an image" do
108
+ setup do
109
+ @user = UserThree.new
110
+ @user.name = 'Myname'
111
+ @user.avatar = File.open(JpgImageFile, 'rb')
112
+ end
113
+
114
+ should "respond with path and url methods by styles" do
115
+ [:small, :medium, :original].each do |style|
116
+ destiny_file = Rails.root + 'public/system/avatars/' + @user.id.to_s + '/' + style.to_s + '/lake_moraine.jpg'
117
+ assert_equal(destiny_file, @user.avatar.path(style))
118
+
119
+ destiny_url = '/system/avatars/' + @user.id.to_s + '/' + style.to_s + '/lake_moraine.jpg'
120
+ assert_equal(destiny_url, @user.avatar.url(style))
121
+ end
122
+ end
123
+
124
+ should "create file after save by styles" do
125
+ @user.save
126
+ [:small, :medium, :original].each do |style|
127
+ destiny_file = Rails.root + 'public/system/avatars/' + @user.id.to_s + '/' + style.to_s + '/lake_moraine.jpg'
128
+ assert(File.exist?(destiny_file))
129
+ end
130
+ end
131
+
132
+ should "return the base64 from file" do
133
+ @user.save
134
+ assert_match(/^data:image\/jpeg;base64,/, @user.avatar.base64)
135
+ assert_match(/^data:image\/jpeg;base64,/, @user.avatar.base64('small'))
136
+ assert_match(/^data:image\/jpeg;base64,/, @user.avatar.base64('medium'))
137
+ assert_match(/^data:image\/jpeg;base64,/, @user.avatar.base64('original'))
138
+ end
139
+ end
140
+
141
+ context "Set the url and path" do
142
+ setup do
143
+ @user = UserTwo.new
144
+ @user.name = 'Myname'
145
+ @user.avatar = File.open(JpgImageFile, 'rb')
146
+ end
147
+
148
+ should "respond with path and url" do
149
+ destiny_file = Rails.root + 'public/assets/users/' + @user.id.to_s + '/lake_moraine.jpg'
150
+ assert_equal(destiny_file, @user.avatar.path)
151
+
152
+ destiny_url = '/assets/users/' + @user.id.to_s + '/lake_moraine.jpg'
153
+ assert_equal(destiny_url, @user.avatar.url)
154
+ end
155
+
156
+ should "retreive correct informations from db" do
157
+ destiny_file = Rails.root + 'public/assets/users/' + @user.id.to_s + '/lake_moraine.jpg'
158
+ destiny_url = '/assets/users/' + @user.id.to_s + '/lake_moraine.jpg'
159
+
160
+ @user.save
161
+
162
+ saved_user = UserTwo.find(@user.id.to_s)
163
+ assert_equal(destiny_file, saved_user.avatar.path)
164
+ assert_equal(destiny_url, saved_user.avatar.url)
165
+ end
166
+ end
167
+
168
+ context "Use default URL" do
169
+ setup do
170
+ @user = UserFour.new
171
+ @user.name = 'Myname'
172
+ end
173
+
174
+ should "retreive correct informations from db" do
175
+ destiny_url = '/images/default/avatar.jpg'
176
+
177
+ @user.save
178
+
179
+ saved_user = UserFour.find(@user.id.to_s)
180
+ assert_equal(destiny_url, saved_user.avatar.url)
181
+ end
182
+ end
183
+
184
+ context "Handle errors" do
185
+ setup do
186
+ end
187
+
188
+ should "have an error if file can't be resized" do
189
+ user = UserThree.new
190
+ user.name = 'Myname'
191
+ user.avatar = File.open(FakeImageFile)
192
+ user.save
193
+
194
+ assert_equal(user.errors.size, 1)
195
+ assert_equal(user.errors[:avatar].first, 'Could not resize file')
196
+ end
197
+
198
+ should "haven't an error if file size is less than a specific value" do
199
+ user = UserFive.new
200
+ user.name = 'Myname'
201
+ user.avatar = File.open(JpgImageFile, 'rb')
202
+ user.save
203
+
204
+ assert_equal(user.errors.size, 0)
205
+ end
206
+
207
+ should "have an error if file size is not less than a specific value" do
208
+ user = UserSix.new
209
+ user.name = 'Myname'
210
+ user.avatar = File.open(JpgImageFile, 'rb')
211
+ user.save
212
+
213
+ assert_equal(user.errors.size, 1)
214
+ assert_equal(user.errors[:avatar_file_size].first, 'file size must be between 0 and 92160 bytes')
215
+ end
216
+
217
+ should "haven't error if file size is greater than a specific value" do
218
+ user = UserSeven.new
219
+ user.name = 'Myname'
220
+ user.avatar = File.open(JpgImageFile, 'rb')
221
+ user.save
222
+
223
+ assert_equal(user.errors.size, 0)
224
+ end
225
+
226
+ should "have an error if file size is not greater than a specific value" do
227
+ user = UserEight.new
228
+ user.name = 'Myname'
229
+ user.avatar = File.open(JpgImageFile, 'rb')
230
+ user.save
231
+
232
+ assert_equal(user.errors.size, 1)
233
+ assert_equal(user.errors[:avatar_file_size].first, 'file size must be between 1048576 and Infinity bytes')
234
+ end
235
+
236
+ should "have an error if file size is not greater than a specific value" do
237
+ user = UserNine.new
238
+ user.name = 'Myname'
239
+ user.save
240
+
241
+ assert_equal(user.errors.size, 1)
242
+ assert_equal(user.errors[:avatar_file_name].first, 'must be set')
243
+ end
244
+
245
+ should "have an error if file content type is not one of the specifieds" do
246
+ user = UserTen.new
247
+ user.name = 'Myname'
248
+ user.avatar = File.open(JpgImageFile, 'rb')
249
+ user.save
250
+
251
+ assert_equal(user.errors.size, 1)
252
+ assert_equal(user.errors[:avatar_content_type].first, 'is not one of image/gif, image/png')
253
+ end
254
+
255
+ should "haven't error if file content type is one of the specifieds" do
256
+ user = UserEleven.new
257
+ user.name = 'Myname'
258
+ user.avatar = File.open(JpgImageFile, 'rb')
259
+ user.save
260
+
261
+ assert_equal(user.errors.size, 0)
262
+ end
263
+ end
264
+
265
+ context "Gridfs behavior" do
266
+ setup do
267
+ @user = UserTwelve.new
268
+ @user.name = 'Myname'
269
+ @user.avatar = File.open(JpgImageFile, 'rb')
270
+ @user.save
271
+ end
272
+
273
+ should "save the images resizeds and the original" do
274
+ assert_equal(@user.avatar.get_from_gridfs.class, Mongo::GridIO)
275
+ assert_equal(@user.avatar.get_from_gridfs('small').class, Mongo::GridIO)
276
+ assert_equal(@user.avatar.get_from_gridfs('medium').class, Mongo::GridIO)
277
+ assert_equal(@user.avatar.get_from_gridfs('original').class, Mongo::GridIO)
278
+ end
279
+
280
+ should "return the base64 from file" do
281
+ assert_match(/^data:image\/jpeg;base64,/, @user.avatar.base64)
282
+ assert_match(/^data:image\/jpeg;base64,/, @user.avatar.base64('small'))
283
+ assert_match(/^data:image\/jpeg;base64,/, @user.avatar.base64('medium'))
284
+ assert_match(/^data:image\/jpeg;base64,/, @user.avatar.base64('original'))
285
+ end
286
+
287
+ end
288
+
289
+ end
metadata ADDED
@@ -0,0 +1,164 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mm-attach-it
3
+ version: !ruby/object:Gem::Version
4
+ hash: 31
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 2
10
+ version: 0.1.2
11
+ platform: ruby
12
+ authors:
13
+ - Adilson Chacon
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-05-30 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: wand
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: mongo_mapper
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ hash: 3
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ type: :runtime
47
+ version_requirements: *id002
48
+ - !ruby/object:Gem::Dependency
49
+ name: rmagick
50
+ prerelease: false
51
+ requirement: &id003 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ hash: 3
57
+ segments:
58
+ - 0
59
+ version: "0"
60
+ type: :runtime
61
+ version_requirements: *id003
62
+ - !ruby/object:Gem::Dependency
63
+ name: mime-types
64
+ prerelease: false
65
+ requirement: &id004 !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ hash: 3
71
+ segments:
72
+ - 0
73
+ version: "0"
74
+ type: :runtime
75
+ version_requirements: *id004
76
+ - !ruby/object:Gem::Dependency
77
+ name: shoulda
78
+ prerelease: false
79
+ requirement: &id005 !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ hash: 3
85
+ segments:
86
+ - 0
87
+ version: "0"
88
+ type: :development
89
+ version_requirements: *id005
90
+ - !ruby/object:Gem::Dependency
91
+ name: mocha
92
+ prerelease: false
93
+ requirement: &id006 !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ hash: 3
99
+ segments:
100
+ - 0
101
+ version: "0"
102
+ type: :development
103
+ version_requirements: *id006
104
+ description: Attach files (images, videos, pdfs, txts, zips and etc) to a MongoMapper record. You can choose if you to store it on file system or GridFS.
105
+ email: adilsonchacon@gmail.com
106
+ executables: []
107
+
108
+ extensions: []
109
+
110
+ extra_rdoc_files:
111
+ - README
112
+ - README.rdoc
113
+ files:
114
+ - README
115
+ - README.rdoc
116
+ - LICENSE
117
+ - Rakefile
118
+ - Gemfile
119
+ - lib/mm-attach-it.rb
120
+ - lib/attach_it/version.rb
121
+ - lib/attach_it/storage/gridfs.rb
122
+ - lib/attach_it/storage/filesystem.rb
123
+ - lib/attach_it/storage/storage.rb
124
+ - lib/attach_it/attachment_options.rb
125
+ - lib/attach_it/attach_it.rb
126
+ - lib/attach_it/storage.rb
127
+ - test/test_helper.rb
128
+ - test/unit/test_attach_it.rb
129
+ homepage: https://github.com/adilsonchacon/mm-attach-it
130
+ licenses: []
131
+
132
+ post_install_message:
133
+ rdoc_options: []
134
+
135
+ require_paths:
136
+ - lib
137
+ required_ruby_version: !ruby/object:Gem::Requirement
138
+ none: false
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ hash: 3
143
+ segments:
144
+ - 0
145
+ version: "0"
146
+ required_rubygems_version: !ruby/object:Gem::Requirement
147
+ none: false
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ hash: 3
152
+ segments:
153
+ - 0
154
+ version: "0"
155
+ requirements:
156
+ - ImageMagick
157
+ rubyforge_project: mm-attach_it
158
+ rubygems_version: 1.8.4
159
+ signing_key:
160
+ specification_version: 3
161
+ summary: MongoMapper Plugin File Attacher.
162
+ test_files:
163
+ - test/unit/test_attach_it.rb
164
+ - test/test_helper.rb