joint 0.1.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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