halfbyte-mongoid_grid 0.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.
@@ -0,0 +1,59 @@
1
+ Mongoid::Grid / Rack::Grid
2
+
3
+ Mongoid::Grid is a plugin for mongoid that uses GridFS. Heavily inspired
4
+ by grip (http://github.com/jnunemaker/grip)
5
+
6
+ Rack::Grid is used to serve a GridFS file from rack. Mostly copied
7
+ from http://github.com/skinandbones/rack-gridfs
8
+
9
+ Download the source at
10
+ http://github.com/dusty/mongoid_grid
11
+
12
+
13
+ Installation
14
+
15
+ Put the libraries in your project however you want.
16
+
17
+ You could make a gem to install or use with bundler.
18
+
19
+ # git clone http://github.com/dusty/mongoid_grid
20
+ # cd mongoid_grid
21
+ # gem build mongoid_grid.gemspec
22
+
23
+ Then require the libraries you want to use.
24
+
25
+ require 'mongoid/grid'
26
+ require 'rack/grid'
27
+
28
+
29
+ Usage
30
+
31
+ class Monkey
32
+ include Mongoid::Document
33
+ include Mongoid::Grid
34
+ field :name
35
+ attachment :image
36
+ end
37
+
38
+ m = Monkey.create(:name => 'name')
39
+
40
+ # To add an attachment
41
+ m.image = File.open('/tmp/me.jpg')
42
+ m.save
43
+
44
+ # To remove an attachment
45
+ m.image = nil
46
+ m.save
47
+
48
+ # To get the attachment
49
+ m.image.read
50
+
51
+ # To use Rack::Grid with Sinatra
52
+
53
+ configure do
54
+ use Rack::Grid, :database => 'my_db'
55
+ end
56
+
57
+ <img src="<%= m.image_url %>" alt="<%= m.image_name %>" />
58
+
59
+
@@ -0,0 +1,182 @@
1
+ require 'mime/types'
2
+ require 'mongoid'
3
+ require 'active_support/all'
4
+ module Mongoid
5
+ module Grid
6
+
7
+ def self.included(base)
8
+ base.send(:extend, ClassMethods)
9
+ base.send(:include, InstanceMethods)
10
+ base.class_inheritable_accessor :attachment_types
11
+ base.attachment_types = []
12
+
13
+ end
14
+
15
+ module ClassMethods
16
+
17
+ ##
18
+ # Declare an attachment for the object
19
+ #
20
+ # eg: attachment :image
21
+ def attachment(name,prefix='grid')
22
+ ##
23
+ # Callbacks to handle the attachment saving and deleting
24
+ after_save :create_attachments
25
+ after_save :delete_attachments
26
+ after_destroy :destroy_attachments
27
+
28
+ ##
29
+ # Fields for the attachment.
30
+ #
31
+ # Only the _id is really needed, the others are helpful cached
32
+ # so you don't need to hit GridFS
33
+ field "#{name}_id".to_sym, :type => BSON::ObjectID
34
+ field "#{name}_name".to_sym, :type => String
35
+ field "#{name}_size".to_sym, :type => Integer
36
+ field "#{name}_type".to_sym, :type => String
37
+
38
+ ##
39
+ # Add this name to the attachment_types
40
+ attachment_types.push(name).uniq!
41
+
42
+ ##
43
+ # Return the GridFS object.
44
+ # eg: image.filename, image.read
45
+ define_method(name) do
46
+ grid.get(attributes["#{name}_id"]) if attributes["#{name}_id"]
47
+ end
48
+
49
+ ##
50
+ # Returns true if attachment exists.
51
+ # eg: image?
52
+ define_method("#{name}?") do
53
+ !!attributes["#{name}_id"]
54
+ end
55
+
56
+ ##
57
+ # Create a method to set the attachment
58
+ # eg: object.image = File.open('/tmp/somefile.jpg')
59
+ define_method("#{name}=") do |file|
60
+ if file.respond_to?(:read)
61
+ send(:create_attachment, name, file)
62
+ else
63
+ send(:delete_attachment, name, send("#{name}_id"))
64
+ end
65
+ end
66
+
67
+ ##
68
+ # Return the relative URL to the file for use with Rack::Grid
69
+ # eg: /grid/4ba69fde8c8f369a6e000003/somefile.png
70
+ define_method("#{name}_url") do
71
+ _id = send("#{name}_id")
72
+ _name = send("#{name}_name")
73
+ ["/#{prefix}", _id, _name].join('/') if _id && _name
74
+ end
75
+
76
+ end
77
+
78
+ ##
79
+ # Accessor to GridFS
80
+ def grid
81
+ @grid ||= Mongo::Grid.new(Mongoid.database)
82
+ end
83
+
84
+ ##
85
+ # # All the attachments types for this class
86
+ # def attachment_types
87
+ # super + (@attachment_types ||= [])
88
+ # end
89
+
90
+ end
91
+
92
+ module InstanceMethods
93
+
94
+ private
95
+ ##
96
+ # Accessor to GridFS
97
+ def grid
98
+ @grid ||= self.class.grid
99
+ end
100
+
101
+ ##
102
+ # Holds queue of attachments to create
103
+ def create_attachment_queue
104
+ @create_attachment_queue ||= {}
105
+ end
106
+
107
+ ##
108
+ # Holds queue of attachments to delete
109
+ def delete_attachment_queue
110
+ @delete_attachment_queue ||= {}
111
+ end
112
+
113
+ ##
114
+ # Attachments we need to add after save.
115
+ def create_attachment(name,file)
116
+ if file.respond_to?(:read)
117
+ filename = file.respond_to?(:original_filename) ?
118
+ file.original_filename : File.basename(file.path)
119
+ type = MIME::Types.type_for(filename).first
120
+ mime = type ? type.content_type : "application/octet-stream"
121
+ send("#{name}_id=", BSON::ObjectID.new) if attributes["#{name}_id"].nil?
122
+ send("#{name}_name=", filename)
123
+ send("#{name}_size=", File.size(file))
124
+ send("#{name}_type=", mime)
125
+ create_attachment_queue[name] = file
126
+ end
127
+ end
128
+
129
+ ##
130
+ # Save an attachment to GridFS
131
+ def create_grid_attachment(name,file)
132
+ file.rewind if file.respond_to?(:rewind)
133
+ grid.delete(attributes["#{name}_id"])
134
+ grid.put(
135
+ file.read,
136
+ :filename => attributes["#{name}_name"],
137
+ :content_type => attributes["#{name}_type"],
138
+ :_id => attributes["#{name}_id"]
139
+ )
140
+ create_attachment_queue.delete(name)
141
+ end
142
+
143
+ ##
144
+ # Attachments we need to remove after save
145
+ def delete_attachment(name,id)
146
+ delete_attachment_queue[name] = id if id.is_a?(BSON::ObjectID)
147
+ send("#{name}_id=", nil)
148
+ send("#{name}_name=", nil)
149
+ send("#{name}_size=", nil)
150
+ send("#{name}_type=", nil)
151
+ end
152
+
153
+ ##
154
+ # Delete an attachment from GridFS
155
+ def delete_grid_attachment(name,id)
156
+ grid.delete(id) if id.is_a?(BSON::ObjectID)
157
+ delete_attachment_queue.delete(name)
158
+ end
159
+
160
+ ##
161
+ # Create attachments marked for creation
162
+ def create_attachments
163
+ create_attachment_queue.each {|k,v| create_grid_attachment(k,v)}
164
+ end
165
+
166
+ ##
167
+ # Delete attachments marked for deletion
168
+ def delete_attachments
169
+ delete_attachment_queue.each {|k,v| delete_grid_attachment(k,v)}
170
+ end
171
+
172
+ ##
173
+ # Deletes all attachments from document
174
+ def destroy_attachments
175
+ self.class.attachment_types.each do |name|
176
+ delete_grid_attachment(name, send("#{name}_id"))
177
+ end
178
+ end
179
+
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,59 @@
1
+ require 'timeout'
2
+ require 'mongo'
3
+
4
+ module Rack
5
+ class Grid
6
+ class ConnectionError < StandardError ; end
7
+
8
+ attr_reader :hostname, :port, :database, :prefix, :db
9
+
10
+ def initialize(app, options = {})
11
+ options = {
12
+ :hostname => 'localhost',
13
+ :prefix => 'grid',
14
+ :port => Mongo::Connection::DEFAULT_PORT,
15
+ }.merge(options)
16
+
17
+ @app = app
18
+ @hostname = options[:hostname]
19
+ @port = options[:port]
20
+ @database = options[:database]
21
+ @prefix = options[:prefix]
22
+ @db = nil
23
+
24
+ connect!
25
+ end
26
+
27
+ ##
28
+ # Strip the _id out of the path. This allows the user to send something
29
+ # like /grid/4ba69fde8c8f369a6e000003/filename.jpg to find the file
30
+ # with an id of 4ba69fde8c8f369a6e000003.
31
+ def call(env)
32
+ request = Rack::Request.new(env)
33
+ if request.path_info =~ /^\/#{prefix}\/(\w+).*$/
34
+ grid_request($1)
35
+ else
36
+ @app.call(env)
37
+ end
38
+ end
39
+
40
+ ##
41
+ # Get file from GridFS or return a 404
42
+ def grid_request(id)
43
+ file = Mongo::Grid.new(db).get(BSON::ObjectID.from_string(id))
44
+ [200, {'Content-Type' => file.content_type}, [file.read]]
45
+ rescue Mongo::GridError, BSON::InvalidObjectID
46
+ [404, {'Content-Type' => 'text/plain'}, ['File not found.']]
47
+ end
48
+
49
+ private
50
+ def connect!
51
+ Timeout::timeout(5) do
52
+ @db = Mongo::Connection.new(hostname).db(database)
53
+ end
54
+ rescue StandardError => e
55
+ raise ConnectionError, "Timeout connecting to GridFS (#{e.to_s})"
56
+ end
57
+
58
+ end
59
+ end
Binary file
Binary file
@@ -0,0 +1 @@
1
+ test1
@@ -0,0 +1 @@
1
+ test2
Binary file
@@ -0,0 +1,45 @@
1
+ require 'tempfile'
2
+ require 'pp'
3
+ require 'shoulda'
4
+ #require 'matchy'
5
+ require 'mocha'
6
+ require 'mongoid'
7
+
8
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/mongoid/grid')
9
+
10
+ Mongoid.configure do |config|
11
+ name = "mongoid_grid_test"
12
+ host = "localhost"
13
+ config.allow_dynamic_fields = false
14
+ config.master = Mongo::Connection.new.db(name)
15
+ end
16
+
17
+ class Test::Unit::TestCase
18
+ def setup
19
+ Mongoid.database.collections.each(&:remove)
20
+ end
21
+
22
+ def assert_difference(expression, difference = 1, message = nil, &block)
23
+ b = block.send(:binding)
24
+ exps = Array.wrap(expression)
25
+ before = exps.map { |e| eval(e, b) }
26
+ yield
27
+ exps.each_with_index do |e, i|
28
+ error = "#{e.inspect} didn't change by #{difference}"
29
+ error = "#{message}.\n#{error}" if message
30
+ assert_equal(before[i] + difference, eval(e, b), error)
31
+ end
32
+ end
33
+
34
+ def assert_no_difference(expression, message = nil, &block)
35
+ assert_difference(expression, 0, message, &block)
36
+ end
37
+
38
+ def assert_grid_difference(difference=1, &block)
39
+ assert_difference("Mongoid.database['fs.files'].find().count", difference, &block)
40
+ end
41
+
42
+ def assert_no_grid_difference(&block)
43
+ assert_grid_difference(0, &block)
44
+ end
45
+ end
@@ -0,0 +1,284 @@
1
+ require 'test_helper'
2
+
3
+ class Asset
4
+ include Mongoid::Document
5
+ include Mongoid::Grid
6
+
7
+ field :title, :type => String
8
+ attachment :image
9
+ attachment :file
10
+ end
11
+
12
+ class BaseModel
13
+ include Mongoid::Document
14
+ include Mongoid::Grid
15
+ attachment :file
16
+ end
17
+
18
+ class Image < BaseModel; attachment :image end
19
+ class Video < BaseModel; attachment :video end
20
+
21
+ module Mongoid::GridTestHelpers
22
+ def all_files
23
+ [@file, @image, @image2, @test1, @test2]
24
+ end
25
+
26
+ def rewind_files
27
+ all_files.each { |file| file.rewind }
28
+ end
29
+
30
+ def open_file(name)
31
+ File.open(File.join(File.dirname(__FILE__), 'fixtures', name), 'r')
32
+ end
33
+
34
+ def grid
35
+ @grid ||= Mongo::Grid.new(Mongoid.database)
36
+ end
37
+
38
+ def key_names
39
+ [:id, :name, :type, :size]
40
+ end
41
+ end
42
+
43
+ class Mongoid::GridTest < Test::Unit::TestCase
44
+ include Mongoid::GridTestHelpers
45
+
46
+ def setup
47
+ super
48
+ @file = open_file('unixref.pdf')
49
+ @image = open_file('mr_t.jpg')
50
+ @image2 = open_file('harmony.png')
51
+ @test1 = open_file('test1.txt')
52
+ @test2 = open_file('test2.txt')
53
+ end
54
+
55
+ def teardown
56
+ all_files.each { |file| file.close }
57
+ end
58
+
59
+ context "Using Grid plugin" do
60
+ should "add each attachment to attachment_types" do
61
+ assert_equal [:image, :file], Asset.attachment_types
62
+ end
63
+
64
+ should "add keys for each attachment" do
65
+ key_names.each do |key|
66
+ assert Asset.fields.keys.include?("image_#{key}")
67
+ assert Asset.fields.keys.include?("file_#{key}")
68
+ end
69
+ end
70
+
71
+ context "with inheritance" do
72
+ should "add attachment to attachment_types" do
73
+ assert_equal [:file], BaseModel.attachment_types
74
+ end
75
+
76
+ should "inherit attachments from superclass, but not share other inherited class attachments" do
77
+ assert_equal [:file, :image], Image.attachment_types
78
+ assert_equal [:file, :video], Video.attachment_types
79
+ end
80
+
81
+ should "add inherit keys from superclass" do
82
+ key_names.each do |key|
83
+ assert BaseModel.fields.keys.include?("file_#{key}")
84
+ assert Image.fields.keys.include?("file_#{key}")
85
+ assert Video.fields.keys.include?("file_#{key}")
86
+ assert Video.fields.keys.include?("video_#{key}")
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ context "Assigning new attachments to document" do
93
+ setup do
94
+ @doc = Asset.create(:image => @image, :file => @file)
95
+ rewind_files
96
+ end
97
+ subject { @doc }
98
+
99
+ should "assign GridFS content_type" do
100
+ assert_equal 'image/jpeg', grid.get(subject.image_id).content_type
101
+ assert_equal 'application/pdf', grid.get(subject.file_id).content_type
102
+ end
103
+
104
+ should "assign joint keys" do
105
+ assert_equal 13661, subject.image_size
106
+ assert_equal 68926, subject.file_size
107
+
108
+ assert_equal "image/jpeg", subject.image_type
109
+ assert_equal "application/pdf", subject.file_type
110
+
111
+ assert_not_nil subject.image_id
112
+ assert_not_nil subject.file_id
113
+
114
+ assert subject.image_id.instance_of?(BSON::ObjectID)
115
+ assert subject.file_id.instance_of?(BSON::ObjectID)
116
+ end
117
+
118
+ should "allow accessing keys through attachment proxy" do
119
+ assert_equal 13661, subject.image_size
120
+ assert_equal 68926, subject.file_size
121
+
122
+ assert_equal "image/jpeg", subject.image_type
123
+ assert_equal "application/pdf", subject.file_type
124
+
125
+ assert_not_nil subject.image_id
126
+ assert_not_nil subject.file_id
127
+
128
+ # assert subject.image.id.instance_of?(BSON::ObjectID),
129
+ # assert subject.file.id.instance_of?(BSON::ObjectID)
130
+ end
131
+
132
+ should "proxy unknown methods to GridIO object" do
133
+ assert_equal subject.image_id, subject.image.files_id
134
+ assert_equal 'image/jpeg', subject.image.content_type
135
+ assert_equal 'mr_t.jpg', subject.image.filename
136
+ assert_equal 13661, subject.image.file_length
137
+ end
138
+
139
+ should "assign file name from path if original file name not available" do
140
+ assert_equal 'mr_t.jpg', subject.image_name
141
+ assert_equal 'unixref.pdf', subject.file_name
142
+ end
143
+
144
+ should "save attachment contents correctly" do
145
+ assert_equal @file.read, subject.file.read
146
+ assert_equal @image.read, subject.image.read
147
+ end
148
+
149
+ should "know that attachment exists" do
150
+ assert subject.image?
151
+ assert subject.file?
152
+ end
153
+
154
+ should "clear assigned attachments so they don't get uploaded twice" do
155
+ Mongo::Grid.any_instance.expects(:put).never
156
+ subject.save
157
+ end
158
+ end
159
+
160
+ context "Updating existing attachment" do
161
+ setup do
162
+ @doc = Asset.create(:file => @test1)
163
+ assert_no_grid_difference do
164
+ @doc.file = @test2
165
+ @doc.save!
166
+ end
167
+ rewind_files
168
+ end
169
+ subject { @doc }
170
+
171
+ should "not change attachment id" do
172
+ assert !subject.file_id_changed?
173
+ end
174
+
175
+ should "update keys" do
176
+ assert_equal 'test2.txt', subject.file_name
177
+ assert_equal "text/plain", subject.file_type
178
+ assert_equal 5, subject.file_size
179
+ end
180
+
181
+ should "update GridFS" do
182
+ grid_obj = grid.get(subject.file_id)
183
+ assert_equal 'test2.txt', grid_obj.filename
184
+ assert_equal 'text/plain', grid_obj.content_type
185
+ assert_equal 5, grid_obj.file_length
186
+ assert_equal @test2.read, grid_obj.read
187
+ end
188
+ end
189
+
190
+ context "Updating document but not attachments" do
191
+ setup do
192
+ @doc = Asset.create(:image => @image)
193
+ @doc.update_attributes(:title => 'Updated')
194
+ @doc.reload
195
+ rewind_files
196
+ end
197
+ subject { @doc }
198
+
199
+ should "not affect attachment" do
200
+ assert_equal @image.read, subject.image.read
201
+ end
202
+
203
+ should "update document attributes" do
204
+ assert_equal('Updated', subject.title)
205
+ end
206
+ end
207
+
208
+ context "Assigning file where file pointer is not at beginning" do
209
+ setup do
210
+ @image.read
211
+ @doc = Asset.create(:image => @image)
212
+ @doc.reload
213
+ rewind_files
214
+ end
215
+ subject { @doc }
216
+
217
+ should "rewind and correctly store contents" do
218
+ assert_equal @image.read, subject.image.read
219
+ end
220
+ end
221
+
222
+ context "Setting attachment to nil" do
223
+ setup do
224
+ @doc = Asset.create(:image => @image)
225
+ rewind_files
226
+ end
227
+ subject { @doc }
228
+
229
+ should "delete attachment after save" do
230
+ assert_no_grid_difference { subject.image = nil }
231
+ assert_grid_difference(-1) { subject.save }
232
+ end
233
+
234
+ should "clear nil attachments after save and not attempt to delete again" do
235
+ Mongo::Grid.any_instance.expects(:delete).once
236
+ subject.image = nil
237
+ subject.save
238
+ Mongo::Grid.any_instance.expects(:delete).never
239
+ subject.save
240
+ end
241
+ end
242
+
243
+ context "Retrieving attachment that does not exist" do
244
+ setup do
245
+ @doc = Asset.create
246
+ rewind_files
247
+ end
248
+ subject { @doc }
249
+
250
+ should "know that the attachment is not present" do
251
+ assert !subject.image?
252
+ end
253
+
254
+ # should "raise Mongo::GridFileNotFound" do
255
+ # assert_raises(Mongo::GridFileNotFound) { subject.image.read }
256
+ # end
257
+ end
258
+
259
+ context "Destroying a document" do
260
+ setup do
261
+ @doc = Asset.create(:image => @image)
262
+ rewind_files
263
+ end
264
+ subject { @doc }
265
+
266
+ should "remove files from grid fs as well" do
267
+ assert_grid_difference(-1) { subject.destroy }
268
+ end
269
+ end
270
+
271
+ context "Assigning file name" do
272
+ should "default to path" do
273
+ assert_equal 'mr_t.jpg', Asset.create(:image => @image).image_name
274
+ end
275
+
276
+ should "use original_filename if available" do
277
+ def @image.original_filename
278
+ 'testing.txt'
279
+ end
280
+ doc = Asset.create(:image => @image)
281
+ assert_equal 'testing.txt', doc.image_name
282
+ end
283
+ end
284
+ end
File without changes
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: halfbyte-mongoid_grid
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 2
9
+ version: 0.0.2
10
+ platform: ruby
11
+ authors:
12
+ - Jan Krutisch
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-05-24 00:00:00 +02:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: mime-types
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ version: "0"
30
+ type: :runtime
31
+ version_requirements: *id001
32
+ - !ruby/object:Gem::Dependency
33
+ name: mongoid
34
+ prerelease: false
35
+ requirement: &id002 !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ segments:
40
+ - 1
41
+ - 9
42
+ version: "1.9"
43
+ type: :runtime
44
+ version_requirements: *id002
45
+ description: Plugin for Mongoid to use GridFS and a Rack helper
46
+ email: jan+mongoid-grid@krutisch.de
47
+ executables: []
48
+
49
+ extensions: []
50
+
51
+ extra_rdoc_files:
52
+ - README.txt
53
+ files:
54
+ - README.txt
55
+ - lib/mongoid/grid.rb
56
+ - lib/rack/grid.rb
57
+ - test/test_helper.rb
58
+ - test/test_mongoid_grid.rb
59
+ - test/test_rack_grid.rb
60
+ - test/fixtures/harmony.png
61
+ - test/fixtures/mr_t.jpg
62
+ - test/fixtures/test1.txt
63
+ - test/fixtures/test2.txt
64
+ - test/fixtures/unixref.pdf
65
+ has_rdoc: true
66
+ homepage: http://github.com/halfbyte/mongoid_grid
67
+ licenses: []
68
+
69
+ post_install_message:
70
+ rdoc_options: []
71
+
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ segments:
79
+ - 0
80
+ version: "0"
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ segments:
86
+ - 0
87
+ version: "0"
88
+ requirements: []
89
+
90
+ rubyforge_project: none
91
+ rubygems_version: 1.3.6
92
+ signing_key:
93
+ specification_version: 3
94
+ summary: Plugin for Mongoid to use GridFS and a Rack helper
95
+ test_files: []
96
+