jamieorc-joint 0.5.6

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ .DS_Store
2
+ log/*
3
+ tmp/*
4
+ doc/*
5
+ pkg/*
6
+ .bundle
7
+ *.swp
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem 'i18n', '0.4.2'
4
+ gem 'bson_ext', '~> 1.3', :require => false
5
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,49 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ joint (0.5.6)
5
+ mime-types
6
+ mongo_mapper (~> 0.8.6)
7
+ wand (~> 0.4)
8
+
9
+ GEM
10
+ remote: http://rubygems.org/
11
+ specs:
12
+ activesupport (3.0.3)
13
+ bson (1.3.1)
14
+ bson_ext (1.3.1)
15
+ i18n (0.4.2)
16
+ jnunemaker-matchy (0.4.0)
17
+ jnunemaker-validatable (1.8.4)
18
+ activesupport (>= 2.3.4)
19
+ mime-types (1.16)
20
+ mocha (0.9.10)
21
+ rake
22
+ mongo (1.3.1)
23
+ bson (>= 1.3.1)
24
+ mongo_mapper (0.8.6)
25
+ activesupport (>= 2.3.4)
26
+ jnunemaker-validatable (~> 1.8.4)
27
+ plucky (~> 0.3.6)
28
+ plucky (0.3.6)
29
+ mongo (~> 1.1)
30
+ rake (0.8.7)
31
+ safe_shell (1.0.0)
32
+ shoulda (2.11.3)
33
+ wand (0.4)
34
+ mime-types
35
+ safe_shell (~> 1.0.0)
36
+
37
+ PLATFORMS
38
+ ruby
39
+
40
+ DEPENDENCIES
41
+ bson_ext (~> 1.3)
42
+ i18n (= 0.4.2)
43
+ jnunemaker-matchy
44
+ joint!
45
+ mime-types
46
+ mocha
47
+ mongo_mapper (~> 0.8.6)
48
+ shoulda
49
+ wand (~> 0.4)
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 John Nunemaker
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,37 @@
1
+ = Joint
2
+
3
+ MongoMapper and GridFS joined in file upload love.
4
+
5
+ == Usage
6
+
7
+ Declare the plugin and use the attachment method to make attachments.
8
+
9
+ class Foo
10
+ include MongoMapper::Document
11
+ plugin Joint
12
+
13
+ attachment :image
14
+ attachment :pdf
15
+ end
16
+
17
+ This gives you #image, #image=, #pdf, and #pdf=. The = methods take any IO that responds to read (File, Tempfile, etc). The image and pdf methods return a GridIO instance (can be found in the ruby driver).
18
+
19
+ Also, #image and #pdf are proxies so you can do stuff like:
20
+
21
+ doc.image.id, doc.image.size, doc.image.type, doc.image.name
22
+
23
+ If you call a method other than those in the proxy it calls it on the GridIO instance so you can still get at all the GridIO instance methods.
24
+
25
+ == Note on Patches/Pull Requests
26
+
27
+ * Fork the project.
28
+ * Make your feature addition or bug fix.
29
+ * Add tests for it. This is important so I don't break it in a
30
+ future version unintentionally.
31
+ * Commit, do not mess with rakefile, version, or history.
32
+ (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)
33
+ * Send me a pull request. Bonus points for topic branches.
34
+
35
+ == Copyright
36
+
37
+ Copyright (c) 2010 John Nunemaker. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rake/testtask'
5
+ Rake::TestTask.new do |test|
6
+ test.libs << 'lib' << 'test'
7
+ test.pattern = 'test/**/test_*.rb'
8
+ test.ruby_opts = ['-rubygems']
9
+ test.verbose = true
10
+ end
11
+
12
+ task :default => :test
data/joint.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # encoding: UTF-8
2
+ require File.expand_path('../lib/joint/version', __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "jamieorc-joint"
6
+ s.summary = %Q{MongoMapper and GridFS joined in file upload love.}
7
+ s.description = %Q{MongoMapper and GridFS joined in file upload love.}
8
+ s.email = "jamieorc@gmail.com"
9
+ s.homepage = "http://github.com/jamieorc/joint"
10
+ s.authors = ["John Nunemaker", "Jamie Orchard-Hays"]
11
+ s.version = Joint::Version
12
+
13
+ s.add_dependency 'wand', '~> 0.4'
14
+ s.add_dependency 'mime-types'
15
+ s.add_dependency 'mongo_mapper', '~> 0.8.6'
16
+
17
+ s.add_development_dependency 'shoulda'
18
+ s.add_development_dependency 'mocha'
19
+ s.add_development_dependency 'jnunemaker-matchy'
20
+
21
+ s.files = `git ls-files`.split("\n")
22
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
23
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
24
+ s.require_paths = ["lib"]
25
+ end
@@ -0,0 +1,35 @@
1
+ module Joint
2
+ class AttachmentProxy
3
+ def initialize(instance, name)
4
+ @instance, @name = instance, name
5
+ end
6
+
7
+ def id
8
+ @instance.send("#{@name}_id")
9
+ end
10
+
11
+ def name
12
+ @instance.send("#{@name}_name")
13
+ end
14
+
15
+ def size
16
+ @instance.send("#{@name}_size")
17
+ end
18
+
19
+ def type
20
+ @instance.send("#{@name}_type")
21
+ end
22
+
23
+ def nil?
24
+ !@instance.send("#{@name}?")
25
+ end
26
+
27
+ def grid_io
28
+ @grid_io ||= @instance.grid.get(id)
29
+ end
30
+
31
+ def method_missing(method, *args, &block)
32
+ grid_io.send(method, *args, &block)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,50 @@
1
+ module Joint
2
+ module ClassMethods
3
+ def attachment_accessor_module
4
+ @attachment_accessor_module ||= Module.new
5
+ end
6
+
7
+ def attachment(name, options = {})
8
+ options.symbolize_keys!
9
+ name = name.to_sym
10
+
11
+ self.attachment_names << name
12
+
13
+ after_save :save_attachments
14
+ before_save :nullify_nil_attachments_attributes
15
+ after_save :destroy_nil_attachments
16
+ before_destroy :destroy_all_attachments
17
+
18
+ key :"#{name}_id", ObjectId
19
+ key :"#{name}_name", String
20
+ key :"#{name}_size", Integer
21
+ key :"#{name}_type", String
22
+
23
+ validates_presence_of(name) if options[:required]
24
+
25
+ attachment_accessor_module.module_eval <<-EOC
26
+ def #{name}
27
+ @#{name} ||= AttachmentProxy.new(self, :#{name})
28
+ end
29
+
30
+ def #{name}?
31
+ !nil_attachments.has_key?(:#{name}) && send(:#{name}_id?)
32
+ end
33
+
34
+ def #{name}=(file)
35
+ if file.nil?
36
+ nil_attachments[:#{name}] = send("#{name}_id")
37
+ assigned_attachments.delete(:#{name})
38
+ else
39
+ send("#{name}_id=", BSON::ObjectId.new) if send("#{name}_id").nil?
40
+ send("#{name}_name=", Joint.name(file))
41
+ send("#{name}_size=", Joint.size(file))
42
+ send("#{name}_type=", Joint.type(file))
43
+ assigned_attachments[:#{name}] = file
44
+ nil_attachments.delete(:#{name})
45
+ end
46
+ end
47
+ EOC
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,52 @@
1
+ module Joint
2
+ module InstanceMethods
3
+ def grid
4
+ @grid ||= Mongo::Grid.new(database)
5
+ end
6
+
7
+ private
8
+ def assigned_attachments
9
+ @assigned_attachments ||= {}
10
+ end
11
+
12
+ def nil_attachments
13
+ @nil_attachments ||= {}
14
+ end
15
+
16
+ # IO must respond to read and rewind
17
+ def save_attachments
18
+ assigned_attachments.each_pair do |name, io|
19
+ next unless io.respond_to?(:read)
20
+ io.rewind if io.respond_to?(:rewind)
21
+ grid.delete(send(name).id)
22
+ grid.put(io.read, {
23
+ :_id => send(name).id,
24
+ :filename => send(name).name,
25
+ :content_type => send(name).type,
26
+ })
27
+ end
28
+ assigned_attachments.clear
29
+ end
30
+
31
+ def nullify_nil_attachments_attributes
32
+ nil_attachments.each_key do |name|
33
+ send(:"#{name}_id=", nil)
34
+ send(:"#{name}_size=", nil)
35
+ send(:"#{name}_type=", nil)
36
+ send(:"#{name}_name=", nil)
37
+ end
38
+ end
39
+
40
+ def destroy_nil_attachments
41
+ nil_attachments.each_value do |id|
42
+ grid.delete(id)
43
+ end
44
+
45
+ nil_attachments.clear
46
+ end
47
+
48
+ def destroy_all_attachments
49
+ self.class.attachment_names.map { |name| grid.delete(send(name).id) }
50
+ end
51
+ end
52
+ end
data/lib/joint/io.rb ADDED
@@ -0,0 +1,14 @@
1
+ module Joint
2
+ class IO
3
+ attr_accessor :name, :content, :type, :size
4
+
5
+ def initialize(attrs={})
6
+ attrs.each { |key, value| send("#{key}=", value) }
7
+ @type ||= 'plain/text'
8
+ @size ||= @content.size unless @content.nil?
9
+ end
10
+
11
+ alias path name
12
+ alias read content
13
+ end
14
+ end
@@ -0,0 +1,3 @@
1
+ module Joint
2
+ Version = '0.5.6'
3
+ end
data/lib/joint.rb ADDED
@@ -0,0 +1,42 @@
1
+ require 'set'
2
+ require 'mime/types'
3
+ require 'wand'
4
+
5
+ module Joint
6
+ def self.configure(model)
7
+ model.class_inheritable_accessor :attachment_names
8
+ model.attachment_names = Set.new
9
+ model.send(:include, model.attachment_accessor_module)
10
+ end
11
+
12
+ def self.name(file)
13
+ if file.respond_to?(:original_filename)
14
+ file.original_filename
15
+ else
16
+ File.basename(file.path)
17
+ end
18
+ end
19
+
20
+ def self.size(file)
21
+ if file.respond_to?(:size)
22
+ file.size
23
+ else
24
+ File.size(file)
25
+ end
26
+ end
27
+
28
+ def self.type(file)
29
+ return file.type if file.is_a?(Joint::IO)
30
+ Wand.wave(file.path, :original_filename => Joint.name(file))
31
+ end
32
+
33
+ private
34
+ def self.blank?(str)
35
+ str.nil? || str !~ /\S/
36
+ end
37
+ end
38
+
39
+ require 'joint/class_methods'
40
+ require 'joint/instance_methods'
41
+ require 'joint/attachment_proxy'
42
+ require 'joint/io'
data/specs.watchr ADDED
@@ -0,0 +1,38 @@
1
+ def run(cmd)
2
+ puts(cmd)
3
+ output = ""
4
+ IO.popen(cmd) do |com|
5
+ com.each_char do |c|
6
+ print c
7
+ output << c
8
+ $stdout.flush
9
+ end
10
+ end
11
+ output
12
+ end
13
+
14
+ def run_test_file(file)
15
+ run %Q(ruby -I"lib:test" -rubygems #{file})
16
+ end
17
+
18
+ def run_all_tests
19
+ run "rake test"
20
+ end
21
+
22
+ def related_test_files(path)
23
+ Dir['test/**/*.rb'].select { |file| file =~ /test_#{File.basename(path)}/ }
24
+ end
25
+
26
+ watch('test/test_helper\.rb') { system('clear'); run_all_tests }
27
+ watch('test/.*test_.*\.rb') { |m| system('clear'); run_test_file(m[0]) }
28
+ watch('lib/.*') { |m| related_test_files(m[0]).each { |file| run_test_file(file) } }
29
+
30
+ # Ctrl-\
31
+ Signal.trap('QUIT') do
32
+ puts " --- Running all tests ---\n\n"
33
+ run_all_tests
34
+ end
35
+
36
+ # Ctrl-C
37
+ Signal.trap('INT') { abort("\n") }
38
+
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1 @@
1
+ test1
@@ -0,0 +1 @@
1
+ test2
Binary file
data/test/helper.rb ADDED
@@ -0,0 +1,91 @@
1
+ require 'tempfile'
2
+ require 'pp'
3
+ require 'shoulda'
4
+ require 'matchy'
5
+ require 'mocha'
6
+ require 'mongo_mapper'
7
+
8
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/joint')
9
+
10
+ MongoMapper.database = "testing"
11
+
12
+ class Test::Unit::TestCase
13
+ def setup
14
+ MongoMapper.database.collections.each(&:remove)
15
+ end
16
+
17
+ def assert_difference(expression, difference = 1, message = nil, &block)
18
+ b = block.send(:binding)
19
+ exps = Array.wrap(expression)
20
+ before = exps.map { |e| eval(e, b) }
21
+ yield
22
+ exps.each_with_index do |e, i|
23
+ error = "#{e.inspect} didn't change by #{difference}"
24
+ error = "#{message}.\n#{error}" if message
25
+ after = eval(e, b)
26
+ assert_equal(before[i] + difference, after, error)
27
+ end
28
+ end
29
+
30
+ def assert_no_difference(expression, message = nil, &block)
31
+ assert_difference(expression, 0, message, &block)
32
+ end
33
+
34
+ def assert_grid_difference(difference=1, &block)
35
+ assert_difference("MongoMapper.database['fs.files'].find().count", difference, &block)
36
+ end
37
+
38
+ def assert_no_grid_difference(&block)
39
+ assert_grid_difference(0, &block)
40
+ end
41
+ end
42
+
43
+ class Asset
44
+ include MongoMapper::Document
45
+ plugin Joint
46
+
47
+ key :title, String
48
+ attachment :image
49
+ attachment :file
50
+ has_many :embedded_assets
51
+ end
52
+
53
+ class EmbeddedAsset
54
+ include MongoMapper::EmbeddedDocument
55
+ plugin Joint
56
+
57
+ key :title, String
58
+ attachment :image
59
+ attachment :file
60
+ end
61
+
62
+ class BaseModel
63
+ include MongoMapper::Document
64
+ plugin Joint
65
+ attachment :file
66
+ end
67
+
68
+ class Image < BaseModel; attachment :image end
69
+ class Video < BaseModel; attachment :video end
70
+
71
+ module JointTestHelpers
72
+ def all_files
73
+ [@file, @image, @image2, @test1, @test2]
74
+ end
75
+
76
+ def rewind_files
77
+ all_files.each { |file| file.rewind }
78
+ end
79
+
80
+ def open_file(name)
81
+ File.open(File.join(File.dirname(__FILE__), 'fixtures', name), 'r')
82
+ end
83
+
84
+ def grid
85
+ @grid ||= Mongo::Grid.new(MongoMapper.database)
86
+ end
87
+
88
+ def key_names
89
+ [:id, :name, :type, :size]
90
+ end
91
+ end
@@ -0,0 +1,28 @@
1
+ require 'helper'
2
+
3
+ class IOTest < Test::Unit::TestCase
4
+ context "#initialize" do
5
+ should "set attributes from hash" do
6
+ Joint::IO.new(:name => 'foo').name.should == 'foo'
7
+ end
8
+ end
9
+
10
+ should "default type to plain text" do
11
+ Joint::IO.new.type.should == 'plain/text'
12
+ end
13
+
14
+ should "default size to content size" do
15
+ content = 'This is my content'
16
+ Joint::IO.new(:content => content).size.should == content.size
17
+ end
18
+
19
+ should "alias path to name" do
20
+ Joint::IO.new(:name => 'foo').path.should == 'foo'
21
+ end
22
+
23
+ context "#read" do
24
+ should "return content" do
25
+ Joint::IO.new(:content => 'Testing').read.should == 'Testing'
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,585 @@
1
+ require 'helper'
2
+
3
+ class JointTest < Test::Unit::TestCase
4
+ include JointTestHelpers
5
+
6
+ def setup
7
+ super
8
+ @file = open_file('unixref.pdf')
9
+ @image = open_file('mr_t.jpg')
10
+ @image2 = open_file('harmony.png')
11
+ @test1 = open_file('test1.txt')
12
+ @test2 = open_file('test2.txt')
13
+ end
14
+
15
+ def teardown
16
+ all_files.each { |file| file.close }
17
+ end
18
+
19
+ context ".name" do
20
+ should "return original_filename" do
21
+ def @image.original_filename
22
+ 'frank.jpg'
23
+ end
24
+ Joint.name(@image).should == 'frank.jpg'
25
+ end
26
+
27
+ should "fall back to File.basename" do
28
+ Joint.name(@image).should == 'mr_t.jpg'
29
+ end
30
+ end
31
+
32
+ context ".size" do
33
+ should "return size" do
34
+ def @image.size
35
+ 25
36
+ end
37
+ Joint.size(@image).should == 25
38
+ end
39
+
40
+ should "fall back to File.size" do
41
+ Joint.size(@image).should == 13661
42
+ end
43
+ end
44
+
45
+ context ".type" do
46
+ should "return type if Joint::Io instance" do
47
+ file = Joint::IO.new(:type => 'image/jpeg')
48
+ Joint.type(@image).should == 'image/jpeg'
49
+ end
50
+
51
+ should "fall back to Wand" do
52
+ Joint.type(@image).should == 'image/jpeg'
53
+ end
54
+ end
55
+
56
+ context "Using Joint plugin" do
57
+ should "add each attachment to attachment_names" do
58
+ Asset.attachment_names.should == Set.new([:image, :file])
59
+ EmbeddedAsset.attachment_names.should == Set.new([:image, :file])
60
+ end
61
+
62
+ should "add keys for each attachment" do
63
+ key_names.each do |key|
64
+ Asset.keys.should include("image_#{key}")
65
+ Asset.keys.should include("file_#{key}")
66
+ EmbeddedAsset.keys.should include("image_#{key}")
67
+ EmbeddedAsset.keys.should include("file_#{key}")
68
+ end
69
+ end
70
+
71
+ should "add memoized accessors module" do
72
+ Asset.attachment_accessor_module.should be_instance_of(Module)
73
+ EmbeddedAsset.attachment_accessor_module.should be_instance_of(Module)
74
+ end
75
+
76
+ context "with inheritance" do
77
+ should "add attachment to attachment_names" do
78
+ BaseModel.attachment_names.should == Set.new([:file])
79
+ end
80
+
81
+ should "inherit attachments from superclass, but not share other inherited class attachments" do
82
+ Image.attachment_names.should == Set.new([:file, :image])
83
+ Video.attachment_names.should == Set.new([:file, :video])
84
+ end
85
+
86
+ should "add inherit keys from superclass" do
87
+ key_names.each do |key|
88
+ BaseModel.keys.should include("file_#{key}")
89
+ Image.keys.should include("file_#{key}")
90
+ Image.keys.should include("image_#{key}")
91
+ Video.keys.should include("file_#{key}")
92
+ Video.keys.should include("video_#{key}")
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ context "Assigning new attachments to document" do
99
+ setup do
100
+ @doc = Asset.create(:image => @image, :file => @file)
101
+ rewind_files
102
+ end
103
+ subject { @doc }
104
+
105
+ should "assign GridFS content_type" do
106
+ grid.get(subject.image_id).content_type.should == 'image/jpeg'
107
+ grid.get(subject.file_id).content_type.should == 'application/pdf'
108
+ end
109
+
110
+ should "assign joint keys" do
111
+ subject.image_size.should == 13661
112
+ subject.file_size.should == 68926
113
+
114
+ subject.image_type.should == "image/jpeg"
115
+ subject.file_type.should == "application/pdf"
116
+
117
+ subject.image_id.should_not be_nil
118
+ subject.file_id.should_not be_nil
119
+
120
+ subject.image_id.should be_instance_of(BSON::ObjectId)
121
+ subject.file_id.should be_instance_of(BSON::ObjectId)
122
+ end
123
+
124
+ should "allow accessing keys through attachment proxy" do
125
+ subject.image.size.should == 13661
126
+ subject.file.size.should == 68926
127
+
128
+ subject.image.type.should == "image/jpeg"
129
+ subject.file.type.should == "application/pdf"
130
+
131
+ subject.image.id.should_not be_nil
132
+ subject.file.id.should_not be_nil
133
+
134
+ subject.image.id.should be_instance_of(BSON::ObjectId)
135
+ subject.file.id.should be_instance_of(BSON::ObjectId)
136
+ end
137
+
138
+ should "proxy unknown methods to GridIO object" do
139
+ subject.image.files_id.should == subject.image_id
140
+ subject.image.content_type.should == 'image/jpeg'
141
+ subject.image.filename.should == 'mr_t.jpg'
142
+ subject.image.file_length.should == 13661
143
+ end
144
+
145
+ should "assign file name from path if original file name not available" do
146
+ subject.image_name.should == 'mr_t.jpg'
147
+ subject.file_name.should == 'unixref.pdf'
148
+ end
149
+
150
+ should "save attachment contents correctly" do
151
+ subject.file.read.should == @file.read
152
+ subject.image.read.should == @image.read
153
+ end
154
+
155
+ should "know that attachment exists" do
156
+ subject.image?.should be(true)
157
+ subject.file?.should be(true)
158
+ end
159
+
160
+ should "respond with false when asked if the attachment is nil?" do
161
+ subject.image.nil?.should be(false)
162
+ subject.file.nil?.should be(false)
163
+ end
164
+
165
+ should "clear assigned attachments so they don't get uploaded twice" do
166
+ Mongo::Grid.any_instance.expects(:put).never
167
+ subject.save
168
+ end
169
+ end
170
+
171
+ context "Assigning new attachments to embedded document" do
172
+ setup do
173
+ @asset = Asset.new
174
+ @doc = @asset.embedded_assets.build(:image => @image, :file => @file)
175
+ @asset.save!
176
+ rewind_files
177
+ end
178
+ subject { @doc }
179
+
180
+ should "assign GridFS content_type" do
181
+ grid.get(subject.image_id).content_type.should == 'image/jpeg'
182
+ grid.get(subject.file_id).content_type.should == 'application/pdf'
183
+ end
184
+
185
+ should "assign joint keys" do
186
+ subject.image_size.should == 13661
187
+ subject.file_size.should == 68926
188
+
189
+ subject.image_type.should == "image/jpeg"
190
+ subject.file_type.should == "application/pdf"
191
+
192
+ subject.image_id.should_not be_nil
193
+ subject.file_id.should_not be_nil
194
+
195
+ subject.image_id.should be_instance_of(BSON::ObjectId)
196
+ subject.file_id.should be_instance_of(BSON::ObjectId)
197
+ end
198
+
199
+ should "allow accessing keys through attachment proxy" do
200
+ subject.image.size.should == 13661
201
+ subject.file.size.should == 68926
202
+
203
+ subject.image.type.should == "image/jpeg"
204
+ subject.file.type.should == "application/pdf"
205
+
206
+ subject.image.id.should_not be_nil
207
+ subject.file.id.should_not be_nil
208
+
209
+ subject.image.id.should be_instance_of(BSON::ObjectId)
210
+ subject.file.id.should be_instance_of(BSON::ObjectId)
211
+ end
212
+
213
+ should "proxy unknown methods to GridIO object" do
214
+ subject.image.files_id.should == subject.image_id
215
+ subject.image.content_type.should == 'image/jpeg'
216
+ subject.image.filename.should == 'mr_t.jpg'
217
+ subject.image.file_length.should == 13661
218
+ end
219
+
220
+ should "assign file name from path if original file name not available" do
221
+ subject.image_name.should == 'mr_t.jpg'
222
+ subject.file_name.should == 'unixref.pdf'
223
+ end
224
+
225
+ should "save attachment contents correctly" do
226
+ subject.file.read.should == @file.read
227
+ subject.image.read.should == @image.read
228
+ end
229
+
230
+ should "know that attachment exists" do
231
+ subject.image?.should be(true)
232
+ subject.file?.should be(true)
233
+ end
234
+
235
+ should "respond with false when asked if the attachment is nil?" do
236
+ subject.image.nil?.should be(false)
237
+ subject.file.nil?.should be(false)
238
+ end
239
+
240
+ should "clear assigned attachments so they don't get uploaded twice" do
241
+ Mongo::Grid.any_instance.expects(:put).never
242
+ subject.save
243
+ end
244
+ end
245
+
246
+
247
+ context "Updating existing attachment" do
248
+ setup do
249
+ @doc = Asset.create(:file => @test1)
250
+ assert_no_grid_difference do
251
+ @doc.file = @test2
252
+ @doc.save!
253
+ end
254
+ rewind_files
255
+ end
256
+ subject { @doc }
257
+
258
+ should "not change attachment id" do
259
+ subject.file_id_changed?.should be(false)
260
+ end
261
+
262
+ should "update keys" do
263
+ subject.file_name.should == 'test2.txt'
264
+ subject.file_type.should == "text/plain"
265
+ subject.file_size.should == 5
266
+ end
267
+
268
+ should "update GridFS" do
269
+ grid.get(subject.file_id).filename.should == 'test2.txt'
270
+ grid.get(subject.file_id).content_type.should == 'text/plain'
271
+ grid.get(subject.file_id).file_length.should == 5
272
+ grid.get(subject.file_id).read.should == @test2.read
273
+ end
274
+ end
275
+
276
+ context "Updating existing attachment in embedded document" do
277
+ setup do
278
+ @asset = Asset.new
279
+ @doc = @asset.embedded_assets.build(:file => @test1)
280
+ @asset.save!
281
+ assert_no_grid_difference do
282
+ @doc.file = @test2
283
+ @doc.save!
284
+ end
285
+ rewind_files
286
+ end
287
+ subject { @doc }
288
+
289
+ # should "not change attachment id" do
290
+ # subject.file_id_changed?.should be(false)
291
+ # end
292
+
293
+ should "update keys" do
294
+ subject.file_name.should == 'test2.txt'
295
+ subject.file_type.should == "text/plain"
296
+ subject.file_size.should == 5
297
+ end
298
+
299
+ should "update GridFS" do
300
+ grid.get(subject.file_id).filename.should == 'test2.txt'
301
+ grid.get(subject.file_id).content_type.should == 'text/plain'
302
+ grid.get(subject.file_id).file_length.should == 5
303
+ grid.get(subject.file_id).read.should == @test2.read
304
+ end
305
+ end
306
+
307
+ context "Updating document but not attachments" do
308
+ setup do
309
+ @doc = Asset.create(:image => @image)
310
+ @doc.update_attributes(:title => 'Updated')
311
+ @doc.reload
312
+ rewind_files
313
+ end
314
+ subject { @doc }
315
+
316
+ should "not affect attachment" do
317
+ subject.image.read.should == @image.read
318
+ end
319
+
320
+ should "update document attributes" do
321
+ subject.title.should == 'Updated'
322
+ end
323
+ end
324
+
325
+ context "Updating embedded document but not attachments" do
326
+ setup do
327
+ @asset = Asset.new
328
+ @doc = @asset.embedded_assets.build(:image => @image)
329
+ @doc.update_attributes(:title => 'Updated')
330
+ @asset.reload
331
+ @doc = @asset.embedded_assets.first
332
+ rewind_files
333
+ end
334
+ subject { @doc }
335
+
336
+ should "not affect attachment" do
337
+ subject.image.read.should == @image.read
338
+ end
339
+
340
+ should "update document attributes" do
341
+ subject.title.should == 'Updated'
342
+ end
343
+ end
344
+
345
+ context "Assigning file where file pointer is not at beginning" do
346
+ setup do
347
+ @image.read
348
+ @doc = Asset.create(:image => @image)
349
+ @doc.reload
350
+ rewind_files
351
+ end
352
+ subject { @doc }
353
+
354
+ should "rewind and correctly store contents" do
355
+ subject.image.read.should == @image.read
356
+ end
357
+ end
358
+
359
+ context "Setting attachment to nil" do
360
+ setup do
361
+ @doc = Asset.create(:image => @image)
362
+ rewind_files
363
+ end
364
+ subject { @doc }
365
+
366
+ should "delete attachment after save" do
367
+ assert_no_grid_difference { subject.image = nil }
368
+ assert_grid_difference(-1) { subject.save }
369
+ end
370
+
371
+ should "know that the attachment has been nullified" do
372
+ subject.image = nil
373
+ subject.image?.should be(false)
374
+ end
375
+
376
+ should "respond with true when asked if the attachment is nil?" do
377
+ subject.image = nil
378
+ subject.image.nil?.should be(true)
379
+ end
380
+
381
+ should "clear nil attachments after save and not attempt to delete again" do
382
+ Mongo::Grid.any_instance.expects(:delete).once
383
+ subject.image = nil
384
+ subject.save
385
+ Mongo::Grid.any_instance.expects(:delete).never
386
+ subject.save
387
+ end
388
+
389
+ should "clear id, name, type, size" do
390
+ subject.image = nil
391
+ subject.save
392
+ assert_nil subject.image_id
393
+ assert_nil subject.image_name
394
+ assert_nil subject.image_type
395
+ assert_nil subject.image_size
396
+ subject.reload
397
+ assert_nil subject.image_id
398
+ assert_nil subject.image_name
399
+ assert_nil subject.image_type
400
+ assert_nil subject.image_size
401
+ end
402
+ end
403
+
404
+ context "Setting attachment to nil on embedded document" do
405
+ setup do
406
+ @asset = Asset.new
407
+ @doc = @asset.embedded_assets.build(:image => @image)
408
+ @asset.save!
409
+ rewind_files
410
+ end
411
+ subject { @doc }
412
+
413
+ should "delete attachment after save" do
414
+ assert_no_grid_difference { subject.image = nil }
415
+ assert_grid_difference(-1) { subject.save }
416
+ end
417
+
418
+ should "know that the attachment has been nullified" do
419
+ subject.image = nil
420
+ subject.image?.should be(false)
421
+ end
422
+
423
+ should "respond with true when asked if the attachment is nil?" do
424
+ subject.image = nil
425
+ subject.image.nil?.should be(true)
426
+ end
427
+
428
+ should "clear nil attachments after save and not attempt to delete again" do
429
+ Mongo::Grid.any_instance.expects(:delete).once
430
+ subject.image = nil
431
+ subject.save
432
+ Mongo::Grid.any_instance.expects(:delete).never
433
+ subject.save
434
+ end
435
+
436
+ should "clear id, name, type, size" do
437
+ subject.image = nil
438
+ subject.save
439
+ assert_nil subject.image_id
440
+ assert_nil subject.image_name
441
+ assert_nil subject.image_type
442
+ assert_nil subject.image_size
443
+ s = subject._root_document.reload.embedded_assets.first
444
+ assert_nil s.image_id
445
+ assert_nil s.image_name
446
+ assert_nil s.image_type
447
+ assert_nil s.image_size
448
+ end
449
+ end
450
+
451
+ context "Retrieving attachment that does not exist" do
452
+ setup do
453
+ @doc = Asset.create
454
+ rewind_files
455
+ end
456
+ subject { @doc }
457
+
458
+ should "know that the attachment is not present" do
459
+ subject.image?.should be(false)
460
+ end
461
+
462
+ should "respond with true when asked if the attachment is nil?" do
463
+ subject.image.nil?.should be(true)
464
+ end
465
+
466
+ should "raise Mongo::GridFileNotFound" do
467
+ assert_raises(Mongo::GridFileNotFound) { subject.image.read }
468
+ end
469
+ end
470
+
471
+ context "Destroying a document" do
472
+ setup do
473
+ @doc = Asset.create(:image => @image)
474
+ rewind_files
475
+ end
476
+ subject { @doc }
477
+
478
+ should "remove files from grid fs as well" do
479
+ assert_grid_difference(-1) { subject.destroy }
480
+ end
481
+ end
482
+
483
+ context "Destroying an embedded document's _root_document" do
484
+ setup do
485
+ @asset = Asset.new
486
+ @doc = @asset.embedded_assets.build(:image => @image)
487
+ @doc.save!
488
+ rewind_files
489
+ end
490
+ subject { @doc }
491
+
492
+ should "remove files from grid fs as well" do
493
+ assert_grid_difference(-1) { subject._root_document.destroy }
494
+ end
495
+ end
496
+
497
+ # What about when an embedded document is removed?
498
+
499
+ context "Assigning file name" do
500
+ should "default to path" do
501
+ Asset.create(:image => @image).image.name.should == 'mr_t.jpg'
502
+ end
503
+
504
+ should "use original_filename if available" do
505
+ def @image.original_filename
506
+ 'testing.txt'
507
+ end
508
+ doc = Asset.create(:image => @image)
509
+ assert_equal 'testing.txt', doc.image_name
510
+ end
511
+ end
512
+
513
+ context "Validating attachment presence" do
514
+ setup do
515
+ @model_class = Class.new do
516
+ include MongoMapper::Document
517
+ plugin Joint
518
+ attachment :file, :required => true
519
+ end
520
+ end
521
+
522
+ should "work" do
523
+ model = @model_class.new
524
+ model.should_not be_valid
525
+
526
+ model.file = @file
527
+ model.should be_valid
528
+
529
+ model.file = nil
530
+ model.should_not be_valid
531
+
532
+ model.file = @image
533
+ model.should be_valid
534
+ end
535
+ end
536
+
537
+ context "Assigning joint io instance" do
538
+ setup do
539
+ io = Joint::IO.new({
540
+ :name => 'foo.txt',
541
+ :type => 'plain/text',
542
+ :content => 'This is my stuff',
543
+ :size => 19,
544
+ })
545
+ @asset = Asset.create(:file => io)
546
+ end
547
+
548
+ should "work" do
549
+ @asset.file_name.should == 'foo.txt'
550
+ @asset.file_size.should == 19
551
+ @asset.file_type.should == 'plain/text'
552
+ @asset.file.read.should == 'This is my stuff'
553
+ end
554
+ end
555
+
556
+ context "A font file" do
557
+ setup do
558
+ @file = open_file('font.eot')
559
+ @doc = Asset.create(:file => @file)
560
+ end
561
+ subject { @doc }
562
+
563
+ should "assign joint keys" do
564
+ subject.file_size.should == 17610
565
+ subject.file_type.should == "application/octet-stream"
566
+ subject.file_id.should_not be_nil
567
+ subject.file_id.should be_instance_of(BSON::ObjectId)
568
+ end
569
+ end
570
+
571
+ context "A music file" do
572
+ setup do
573
+ @file = open_file('example.m4r')
574
+ @doc = Asset.create(:file => @file)
575
+ end
576
+ subject { @doc }
577
+
578
+ should "assign joint keys" do
579
+ subject.file_size.should == 50790
580
+ subject.file_type.should == "audio/mp4"
581
+ subject.file_id.should_not be_nil
582
+ subject.file_id.should be_instance_of(BSON::ObjectId)
583
+ end
584
+ end
585
+ end
metadata ADDED
@@ -0,0 +1,186 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jamieorc-joint
3
+ version: !ruby/object:Gem::Version
4
+ hash: 7
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 5
9
+ - 6
10
+ version: 0.5.6
11
+ platform: ruby
12
+ authors:
13
+ - John Nunemaker
14
+ - Jamie Orchard-Hays
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2011-11-23 00:00:00 -05:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: wand
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ~>
29
+ - !ruby/object:Gem::Version
30
+ hash: 3
31
+ segments:
32
+ - 0
33
+ - 4
34
+ version: "0.4"
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: mime-types
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 3
46
+ segments:
47
+ - 0
48
+ version: "0"
49
+ type: :runtime
50
+ version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
52
+ name: mongo_mapper
53
+ prerelease: false
54
+ requirement: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ~>
58
+ - !ruby/object:Gem::Version
59
+ hash: 51
60
+ segments:
61
+ - 0
62
+ - 8
63
+ - 6
64
+ version: 0.8.6
65
+ type: :runtime
66
+ version_requirements: *id003
67
+ - !ruby/object:Gem::Dependency
68
+ name: shoulda
69
+ prerelease: false
70
+ requirement: &id004 !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ hash: 3
76
+ segments:
77
+ - 0
78
+ version: "0"
79
+ type: :development
80
+ version_requirements: *id004
81
+ - !ruby/object:Gem::Dependency
82
+ name: mocha
83
+ prerelease: false
84
+ requirement: &id005 !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ hash: 3
90
+ segments:
91
+ - 0
92
+ version: "0"
93
+ type: :development
94
+ version_requirements: *id005
95
+ - !ruby/object:Gem::Dependency
96
+ name: jnunemaker-matchy
97
+ prerelease: false
98
+ requirement: &id006 !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ hash: 3
104
+ segments:
105
+ - 0
106
+ version: "0"
107
+ type: :development
108
+ version_requirements: *id006
109
+ description: MongoMapper and GridFS joined in file upload love.
110
+ email: jamieorc@gmail.com
111
+ executables: []
112
+
113
+ extensions: []
114
+
115
+ extra_rdoc_files: []
116
+
117
+ files:
118
+ - .gitignore
119
+ - Gemfile
120
+ - Gemfile.lock
121
+ - LICENSE
122
+ - README.rdoc
123
+ - Rakefile
124
+ - joint.gemspec
125
+ - lib/joint.rb
126
+ - lib/joint/attachment_proxy.rb
127
+ - lib/joint/class_methods.rb
128
+ - lib/joint/instance_methods.rb
129
+ - lib/joint/io.rb
130
+ - lib/joint/version.rb
131
+ - specs.watchr
132
+ - test/fixtures/example.m4r
133
+ - test/fixtures/font.eot
134
+ - test/fixtures/harmony.png
135
+ - test/fixtures/mr_t.jpg
136
+ - test/fixtures/test1.txt
137
+ - test/fixtures/test2.txt
138
+ - test/fixtures/unixref.pdf
139
+ - test/helper.rb
140
+ - test/joint/test_io.rb
141
+ - test/test_joint.rb
142
+ has_rdoc: true
143
+ homepage: http://github.com/jamieorc/joint
144
+ licenses: []
145
+
146
+ post_install_message:
147
+ rdoc_options: []
148
+
149
+ require_paths:
150
+ - lib
151
+ required_ruby_version: !ruby/object:Gem::Requirement
152
+ none: false
153
+ requirements:
154
+ - - ">="
155
+ - !ruby/object:Gem::Version
156
+ hash: 3
157
+ segments:
158
+ - 0
159
+ version: "0"
160
+ required_rubygems_version: !ruby/object:Gem::Requirement
161
+ none: false
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ hash: 3
166
+ segments:
167
+ - 0
168
+ version: "0"
169
+ requirements: []
170
+
171
+ rubyforge_project:
172
+ rubygems_version: 1.5.2
173
+ signing_key:
174
+ specification_version: 3
175
+ summary: MongoMapper and GridFS joined in file upload love.
176
+ test_files:
177
+ - test/fixtures/example.m4r
178
+ - test/fixtures/font.eot
179
+ - test/fixtures/harmony.png
180
+ - test/fixtures/mr_t.jpg
181
+ - test/fixtures/test1.txt
182
+ - test/fixtures/test2.txt
183
+ - test/fixtures/unixref.pdf
184
+ - test/helper.rb
185
+ - test/joint/test_io.rb
186
+ - test/test_joint.rb