file_column_with_s3 0.1.0

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.
@@ -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