mongoid-grid_fs 1.0.0

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/README.md ADDED
@@ -0,0 +1,52 @@
1
+ NAME
2
+ ----
3
+ mongoid_grid_fs
4
+
5
+ SYNOPSIS
6
+ --------
7
+
8
+ ````ruby
9
+
10
+ require 'mongoid-grid_fs'
11
+
12
+ g = GridFs.put anthing_that_respons_to_read
13
+
14
+ GridFS.get id
15
+
16
+ GridFS.delete id
17
+
18
+
19
+ ````
20
+
21
+ DESCRIPTION
22
+ -----------
23
+ mongoid_grid_fs is pure mongoid 3 / moped implementation of the mongodb
24
+ grid_fs specification
25
+
26
+ ref: http://www.mongodb.org/display/DOCS/GridFS+Specification
27
+
28
+ it has the following features:
29
+
30
+ - implementation is on top of mongoid for portability. moped (the drive)
31
+ is barely used
32
+
33
+ - simple, REST-like api
34
+
35
+ - support for custom namespaces (fs.files vs. image.files)
36
+
37
+ - pathnames and io-like objects can be written to the grid
38
+
39
+ - auto-unique pathnames are generated (by default) to avoid collisions using #put
40
+
41
+ 'path/info/a.rb' -> '$object_id/a.rb'
42
+
43
+ - #[] and #[]= methods which allow the grid to be used like a giant file
44
+ hash in the sky
45
+
46
+ - supprt for data_uris
47
+
48
+ ````eruby
49
+
50
+ <%= image_tag :src => file.data_url %>
51
+
52
+ ````
data/Rakefile ADDED
@@ -0,0 +1,446 @@
1
+ This.name =
2
+ "GridFs"
3
+
4
+ This.synopsis =
5
+ "a mongoid 3/moped compatible implementation of the grid_fs specification"
6
+
7
+ This.rubyforge_project = 'codeforpeople'
8
+ This.author = "Ara T. Howard"
9
+ This.email = "ara.t.howard@gmail.com"
10
+ This.homepage = "https://github.com/ahoward/#{ This.lib }"
11
+
12
+ This.setup!
13
+
14
+
15
+
16
+ task :default do
17
+ puts((Rake::Task.tasks.map{|task| task.name.gsub(/::/,':')} - ['default']).sort)
18
+ end
19
+
20
+ task :test do
21
+ This.run_tests!
22
+ end
23
+
24
+ namespace :test do
25
+ task(:unit){ This.run_tests!(:unit) }
26
+ task(:functional){ This.run_tests!(:functional) }
27
+ task(:integration){ This.run_tests!(:integration) }
28
+ end
29
+
30
+ def This.run_tests!(which = nil)
31
+ which ||= '**'
32
+ test_dir = File.join(This.dir, "test")
33
+ test_glob ||= File.join(test_dir, "#{ which }/**_test.rb")
34
+ test_rbs = Dir.glob(test_glob).sort
35
+
36
+ div = ('=' * 119)
37
+ line = ('-' * 119)
38
+
39
+ test_rbs.each_with_index do |test_rb, index|
40
+ testno = index + 1
41
+ command = "#{ File.basename(This.ruby) } -I ./lib -I ./test/lib #{ test_rb }"
42
+
43
+ puts
44
+ This.say(div, :color => :cyan, :bold => true)
45
+ This.say("@#{ testno } => ", :bold => true, :method => :print)
46
+ This.say(command, :color => :cyan, :bold => true)
47
+ This.say(line, :color => :cyan, :bold => true)
48
+
49
+ system(command)
50
+
51
+ This.say(line, :color => :cyan, :bold => true)
52
+
53
+ status = $?.exitstatus
54
+
55
+ if status.zero?
56
+ This.say("@#{ testno } <= ", :bold => true, :color => :white, :method => :print)
57
+ This.say("SUCCESS", :color => :green, :bold => true)
58
+ else
59
+ This.say("@#{ testno } <= ", :bold => true, :color => :white, :method => :print)
60
+ This.say("FAILURE", :color => :red, :bold => true)
61
+ end
62
+ This.say(line, :color => :cyan, :bold => true)
63
+
64
+ exit(status) unless status.zero?
65
+ end
66
+ end
67
+
68
+
69
+ task :gemspec do
70
+ ignore_extensions = ['git', 'svn', 'tmp', /sw./, 'bak', 'gem']
71
+ ignore_directories = ['pkg', 'db']
72
+ ignore_files = ['test/log', 'test/db.yml', 'a.rb', 'b.rb'] + Dir['db/*'] + %w'db'
73
+
74
+ shiteless =
75
+ lambda do |list|
76
+ list.delete_if do |entry|
77
+ next unless test(?e, entry)
78
+ extension = File.basename(entry).split(%r/[.]/).last
79
+ ignore_extensions.any?{|ext| ext === extension}
80
+ end
81
+ list.delete_if do |entry|
82
+ next unless test(?d, entry)
83
+ dirname = File.expand_path(entry)
84
+ ignore_directories.any?{|dir| File.expand_path(dir) == dirname}
85
+ end
86
+ list.delete_if do |entry|
87
+ next unless test(?f, entry)
88
+ filename = File.expand_path(entry)
89
+ ignore_files.any?{|file| File.expand_path(file) == filename}
90
+ end
91
+ end
92
+
93
+ lib = This.lib
94
+ object = This.object
95
+ version = This.version
96
+ files = shiteless[Dir::glob("**/**")]
97
+ executables = shiteless[Dir::glob("bin/*")].map{|exe| File.basename(exe)}
98
+ #has_rdoc = true #File.exist?('doc')
99
+ test_files = test(?e, "test/#{ lib }.rb") ? "test/#{ lib }.rb" : nil
100
+ summary = This.summary || This.synopsis || "#{ lib } kicks the ass"
101
+ description = This.description || summary
102
+
103
+ if This.extensions.nil?
104
+ This.extensions = []
105
+ extensions = This.extensions
106
+ %w( Makefile configure extconf.rb ).each do |ext|
107
+ extensions << ext if File.exists?(ext)
108
+ end
109
+ end
110
+ extensions = [extensions].flatten.compact
111
+
112
+ # TODO
113
+ if This.dependencies.nil?
114
+ dependencies = []
115
+ else
116
+ case This.dependencies
117
+ when Hash
118
+ dependencies = This.dependencies.values
119
+ when Array
120
+ dependencies = This.dependencies
121
+ end
122
+ end
123
+
124
+ template =
125
+ if test(?e, 'gemspec.erb')
126
+ This.template_for{ IO.read('gemspec.erb') }
127
+ else
128
+ This.template_for {
129
+ <<-__
130
+ ## <%= lib %>.gemspec
131
+ #
132
+
133
+ Gem::Specification::new do |spec|
134
+ spec.name = <%= lib.inspect %>
135
+ spec.version = <%= version.inspect %>
136
+ spec.platform = Gem::Platform::RUBY
137
+ spec.summary = <%= lib.inspect %>
138
+ spec.description = <%= description.inspect %>
139
+
140
+ spec.files =\n<%= files.sort.pretty_inspect %>
141
+ spec.executables = <%= executables.inspect %>
142
+
143
+ spec.require_path = "lib"
144
+
145
+ spec.test_files = <%= test_files.inspect %>
146
+
147
+ <% dependencies.each do |lib_version| %>
148
+ spec.add_dependency(*<%= Array(lib_version).flatten.inspect %>)
149
+ <% end %>
150
+
151
+ spec.extensions.push(*<%= extensions.inspect %>)
152
+
153
+ spec.rubyforge_project = <%= This.rubyforge_project.inspect %>
154
+ spec.author = <%= This.author.inspect %>
155
+ spec.email = <%= This.email.inspect %>
156
+ spec.homepage = <%= This.homepage.inspect %>
157
+ end
158
+ __
159
+ }
160
+ end
161
+
162
+ FileUtils.mkdir_p(This.pkgdir)
163
+ gemspec = "#{ lib }.gemspec"
164
+ open(gemspec, "w"){|fd| fd.puts(template)}
165
+ This.gemspec = gemspec
166
+ end
167
+
168
+ task :gem => [:clean, :gemspec] do
169
+ FileUtils.mkdir_p(This.pkgdir)
170
+ before = Dir['*.gem']
171
+ cmd = "gem build #{ This.gemspec }"
172
+ `#{ cmd }`
173
+ after = Dir['*.gem']
174
+ gem = ((after - before).first || after.first) or abort('no gem!')
175
+ FileUtils.mv(gem, This.pkgdir)
176
+ This.gem = File.join(This.pkgdir, File.basename(gem))
177
+ end
178
+
179
+ task :readme do
180
+ samples = ''
181
+ prompt = '~ > '
182
+ lib = This.lib
183
+ version = This.version
184
+
185
+ Dir['sample*/*'].sort.each do |sample|
186
+ samples << "\n" << " <========< #{ sample } >========>" << "\n\n"
187
+
188
+ cmd = "cat #{ sample }"
189
+ samples << This.util.indent(prompt + cmd, 2) << "\n\n"
190
+ samples << This.util.indent(`#{ cmd }`, 4) << "\n"
191
+
192
+ cmd = "ruby #{ sample }"
193
+ samples << This.util.indent(prompt + cmd, 2) << "\n\n"
194
+
195
+ cmd = "ruby -e'STDOUT.sync=true; exec %(ruby -I ./lib #{ sample })'"
196
+ samples << This.util.indent(`#{ cmd } 2>&1`, 4) << "\n"
197
+ end
198
+
199
+ template =
200
+ if test(?e, 'readme.erb')
201
+ This.template_for{ IO.read('readme.erb') }
202
+ else
203
+ This.template_for {
204
+ <<-__
205
+ NAME
206
+ #{ lib }
207
+
208
+ DESCRIPTION
209
+
210
+ INSTALL
211
+ gem install #{ lib }
212
+
213
+ SAMPLES
214
+ #{ samples }
215
+ __
216
+ }
217
+ end
218
+
219
+ open("README", "w"){|fd| fd.puts template}
220
+ end
221
+
222
+
223
+ task :clean do
224
+ Dir[File.join(This.pkgdir, '**/**')].each{|entry| FileUtils.rm_rf(entry)}
225
+ end
226
+
227
+
228
+ task :release => [:clean, :gemspec, :gem] do
229
+ gems = Dir[File.join(This.pkgdir, '*.gem')].flatten
230
+ raise "which one? : #{ gems.inspect }" if gems.size > 1
231
+ raise "no gems?" if gems.size < 1
232
+
233
+ cmd = "gem push #{ This.gem }"
234
+ puts cmd
235
+ puts
236
+ system(cmd)
237
+ abort("cmd(#{ cmd }) failed with (#{ $?.inspect })") unless $?.exitstatus.zero?
238
+
239
+ #cmd = "rubyforge login && rubyforge add_release #{ This.rubyforge_project } #{ This.lib } #{ This.version } #{ This.gem }"
240
+ #puts cmd
241
+ #puts
242
+ #system(cmd)
243
+ #abort("cmd(#{ cmd }) failed with (#{ $?.inspect })") unless $?.exitstatus.zero?
244
+ end
245
+
246
+
247
+
248
+
249
+
250
+ BEGIN {
251
+ # support for this rakefile
252
+ #
253
+ $VERBOSE = nil
254
+
255
+ require 'erb'
256
+ require 'fileutils'
257
+ require 'rbconfig'
258
+ require 'pp'
259
+
260
+ # cache a bunch of stuff about this rakefile/environment
261
+ #
262
+
263
+ This =
264
+ Class.new(Hash) do
265
+
266
+ def method_missing(method, *args, &block)
267
+ if method.to_s =~ /=/
268
+ key = method.to_s.chomp('=')
269
+ value = block ? block : args.shift
270
+ self[key] = value
271
+ else
272
+ key = method.to_s
273
+ if block
274
+ value = block
275
+ self[key] = value
276
+ else
277
+ value = self[key]
278
+
279
+ if value.respond_to?(:call)
280
+ self[key] = value.call()
281
+ else
282
+ value
283
+ end
284
+ end
285
+ end
286
+ end
287
+
288
+ def inspect
289
+ expand!
290
+ PP.pp(self, '')
291
+ end
292
+
293
+ def expand!
294
+ keys.each do |key|
295
+ value = self[key]
296
+ if value.respond_to?(:call)
297
+ self[key] = value.call()
298
+ end
299
+ end
300
+ end
301
+
302
+ end.new()
303
+
304
+ This.file = File.expand_path(__FILE__)
305
+ This.dir = File.dirname(This.file)
306
+ This.pkgdir = File.join(This.dir, 'pkg')
307
+
308
+ # defaults
309
+ #
310
+ This.lib do
311
+ File.basename(Dir.pwd)
312
+ end
313
+
314
+ def This.setup!
315
+ begin
316
+ require "./lib/#{ This.lib }"
317
+ rescue LoadError
318
+ abort("could not load #{ This.lib }")
319
+ end
320
+ end
321
+
322
+ This.name do
323
+ This.name = This.lib.capitalize
324
+ end
325
+
326
+ This.object do
327
+ begin
328
+ This.object = eval(This.name)
329
+ rescue Object
330
+ abort("could not determine object from #{ This.name }")
331
+ end
332
+ end
333
+
334
+ This.version do
335
+ This.object.send(:version)
336
+ end
337
+
338
+ This.dependencies do
339
+ if This.object.respond_to?(:dependencies)
340
+ This.object.dependencies
341
+ end
342
+ end
343
+
344
+ This.ruby do
345
+ c = Config::CONFIG
346
+ bindir = c["bindir"] || c['BINDIR']
347
+ ruby_install_name = c['ruby_install_name'] || c['RUBY_INSTALL_NAME'] || 'ruby'
348
+ ruby_ext = c['EXEEXT'] || ''
349
+ File.join(bindir, (ruby_install_name + ruby_ext))
350
+ end
351
+
352
+ # some utils
353
+ #
354
+ This.util = Module.new do
355
+ def indent(s, n = 2)
356
+ s = unindent(s)
357
+ ws = ' ' * n
358
+ s.gsub(%r/^/, ws)
359
+ end
360
+
361
+ def unindent(s)
362
+ indent = nil
363
+ s.each_line do |line|
364
+ next if line =~ %r/^\s*$/
365
+ indent = line[%r/^\s*/] and break
366
+ end
367
+ indent ? s.gsub(%r/^#{ indent }/, "") : s
368
+ end
369
+
370
+ extend self
371
+ end
372
+
373
+ # template support
374
+ #
375
+ This.template = Class.new do
376
+ def initialize(&block)
377
+ @block = block
378
+ @template = block.call.to_s
379
+ end
380
+
381
+ def expand(b=nil)
382
+ ERB.new(This.util.unindent(@template)).result((b||@block).binding)
383
+ end
384
+
385
+ alias_method 'to_s', 'expand'
386
+ end
387
+
388
+ def This.template_for(*args, &block)
389
+ This.template.new(*args, &block)
390
+ end
391
+
392
+ # colored console output support
393
+ #
394
+ This.ansi = {
395
+ :clear => "\e[0m",
396
+ :reset => "\e[0m",
397
+ :erase_line => "\e[K",
398
+ :erase_char => "\e[P",
399
+ :bold => "\e[1m",
400
+ :dark => "\e[2m",
401
+ :underline => "\e[4m",
402
+ :underscore => "\e[4m",
403
+ :blink => "\e[5m",
404
+ :reverse => "\e[7m",
405
+ :concealed => "\e[8m",
406
+ :black => "\e[30m",
407
+ :red => "\e[31m",
408
+ :green => "\e[32m",
409
+ :yellow => "\e[33m",
410
+ :blue => "\e[34m",
411
+ :magenta => "\e[35m",
412
+ :cyan => "\e[36m",
413
+ :white => "\e[37m",
414
+ :on_black => "\e[40m",
415
+ :on_red => "\e[41m",
416
+ :on_green => "\e[42m",
417
+ :on_yellow => "\e[43m",
418
+ :on_blue => "\e[44m",
419
+ :on_magenta => "\e[45m",
420
+ :on_cyan => "\e[46m",
421
+ :on_white => "\e[47m"
422
+ }
423
+
424
+ def This.say(something, *args)
425
+ options = args.last.is_a?(Hash) ? args.pop : {}
426
+ options[:color] = args.shift.to_s.to_sym unless args.empty?
427
+ keys = options.keys
428
+ keys.each{|key| options[key.to_s.to_sym] = options.delete(key)}
429
+
430
+ color = options[:color]
431
+ bold = options.has_key?(:bold)
432
+
433
+ parts = [something]
434
+ parts.unshift(This.ansi[color]) if color
435
+ parts.unshift(This.ansi[:bold]) if bold
436
+ parts.push(This.ansi[:clear]) if parts.size > 1
437
+
438
+ method = options[:method] || :puts
439
+
440
+ Kernel.send(method, parts.join)
441
+ end
442
+
443
+ # always run out of the project dir
444
+ #
445
+ Dir.chdir(This.dir)
446
+ }
@@ -0,0 +1,419 @@
1
+ require "mongoid"
2
+ require "mime/types"
3
+ require "digest/md5"
4
+ require "cgi"
5
+
6
+ class GridFS
7
+ ##
8
+ #
9
+ class << GridFS
10
+ def version
11
+ "1.0.0"
12
+ end
13
+
14
+ attr_accessor :namespace
15
+ attr_accessor :file_model
16
+ attr_accessor :chunk_model
17
+
18
+ def init!
19
+ GridFS.build_namespace_for(:Fs)
20
+
21
+ GridFS.namespace = Fs
22
+ GridFS.file_model = Fs.file_model
23
+ GridFS.chunk_model = Fs.chunk_model
24
+
25
+ const_set(:File, Fs.file_model)
26
+ const_set(:Chunk, Fs.chunk_model)
27
+
28
+ to_delegate = %w(
29
+ put
30
+ get
31
+ delete
32
+ find
33
+ []
34
+ []=
35
+ )
36
+
37
+ to_delegate.each do |method|
38
+ class_eval <<-__
39
+ def GridFS.#{ method }(*args, &block)
40
+ ::GridFS::Fs::#{ method }(*args, &block)
41
+ end
42
+ __
43
+ end
44
+ end
45
+ end
46
+
47
+ ##
48
+ #
49
+ def GridFS.namespace_for(prefix)
50
+ prefix = prefix.to_s.downcase
51
+ const = "::GridFS::#{ prefix.to_s.camelize }"
52
+ namespace = const.split(/::/).last
53
+ const_defined?(namespace) ? const_get(namespace) : build_namespace_for(namespace)
54
+ end
55
+
56
+ ##
57
+ #
58
+ def GridFS.build_namespace_for(prefix)
59
+ prefix = prefix.to_s.downcase
60
+ const = prefix.camelize
61
+
62
+ namespace =
63
+ Module.new do
64
+ module_eval(&NamespaceMixin)
65
+ self
66
+ end
67
+
68
+ const_set(const, namespace)
69
+
70
+ file_model = build_file_model_for(namespace)
71
+ chunk_model = build_chunk_model_for(namespace)
72
+
73
+ file_model.namespace = namespace
74
+ chunk_model.namespace = namespace
75
+
76
+ file_model.chunk_model = chunk_model
77
+ chunk_model.file_model = file_model
78
+
79
+ namespace.prefix = prefix
80
+ namespace.file_model = file_model
81
+ namespace.chunk_model = chunk_model
82
+
83
+ namespace.send(:const_set, :File, file_model)
84
+ namespace.send(:const_set, :Chunk, chunk_model)
85
+
86
+ #at_exit{ file_model.create_indexes rescue nil }
87
+ #at_exit{ chunk_model.create_indexes rescue nil }
88
+
89
+ const_get(const)
90
+ end
91
+
92
+ NamespaceMixin = proc do
93
+ class << self
94
+ attr_accessor :prefix
95
+ attr_accessor :file_model
96
+ attr_accessor :chunk_model
97
+
98
+ def to_s
99
+ prefix
100
+ end
101
+
102
+ def namespace
103
+ prefix
104
+ end
105
+
106
+ def put(readable, attributes = {})
107
+ chunks = []
108
+ file = file_model.new
109
+ attributes.to_options!
110
+
111
+ if attributes.has_key?(:id)
112
+ file.id = attributes.delete(:id)
113
+ end
114
+
115
+ if attributes.has_key?(:_id)
116
+ file.id = attributes.delete(:_id)
117
+ end
118
+
119
+ if attributes.has_key?(:content_type)
120
+ attributes[:contentType] = attributes.delete(:content_type)
121
+ end
122
+
123
+ if attributes.has_key?(:upload_date)
124
+ attributes[:uploadDate] = attributes.delete(:upload_date)
125
+ end
126
+
127
+ md5 = Digest::MD5.new
128
+ length = 0
129
+ chunkSize = file.chunkSize
130
+ n = 0
131
+
132
+ GridFS.reading(readable) do |io|
133
+
134
+ filename =
135
+ attributes[:filename] ||=
136
+ [file.id.to_s, GridFS.extract_basename(io)].join('/').squeeze('/')
137
+
138
+ content_type =
139
+ attributes[:contentType] ||=
140
+ GridFS.extract_content_type(filename) || file.contentType
141
+
142
+ while((buf = io.read(chunkSize)))
143
+ md5 << buf
144
+ length += buf.size
145
+ chunk = file.chunks.build
146
+ chunk.data = binary_for(buf)
147
+ chunk.n = n
148
+ n += 1
149
+ chunk.save!
150
+ chunks.push(chunk)
151
+ end
152
+
153
+ end
154
+
155
+ attributes[:length] ||= length
156
+ attributes[:uploadDate] ||= Time.now.utc
157
+ attributes[:md5] ||= md5.hexdigest
158
+
159
+ file.update_attributes(attributes)
160
+
161
+ file.save!
162
+ file
163
+ ensure
164
+ chunks.each{|chunk| chunk.destroy rescue nil} if $!
165
+ end
166
+
167
+ if defined?(Moped)
168
+ def binary_for(*buf)
169
+ Moped::BSON::Binary.new(:generic, buf.join)
170
+ end
171
+ else
172
+ def binary_for(buf)
173
+ BSON::Binary.new(buf.bytes.to_a)
174
+ end
175
+ end
176
+
177
+ def get(id)
178
+ file_model.find(id)
179
+ end
180
+
181
+ def delete(id)
182
+ file_model.find(id).destroy
183
+ rescue
184
+ nil
185
+ end
186
+
187
+ def where(conditions = {})
188
+ case conditions
189
+ when String
190
+ file_model.where(:filename => conditions)
191
+ else
192
+ file_model.where(conditions)
193
+ end
194
+ end
195
+
196
+ def find(*args)
197
+ where(*args).first
198
+ end
199
+
200
+ def [](filename)
201
+ file_model.where(:filename => filename.to_s).first
202
+ end
203
+
204
+ def []=(filename, readable)
205
+ file = self[filename]
206
+ file.destroy if file
207
+ put(readable, :filename => filename.to_s)
208
+ end
209
+
210
+ # TODO - opening with a mode = 'w' should return a GridIO::IOProxy
211
+ # implementing a StringIO-like interface
212
+ #
213
+ def open(filename, mode = 'r', &block)
214
+ raise NotImplementedError
215
+ end
216
+ end
217
+ end
218
+
219
+ ##
220
+ #
221
+ def GridFS.build_file_model_for(namespace)
222
+ prefix = namespace.name.split(/::/).last.downcase
223
+ file_model_name = "#{ namespace.name }::File"
224
+ chunk_model_name = "#{ namespace.name }::Chunk"
225
+
226
+ Class.new do
227
+ include Mongoid::Document
228
+
229
+ singleton_class = class << self; self; end
230
+
231
+ singleton_class.instance_eval do
232
+ define_method(:name){ file_model_name }
233
+ attr_accessor :chunk_model
234
+ attr_accessor :namespace
235
+ end
236
+
237
+ self.default_collection_name = "#{ prefix }.files"
238
+
239
+ field(:filename, :type => String)
240
+ field(:contentType, :type => String, :default => 'application/octet-stream')
241
+
242
+ field(:length, :type => Integer, :default => 0)
243
+ field(:chunkSize, :type => Integer, :default => (256 * (2 ** 20)))
244
+ field(:uploadDate, :type => Date, :default => Time.now.utc)
245
+ field(:md5, :type => String, :default => Digest::MD5.hexdigest(''))
246
+
247
+ %w( filename contentType length chunkSize uploadDate md5 ).each do |f|
248
+ validates_presence_of(f)
249
+ end
250
+ validates_uniqueness_of(:filename)
251
+
252
+ has_many(:chunks, :class_name => chunk_model_name, :inverse_of => :files, :dependent => :destroy, :order => [:n, :asc])
253
+
254
+ index({:filename => 1}, :unique => true)
255
+
256
+ def path
257
+ filename
258
+ end
259
+
260
+ def basename
261
+ ::File.basename(filename)
262
+ end
263
+
264
+ def prefix
265
+ self.class.namespace.prefix
266
+ end
267
+
268
+ def each(&block)
269
+ chunks.all.order_by([:n, :asc]).each do |chunk|
270
+ block.call(chunk.to_s)
271
+ end
272
+ end
273
+
274
+ def data
275
+ data = ''
276
+ each{|chunk| data << chunk}
277
+ data
278
+ end
279
+
280
+ def base64
281
+ Array(to_s).pack('m')
282
+ end
283
+
284
+ def data_uri(options = {})
285
+ data = base64.chomp
286
+ "data:#{ content_type };base64,".concat(data)
287
+ end
288
+
289
+ def bytes(&block)
290
+ if block
291
+ each{|data| block.call(data)}
292
+ length
293
+ else
294
+ bytes = []
295
+ each{|data| bytes.push(*data)}
296
+ bytes
297
+ end
298
+ end
299
+
300
+ def close
301
+ self
302
+ end
303
+
304
+ def content_type
305
+ contentType
306
+ end
307
+
308
+ def update_date
309
+ updateDate
310
+ end
311
+
312
+ def created_at
313
+ updateDate
314
+ end
315
+
316
+ def namespace
317
+ self.class.namespace
318
+ end
319
+ end
320
+ end
321
+
322
+ ##
323
+ #
324
+ def GridFS.build_chunk_model_for(namespace)
325
+ prefix = namespace.name.split(/::/).last.downcase
326
+ file_model_name = "#{ namespace.name }::File"
327
+ chunk_model_name = "#{ namespace.name }::Chunk"
328
+
329
+ Class.new do
330
+ include Mongoid::Document
331
+
332
+ singleton_class = class << self; self; end
333
+
334
+ singleton_class.instance_eval do
335
+ define_method(:name){ chunk_model_name }
336
+ attr_accessor :file_model
337
+ attr_accessor :namespace
338
+ end
339
+
340
+ self.default_collection_name = "#{ prefix }.chunks"
341
+
342
+ field(:n, :type => Integer, :default => 0)
343
+ field(:data, :type => Moped::BSON::Binary)
344
+
345
+ belongs_to(:file, :foreign_key => :files_id, :class_name => file_model_name)
346
+
347
+ index({:files_id => 1, :n => -1}, :unique => true)
348
+
349
+ def namespace
350
+ self.class.namespace
351
+ end
352
+
353
+ def to_s
354
+ data.data
355
+ end
356
+
357
+ alias_method 'to_str', 'to_s'
358
+ end
359
+ end
360
+
361
+ ##
362
+ #
363
+ def GridFS.reading(arg, &block)
364
+ if arg.respond_to?(:read)
365
+ rewind(arg) do |io|
366
+ block.call(io)
367
+ end
368
+ else
369
+ open(arg.to_s) do |io|
370
+ block.call(io)
371
+ end
372
+ end
373
+ end
374
+
375
+ def GridFS.rewind(io, &block)
376
+ begin
377
+ pos = io.pos
378
+ io.flush
379
+ io.rewind
380
+ rescue
381
+ nil
382
+ end
383
+
384
+ begin
385
+ block.call(io)
386
+ ensure
387
+ begin
388
+ io.pos = pos
389
+ rescue
390
+ nil
391
+ end
392
+ end
393
+ end
394
+
395
+ def GridFS.extract_basename(object)
396
+ filename = nil
397
+ [:original_path, :original_filename, :path, :filename, :pathname].each do |msg|
398
+ if object.respond_to?(msg)
399
+ filename = object.send(msg)
400
+ break
401
+ end
402
+ end
403
+ filename ? cleanname(filename) : nil
404
+ end
405
+
406
+ def GridFS.extract_content_type(filename)
407
+ content_type = MIME::Types.type_for(::File.basename(filename.to_s)).first
408
+ content_type.to_s if content_type
409
+ end
410
+
411
+ def GridFS.cleanname(pathname)
412
+ basename = ::File.basename(pathname.to_s)
413
+ CGI.unescape(basename).gsub(%r/[^0-9a-zA-Z_@)(~.-]/, '_').gsub(%r/_+/,'_')
414
+ end
415
+ end
416
+
417
+ GridFs = GridFS
418
+
419
+ GridFS.init!
@@ -0,0 +1,36 @@
1
+ ## mongoid-grid_fs.gemspec
2
+ #
3
+
4
+ Gem::Specification::new do |spec|
5
+ spec.name = "mongoid-grid_fs"
6
+ spec.version = "1.0.0"
7
+ spec.platform = Gem::Platform::RUBY
8
+ spec.summary = "mongoid-grid_fs"
9
+ spec.description = "a mongoid 3/moped compatible implementation of the grid_fs specification"
10
+
11
+ spec.files =
12
+ ["README.md",
13
+ "Rakefile",
14
+ "lib",
15
+ "lib/mongoid-grid_fs.rb",
16
+ "mongoid-grid_fs.gemspec",
17
+ "test",
18
+ "test/helper.rb",
19
+ "test/mongoid-grid_fs_test.rb",
20
+ "test/testing.rb"]
21
+
22
+ spec.executables = []
23
+
24
+ spec.require_path = "lib"
25
+
26
+ spec.test_files = nil
27
+
28
+
29
+
30
+ spec.extensions.push(*[])
31
+
32
+ spec.rubyforge_project = "codeforpeople"
33
+ spec.author = "Ara T. Howard"
34
+ spec.email = "ara.t.howard@gmail.com"
35
+ spec.homepage = "https://github.com/ahoward/mongoid-grid_fs"
36
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,18 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require_relative 'testing'
3
+ require_relative '../lib/mongoid-grid_fs.rb'
4
+
5
+ Mongoid.configure do |config|
6
+ config.connect_to('mongoid-grid_fs_test')
7
+ end
8
+
9
+ require 'stringio'
10
+
11
+ class SIO < StringIO
12
+ attr_accessor :filename
13
+
14
+ def initialize(filename, *args, &block)
15
+ @filename = filename
16
+ super(*args, &block)
17
+ end
18
+ end
@@ -0,0 +1,147 @@
1
+ require_relative 'helper'
2
+
3
+ Testing GridFs do
4
+ ##
5
+ #
6
+ prepare do
7
+ GridFS::File.destroy_all
8
+ GridFS::Chunk.destroy_all
9
+ end
10
+
11
+ ##
12
+ #
13
+ context '#put' do
14
+
15
+ test 'default' do
16
+ filename = __FILE__
17
+ basename = File.basename(filename)
18
+
19
+ g = assert{ GridFS.put(filename) }
20
+
21
+ assert{ g.filename =~ %r| #{ object_id_re } / #{ basename } \Z|imox }
22
+ assert{ g.content_type == "application/x-ruby" }
23
+ assert{ g.data == IO.read(filename) }
24
+ end
25
+
26
+ test 'with a :filename' do
27
+ filename = 'path/info/a.rb'
28
+
29
+ g = assert{ GridFS.put(__FILE__, :filename => filename) }
30
+
31
+ assert{ g.filename == filename }
32
+ end
33
+
34
+ end
35
+
36
+
37
+ ##
38
+ #
39
+ context '#get' do
40
+
41
+ test 'default' do
42
+ id = assert{ GridFS::File.last.id }
43
+ g = assert{ GridFs.get(id) }
44
+ end
45
+
46
+ end
47
+
48
+ ##
49
+ #
50
+ context '#delete' do
51
+
52
+ test 'default' do
53
+ id = assert{ GridFS::File.last.id }
54
+ g = assert{ GridFs.get(id) }
55
+ assert{ GridFs.delete(id) }
56
+ assert_raises( Mongoid::Errors::DocumentNotFound){ GridFs.get(id) }
57
+ end
58
+
59
+ end
60
+
61
+ ##
62
+ #
63
+ context '[] and []=' do
64
+
65
+ test 'default' do
66
+ path = 'a.rb'
67
+ data = IO.read(__FILE__)
68
+
69
+ sio = SIO.new(path, data)
70
+
71
+ g = assert{ GridFs[path] = sio and GridFs[path] }
72
+
73
+ assert{ g.data == data }
74
+ assert{ g.content_type == "application/x-ruby" }
75
+
76
+ before = GridFs::File.count
77
+
78
+ assert{ GridFs[path] = SIO.new(path, 'foobar') }
79
+ assert{ GridFs[path].data == 'foobar' }
80
+
81
+ after = GridFs::File.count
82
+
83
+ created = after - before
84
+
85
+ assert{ created.zero? }
86
+ end
87
+
88
+ ##
89
+ #
90
+ context 'data uris' do
91
+
92
+ test 'default' do
93
+ id = assert{ GridFS::File.last.id }
94
+ g = assert{ GridFs.get(id) }
95
+
96
+ content_type = g.content_type
97
+ base64 = [g.to_s].pack('m').chomp
98
+
99
+ data_uri = "data:#{ content_type };base64,".concat(base64)
100
+
101
+ assert{ g.data_uri == data_uri }
102
+ end
103
+
104
+ end
105
+
106
+ ##
107
+ #
108
+ context 'namespaces' do
109
+ test 'default' do
110
+ assert{ GridFs.namespace.prefix == 'fs' }
111
+ assert{ GridFs.file_model.collection_name == 'fs.files' }
112
+ assert{ GridFs.chunk_model.collection_name == 'fs.chunks' }
113
+ end
114
+
115
+ test 'new' do
116
+ ns = GridFs.namespace_for(:ns)
117
+
118
+ assert{ ns.prefix == 'ns' }
119
+
120
+ assert{ ns.file_model < Mongoid::Document }
121
+ assert{ ns.file_model.collection_name == 'ns.files' }
122
+
123
+ assert{ ns.chunk_model < Mongoid::Document }
124
+ assert{ ns.chunk_model.collection_name == 'ns.chunks' }
125
+
126
+ assert{ ns.file_model.destroy_all }
127
+
128
+ count = GridFs::File.count
129
+
130
+ assert{ ns.file_model.count == 0}
131
+ assert{ ns.put __FILE__ }
132
+ assert{ ns.file_model.count == 1}
133
+
134
+ assert{ count == GridFs::File.count }
135
+ end
136
+ end
137
+
138
+ end
139
+
140
+ ##
141
+ #
142
+
143
+ protected
144
+ def object_id_re
145
+ %r| \w{#{ BSON::ObjectId.new.to_s.size }} |iomx
146
+ end
147
+ end
data/test/testing.rb ADDED
@@ -0,0 +1,196 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'test/unit'
3
+
4
+ testdir = File.expand_path(File.dirname(__FILE__))
5
+ rootdir = File.dirname(testdir)
6
+ libdir = File.join(rootdir, 'lib')
7
+
8
+ STDOUT.sync = true
9
+
10
+ $:.unshift(testdir) unless $:.include?(testdir)
11
+ $:.unshift(libdir) unless $:.include?(libdir)
12
+ $:.unshift(rootdir) unless $:.include?(rootdir)
13
+
14
+ class Testing
15
+ class Slug < ::String
16
+ def Slug.for(*args)
17
+ string = args.flatten.compact.join('-')
18
+ words = string.to_s.scan(%r/\w+/)
19
+ words.map!{|word| word.gsub %r/[^0-9a-zA-Z_-]/, ''}
20
+ words.delete_if{|word| word.nil? or word.strip.empty?}
21
+ new(words.join('-').downcase)
22
+ end
23
+ end
24
+
25
+ class Context
26
+ attr_accessor :name
27
+
28
+ def initialize(name, *args)
29
+ @name = name
30
+ end
31
+
32
+ def to_s
33
+ Slug.for(name)
34
+ end
35
+ end
36
+ end
37
+
38
+ def Testing(*args, &block)
39
+ Class.new(::Test::Unit::TestCase) do
40
+
41
+ ## class methods
42
+ #
43
+ class << self
44
+ def contexts
45
+ @contexts ||= []
46
+ end
47
+
48
+ def context(*args, &block)
49
+ return contexts.last if(args.empty? and block.nil?)
50
+
51
+ context = Testing::Context.new(*args)
52
+ contexts.push(context)
53
+
54
+ begin
55
+ block.call(context)
56
+ ensure
57
+ contexts.pop
58
+ end
59
+ end
60
+
61
+ def slug_for(*args)
62
+ string = [context, args].flatten.compact.join('-')
63
+ words = string.to_s.scan(%r/\w+/)
64
+ words.map!{|word| word.gsub %r/[^0-9a-zA-Z_-]/, ''}
65
+ words.delete_if{|word| word.nil? or word.strip.empty?}
66
+ words.join('-').downcase.sub(/_$/, '')
67
+ end
68
+
69
+ def name() const_get(:Name) end
70
+
71
+ def testno()
72
+ '%05d' % (@testno ||= 0)
73
+ ensure
74
+ @testno += 1
75
+ end
76
+
77
+ def testing(*args, &block)
78
+ method = ["test", testno, slug_for(*args)].delete_if{|part| part.empty?}.join('_')
79
+ define_method(method, &block)
80
+ end
81
+
82
+ def test(*args, &block)
83
+ testing(*args, &block)
84
+ end
85
+
86
+ def setup(&block)
87
+ define_method(:setup, &block) if block
88
+ end
89
+
90
+ def teardown(&block)
91
+ define_method(:teardown, &block) if block
92
+ end
93
+
94
+ def prepare(&block)
95
+ @prepare ||= []
96
+ @prepare.push(block) if block
97
+ @prepare
98
+ end
99
+
100
+ def cleanup(&block)
101
+ @cleanup ||= []
102
+ @cleanup.push(block) if block
103
+ @cleanup
104
+ end
105
+ end
106
+
107
+ ## configure the subclass!
108
+ #
109
+ const_set(:Testno, '0')
110
+ slug = slug_for(*args).gsub(%r/-/,'_')
111
+ name = ['TESTING', '%03d' % const_get(:Testno), slug].delete_if{|part| part.empty?}.join('_')
112
+ name = name.upcase!
113
+ const_set(:Name, name)
114
+ const_set(:Missing, Object.new.freeze)
115
+
116
+ ## instance methods
117
+ #
118
+ alias_method('__assert__', 'assert')
119
+
120
+ def assert(*args, &block)
121
+ if args.size == 1 and args.first.is_a?(Hash)
122
+ options = args.first
123
+ expected = getopt(:expected, options){ missing }
124
+ actual = getopt(:actual, options){ missing }
125
+ if expected == missing and actual == missing
126
+ actual, expected, *ignored = options.to_a.flatten
127
+ end
128
+ expected = expected.call() if expected.respond_to?(:call)
129
+ actual = actual.call() if actual.respond_to?(:call)
130
+ assert_equal(expected, actual)
131
+ end
132
+
133
+ if block
134
+ label = "assert(#{ args.join(' ') })"
135
+ result = nil
136
+ assert_nothing_raised{ result = block.call }
137
+ __assert__(result, label)
138
+ result
139
+ else
140
+ result = args.shift
141
+ label = "assert(#{ args.join(' ') })"
142
+ __assert__(result, label)
143
+ result
144
+ end
145
+ end
146
+
147
+ def missing
148
+ self.class.const_get(:Missing)
149
+ end
150
+
151
+ def getopt(opt, hash, options = nil, &block)
152
+ [opt.to_s, opt.to_s.to_sym].each do |key|
153
+ return hash[key] if hash.has_key?(key)
154
+ end
155
+ default =
156
+ if block
157
+ block.call
158
+ else
159
+ options.is_a?(Hash) ? options[:default] : nil
160
+ end
161
+ return default
162
+ end
163
+
164
+ def subclass_of exception
165
+ class << exception
166
+ def ==(other) super or self > other end
167
+ end
168
+ exception
169
+ end
170
+
171
+ ##
172
+ #
173
+ module_eval(&block)
174
+
175
+ self.setup()
176
+ self.prepare.each{|b| b.call()}
177
+
178
+ at_exit{
179
+ self.teardown()
180
+ self.cleanup.each{|b| b.call()}
181
+ }
182
+
183
+ self
184
+ end
185
+ end
186
+
187
+
188
+ if $0 == __FILE__
189
+
190
+ Testing 'Testing' do
191
+ testing('foo'){ assert true }
192
+ test{ assert true }
193
+ p instance_methods.grep(/test/)
194
+ end
195
+
196
+ end
metadata ADDED
@@ -0,0 +1,51 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mongoid-grid_fs
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ara T. Howard
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-07-23 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: a mongoid 3/moped compatible implementation of the grid_fs specification
15
+ email: ara.t.howard@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - README.md
21
+ - Rakefile
22
+ - lib/mongoid-grid_fs.rb
23
+ - mongoid-grid_fs.gemspec
24
+ - test/helper.rb
25
+ - test/mongoid-grid_fs_test.rb
26
+ - test/testing.rb
27
+ homepage: https://github.com/ahoward/mongoid-grid_fs
28
+ licenses: []
29
+ post_install_message:
30
+ rdoc_options: []
31
+ require_paths:
32
+ - lib
33
+ required_ruby_version: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ required_rubygems_version: !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ requirements: []
46
+ rubyforge_project: codeforpeople
47
+ rubygems_version: 1.8.24
48
+ signing_key:
49
+ specification_version: 3
50
+ summary: mongoid-grid_fs
51
+ test_files: []