progstr-filer 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,24 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "sqlite3"
10
+ gem "guard-test"
11
+ gem "rb-inotify"
12
+ gem "libnotify"
13
+ gem "shoulda", ">= 0"
14
+ #gem "rspec", "~> 2.3.0"
15
+ gem "bundler", "~> 1.0.0"
16
+ gem "jeweler", "~> 1.6.4"
17
+ gem "rcov", ">= 0"
18
+ gem "rdoc", ">= 0"
19
+ end
20
+
21
+ gem "activerecord", ">=3.0.0"
22
+ gem "uuid"
23
+ gem "rest-client"
24
+ gem "multi_json"
data/Gemfile.lock ADDED
@@ -0,0 +1,69 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activemodel (3.1.3)
5
+ activesupport (= 3.1.3)
6
+ builder (~> 3.0.0)
7
+ i18n (~> 0.6)
8
+ activerecord (3.1.3)
9
+ activemodel (= 3.1.3)
10
+ activesupport (= 3.1.3)
11
+ arel (~> 2.2.1)
12
+ tzinfo (~> 0.3.29)
13
+ activesupport (3.1.3)
14
+ multi_json (~> 1.0)
15
+ arel (2.2.1)
16
+ builder (3.0.0)
17
+ ffi (1.0.11)
18
+ git (1.2.5)
19
+ guard (0.9.1)
20
+ ffi (>= 0.5.0)
21
+ thor (~> 0.14.6)
22
+ guard-test (0.4.2)
23
+ guard (>= 0.4)
24
+ test-unit (~> 2.2)
25
+ i18n (0.6.0)
26
+ jeweler (1.6.4)
27
+ bundler (~> 1.0)
28
+ git (>= 1.2.5)
29
+ rake
30
+ json (1.6.3)
31
+ libnotify (0.6.0)
32
+ macaddr (1.5.0)
33
+ systemu (>= 2.4.0)
34
+ mime-types (1.17.2)
35
+ multi_json (1.0.4)
36
+ rake (0.9.2.2)
37
+ rb-inotify (0.8.8)
38
+ ffi (>= 0.5.0)
39
+ rcov (0.9.11)
40
+ rdoc (3.12)
41
+ json (~> 1.4)
42
+ rest-client (1.6.7)
43
+ mime-types (>= 1.16)
44
+ shoulda (2.11.3)
45
+ sqlite3 (1.3.5)
46
+ systemu (2.4.2)
47
+ test-unit (2.4.3)
48
+ thor (0.14.6)
49
+ tzinfo (0.3.31)
50
+ uuid (2.3.4)
51
+ macaddr (~> 1.0)
52
+
53
+ PLATFORMS
54
+ ruby
55
+
56
+ DEPENDENCIES
57
+ activerecord (>= 3.0.0)
58
+ bundler (~> 1.0.0)
59
+ guard-test
60
+ jeweler (~> 1.6.4)
61
+ libnotify
62
+ multi_json
63
+ rb-inotify
64
+ rcov
65
+ rdoc
66
+ rest-client
67
+ shoulda
68
+ sqlite3
69
+ uuid
data/Guardfile ADDED
@@ -0,0 +1,17 @@
1
+ # encoding: utf-8
2
+
3
+ # A sample Guardfile
4
+ # More info at https://github.com/guard/guard#readme
5
+
6
+ guard :test do
7
+ watch(%r{^lib/(.+)\.rb$}) { puts "REEVALUATE and run ALL tests."; Dsl.reevaluate_guardfile; "test" }
8
+ watch(%r{^test/.+_test\.rb$})
9
+ watch(%r{^test/test_.+\.rb$})
10
+ watch('test/helper.rb') { "test" }
11
+
12
+ # Rails example
13
+ # watch(%r{^app/models/(.+)\.rb$}) { |m| "test/unit/#{m[1]}_test.rb" }
14
+ # watch(%r{^app/controllers/(.+)\.rb$}) { |m| "test/functional/#{m[1]}_test.rb" }
15
+ # watch(%r{^app/views/.+\.rb$}) { "test/integration" }
16
+ # watch('app/controllers/application_controller.rb') { ["test/functional", "test/integration"] }
17
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Hristo Deshev
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,19 @@
1
+ = filer-gem
2
+
3
+ Description goes here.
4
+
5
+ == Contributing to filer-gem
6
+
7
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
8
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
9
+ * Fork the project
10
+ * Start a feature/bugfix branch
11
+ * Commit and push until you are happy with your contribution
12
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
13
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2011 Hristo Deshev. See LICENSE.txt for
18
+ further details.
19
+
data/Rakefile ADDED
@@ -0,0 +1,63 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "progstr-filer"
18
+ gem.homepage = "http://github.com/progstr/progstr-filer-gem"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Progstr Filer API}
21
+ gem.description = %Q{Progstr Filer is a developer-friendly file and attachment hosting service that lets you easily build apps that store and share files.}
22
+ gem.email = "hristo@deshev.com"
23
+ gem.authors = ["Hristo Deshev"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ #require 'rspec/core'
29
+ #require 'rspec/core/rake_task'
30
+ #RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ #spec.pattern = FileList['spec/**/*_spec.rb']
32
+ #end
33
+
34
+ require 'rake/testtask'
35
+ Rake::TestTask.new(:test) do |test|
36
+ test.libs << 'lib' << 'test'
37
+ test.pattern = 'test/**/test_*.rb'
38
+ test.verbose = true
39
+ end
40
+
41
+ #RSpec::Core::RakeTask.new(:rcov) do |spec|
42
+ #spec.pattern = 'spec/**/*_spec.rb'
43
+ #spec.rcov = true
44
+ #end
45
+ require 'rcov/rcovtask'
46
+ Rcov::RcovTask.new do |test|
47
+ test.libs << 'test'
48
+ test.pattern = 'test/**/test_*.rb'
49
+ test.verbose = true
50
+ test.rcov_opts << '--exclude "gems/*"'
51
+ end
52
+
53
+ task :default => :spec
54
+
55
+ require 'rdoc/task'
56
+ Rake::RDocTask.new do |rdoc|
57
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
58
+
59
+ rdoc.rdoc_dir = 'rdoc'
60
+ rdoc.title = "filer-gem #{version}"
61
+ rdoc.rdoc_files.include('README*')
62
+ rdoc.rdoc_files.include('lib/**/*.rb')
63
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.8.0
@@ -0,0 +1,116 @@
1
+ require 'active_record'
2
+
3
+ module Progstr
4
+ module Filer
5
+ module ActiveRecordClassMethods
6
+ def _uploaders
7
+ if @uploaders.nil?
8
+ @uploaders = {}
9
+ @uploaders = superclass._uploaders.merge(@uploaders) if superclass.respond_to?(:_uploaders)
10
+ after_save :"_filer_after_save"
11
+ before_destroy :"_filer_before_delete"
12
+ end
13
+ @uploaders
14
+ end
15
+
16
+ def has_file(attribute, uploaderClass)
17
+ class_eval <<-RUBY, __FILE__, __LINE__+1
18
+ def #{attribute}
19
+ _get_attachment(:#{attribute})
20
+ end
21
+ def #{attribute}=(new_file)
22
+ _set_attachment(:#{attribute}, new_file)
23
+ end
24
+ def upload_#{attribute}!
25
+ _upload_attachment(:#{attribute})
26
+ end
27
+ RUBY
28
+
29
+ _uploaders[attribute] = uploaderClass.new
30
+ end
31
+ end
32
+
33
+ module ActiveRecordInstanceMethods
34
+ def _attachments
35
+ @_filer_attachments ||= {}
36
+ @_filer_attachments
37
+ end
38
+
39
+ def _attachments_to_delete
40
+ @_filer_attachments_to_delete ||= []
41
+ @_filer_attachments_to_delete
42
+ end
43
+
44
+ def _get_attachment(attribute)
45
+ if _attachments[attribute].nil?
46
+ id = read_attribute(attribute)
47
+ if id.nil?
48
+ _attachments[attribute] = Attachment.empty
49
+ else
50
+ _attachments[attribute] = Attachment.from_id(attribute, id)
51
+ end
52
+ else
53
+ _attachments[attribute]
54
+ end
55
+ end
56
+
57
+ def _set_attachment(attribute, file)
58
+ old_attachment = _get_attachment(attribute)
59
+ unless old_attachment.empty?
60
+ _attachments_to_delete << old_attachment
61
+ end
62
+
63
+ attachment = Attachment.from_file(attribute, file)
64
+ _attachments[attribute] = attachment
65
+ write_attribute(attribute, attachment.id)
66
+ end
67
+
68
+ def _upload_attachments
69
+ self.class._uploaders.each do |item|
70
+ attribute = item[0]
71
+ _upload_attachment(attribute)
72
+ end
73
+ end
74
+
75
+ def _upload_attachment(attribute)
76
+ attachment = _get_attachment(attribute)
77
+ if (!attachment.empty?) && (!attachment.file.nil?)
78
+ uploader = self.class._uploaders[attribute]
79
+ uploader.upload_attachment(attachment) unless uploader.nil?
80
+ end
81
+ end
82
+
83
+ def _delete_expired_attachments
84
+ _attachments_to_delete.each do |attachment|
85
+ uploader = self.class._uploaders[attachment.attribute]
86
+ uploader.delete_attachment(attachment) unless uploader.nil?
87
+ end
88
+ end
89
+
90
+ def _filer_after_save
91
+ _upload_attachments
92
+ _delete_expired_attachments
93
+ end
94
+
95
+ def _expire_all_attachments
96
+ # trigger entity load
97
+ dummy = read_attribute(:dummy)
98
+ self.class._uploaders.each do |item|
99
+ attribute = item[0]
100
+ #load attachment object
101
+ dummy = _get_attachment(attribute)
102
+ #schedule it for deletion
103
+ _set_attachment(attribute, nil)
104
+ end
105
+ end
106
+
107
+ def _filer_before_delete
108
+ _expire_all_attachments
109
+ _delete_expired_attachments
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ ActiveRecord::Base.extend Progstr::Filer::ActiveRecordClassMethods
116
+ ActiveRecord::Base.send(:include, Progstr::Filer::ActiveRecordInstanceMethods)
@@ -0,0 +1,51 @@
1
+ module Progstr
2
+ module Filer
3
+ class Attachment
4
+ attr_accessor :id, :attribute, :file
5
+
6
+ @@id_generator = ::UUID.new
7
+
8
+ class EmptyAttachment
9
+ def empty?
10
+ true
11
+ end
12
+ end
13
+
14
+ def self.empty
15
+ EmptyAttachment.new
16
+ end
17
+
18
+ def self.from_file(attribute, file)
19
+ result = Attachment.new
20
+ result.id = generate_id
21
+ result.attribute = attribute
22
+ result.file = file
23
+ result
24
+ end
25
+
26
+ def self.generate_id
27
+ uuid = @@id_generator.generate
28
+ uuid.gsub("-", "")
29
+ end
30
+
31
+ def self.from_id(attribute, id)
32
+ result = Attachment.new
33
+ result.id = id
34
+ result.attribute = attribute
35
+ result
36
+ end
37
+
38
+ def empty?
39
+ false
40
+ end
41
+
42
+ def url
43
+ if !empty?
44
+ "#{Progstr::Filer.url_prefix}files/data/#{Progstr::Filer.access_key}/#{id}"
45
+ else
46
+ ""
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,38 @@
1
+
2
+ module Progstr
3
+ module Filer
4
+ class << self
5
+ attr_accessor :host, :port, :path_prefix, :access_key, :secret_key, :session_timeout
6
+
7
+ def host
8
+ @host ||= "filer-api.progstr.com"
9
+ end
10
+ def port
11
+ @port || 80
12
+ end
13
+ def path_prefix
14
+ @path_prefix ||= '/'
15
+ end
16
+ def session_timeout
17
+ @http_read_timeout ||= 30 * 60 # 30 minutes
18
+ end
19
+
20
+ def generate_auth_token
21
+ expiration_seconds = (Time.now + session_timeout).to_i
22
+ expiration_millis = expiration_seconds * 1000
23
+ data = "#{access_key}-#{expiration_millis}-#{secret_key}"
24
+ signature = Digest::SHA1.hexdigest(data)
25
+ "#{access_key}-#{expiration_millis}-#{signature}"
26
+ end
27
+
28
+ def url_prefix
29
+ prefix = "http://#{Progstr::Filer.host}:#{Progstr::Filer.port}#{Progstr::Filer.path_prefix}"
30
+ if prefix.end_with? "/"
31
+ prefix
32
+ else
33
+ prefix + "/"
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,27 @@
1
+ class FileInfo
2
+ def initialize(values)
3
+ @values = values
4
+ end
5
+
6
+ def id
7
+ @values["_id"]
8
+ end
9
+ def name
10
+ @values["name"]
11
+ end
12
+ def content_type
13
+ @values["contentType"]
14
+ end
15
+ def property
16
+ @values["property"]
17
+ end
18
+ def uploader
19
+ @values["uploader"]
20
+ end
21
+ def size
22
+ @values["size"]
23
+ end
24
+ def created_at
25
+ @values["createdAt"]
26
+ end
27
+ end
@@ -0,0 +1,68 @@
1
+ module Progstr
2
+ module Filer
3
+ class UploadStatus
4
+ def initialize(parsed)
5
+ @parsed = parsed
6
+ end
7
+
8
+ def name
9
+ @parsed["name"]
10
+ end
11
+ def success
12
+ @parsed["success"]
13
+ end
14
+ def message
15
+ @parsed["message"]
16
+ end
17
+ end
18
+
19
+ class Uploader
20
+ def json_headers
21
+ {
22
+ :accept => "application/json",
23
+ :"X-Progstr-Filer-Auth-Token" => Progstr::Filer.generate_auth_token,
24
+ }
25
+ end
26
+
27
+ def upload_attachment(attachment)
28
+ url = "#{Progstr::Filer.url_prefix}upload/new"
29
+ begin
30
+ response = RestClient.post(url, {
31
+ :upload1 => attachment.file,
32
+ :authToken => Progstr::Filer.generate_auth_token,
33
+ :property => attachment.attribute,
34
+ :uploader => self.class.name,
35
+ :id => attachment.id
36
+ }, json_headers)
37
+ UploadStatus.new MultiJson.decode(response)
38
+ rescue => e
39
+ if e.respond_to?(:response) && !e.response.nil?
40
+ raise ApiError.new(UploadStatus.new MultiJson.decode(e.response))
41
+ else
42
+ raise
43
+ end
44
+ end
45
+ end
46
+
47
+ def delete_attachment(attachment)
48
+ url = "#{Progstr::Filer.url_prefix}files/#{attachment.id}"
49
+ begin
50
+ response = RestClient.delete(url, json_headers)
51
+ UploadStatus.new MultiJson.decode(response)
52
+ rescue => e
53
+ raise ApiError.new(MultiJson.decode(e.response))
54
+ end
55
+ end
56
+
57
+ def file_info(attachment)
58
+ url = "#{Progstr::Filer.url_prefix}files/info/#{attachment.id}"
59
+ begin
60
+ response = RestClient.get(url, json_headers)
61
+ FileInfo.new MultiJson.decode(response)
62
+ rescue => e
63
+ raise ApiError.new(MultiJson.decode(e.response))
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,27 @@
1
+ require 'pp'
2
+ require 'digest'
3
+ require 'rest-client'
4
+ require 'uuid'
5
+ UUID.state_file = false
6
+
7
+ require 'filer/config'
8
+ require 'filer/uploader'
9
+ require 'filer/attachment'
10
+ require 'filer/file_info'
11
+ require 'filer/activerecord'
12
+
13
+ class ApiError < StandardError
14
+ attr_accessor :server_response
15
+
16
+ def initialize(server_response)
17
+ @server_response = server_response
18
+ end
19
+
20
+ def message
21
+ @server_response["message"]
22
+ end
23
+
24
+ def http_code
25
+ @server_response["httpCode"]
26
+ end
27
+ end
data/test/file_like.rb ADDED
@@ -0,0 +1,6 @@
1
+ class FileLike
2
+ def read
3
+ "contents"
4
+ end
5
+ end
6
+
@@ -0,0 +1,11 @@
1
+ require 'test_helper'
2
+
3
+ class TestIdGeneration < Test::Unit::TestCase
4
+ should "generate file URLs using accessKey and file ID" do
5
+ a = Progstr::Filer::Attachment.from_id(:avatar, "some-attachment-id")
6
+
7
+ assert_match "/files/data/DEMO/some-attachment-id", a.url
8
+ assert_match Progstr::Filer.host, a.url
9
+ assert_match Progstr::Filer.host, a.url
10
+ end
11
+ end
@@ -0,0 +1,31 @@
1
+ require 'test_helper'
2
+
3
+ class TestFileMount < UserTest
4
+ should "start with an empty uploader field" do
5
+ empty = User.new
6
+ assert_true empty.avatar.empty?
7
+ end
8
+
9
+ should "create an uploader when given a file" do
10
+ non_empty = User.new
11
+ non_empty.avatar = FileLike.new
12
+ assert_false non_empty.avatar.empty?
13
+ end
14
+
15
+ should "save persist attachment id only" do
16
+ u = User.new
17
+ u.avatar = FileLike.new
18
+ assert_not_nil u.avatar.id, "attachment id generated"
19
+ assert_equal u.read_attribute(:avatar), u.avatar.id, "attachment id saved to db"
20
+ end
21
+
22
+ should "save and load attachment id to db" do
23
+ u = User.new
24
+ u.avatar = FileLike.new
25
+ u.save!
26
+
27
+ loaded = User.find(u.id)
28
+ assert_equal loaded.read_attribute(:avatar), u.avatar.id, "Loaded avatar id from db"
29
+ assert_equal loaded.avatar.id, u.avatar.id, "Loaded avatar id matches the saved one."
30
+ end
31
+ end
@@ -0,0 +1,24 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ #require 'rspec'
4
+ require 'progstr-filer'
5
+
6
+ require 'test/unit'
7
+ require 'shoulda'
8
+ require 'file_like'
9
+ require 'user_data'
10
+ require 'pp'
11
+
12
+ Progstr::Filer.host = "filer.local"
13
+ Progstr::Filer.port = "8080"
14
+
15
+ Progstr::Filer.access_key = "DEMO"
16
+ Progstr::Filer.secret_key = "DEMO"
17
+
18
+ # Requires supporting files with custom matchers and macros, etc,
19
+ # in ./support/ and its subdirectories.
20
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
21
+
22
+ #RSpec.configure do |config|
23
+
24
+ #end
@@ -0,0 +1,44 @@
1
+ require 'test_helper'
2
+
3
+ class DemoUploader < Progstr::Filer::Uploader
4
+ end
5
+
6
+ class TestHttpUpload < Test::Unit::TestCase
7
+ # The mother of all tests calling the real API
8
+ test "upload attachment" do
9
+ attachment = Progstr::Filer::Attachment.from_file(:version, File.open("VERSION"))
10
+ uploader = DemoUploader.new
11
+ response = uploader.upload_attachment attachment
12
+
13
+ assert_equal response.name, "VERSION"
14
+ assert_equal response.success, true
15
+ assert_equal response.message, "OK"
16
+
17
+ info = uploader.file_info attachment
18
+
19
+ assert_equal info.name, "VERSION"
20
+ assert_equal info.id, attachment.id
21
+ assert_equal info.content_type, "text/plain"
22
+ assert_equal info.property, "version"
23
+ assert_equal info.uploader, "DemoUploader"
24
+ assert_true info.size > 0
25
+
26
+ response = uploader.delete_attachment attachment
27
+ assert_equal response.message, "OK"
28
+ end
29
+
30
+ test "report error on failed upload" do
31
+ begin
32
+ Progstr::Filer.secret_key = "BROKEN"
33
+
34
+ attachment = Progstr::Filer::Attachment.from_file(:version, File.open("VERSION"))
35
+ uploader = DemoUploader.new
36
+ response = uploader.upload_attachment attachment
37
+
38
+ assert_equal response.success, false
39
+ assert_match "Session expired or authorization failed", response.message,
40
+ ensure
41
+ Progstr::Filer.secret_key = "DEMO"
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,17 @@
1
+ require 'test_helper'
2
+
3
+ class TestIdGeneration < Test::Unit::TestCase
4
+ should "use UUIDs for attachment IDs" do
5
+ a = Progstr::Filer::Attachment.from_file(:avatar, FileLike.new)
6
+ b = Progstr::Filer::Attachment.from_file(:avatar, FileLike.new)
7
+
8
+ assert_not_nil a.id
9
+ assert_not_nil b.id
10
+ assert_not_equal a.id, b.id
11
+ end
12
+
13
+ should "have attachment IDs with no hyphens" do
14
+ a = Progstr::Filer::Attachment.from_file(:avatar, FileLike.new)
15
+ assert_match /^[^-]+$/, a.id, "IDs should contain no hyphens"
16
+ end
17
+ end
@@ -0,0 +1,37 @@
1
+ require 'test_helper'
2
+
3
+ class TestUploadOnSave < UserTest
4
+ should "upload file on record save" do
5
+ u = User.new
6
+ f = FileLike.new
7
+ u.avatar = f
8
+
9
+ u.save!
10
+ assert_same $lastUploadedAtachment.file, f, "avatar file sent to uploader"
11
+ end
12
+
13
+ should "delete the previous attachment on save" do
14
+ u = User.new
15
+ f1 = FileLike.new
16
+ u.avatar = f1
17
+ u.save!
18
+
19
+ f2 = FileLike.new
20
+ u.avatar = f2
21
+
22
+ u.save!
23
+ assert_same $lastDeletedAtachment.file, f1, "overwritten attachment deleted"
24
+ end
25
+
26
+ should "delete attachments on record delete" do
27
+ u = User.new
28
+ f1 = FileLike.new
29
+ u.avatar = f1
30
+ u.save!
31
+
32
+ # Note that calling `delete` doesn't run the before_destroy hook and attachments won't get deleted
33
+ # u.delete
34
+ u.destroy
35
+ assert_same $lastDeletedAtachment.file, f1, "overwritten attachment deleted"
36
+ end
37
+ end
data/test/user_data.rb ADDED
@@ -0,0 +1,48 @@
1
+ dbconfig = {
2
+ :adapter => 'sqlite3',
3
+ :database => ':memory:'
4
+ }
5
+
6
+ ActiveRecord::Base.establish_connection(dbconfig)
7
+ ActiveRecord::Migration.verbose = false
8
+
9
+ class TestUserMigration < ActiveRecord::Migration
10
+ def self.up
11
+ create_table :users, :force => true do |t|
12
+ t.column :name, :string
13
+ t.column :avatar, :string
14
+ end
15
+ end
16
+
17
+ def self.down
18
+ drop_table :users
19
+ end
20
+ end
21
+
22
+ $lastUploadedAtachment = nil
23
+ $lastDeletedAtachment = nil
24
+
25
+ class MockUploader < Progstr::Filer::Uploader
26
+ def upload_attachment(attachment)
27
+ $lastUploadedAtachment = attachment
28
+ end
29
+ def delete_attachment(attachment)
30
+ $lastDeletedAtachment = attachment
31
+ end
32
+ end
33
+
34
+
35
+ class User < ActiveRecord::Base
36
+ has_file :avatar, MockUploader
37
+ end
38
+
39
+
40
+ class UserTest < Test::Unit::TestCase
41
+ def setup
42
+ TestUserMigration.up
43
+ end
44
+
45
+ def teardown
46
+ TestUserMigration.down
47
+ end
48
+ end
metadata ADDED
@@ -0,0 +1,217 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: progstr-filer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.8.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Hristo Deshev
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-12-22 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activerecord
16
+ requirement: &15593540 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 3.0.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *15593540
25
+ - !ruby/object:Gem::Dependency
26
+ name: uuid
27
+ requirement: &15592400 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *15592400
36
+ - !ruby/object:Gem::Dependency
37
+ name: rest-client
38
+ requirement: &15591680 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *15591680
47
+ - !ruby/object:Gem::Dependency
48
+ name: multi_json
49
+ requirement: &15590700 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *15590700
58
+ - !ruby/object:Gem::Dependency
59
+ name: sqlite3
60
+ requirement: &15590140 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *15590140
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard-test
71
+ requirement: &15556920 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *15556920
80
+ - !ruby/object:Gem::Dependency
81
+ name: rb-inotify
82
+ requirement: &15555680 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: *15555680
91
+ - !ruby/object:Gem::Dependency
92
+ name: libnotify
93
+ requirement: &15554380 !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ type: :development
100
+ prerelease: false
101
+ version_requirements: *15554380
102
+ - !ruby/object:Gem::Dependency
103
+ name: shoulda
104
+ requirement: &15553320 !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: *15553320
113
+ - !ruby/object:Gem::Dependency
114
+ name: bundler
115
+ requirement: &15552060 !ruby/object:Gem::Requirement
116
+ none: false
117
+ requirements:
118
+ - - ~>
119
+ - !ruby/object:Gem::Version
120
+ version: 1.0.0
121
+ type: :development
122
+ prerelease: false
123
+ version_requirements: *15552060
124
+ - !ruby/object:Gem::Dependency
125
+ name: jeweler
126
+ requirement: &15550400 !ruby/object:Gem::Requirement
127
+ none: false
128
+ requirements:
129
+ - - ~>
130
+ - !ruby/object:Gem::Version
131
+ version: 1.6.4
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: *15550400
135
+ - !ruby/object:Gem::Dependency
136
+ name: rcov
137
+ requirement: &15549600 !ruby/object:Gem::Requirement
138
+ none: false
139
+ requirements:
140
+ - - ! '>='
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ type: :development
144
+ prerelease: false
145
+ version_requirements: *15549600
146
+ - !ruby/object:Gem::Dependency
147
+ name: rdoc
148
+ requirement: &15543260 !ruby/object:Gem::Requirement
149
+ none: false
150
+ requirements:
151
+ - - ! '>='
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ type: :development
155
+ prerelease: false
156
+ version_requirements: *15543260
157
+ description: Progstr Filer is a developer-friendly file and attachment hosting service
158
+ that lets you easily build apps that store and share files.
159
+ email: hristo@deshev.com
160
+ executables: []
161
+ extensions: []
162
+ extra_rdoc_files:
163
+ - LICENSE.txt
164
+ - README.rdoc
165
+ files:
166
+ - .document
167
+ - .rspec
168
+ - Gemfile
169
+ - Gemfile.lock
170
+ - Guardfile
171
+ - LICENSE.txt
172
+ - README.rdoc
173
+ - Rakefile
174
+ - VERSION
175
+ - lib/filer/activerecord.rb
176
+ - lib/filer/attachment.rb
177
+ - lib/filer/config.rb
178
+ - lib/filer/file_info.rb
179
+ - lib/filer/uploader.rb
180
+ - lib/progstr-filer.rb
181
+ - test/file_like.rb
182
+ - test/test_attachment_url.rb
183
+ - test/test_file_mount.rb
184
+ - test/test_helper.rb
185
+ - test/test_http_upload.rb
186
+ - test/test_id_generation.rb
187
+ - test/test_upload_on_save.rb
188
+ - test/user_data.rb
189
+ homepage: http://github.com/progstr/progstr-filer-gem
190
+ licenses:
191
+ - MIT
192
+ post_install_message:
193
+ rdoc_options: []
194
+ require_paths:
195
+ - lib
196
+ required_ruby_version: !ruby/object:Gem::Requirement
197
+ none: false
198
+ requirements:
199
+ - - ! '>='
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ segments:
203
+ - 0
204
+ hash: 2703032228924922704
205
+ required_rubygems_version: !ruby/object:Gem::Requirement
206
+ none: false
207
+ requirements:
208
+ - - ! '>='
209
+ - !ruby/object:Gem::Version
210
+ version: '0'
211
+ requirements: []
212
+ rubyforge_project:
213
+ rubygems_version: 1.8.6
214
+ signing_key:
215
+ specification_version: 3
216
+ summary: Progstr Filer API
217
+ test_files: []