file_column_with_s3 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,19 @@
1
+ # require this file from your "config/environment.rb" (after rails has been loaded)
2
+ # to integrate the file_column extension into rails.
3
+
4
+ require 'file_column'
5
+ require 'file_column_helper'
6
+
7
+
8
+ module ActiveRecord # :nodoc:
9
+ class Base # :nodoc:
10
+ # make file_column method available in all active record decendants
11
+ include FileColumn
12
+ end
13
+ end
14
+
15
+ module ActionView # :nodoc:
16
+ class Base # :nodoc:
17
+ include FileColumnHelper
18
+ end
19
+ end
data/lib/test_case.rb ADDED
@@ -0,0 +1,124 @@
1
+ require 'test/unit'
2
+
3
+ # Add the methods +upload+, the <tt>setup_file_fixtures</tt> and
4
+ # <tt>teardown_file_fixtures</tt> to the class Test::Unit::TestCase.
5
+ class Test::Unit::TestCase
6
+ # Returns a +Tempfile+ object as it would have been generated on file upload.
7
+ # Use this method to create the parameters when emulating form posts with
8
+ # file fields.
9
+ #
10
+ # === Example:
11
+ #
12
+ # def test_file_column_post
13
+ # entry = { :title => 'foo', :file => upload('/tmp/foo.txt')}
14
+ # post :upload, :entry => entry
15
+ #
16
+ # # ...
17
+ # end
18
+ #
19
+ # === Parameters
20
+ #
21
+ # * <tt>path</tt> The path to the file to upload.
22
+ # * <tt>content_type</tt> The MIME type of the file. If it is <tt>:guess</tt>,
23
+ # the method will try to guess it.
24
+ def upload(path, content_type=:guess, type=:tempfile)
25
+ if content_type == :guess
26
+ case path
27
+ when /\.jpg$/ then content_type = "image/jpeg"
28
+ when /\.png$/ then content_type = "image/png"
29
+ else content_type = nil
30
+ end
31
+ end
32
+ uploaded_file(path, content_type, File.basename(path), type)
33
+ end
34
+
35
+ # Copies the fixture files from "RAILS_ROOT/test/fixtures/file_column" into
36
+ # the temporary storage directory used for testing
37
+ # ("RAILS_ROOT/test/tmp/file_column"). Call this method in your
38
+ # <tt>setup</tt> methods to get the file fixtures (images, for example) into
39
+ # the directory used by file_column in testing.
40
+ #
41
+ # Note that the files and directories in the "fixtures/file_column" directory
42
+ # must have the same structure as you would expect in your "/public" directory
43
+ # after uploading with FileColumn.
44
+ #
45
+ # For example, the directory structure could look like this:
46
+ #
47
+ # test/fixtures/file_column/
48
+ # `-- container
49
+ # |-- first_image
50
+ # | |-- 1
51
+ # | | `-- image1.jpg
52
+ # | `-- tmp
53
+ # `-- second_image
54
+ # |-- 1
55
+ # | `-- image2.jpg
56
+ # `-- tmp
57
+ #
58
+ # Your fixture file for this one "container" class fixture could look like this:
59
+ #
60
+ # first:
61
+ # id: 1
62
+ # first_image: image1.jpg
63
+ # second_image: image1.jpg
64
+ #
65
+ # A usage example:
66
+ #
67
+ # def setup
68
+ # setup_fixture_files
69
+ #
70
+ # # ...
71
+ # end
72
+ def setup_fixture_files
73
+ tmp_path = File.join(RAILS_ROOT, "test", "tmp", "file_column")
74
+ file_fixtures = Dir.glob File.join(RAILS_ROOT, "test", "fixtures", "file_column", "*")
75
+
76
+ FileUtils.mkdir_p tmp_path unless File.exists?(tmp_path)
77
+ FileUtils.cp_r file_fixtures, tmp_path
78
+ end
79
+
80
+ # Removes the directory "RAILS_ROOT/test/tmp/file_column/" so the files
81
+ # copied on test startup are removed. Call this in your unit test's +teardown+
82
+ # method.
83
+ #
84
+ # A usage example:
85
+ #
86
+ # def teardown
87
+ # teardown_fixture_files
88
+ #
89
+ # # ...
90
+ # end
91
+ def teardown_fixture_files
92
+ FileUtils.rm_rf File.join(RAILS_ROOT, "test", "tmp", "file_column")
93
+ end
94
+
95
+ private
96
+
97
+ def uploaded_file(path, content_type, filename, type=:tempfile) # :nodoc:
98
+ if type == :tempfile
99
+ t = Tempfile.new(File.basename(filename))
100
+ FileUtils.copy_file(path, t.path)
101
+ else
102
+ if path
103
+ t = StringIO.new(IO.read(path))
104
+ else
105
+ t = StringIO.new
106
+ end
107
+ end
108
+ (class << t; self; end).class_eval do
109
+ alias local_path path if type == :tempfile
110
+ define_method(:local_path) { "" } if type == :stringio
111
+ define_method(:original_filename) {filename}
112
+ define_method(:content_type) {content_type}
113
+ end
114
+ return t
115
+ end
116
+ end
117
+
118
+ # If we are running in the "test" environment, we overwrite the default
119
+ # settings for FileColumn so that files are not uploaded into "/public/"
120
+ # in tests but rather into the directory "/test/tmp/file_column".
121
+ if RAILS_ENV == "test"
122
+ FileColumn::ClassMethods::DEFAULT_OPTIONS[:root_path] =
123
+ File.join(RAILS_ROOT, "test", "tmp", "file_column")
124
+ end
@@ -0,0 +1,112 @@
1
+ module FileColumn
2
+ module Validations #:nodoc:
3
+
4
+ def self.append_features(base)
5
+ super
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ # This module contains methods to create validations of uploaded files. All methods
10
+ # in this module will be included as class methods into <tt>ActiveRecord::Base</tt>
11
+ # so that you can use them in your models like this:
12
+ #
13
+ # class Entry < ActiveRecord::Base
14
+ # file_column :image
15
+ # validates_filesize_of :image, :in => 0..1.megabyte
16
+ # end
17
+ module ClassMethods
18
+ EXT_REGEXP = /\.([A-z0-9]+)$/
19
+
20
+ # This validates the file type of one or more file_columns. A list of file columns
21
+ # should be given followed by an options hash.
22
+ #
23
+ # Required options:
24
+ # * <tt>:in</tt> => list of extensions or mime types. If mime types are used they
25
+ # will be mapped into an extension via FileColumn::ClassMethods::MIME_EXTENSIONS.
26
+ #
27
+ # Examples:
28
+ # validates_file_format_of :field, :in => ["gif", "png", "jpg"]
29
+ # validates_file_format_of :field, :in => ["image/jpeg"]
30
+ def validates_file_format_of(*attrs)
31
+
32
+ options = attrs.pop if attrs.last.is_a?Hash
33
+ raise ArgumentError, "Please include the :in option." if !options || !options[:in]
34
+ options[:in] = [options[:in]] if options[:in].is_a?String
35
+ raise ArgumentError, "Invalid value for option :in" unless options[:in].is_a?Array
36
+
37
+ validates_each(attrs, options) do |record, attr, value|
38
+ unless value.blank?
39
+ mime_extensions = record.send("#{attr}_options")[:mime_extensions]
40
+ extensions = options[:in].map{|o| mime_extensions[o] || o }
41
+ record.errors.add attr, "is not a valid format." unless extensions.include?(value.scan(EXT_REGEXP).flatten.first)
42
+ end
43
+ end
44
+
45
+ end
46
+
47
+ # This validates the file size of one or more file_columns. A list of file columns
48
+ # should be given followed by an options hash.
49
+ #
50
+ # Required options:
51
+ # * <tt>:in</tt> => A size range. Note that you can use ActiveSupport's
52
+ # numeric extensions for kilobytes, etc.
53
+ #
54
+ # Examples:
55
+ # validates_filesize_of :field, :in => 0..100.megabytes
56
+ # validates_filesize_of :field, :in => 15.kilobytes..1.megabyte
57
+ def validates_filesize_of(*attrs)
58
+
59
+ options = attrs.pop if attrs.last.is_a?Hash
60
+ raise ArgumentError, "Please include the :in option." if !options || !options[:in]
61
+ raise ArgumentError, "Invalid value for option :in" unless options[:in].is_a?Range
62
+
63
+ validates_each(attrs, options) do |record, attr, value|
64
+ unless value.blank?
65
+ size = File.size(value)
66
+ record.errors.add attr, "is smaller than the allowed size range." if size < options[:in].first
67
+ record.errors.add attr, "is larger than the allowed size range." if size > options[:in].last
68
+ end
69
+ end
70
+
71
+ end
72
+
73
+ IMAGE_SIZE_REGEXP = /^(\d+)x(\d+)$/
74
+
75
+ # Validates the image size of one or more file_columns. A list of file columns
76
+ # should be given followed by an options hash. The validation will pass
77
+ # if both image dimensions (rows and columns) are at least as big as
78
+ # given in the <tt>:min</tt> option.
79
+ #
80
+ # Required options:
81
+ # * <tt>:min</tt> => minimum image dimension string, in the format NNxNN
82
+ # (columns x rows).
83
+ #
84
+ # Example:
85
+ # validates_image_size :field, :min => "1200x1800"
86
+ #
87
+ # This validation requires RMagick to be installed on your system
88
+ # to check the image's size.
89
+ def validates_image_size(*attrs)
90
+ options = attrs.pop if attrs.last.is_a?Hash
91
+ raise ArgumentError, "Please include a :min option." if !options || !options[:min]
92
+ minimums = options[:min].scan(IMAGE_SIZE_REGEXP).first.collect{|n| n.to_i} rescue []
93
+ raise ArgumentError, "Invalid value for option :min (should be 'XXxYY')" unless minimums.size == 2
94
+
95
+ require 'RMagick'
96
+
97
+ validates_each(attrs, options) do |record, attr, value|
98
+ unless value.blank?
99
+ begin
100
+ img = ::Magick::Image::read(value).first
101
+ record.errors.add('image', "is too small, must be at least #{minimums[0]}x#{minimums[1]}") if ( img.rows < minimums[1] || img.columns < minimums[0] )
102
+ rescue ::Magick::ImageMagickError
103
+ record.errors.add('image', "invalid image")
104
+ end
105
+ img = nil
106
+ GC.start
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
data/s3_env.example ADDED
@@ -0,0 +1,5 @@
1
+ #!/bin/sh
2
+
3
+ export S3_ACCESS_KEY_ID=<key-id>
4
+ export S3_SECRET_ACCESS_KEY=<access-key>
5
+ export S3_BUCKET_NAME=file-column-attachement-test
@@ -0,0 +1,66 @@
1
+ require 'thread'
2
+ require 'test/unit'
3
+ require 'rubygems'
4
+ require 'active_support'
5
+ require 'active_record'
6
+ require 'action_view'
7
+ require 'action_controller'
8
+ require File.dirname(__FILE__) + '/connection'
9
+ require 'stringio'
10
+
11
+ RAILS_ROOT = File.dirname(__FILE__)
12
+ RAILS_ENV = ""
13
+
14
+ $: << "../lib"
15
+
16
+ require 'file_column'
17
+ require 'file_column_helper'
18
+ require 'file_compat'
19
+ require 'validations'
20
+ require 'test_case'
21
+
22
+ # do not use the file executable normally in our tests as
23
+ # it may not be present on the machine we are running on
24
+ FileColumn::ClassMethods::DEFAULT_OPTIONS =
25
+ FileColumn::ClassMethods::DEFAULT_OPTIONS.merge({:file_exec => nil})
26
+
27
+ class ActiveRecord::Base
28
+ include FileColumn
29
+ include FileColumn::Validations
30
+ end
31
+
32
+
33
+ class RequestMock
34
+ attr_accessor :relative_url_root
35
+
36
+ def initialize
37
+ @relative_url_root = ""
38
+ end
39
+ end
40
+
41
+ class Test::Unit::TestCase
42
+
43
+ def assert_equal_paths(expected_path, path)
44
+ assert_equal normalize_path(expected_path), normalize_path(path)
45
+ end
46
+
47
+
48
+ private
49
+
50
+ def normalize_path(path)
51
+ Pathname.new(path).realpath
52
+ end
53
+
54
+ def clear_validations
55
+ [:validate, :validate_on_create, :validate_on_update].each do |attr|
56
+ Entry.write_inheritable_attribute attr, []
57
+ Movie.write_inheritable_attribute attr, []
58
+ end
59
+ end
60
+
61
+ def file_path(filename)
62
+ File.expand_path("#{File.dirname(__FILE__)}/fixtures/#{filename}")
63
+ end
64
+
65
+ alias_method :f, :file_path
66
+ end
@@ -0,0 +1,96 @@
1
+ require File.dirname(__FILE__) + '/abstract_unit'
2
+ require 'active_support/test_case'
3
+
4
+ class AttachementStoreTest < Test::Unit::TestCase
5
+ extend Test::Unit::Assertions
6
+
7
+ STORE_DIR = File.dirname(__FILE__)+"/public/entry"
8
+ STORE_BUILD_OPTS = [[:filesystem]]
9
+ if !ENV["S3_ACCESS_KEY_ID"].blank?
10
+ STORE_BUILD_OPTS << [:s3, {
11
+ :access_key_id => ENV["S3_ACCESS_KEY_ID"],
12
+ :secret_access_key => ENV["S3_SECRET_ACCESS_KEY"],
13
+ :bucket_name => ENV["S3_BUCKET_NAME"]}]
14
+ end
15
+
16
+ def teardown
17
+ FileColumn.store(STORE_DIR).clear
18
+ FileUtils.rm_rf("/tmp/file_column_test")
19
+ end
20
+
21
+ def self.store_test(test_name, store_type, *store_building_args, &block)
22
+ define_method(test_name + "_for_#{store_type}_store") do
23
+ FileColumn.store = store_type, *store_building_args
24
+ yield
25
+ end
26
+ end
27
+
28
+
29
+ STORE_BUILD_OPTS.each do |store_type, *rest_args|
30
+ store_test "test_build_right_store", store_type, *rest_args do
31
+ assert FileColumn.store("/tmp/attachements").class.name.include?(ActiveSupport::Inflector.camelize(store_type))
32
+ end
33
+
34
+ store_test "test_upload_local_file", store_type, *rest_args do
35
+ file = "/tmp/file_column_test/abc"
36
+ FileUtils.mkdir_p(File.dirname(file))
37
+ FileUtils.touch(file)
38
+ store = FileColumn.store(STORE_DIR)
39
+ store.upload("x/y/z", file)
40
+ assert !store.exists?("x/abc")
41
+ assert store.exists?("x/y/z/abc")
42
+ assert_equal "", store.read("x/y/z/abc")
43
+ end
44
+
45
+ store_test "test_upload_with_same_name_replace_file", store_type, *rest_args do
46
+ file = "/tmp/file_column_test/abc"
47
+ FileUtils.mkdir_p(File.dirname(file))
48
+ File.open(file, "w+") { |f| f << "123" }
49
+
50
+ store = FileColumn.store(STORE_DIR)
51
+ store.upload("x/y/z", file)
52
+
53
+ assert_equal "123", store.read("x/y/z/abc")
54
+
55
+ File.open(file, "w+") { |f| f << "456" }
56
+ store.upload("x/y/z", file)
57
+
58
+ assert_equal "456", store.read("x/y/z/abc")
59
+ end
60
+
61
+ store_test "test_upload_local_dir", store_type, *rest_args do
62
+ local_dir = "/tmp/file_column_test"
63
+ FileUtils.mkdir_p(local_dir)
64
+ FileUtils.touch(File.join(local_dir, "a"))
65
+ FileUtils.touch(File.join(local_dir, "b"))
66
+
67
+ store = FileColumn.store(STORE_DIR)
68
+ store.upload_dir("x/y/z", local_dir)
69
+
70
+ assert store.exists?("x/y/z/a")
71
+ assert store.exists?("x/y/z/b")
72
+ end
73
+
74
+
75
+ store_test "test_upload_local_dir_with_replace_files", store_type, *rest_args do
76
+
77
+ local_dir = "/tmp/file_column_test/old"
78
+ FileUtils.mkdir_p(local_dir)
79
+ FileUtils.touch(File.join(local_dir, "a"))
80
+
81
+ store = FileColumn.store(STORE_DIR)
82
+ store.upload_dir("x/y/z", local_dir)
83
+
84
+ local_dir = "/tmp/file_column_test/new"
85
+ FileUtils.mkdir_p(local_dir)
86
+ FileUtils.touch(File.join(local_dir, "b"))
87
+
88
+ store = FileColumn.store(STORE_DIR)
89
+ store.upload_dir("x/y/z", local_dir)
90
+
91
+ assert store.exists?("x/y/z/b")
92
+ assert !store.exists?("x/y/z/a")
93
+ end
94
+
95
+ end
96
+ end
@@ -0,0 +1,11 @@
1
+ require 'logger'
2
+
3
+ ActiveRecord::Base.logger = Logger.new("debug.log")
4
+
5
+ db = 'file_column_test.sqlite'
6
+
7
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3",
8
+ :database => db)
9
+
10
+
11
+ load File.dirname(__FILE__) + "/fixtures/schema.rb"
@@ -0,0 +1,98 @@
1
+ require File.dirname(__FILE__) + '/abstract_unit'
2
+ require File.dirname(__FILE__) + '/fixtures/entry'
3
+
4
+ class UrlForFileColumnTest < Test::Unit::TestCase
5
+ include FileColumnHelper
6
+
7
+ def setup
8
+ Entry.file_column :image
9
+ @request = RequestMock.new
10
+ end
11
+
12
+ def test_url_for_file_column_with_temp_entry
13
+ @e = Entry.new(:image => upload(f("skanthak.png")))
14
+ url = url_for_file_column("e", "image")
15
+ assert_match %r{^/entry/image/tmp/\d+(\.\d+)+/skanthak.png$}, url
16
+ end
17
+
18
+ def test_url_for_file_column_with_saved_entry
19
+ @e = Entry.new(:image => upload(f("skanthak.png")))
20
+ assert @e.save
21
+
22
+ url = url_for_file_column("e", "image")
23
+ assert_equal "/entry/image/#{@e.file_column_relative_path_prefix}/skanthak.png", url
24
+ end
25
+
26
+ def test_url_for_file_column_works_with_symbol
27
+ @e = Entry.new(:image => upload(f("skanthak.png")))
28
+ assert @e.save
29
+
30
+ url = url_for_file_column(:e, :image)
31
+ assert_equal "/entry/image/#{@e.file_column_relative_path_prefix}/skanthak.png", url
32
+ end
33
+
34
+ def test_url_for_file_column_works_with_object
35
+ e = Entry.new(:image => upload(f("skanthak.png")))
36
+ assert e.save
37
+
38
+ url = url_for_file_column(e, "image")
39
+ assert_equal "/entry/image/#{e.file_column_relative_path_prefix}/skanthak.png", url
40
+ end
41
+
42
+ def test_url_for_file_column_should_return_nil_on_no_uploaded_file
43
+ e = Entry.new
44
+ assert_nil url_for_file_column(e, "image")
45
+ end
46
+
47
+ def test_url_for_file_column_without_extension
48
+ e = Entry.new
49
+ e.image = uploaded_file(file_path("kerb.jpg"), "something/unknown", "local_filename")
50
+ assert e.save
51
+ assert_equal "/entry/image/#{e.file_column_relative_path_prefix}/local_filename", url_for_file_column(e, "image")
52
+ end
53
+ end
54
+
55
+ class UrlForFileColumnTest < Test::Unit::TestCase
56
+ include FileColumnHelper
57
+ include ActionView::Helpers::AssetTagHelper
58
+ include ActionView::Helpers::TagHelper
59
+ include ActionView::Helpers::UrlHelper
60
+
61
+ def setup
62
+ Entry.file_column :image
63
+
64
+ # mock up some request data structures for AssetTagHelper
65
+ @request = RequestMock.new
66
+ ActionController::Base.relative_url_root = "/foo/bar"
67
+ @controller = self
68
+ end
69
+
70
+ def request
71
+ @request
72
+ end
73
+
74
+ IMAGE_URL = %r{^/foo/bar/entry/image/.+/skanthak.png$}
75
+ def test_with_image_tag
76
+ e = Entry.new(:image => upload(f("skanthak.png")))
77
+ html = image_tag url_for_file_column(e, "image")
78
+ url = html.scan(/src=\"(.+)\?.*\"/).first.first
79
+
80
+ assert_match IMAGE_URL, url
81
+ end
82
+
83
+ def test_with_link_to_tag
84
+ e = Entry.new(:image => upload(f("skanthak.png")))
85
+ html = link_to "Download", url_for_file_column(e, "image", :absolute => true)
86
+
87
+ url = html.scan(/href=\"(.+)\"/).first.first
88
+
89
+ assert_match IMAGE_URL, url
90
+ end
91
+
92
+ def test_relative_url_root_not_modified
93
+ e = Entry.new(:image => upload(f("skanthak.png")))
94
+ url_for_file_column(e, "image", :absolute => true)
95
+
96
+ assert_equal "/foo/bar", ActionController::Base.relative_url_root
97
+ end
98
+ end