joint 0.1.1 → 0.2

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.
@@ -1,6 +1,6 @@
1
1
  = Joint
2
2
 
3
- MongoMapper and GridFS united in file upload love.
3
+ MongoMapper and GridFS joined in file upload love.
4
4
 
5
5
  == Usage
6
6
 
@@ -22,11 +22,6 @@ Also, #image and #pdf are proxies so you can do stuff like:
22
22
 
23
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
24
 
25
- == Dependencies
26
-
27
- * MongoMapper >= 0.7 - gem install mongo_mapper
28
- * Wand >= 0.2.1 - gem install wand
29
-
30
25
  == Note on Patches/Pull Requests
31
26
 
32
27
  * Fork the project.
data/Rakefile CHANGED
@@ -8,8 +8,8 @@ require File.join(File.dirname(__FILE__), 'lib', 'joint', 'version')
8
8
 
9
9
  Jeweler::Tasks.new do |gem|
10
10
  gem.name = "joint"
11
- gem.summary = %Q{MongoMapper and GridFS united in file upload love.}
12
- gem.description = %Q{MongoMapper and GridFS united in file upload love.}
11
+ gem.summary = %Q{MongoMapper and GridFS joined in file upload love.}
12
+ gem.description = %Q{MongoMapper and GridFS joined in file upload love.}
13
13
  gem.email = "nunemaker@gmail.com"
14
14
  gem.homepage = "http://github.com/jnunemaker/joint"
15
15
  gem.authors = ["John Nunemaker"]
@@ -1,68 +1,82 @@
1
+ require 'set'
1
2
  require 'mime/types'
2
3
  require 'wand'
3
4
 
4
5
  module Joint
5
6
  autoload :Version, 'joint/version'
6
7
 
7
- def self.file_name(file)
8
- file.respond_to?(:original_filename) ? file.original_filename : File.basename(file.path)
9
- end
10
-
11
8
  module ClassMethods
12
9
  def attachment(name)
13
- self.class.class_inheritable_accessor :attachment_names
10
+ self.class.class_inheritable_accessor :attachment_names unless self.class.respond_to?(:attachment_names)
14
11
  self.class.attachment_names ||= []
15
12
  self.class.attachment_names << name
16
13
 
17
14
  after_save :save_attachments
18
- before_destroy :destroy_attached_files
15
+ after_save :destroy_nil_attachments
16
+ before_destroy :destroy_all_attachments
19
17
 
20
18
  key "#{name}_id".to_sym, ObjectId
21
19
  key "#{name}_name".to_sym, String
22
20
  key "#{name}_size".to_sym, Integer
23
21
  key "#{name}_type".to_sym, String
24
22
 
25
- define_method(name) do
26
- AttachmentProxy.new(self, name)
27
- end
23
+ class_eval <<-EOC
24
+ def #{name}
25
+ @#{name} ||= AttachmentProxy.new(self, :#{name})
26
+ end
28
27
 
29
- define_method("#{name}=") do |file|
30
- self["#{name}_id"] = Mongo::ObjectID.new
31
- self["#{name}_size"] = File.size(file)
32
- self["#{name}_type"] = Wand.wave(file.path)
33
- self["#{name}_name"] = Joint.file_name(file)
34
- attachment_assignments[name] = file
35
- end
28
+ def #{name}?
29
+ self.send(:#{name}_id?)
30
+ end
31
+
32
+ def #{name}=(file)
33
+ if file.nil?
34
+ nil_attachments << :#{name}
35
+ else
36
+ self["#{name}_id"] = Mongo::ObjectID.new
37
+ self["#{name}_size"] = File.size(file)
38
+ self["#{name}_type"] = Wand.wave(file.path)
39
+ self["#{name}_name"] = Joint.file_name(file)
40
+ assigned_attachments[:#{name}] = file
41
+ end
42
+ end
43
+ EOC
36
44
  end
37
45
  end
38
46
 
39
47
  module InstanceMethods
40
- def attachment_assignments
41
- @attachment_assignments ||= {}
42
- end
43
-
44
48
  def grid
45
49
  @grid ||= Mongo::Grid.new(database)
46
50
  end
47
51
 
48
52
  private
53
+ def assigned_attachments
54
+ @assigned_attachments ||= {}
55
+ end
56
+
57
+ def nil_attachments
58
+ @nil_attachments ||= Set.new
59
+ end
60
+
49
61
  def save_attachments
50
- attachment_assignments.each do |attachment|
62
+ assigned_attachments.each do |attachment|
51
63
  name, file = attachment
52
- content_type = self["#{name}_type"]
53
-
54
64
  if file.respond_to?(:read)
55
- grid.put(file.read, self["#{name}_name"], :content_type => content_type, :_id => self["#{name}_id"])
65
+ file.rewind if file.respond_to?(:rewind)
66
+ grid.put(file.read, self["#{name}_name"], {
67
+ :_id => self["#{name}_id"],
68
+ :content_type => self["#{name}_type"],
69
+ })
56
70
  end
57
- end
71
+ end.tap(&:clear)
72
+ end
58
73
 
59
- @attachment_assignments.clear
74
+ def destroy_nil_attachments
75
+ nil_attachments.each { |name| grid.delete(self["#{name}_id"]) }.tap(&:clear)
60
76
  end
61
77
 
62
- def destroy_attached_files
63
- self.class.attachment_names.each do |name|
64
- grid.delete(self["#{name}_id"])
65
- end
78
+ def destroy_all_attachments
79
+ self.class.attachment_names.each { |name| grid.delete(self["#{name}_id"]) }
66
80
  end
67
81
  end
68
82
 
@@ -95,4 +109,8 @@ module Joint
95
109
  grid_io.send(method, *args, &block)
96
110
  end
97
111
  end
112
+
113
+ def self.file_name(file)
114
+ file.respond_to?(:original_filename) ? file.original_filename : File.basename(file.path)
115
+ end
98
116
  end
@@ -1,3 +1,3 @@
1
1
  module Joint
2
- Version = '0.1.1'
2
+ Version = '0.2'
3
3
  end
@@ -1,23 +1,28 @@
1
- require 'test/unit'
2
1
  require 'tempfile'
3
2
  require 'pp'
4
3
  require 'mongo_mapper'
4
+ require 'shoulda'
5
+ require 'matchy'
6
+ require 'mocha'
5
7
 
6
8
  require File.expand_path(File.dirname(__FILE__) + '/../lib/joint')
7
9
 
8
10
  MongoMapper.database = "testing"
9
11
 
10
12
  class Test::Unit::TestCase
11
- def self.test(name, &block)
12
- test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym
13
- defined = instance_method(test_name) rescue false
14
- raise "#{test_name} is already defined in #{self}" if defined
15
- if block_given?
16
- define_method(test_name, &block)
17
- else
18
- define_method(test_name) do
19
- flunk "No implementation provided for #{name}"
20
- end
13
+ def assert_difference(expression, difference = 1, message = nil, &block)
14
+ b = block.send(:binding)
15
+ exps = Array.wrap(expression)
16
+ before = exps.map { |e| eval(e, b) }
17
+ yield
18
+ exps.each_with_index do |e, i|
19
+ error = "#{e.inspect} didn't change by #{difference}"
20
+ error = "#{message}.\n#{error}" if message
21
+ assert_equal(before[i] + difference, eval(e, b), error)
21
22
  end
22
23
  end
24
+
25
+ def assert_no_difference(expression, message = nil, &block)
26
+ assert_difference(expression, 0, message, &block)
27
+ end
23
28
  end
@@ -1,9 +1,10 @@
1
1
  require 'helper'
2
2
 
3
- class Foo
3
+ class Asset
4
4
  include MongoMapper::Document
5
5
  plugin Joint
6
6
 
7
+ key :title, String
7
8
  attachment :image
8
9
  attachment :pdf
9
10
  end
@@ -11,17 +12,14 @@ end
11
12
  class JointTest < Test::Unit::TestCase
12
13
  def setup
13
14
  MongoMapper.database.collections.each(&:remove)
14
- @grid = Mongo::Grid.new(MongoMapper.database)
15
15
 
16
- dir = File.dirname(__FILE__) + '/fixtures'
17
- @pdf = File.open("#{dir}/unixref.pdf", 'r')
18
- @image = File.open("#{dir}/mr_t.jpg", 'r')
19
-
20
- @pdf_contents = File.read("#{dir}/unixref.pdf")
21
- @image_contents = File.read("#{dir}/mr_t.jpg")
22
-
23
- @doc = Foo.create(:image => @image, :pdf => @pdf)
24
- @doc.reload
16
+ dir = File.dirname(__FILE__) + '/fixtures'
17
+ @pdf = File.open("#{dir}/unixref.pdf", 'r')
18
+ @image = File.open("#{dir}/mr_t.jpg", 'r')
19
+ @pdf_contents = File.read("#{dir}/unixref.pdf")
20
+ @image_contents = File.read("#{dir}/mr_t.jpg")
21
+ @grid = Mongo::Grid.new(MongoMapper.database)
22
+ @gridfs_collection = MongoMapper.database['fs.files']
25
23
  end
26
24
 
27
25
  def teardown
@@ -29,80 +27,180 @@ class JointTest < Test::Unit::TestCase
29
27
  @image.close
30
28
  end
31
29
 
32
- test "assigns grid fs content type correctly" do
33
- assert_equal "image/jpeg", @grid.get(@doc.image_id).content_type
34
- assert_equal "application/pdf", @grid.get(@doc.pdf_id).content_type
30
+ context "Using Joint plugin" do
31
+ should "add each attachment to attachment_names" do
32
+ Asset.attachment_names.should == [:image, :pdf]
33
+ end
34
+
35
+ should "add keys for each attachment" do
36
+ [:image, :pdf].each do |attachment|
37
+ [:id, :name, :type, :size].each do |key|
38
+ Asset.keys.include?("#{attachment}_#{key}")
39
+ end
40
+ end
41
+ end
35
42
  end
36
43
 
37
- test "assigns keys correctly" do
38
- assert_equal 13661, @doc.image_size
39
- assert_equal 68926, @doc.pdf_size
44
+ context "Assigning attachments to document" do
45
+ setup do
46
+ @doc = Asset.create(:image => @image, :pdf => @pdf)
47
+ @doc.reload
48
+ end
40
49
 
41
- assert_equal "image/jpeg", @doc.image_type
42
- assert_equal "application/pdf", @doc.pdf_type
50
+ should "assign GridFS content_type" do
51
+ @grid.get(@doc.image_id).content_type.should == 'image/jpeg'
52
+ @grid.get(@doc.pdf_id).content_type.should == 'application/pdf'
53
+ end
43
54
 
44
- assert_not_nil @doc.image_id
45
- assert_not_nil @doc.pdf_id
55
+ should "assign joint keys" do
56
+ @doc.image_size.should == 13661
57
+ @doc.pdf_size.should == 68926
46
58
 
47
- assert_kind_of Mongo::ObjectID, @doc.image_id
48
- assert_kind_of Mongo::ObjectID, @doc.pdf_id
49
- end
59
+ @doc.image_type.should == "image/jpeg"
60
+ @doc.pdf_type.should == "application/pdf"
61
+
62
+ @doc.image_id.should_not be_nil
63
+ @doc.pdf_id.should_not be_nil
64
+
65
+ @doc.image_id.should be_instance_of(Mongo::ObjectID)
66
+ @doc.pdf_id.should be_instance_of(Mongo::ObjectID)
67
+ end
50
68
 
51
- test "accessing keys through attachment proxy" do
52
- assert_equal 13661, @doc.image.size
53
- assert_equal 68926, @doc.pdf.size
69
+ should "allow accessing keys through attachment proxy" do
70
+ @doc.image.size.should == 13661
71
+ @doc.pdf.size.should == 68926
54
72
 
55
- assert_equal "image/jpeg", @doc.image.type
56
- assert_equal "application/pdf", @doc.pdf.type
73
+ @doc.image.type.should == "image/jpeg"
74
+ @doc.pdf.type.should == "application/pdf"
57
75
 
58
- assert_not_nil @doc.image.id
59
- assert_not_nil @doc.pdf.id
76
+ @doc.image.id.should_not be_nil
77
+ @doc.pdf.id.should_not be_nil
60
78
 
61
- assert_kind_of Mongo::ObjectID, @doc.image.id
62
- assert_kind_of Mongo::ObjectID, @doc.pdf.id
79
+ @doc.image.id.should be_instance_of(Mongo::ObjectID)
80
+ @doc.pdf.id.should be_instance_of(Mongo::ObjectID)
81
+ end
82
+
83
+ should "proxy unknown methods to GridIO object" do
84
+ @doc.image.files_id.should == @doc.image_id
85
+ @doc.image.content_type.should == 'image/jpeg'
86
+ @doc.image.filename.should == 'mr_t.jpg'
87
+ @doc.image.file_length.should == 13661
88
+ end
89
+
90
+ should "assign file name from path if original file name not available" do
91
+ @doc.image_name.should == 'mr_t.jpg'
92
+ @doc.pdf_name.should == 'unixref.pdf'
93
+ end
94
+
95
+ should "save attachment contents correctly" do
96
+ @doc.pdf.read.should == @pdf_contents
97
+ @doc.image.read.should == @image_contents
98
+ end
99
+
100
+ should "know that attachment exists" do
101
+ @doc.image?.should be(true)
102
+ @doc.pdf?.should be(true)
103
+ end
104
+
105
+ should "clear assigned attachments so they don't get uploaded twice" do
106
+ Mongo::Grid.any_instance.expects(:put).never
107
+ @doc.save
108
+ end
63
109
  end
64
110
 
65
- test "sends unknown proxy methods to grid io object" do
66
- assert_equal 13661, @doc.image.file_length
67
- assert_equal 'image/jpeg', @doc.image.content_type
68
- assert_equal 'mr_t.jpg', @doc.image.filename
69
- assert_equal @doc.image_id, @doc.image.files_id
111
+ context "Updating document but not attachments" do
112
+ setup do
113
+ @doc = Asset.create(:image => @image)
114
+ @doc.update_attributes(:title => 'Updated')
115
+ @doc.reload
116
+ end
117
+
118
+ should "not affect attachment" do
119
+ @doc.image.read.should == @image_contents
120
+ end
121
+
122
+ should "update document attributes" do
123
+ @doc.title.should == 'Updated'
124
+ end
70
125
  end
71
126
 
72
- test "assigns file name from path if original file name not available" do
73
- assert_equal 'mr_t.jpg', @doc.image_name
74
- assert_equal 'unixref.pdf', @doc.pdf_name
127
+ context "Assigning file with where file pointer is not at beginning" do
128
+ setup do
129
+ @image.read
130
+ @doc = Asset.create(:image => @image)
131
+ @doc.reload
132
+ end
133
+
134
+ should "rewind and correctly store contents" do
135
+ @doc.image.read.should == @image_contents
136
+ end
75
137
  end
76
138
 
77
- test "assigns file name from original filename if available" do
78
- begin
79
- file = Tempfile.new('testing.txt')
80
- def file.original_filename
81
- 'testing.txt'
139
+ context "Setting attachment to nil" do
140
+ setup do
141
+ @doc = Asset.create(:image => @image)
142
+ end
143
+
144
+ should "delete attachment after save" do
145
+ assert_no_difference '@gridfs_collection.find().count' do
146
+ @doc.image = nil
82
147
  end
83
- doc = Foo.create(:image => file)
84
- assert_equal 'testing.txt', doc.image_name
85
- ensure
86
- file.close
148
+
149
+ assert_difference '@gridfs_collection.find().count', -1 do
150
+ @doc.save
151
+ end
152
+ end
153
+
154
+ should "clear nil attachments after save and not attempt to delete again" do
155
+ @doc.image = nil
156
+ @doc.save
157
+ Mongo::Grid.any_instance.expects(:delete).never
158
+ @doc.save
87
159
  end
88
160
  end
89
161
 
90
- test "responds to keys" do
91
- [ :pdf_size, :pdf_id, :pdf_name, :pdf_type,
92
- :image_size, :image_id, :image_name, :image_type
93
- ].each do |method|
94
- assert @doc.respond_to?(method)
162
+ context "Retrieving attachment that does not exist" do
163
+ setup do
164
+ @doc = Asset.create
165
+ end
166
+
167
+ should "know that the attachment is not present" do
168
+ @doc.image?.should be(false)
169
+ end
170
+
171
+ should "raise Mongo::GridError" do
172
+ assert_raises(Mongo::GridError) { @doc.image.read }
95
173
  end
96
174
  end
97
175
 
98
- test "saves attachments correctly" do
99
- assert_equal @pdf_contents, @doc.pdf.read
100
- assert_equal @image_contents, @doc.image.read
176
+ context "Destroying a document" do
177
+ setup do
178
+ @doc = Asset.create(:image => @image)
179
+ end
180
+
181
+ should "remove files from grid fs as well" do
182
+ assert_difference "@gridfs_collection.find().count", -1 do
183
+ @doc.destroy
184
+ end
185
+ end
101
186
  end
102
187
 
103
- test "cleans up attachments on destroy" do
104
- @doc.destroy
105
- assert_raises(Mongo::GridError) { @grid.get(@doc.image_id) }
106
- assert_raises(Mongo::GridError) { @grid.get(@doc.pdf_id) }
188
+ context "Assigning file name" do
189
+ should "default to path" do
190
+ Asset.create(:image => @image).image.name.should == 'mr_t.jpg'
191
+ end
192
+
193
+ should "use original_filename if available" do
194
+ begin
195
+ file = Tempfile.new('testing.txt')
196
+ def file.original_filename
197
+ 'testing.txt'
198
+ end
199
+ doc = Asset.create(:image => file)
200
+ assert_equal 'testing.txt', doc.image_name
201
+ ensure
202
+ file.close
203
+ end
204
+ end
107
205
  end
108
206
  end
metadata CHANGED
@@ -4,9 +4,8 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 1
8
- - 1
9
- version: 0.1.1
7
+ - 2
8
+ version: "0.2"
10
9
  platform: ruby
11
10
  authors:
12
11
  - John Nunemaker
@@ -14,7 +13,7 @@ autorequire:
14
13
  bindir: bin
15
14
  cert_chain: []
16
15
 
17
- date: 2010-03-26 00:00:00 -04:00
16
+ date: 2010-03-29 00:00:00 -04:00
18
17
  default_executable:
19
18
  dependencies:
20
19
  - !ruby/object:Gem::Dependency
@@ -67,7 +66,7 @@ dependencies:
67
66
  version: "0"
68
67
  type: :development
69
68
  version_requirements: *id004
70
- description: MongoMapper and GridFS united in file upload love.
69
+ description: MongoMapper and GridFS joined in file upload love.
71
70
  email: nunemaker@gmail.com
72
71
  executables: []
73
72
 
@@ -113,7 +112,7 @@ rubyforge_project:
113
112
  rubygems_version: 1.3.6
114
113
  signing_key:
115
114
  specification_version: 3
116
- summary: MongoMapper and GridFS united in file upload love.
115
+ summary: MongoMapper and GridFS joined in file upload love.
117
116
  test_files:
118
117
  - test/fixtures/mr_t.jpg
119
118
  - test/fixtures/unixref.pdf