mongoid-grid_fs 1.0.0

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