jamieorc-joint 0.5.6

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