first commit
This commit is contained in:
commit
1325a3632c
BIN
.README.md.swp
Normal file
BIN
.README.md.swp
Normal file
Binary file not shown.
BIN
.Rakefile.swp
Normal file
BIN
.Rakefile.swp
Normal file
Binary file not shown.
52
README.md
Normal file
52
README.md
Normal file
@ -0,0 +1,52 @@
|
||||
NAME
|
||||
----
|
||||
mongoid_grid_fs
|
||||
|
||||
SYNOPSIS
|
||||
--------
|
||||
|
||||
````ruby
|
||||
|
||||
require 'mongoid-grid_fs'
|
||||
|
||||
g = GridFs.put anthing_that_respons_to_read
|
||||
|
||||
GridFS.get id
|
||||
|
||||
GridFS.delete id
|
||||
|
||||
|
||||
````
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
mongoid_grid_fs is pure mongoid 3 / moped implementation of the mongodb
|
||||
grid_fs specification
|
||||
|
||||
ref: http://www.mongodb.org/display/DOCS/GridFS+Specification
|
||||
|
||||
it has the following features:
|
||||
|
||||
- implementation is on top of mongoid for portability. moped (the drive)
|
||||
is barely used
|
||||
|
||||
- simple, REST-like api
|
||||
|
||||
- support for custom namespaces (fs.files vs. image.files)
|
||||
|
||||
- pathnames and io-like objects can be written to the grid
|
||||
|
||||
- auto-unique pathnames are generated (by default) to avoid collisions using #put
|
||||
|
||||
'path/info/a.rb' -> '$object_id/a.rb'
|
||||
|
||||
- #[] and #[]= methods which allow the grid to be used like a giant file
|
||||
hash in the sky
|
||||
|
||||
- supprt for data_uris
|
||||
|
||||
````eruby
|
||||
|
||||
<%= image_tag :src => file.data_url %>
|
||||
|
||||
````
|
446
Rakefile
Normal file
446
Rakefile
Normal file
@ -0,0 +1,446 @@
|
||||
This.name =
|
||||
"GridFs"
|
||||
|
||||
This.synopsis =
|
||||
"a mongoid 3/moped compatible implementation of the grid_fs specification"
|
||||
|
||||
This.rubyforge_project = 'codeforpeople'
|
||||
This.author = "Ara T. Howard"
|
||||
This.email = "ara.t.howard@gmail.com"
|
||||
This.homepage = "https://github.com/ahoward/#{ This.lib }"
|
||||
|
||||
This.setup!
|
||||
|
||||
|
||||
|
||||
task :default do
|
||||
puts((Rake::Task.tasks.map{|task| task.name.gsub(/::/,':')} - ['default']).sort)
|
||||
end
|
||||
|
||||
task :test do
|
||||
This.run_tests!
|
||||
end
|
||||
|
||||
namespace :test do
|
||||
task(:unit){ This.run_tests!(:unit) }
|
||||
task(:functional){ This.run_tests!(:functional) }
|
||||
task(:integration){ This.run_tests!(:integration) }
|
||||
end
|
||||
|
||||
def This.run_tests!(which = nil)
|
||||
which ||= '**'
|
||||
test_dir = File.join(This.dir, "test")
|
||||
test_glob ||= File.join(test_dir, "#{ which }/**_test.rb")
|
||||
test_rbs = Dir.glob(test_glob).sort
|
||||
|
||||
div = ('=' * 119)
|
||||
line = ('-' * 119)
|
||||
|
||||
test_rbs.each_with_index do |test_rb, index|
|
||||
testno = index + 1
|
||||
command = "#{ File.basename(This.ruby) } -I ./lib -I ./test/lib #{ test_rb }"
|
||||
|
||||
puts
|
||||
This.say(div, :color => :cyan, :bold => true)
|
||||
This.say("@#{ testno } => ", :bold => true, :method => :print)
|
||||
This.say(command, :color => :cyan, :bold => true)
|
||||
This.say(line, :color => :cyan, :bold => true)
|
||||
|
||||
system(command)
|
||||
|
||||
This.say(line, :color => :cyan, :bold => true)
|
||||
|
||||
status = $?.exitstatus
|
||||
|
||||
if status.zero?
|
||||
This.say("@#{ testno } <= ", :bold => true, :color => :white, :method => :print)
|
||||
This.say("SUCCESS", :color => :green, :bold => true)
|
||||
else
|
||||
This.say("@#{ testno } <= ", :bold => true, :color => :white, :method => :print)
|
||||
This.say("FAILURE", :color => :red, :bold => true)
|
||||
end
|
||||
This.say(line, :color => :cyan, :bold => true)
|
||||
|
||||
exit(status) unless status.zero?
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
task :gemspec do
|
||||
ignore_extensions = ['git', 'svn', 'tmp', /sw./, 'bak', 'gem']
|
||||
ignore_directories = ['pkg', 'db']
|
||||
ignore_files = ['test/log', 'test/db.yml', 'a.rb', 'b.rb'] + Dir['db/*'] + %w'db'
|
||||
|
||||
shiteless =
|
||||
lambda do |list|
|
||||
list.delete_if do |entry|
|
||||
next unless test(?e, entry)
|
||||
extension = File.basename(entry).split(%r/[.]/).last
|
||||
ignore_extensions.any?{|ext| ext === extension}
|
||||
end
|
||||
list.delete_if do |entry|
|
||||
next unless test(?d, entry)
|
||||
dirname = File.expand_path(entry)
|
||||
ignore_directories.any?{|dir| File.expand_path(dir) == dirname}
|
||||
end
|
||||
list.delete_if do |entry|
|
||||
next unless test(?f, entry)
|
||||
filename = File.expand_path(entry)
|
||||
ignore_files.any?{|file| File.expand_path(file) == filename}
|
||||
end
|
||||
end
|
||||
|
||||
lib = This.lib
|
||||
object = This.object
|
||||
version = This.version
|
||||
files = shiteless[Dir::glob("**/**")]
|
||||
executables = shiteless[Dir::glob("bin/*")].map{|exe| File.basename(exe)}
|
||||
#has_rdoc = true #File.exist?('doc')
|
||||
test_files = test(?e, "test/#{ lib }.rb") ? "test/#{ lib }.rb" : nil
|
||||
summary = This.summary || This.synopsis || "#{ lib } kicks the ass"
|
||||
description = This.description || summary
|
||||
|
||||
if This.extensions.nil?
|
||||
This.extensions = []
|
||||
extensions = This.extensions
|
||||
%w( Makefile configure extconf.rb ).each do |ext|
|
||||
extensions << ext if File.exists?(ext)
|
||||
end
|
||||
end
|
||||
extensions = [extensions].flatten.compact
|
||||
|
||||
# TODO
|
||||
if This.dependencies.nil?
|
||||
dependencies = []
|
||||
else
|
||||
case This.dependencies
|
||||
when Hash
|
||||
dependencies = This.dependencies.values
|
||||
when Array
|
||||
dependencies = This.dependencies
|
||||
end
|
||||
end
|
||||
|
||||
template =
|
||||
if test(?e, 'gemspec.erb')
|
||||
This.template_for{ IO.read('gemspec.erb') }
|
||||
else
|
||||
This.template_for {
|
||||
<<-__
|
||||
## <%= lib %>.gemspec
|
||||
#
|
||||
|
||||
Gem::Specification::new do |spec|
|
||||
spec.name = <%= lib.inspect %>
|
||||
spec.version = <%= version.inspect %>
|
||||
spec.platform = Gem::Platform::RUBY
|
||||
spec.summary = <%= lib.inspect %>
|
||||
spec.description = <%= description.inspect %>
|
||||
|
||||
spec.files =\n<%= files.sort.pretty_inspect %>
|
||||
spec.executables = <%= executables.inspect %>
|
||||
|
||||
spec.require_path = "lib"
|
||||
|
||||
spec.test_files = <%= test_files.inspect %>
|
||||
|
||||
<% dependencies.each do |lib_version| %>
|
||||
spec.add_dependency(*<%= Array(lib_version).flatten.inspect %>)
|
||||
<% end %>
|
||||
|
||||
spec.extensions.push(*<%= extensions.inspect %>)
|
||||
|
||||
spec.rubyforge_project = <%= This.rubyforge_project.inspect %>
|
||||
spec.author = <%= This.author.inspect %>
|
||||
spec.email = <%= This.email.inspect %>
|
||||
spec.homepage = <%= This.homepage.inspect %>
|
||||
end
|
||||
__
|
||||
}
|
||||
end
|
||||
|
||||
FileUtils.mkdir_p(This.pkgdir)
|
||||
gemspec = "#{ lib }.gemspec"
|
||||
open(gemspec, "w"){|fd| fd.puts(template)}
|
||||
This.gemspec = gemspec
|
||||
end
|
||||
|
||||
task :gem => [:clean, :gemspec] do
|
||||
FileUtils.mkdir_p(This.pkgdir)
|
||||
before = Dir['*.gem']
|
||||
cmd = "gem build #{ This.gemspec }"
|
||||
`#{ cmd }`
|
||||
after = Dir['*.gem']
|
||||
gem = ((after - before).first || after.first) or abort('no gem!')
|
||||
FileUtils.mv(gem, This.pkgdir)
|
||||
This.gem = File.join(This.pkgdir, File.basename(gem))
|
||||
end
|
||||
|
||||
task :readme do
|
||||
samples = ''
|
||||
prompt = '~ > '
|
||||
lib = This.lib
|
||||
version = This.version
|
||||
|
||||
Dir['sample*/*'].sort.each do |sample|
|
||||
samples << "\n" << " <========< #{ sample } >========>" << "\n\n"
|
||||
|
||||
cmd = "cat #{ sample }"
|
||||
samples << This.util.indent(prompt + cmd, 2) << "\n\n"
|
||||
samples << This.util.indent(`#{ cmd }`, 4) << "\n"
|
||||
|
||||
cmd = "ruby #{ sample }"
|
||||
samples << This.util.indent(prompt + cmd, 2) << "\n\n"
|
||||
|
||||
cmd = "ruby -e'STDOUT.sync=true; exec %(ruby -I ./lib #{ sample })'"
|
||||
samples << This.util.indent(`#{ cmd } 2>&1`, 4) << "\n"
|
||||
end
|
||||
|
||||
template =
|
||||
if test(?e, 'readme.erb')
|
||||
This.template_for{ IO.read('readme.erb') }
|
||||
else
|
||||
This.template_for {
|
||||
<<-__
|
||||
NAME
|
||||
#{ lib }
|
||||
|
||||
DESCRIPTION
|
||||
|
||||
INSTALL
|
||||
gem install #{ lib }
|
||||
|
||||
SAMPLES
|
||||
#{ samples }
|
||||
__
|
||||
}
|
||||
end
|
||||
|
||||
open("README", "w"){|fd| fd.puts template}
|
||||
end
|
||||
|
||||
|
||||
task :clean do
|
||||
Dir[File.join(This.pkgdir, '**/**')].each{|entry| FileUtils.rm_rf(entry)}
|
||||
end
|
||||
|
||||
|
||||
task :release => [:clean, :gemspec, :gem] do
|
||||
gems = Dir[File.join(This.pkgdir, '*.gem')].flatten
|
||||
raise "which one? : #{ gems.inspect }" if gems.size > 1
|
||||
raise "no gems?" if gems.size < 1
|
||||
|
||||
cmd = "gem push #{ This.gem }"
|
||||
puts cmd
|
||||
puts
|
||||
system(cmd)
|
||||
abort("cmd(#{ cmd }) failed with (#{ $?.inspect })") unless $?.exitstatus.zero?
|
||||
|
||||
#cmd = "rubyforge login && rubyforge add_release #{ This.rubyforge_project } #{ This.lib } #{ This.version } #{ This.gem }"
|
||||
#puts cmd
|
||||
#puts
|
||||
#system(cmd)
|
||||
#abort("cmd(#{ cmd }) failed with (#{ $?.inspect })") unless $?.exitstatus.zero?
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
BEGIN {
|
||||
# support for this rakefile
|
||||
#
|
||||
$VERBOSE = nil
|
||||
|
||||
require 'erb'
|
||||
require 'fileutils'
|
||||
require 'rbconfig'
|
||||
require 'pp'
|
||||
|
||||
# cache a bunch of stuff about this rakefile/environment
|
||||
#
|
||||
|
||||
This =
|
||||
Class.new(Hash) do
|
||||
|
||||
def method_missing(method, *args, &block)
|
||||
if method.to_s =~ /=/
|
||||
key = method.to_s.chomp('=')
|
||||
value = block ? block : args.shift
|
||||
self[key] = value
|
||||
else
|
||||
key = method.to_s
|
||||
if block
|
||||
value = block
|
||||
self[key] = value
|
||||
else
|
||||
value = self[key]
|
||||
|
||||
if value.respond_to?(:call)
|
||||
self[key] = value.call()
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def inspect
|
||||
expand!
|
||||
PP.pp(self, '')
|
||||
end
|
||||
|
||||
def expand!
|
||||
keys.each do |key|
|
||||
value = self[key]
|
||||
if value.respond_to?(:call)
|
||||
self[key] = value.call()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end.new()
|
||||
|
||||
This.file = File.expand_path(__FILE__)
|
||||
This.dir = File.dirname(This.file)
|
||||
This.pkgdir = File.join(This.dir, 'pkg')
|
||||
|
||||
# defaults
|
||||
#
|
||||
This.lib do
|
||||
File.basename(Dir.pwd)
|
||||
end
|
||||
|
||||
def This.setup!
|
||||
begin
|
||||
require "./lib/#{ This.lib }"
|
||||
rescue LoadError
|
||||
abort("could not load #{ This.lib }")
|
||||
end
|
||||
end
|
||||
|
||||
This.name do
|
||||
This.name = This.lib.capitalize
|
||||
end
|
||||
|
||||
This.object do
|
||||
begin
|
||||
This.object = eval(This.name)
|
||||
rescue Object
|
||||
abort("could not determine object from #{ This.name }")
|
||||
end
|
||||
end
|
||||
|
||||
This.version do
|
||||
This.object.send(:version)
|
||||
end
|
||||
|
||||
This.dependencies do
|
||||
if This.object.respond_to?(:dependencies)
|
||||
This.object.dependencies
|
||||
end
|
||||
end
|
||||
|
||||
This.ruby do
|
||||
c = Config::CONFIG
|
||||
bindir = c["bindir"] || c['BINDIR']
|
||||
ruby_install_name = c['ruby_install_name'] || c['RUBY_INSTALL_NAME'] || 'ruby'
|
||||
ruby_ext = c['EXEEXT'] || ''
|
||||
File.join(bindir, (ruby_install_name + ruby_ext))
|
||||
end
|
||||
|
||||
# some utils
|
||||
#
|
||||
This.util = Module.new do
|
||||
def indent(s, n = 2)
|
||||
s = unindent(s)
|
||||
ws = ' ' * n
|
||||
s.gsub(%r/^/, ws)
|
||||
end
|
||||
|
||||
def unindent(s)
|
||||
indent = nil
|
||||
s.each_line do |line|
|
||||
next if line =~ %r/^\s*$/
|
||||
indent = line[%r/^\s*/] and break
|
||||
end
|
||||
indent ? s.gsub(%r/^#{ indent }/, "") : s
|
||||
end
|
||||
|
||||
extend self
|
||||
end
|
||||
|
||||
# template support
|
||||
#
|
||||
This.template = Class.new do
|
||||
def initialize(&block)
|
||||
@block = block
|
||||
@template = block.call.to_s
|
||||
end
|
||||
|
||||
def expand(b=nil)
|
||||
ERB.new(This.util.unindent(@template)).result((b||@block).binding)
|
||||
end
|
||||
|
||||
alias_method 'to_s', 'expand'
|
||||
end
|
||||
|
||||
def This.template_for(*args, &block)
|
||||
This.template.new(*args, &block)
|
||||
end
|
||||
|
||||
# colored console output support
|
||||
#
|
||||
This.ansi = {
|
||||
:clear => "\e[0m",
|
||||
:reset => "\e[0m",
|
||||
:erase_line => "\e[K",
|
||||
:erase_char => "\e[P",
|
||||
:bold => "\e[1m",
|
||||
:dark => "\e[2m",
|
||||
:underline => "\e[4m",
|
||||
:underscore => "\e[4m",
|
||||
:blink => "\e[5m",
|
||||
:reverse => "\e[7m",
|
||||
:concealed => "\e[8m",
|
||||
:black => "\e[30m",
|
||||
:red => "\e[31m",
|
||||
:green => "\e[32m",
|
||||
:yellow => "\e[33m",
|
||||
:blue => "\e[34m",
|
||||
:magenta => "\e[35m",
|
||||
:cyan => "\e[36m",
|
||||
:white => "\e[37m",
|
||||
:on_black => "\e[40m",
|
||||
:on_red => "\e[41m",
|
||||
:on_green => "\e[42m",
|
||||
:on_yellow => "\e[43m",
|
||||
:on_blue => "\e[44m",
|
||||
:on_magenta => "\e[45m",
|
||||
:on_cyan => "\e[46m",
|
||||
:on_white => "\e[47m"
|
||||
}
|
||||
|
||||
def This.say(something, *args)
|
||||
options = args.last.is_a?(Hash) ? args.pop : {}
|
||||
options[:color] = args.shift.to_s.to_sym unless args.empty?
|
||||
keys = options.keys
|
||||
keys.each{|key| options[key.to_s.to_sym] = options.delete(key)}
|
||||
|
||||
color = options[:color]
|
||||
bold = options.has_key?(:bold)
|
||||
|
||||
parts = [something]
|
||||
parts.unshift(This.ansi[color]) if color
|
||||
parts.unshift(This.ansi[:bold]) if bold
|
||||
parts.push(This.ansi[:clear]) if parts.size > 1
|
||||
|
||||
method = options[:method] || :puts
|
||||
|
||||
Kernel.send(method, parts.join)
|
||||
end
|
||||
|
||||
# always run out of the project dir
|
||||
#
|
||||
Dir.chdir(This.dir)
|
||||
}
|
BIN
lib/.mongoid-grid_fs.rb.swp
Normal file
BIN
lib/.mongoid-grid_fs.rb.swp
Normal file
Binary file not shown.
419
lib/mongoid-grid_fs.rb
Normal file
419
lib/mongoid-grid_fs.rb
Normal file
@ -0,0 +1,419 @@
|
||||
require "mongoid"
|
||||
require "mime/types"
|
||||
require "digest/md5"
|
||||
require "cgi"
|
||||
|
||||
class GridFS
|
||||
##
|
||||
#
|
||||
class << GridFS
|
||||
def version
|
||||
"1.0.0"
|
||||
end
|
||||
|
||||
attr_accessor :namespace
|
||||
attr_accessor :file_model
|
||||
attr_accessor :chunk_model
|
||||
|
||||
def init!
|
||||
GridFS.build_namespace_for(:Fs)
|
||||
|
||||
GridFS.namespace = Fs
|
||||
GridFS.file_model = Fs.file_model
|
||||
GridFS.chunk_model = Fs.chunk_model
|
||||
|
||||
const_set(:File, Fs.file_model)
|
||||
const_set(:Chunk, Fs.chunk_model)
|
||||
|
||||
to_delegate = %w(
|
||||
put
|
||||
get
|
||||
delete
|
||||
find
|
||||
[]
|
||||
[]=
|
||||
)
|
||||
|
||||
to_delegate.each do |method|
|
||||
class_eval <<-__
|
||||
def GridFS.#{ method }(*args, &block)
|
||||
::GridFS::Fs::#{ method }(*args, &block)
|
||||
end
|
||||
__
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
#
|
||||
def GridFS.namespace_for(prefix)
|
||||
prefix = prefix.to_s.downcase
|
||||
const = "::GridFS::#{ prefix.to_s.camelize }"
|
||||
namespace = const.split(/::/).last
|
||||
const_defined?(namespace) ? const_get(namespace) : build_namespace_for(namespace)
|
||||
end
|
||||
|
||||
##
|
||||
#
|
||||
def GridFS.build_namespace_for(prefix)
|
||||
prefix = prefix.to_s.downcase
|
||||
const = prefix.camelize
|
||||
|
||||
namespace =
|
||||
Module.new do
|
||||
module_eval(&NamespaceMixin)
|
||||
self
|
||||
end
|
||||
|
||||
const_set(const, namespace)
|
||||
|
||||
file_model = build_file_model_for(namespace)
|
||||
chunk_model = build_chunk_model_for(namespace)
|
||||
|
||||
file_model.namespace = namespace
|
||||
chunk_model.namespace = namespace
|
||||
|
||||
file_model.chunk_model = chunk_model
|
||||
chunk_model.file_model = file_model
|
||||
|
||||
namespace.prefix = prefix
|
||||
namespace.file_model = file_model
|
||||
namespace.chunk_model = chunk_model
|
||||
|
||||
namespace.send(:const_set, :File, file_model)
|
||||
namespace.send(:const_set, :Chunk, chunk_model)
|
||||
|
||||
#at_exit{ file_model.create_indexes rescue nil }
|
||||
#at_exit{ chunk_model.create_indexes rescue nil }
|
||||
|
||||
const_get(const)
|
||||
end
|
||||
|
||||
NamespaceMixin = proc do
|
||||
class << self
|
||||
attr_accessor :prefix
|
||||
attr_accessor :file_model
|
||||
attr_accessor :chunk_model
|
||||
|
||||
def to_s
|
||||
prefix
|
||||
end
|
||||
|
||||
def namespace
|
||||
prefix
|
||||
end
|
||||
|
||||
def put(readable, attributes = {})
|
||||
chunks = []
|
||||
file = file_model.new
|
||||
attributes.to_options!
|
||||
|
||||
if attributes.has_key?(:id)
|
||||
file.id = attributes.delete(:id)
|
||||
end
|
||||
|
||||
if attributes.has_key?(:_id)
|
||||
file.id = attributes.delete(:_id)
|
||||
end
|
||||
|
||||
if attributes.has_key?(:content_type)
|
||||
attributes[:contentType] = attributes.delete(:content_type)
|
||||
end
|
||||
|
||||
if attributes.has_key?(:upload_date)
|
||||
attributes[:uploadDate] = attributes.delete(:upload_date)
|
||||
end
|
||||
|
||||
md5 = Digest::MD5.new
|
||||
length = 0
|
||||
chunkSize = file.chunkSize
|
||||
n = 0
|
||||
|
||||
GridFS.reading(readable) do |io|
|
||||
|
||||
filename =
|
||||
attributes[:filename] ||=
|
||||
[file.id.to_s, GridFS.extract_basename(io)].join('/').squeeze('/')
|
||||
|
||||
content_type =
|
||||
attributes[:contentType] ||=
|
||||
GridFS.extract_content_type(filename) || file.contentType
|
||||
|
||||
while((buf = io.read(chunkSize)))
|
||||
md5 << buf
|
||||
length += buf.size
|
||||
chunk = file.chunks.build
|
||||
chunk.data = binary_for(buf)
|
||||
chunk.n = n
|
||||
n += 1
|
||||
chunk.save!
|
||||
chunks.push(chunk)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
attributes[:length] ||= length
|
||||
attributes[:uploadDate] ||= Time.now.utc
|
||||
attributes[:md5] ||= md5.hexdigest
|
||||
|
||||
file.update_attributes(attributes)
|
||||
|
||||
file.save!
|
||||
file
|
||||
ensure
|
||||
chunks.each{|chunk| chunk.destroy rescue nil} if $!
|
||||
end
|
||||
|
||||
if defined?(Moped)
|
||||
def binary_for(*buf)
|
||||
Moped::BSON::Binary.new(:generic, buf.join)
|
||||
end
|
||||
else
|
||||
def binary_for(buf)
|
||||
BSON::Binary.new(buf.bytes.to_a)
|
||||
end
|
||||
end
|
||||
|
||||
def get(id)
|
||||
file_model.find(id)
|
||||
end
|
||||
|
||||
def delete(id)
|
||||
file_model.find(id).destroy
|
||||
rescue
|
||||
nil
|
||||
end
|
||||
|
||||
def where(conditions = {})
|
||||
case conditions
|
||||
when String
|
||||
file_model.where(:filename => conditions)
|
||||
else
|
||||
file_model.where(conditions)
|
||||
end
|
||||
end
|
||||
|
||||
def find(*args)
|
||||
where(*args).first
|
||||
end
|
||||
|
||||
def [](filename)
|
||||
file_model.where(:filename => filename.to_s).first
|
||||
end
|
||||
|
||||
def []=(filename, readable)
|
||||
file = self[filename]
|
||||
file.destroy if file
|
||||
put(readable, :filename => filename.to_s)
|
||||
end
|
||||
|
||||
# TODO - opening with a mode = 'w' should return a GridIO::IOProxy
|
||||
# implementing a StringIO-like interface
|
||||
#
|
||||
def open(filename, mode = 'r', &block)
|
||||
raise NotImplementedError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
#
|
||||
def GridFS.build_file_model_for(namespace)
|
||||
prefix = namespace.name.split(/::/).last.downcase
|
||||
file_model_name = "#{ namespace.name }::File"
|
||||
chunk_model_name = "#{ namespace.name }::Chunk"
|
||||
|
||||
Class.new do
|
||||
include Mongoid::Document
|
||||
|
||||
singleton_class = class << self; self; end
|
||||
|
||||
singleton_class.instance_eval do
|
||||
define_method(:name){ file_model_name }
|
||||
attr_accessor :chunk_model
|
||||
attr_accessor :namespace
|
||||
end
|
||||
|
||||
self.default_collection_name = "#{ prefix }.files"
|
||||
|
||||
field(:filename, :type => String)
|
||||
field(:contentType, :type => String, :default => 'application/octet-stream')
|
||||
|
||||
field(:length, :type => Integer, :default => 0)
|
||||
field(:chunkSize, :type => Integer, :default => (256 * (2 ** 20)))
|
||||
field(:uploadDate, :type => Date, :default => Time.now.utc)
|
||||
field(:md5, :type => String, :default => Digest::MD5.hexdigest(''))
|
||||
|
||||
%w( filename contentType length chunkSize uploadDate md5 ).each do |f|
|
||||
validates_presence_of(f)
|
||||
end
|
||||
validates_uniqueness_of(:filename)
|
||||
|
||||
has_many(:chunks, :class_name => chunk_model_name, :inverse_of => :files, :dependent => :destroy, :order => [:n, :asc])
|
||||
|
||||
index({:filename => 1}, :unique => true)
|
||||
|
||||
def path
|
||||
filename
|
||||
end
|
||||
|
||||
def basename
|
||||
::File.basename(filename)
|
||||
end
|
||||
|
||||
def prefix
|
||||
self.class.namespace.prefix
|
||||
end
|
||||
|
||||
def each(&block)
|
||||
chunks.all.order_by([:n, :asc]).each do |chunk|
|
||||
block.call(chunk.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
def data
|
||||
data = ''
|
||||
each{|chunk| data << chunk}
|
||||
data
|
||||
end
|
||||
|
||||
def base64
|
||||
Array(to_s).pack('m')
|
||||
end
|
||||
|
||||
def data_uri(options = {})
|
||||
data = base64.chomp
|
||||
"data:#{ content_type };base64,".concat(data)
|
||||
end
|
||||
|
||||
def bytes(&block)
|
||||
if block
|
||||
each{|data| block.call(data)}
|
||||
length
|
||||
else
|
||||
bytes = []
|
||||
each{|data| bytes.push(*data)}
|
||||
bytes
|
||||
end
|
||||
end
|
||||
|
||||
def close
|
||||
self
|
||||
end
|
||||
|
||||
def content_type
|
||||
contentType
|
||||
end
|
||||
|
||||
def update_date
|
||||
updateDate
|
||||
end
|
||||
|
||||
def created_at
|
||||
updateDate
|
||||
end
|
||||
|
||||
def namespace
|
||||
self.class.namespace
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
#
|
||||
def GridFS.build_chunk_model_for(namespace)
|
||||
prefix = namespace.name.split(/::/).last.downcase
|
||||
file_model_name = "#{ namespace.name }::File"
|
||||
chunk_model_name = "#{ namespace.name }::Chunk"
|
||||
|
||||
Class.new do
|
||||
include Mongoid::Document
|
||||
|
||||
singleton_class = class << self; self; end
|
||||
|
||||
singleton_class.instance_eval do
|
||||
define_method(:name){ chunk_model_name }
|
||||
attr_accessor :file_model
|
||||
attr_accessor :namespace
|
||||
end
|
||||
|
||||
self.default_collection_name = "#{ prefix }.chunks"
|
||||
|
||||
field(:n, :type => Integer, :default => 0)
|
||||
field(:data, :type => Moped::BSON::Binary)
|
||||
|
||||
belongs_to(:file, :foreign_key => :files_id, :class_name => file_model_name)
|
||||
|
||||
index({:files_id => 1, :n => -1}, :unique => true)
|
||||
|
||||
def namespace
|
||||
self.class.namespace
|
||||
end
|
||||
|
||||
def to_s
|
||||
data.data
|
||||
end
|
||||
|
||||
alias_method 'to_str', 'to_s'
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
#
|
||||
def GridFS.reading(arg, &block)
|
||||
if arg.respond_to?(:read)
|
||||
rewind(arg) do |io|
|
||||
block.call(io)
|
||||
end
|
||||
else
|
||||
open(arg.to_s) do |io|
|
||||
block.call(io)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def GridFS.rewind(io, &block)
|
||||
begin
|
||||
pos = io.pos
|
||||
io.flush
|
||||
io.rewind
|
||||
rescue
|
||||
nil
|
||||
end
|
||||
|
||||
begin
|
||||
block.call(io)
|
||||
ensure
|
||||
begin
|
||||
io.pos = pos
|
||||
rescue
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def GridFS.extract_basename(object)
|
||||
filename = nil
|
||||
[:original_path, :original_filename, :path, :filename, :pathname].each do |msg|
|
||||
if object.respond_to?(msg)
|
||||
filename = object.send(msg)
|
||||
break
|
||||
end
|
||||
end
|
||||
filename ? cleanname(filename) : nil
|
||||
end
|
||||
|
||||
def GridFS.extract_content_type(filename)
|
||||
content_type = MIME::Types.type_for(::File.basename(filename.to_s)).first
|
||||
content_type.to_s if content_type
|
||||
end
|
||||
|
||||
def GridFS.cleanname(pathname)
|
||||
basename = ::File.basename(pathname.to_s)
|
||||
CGI.unescape(basename).gsub(%r/[^0-9a-zA-Z_@)(~.-]/, '_').gsub(%r/_+/,'_')
|
||||
end
|
||||
end
|
||||
|
||||
GridFs = GridFS
|
||||
|
||||
GridFS.init!
|
36
mongoid-grid_fs.gemspec
Normal file
36
mongoid-grid_fs.gemspec
Normal file
@ -0,0 +1,36 @@
|
||||
## mongoid-grid_fs.gemspec
|
||||
#
|
||||
|
||||
Gem::Specification::new do |spec|
|
||||
spec.name = "mongoid-grid_fs"
|
||||
spec.version = "1.0.0"
|
||||
spec.platform = Gem::Platform::RUBY
|
||||
spec.summary = "mongoid-grid_fs"
|
||||
spec.description = "a mongoid 3/moped compatible implementation of the grid_fs specification"
|
||||
|
||||
spec.files =
|
||||
["README.md",
|
||||
"Rakefile",
|
||||
"lib",
|
||||
"lib/mongoid-grid_fs.rb",
|
||||
"mongoid-grid_fs.gemspec",
|
||||
"test",
|
||||
"test/helper.rb",
|
||||
"test/mongoid-grid_fs_test.rb",
|
||||
"test/testing.rb"]
|
||||
|
||||
spec.executables = []
|
||||
|
||||
spec.require_path = "lib"
|
||||
|
||||
spec.test_files = nil
|
||||
|
||||
|
||||
|
||||
spec.extensions.push(*[])
|
||||
|
||||
spec.rubyforge_project = "codeforpeople"
|
||||
spec.author = "Ara T. Howard"
|
||||
spec.email = "ara.t.howard@gmail.com"
|
||||
spec.homepage = "https://github.com/ahoward/mongoid-grid_fs"
|
||||
end
|
BIN
pkg/mongoid-grid_fs-1.0.0.gem
Normal file
BIN
pkg/mongoid-grid_fs-1.0.0.gem
Normal file
Binary file not shown.
BIN
test/.helper.rb.swp
Normal file
BIN
test/.helper.rb.swp
Normal file
Binary file not shown.
BIN
test/.mongoid-grid_fs_test.rb.swp
Normal file
BIN
test/.mongoid-grid_fs_test.rb.swp
Normal file
Binary file not shown.
BIN
test/.testing.rb.swp
Normal file
BIN
test/.testing.rb.swp
Normal file
Binary file not shown.
18
test/helper.rb
Normal file
18
test/helper.rb
Normal file
@ -0,0 +1,18 @@
|
||||
# -*- encoding : utf-8 -*-
|
||||
require_relative 'testing'
|
||||
require_relative '../lib/mongoid-grid_fs.rb'
|
||||
|
||||
Mongoid.configure do |config|
|
||||
config.connect_to('mongoid-grid_fs_test')
|
||||
end
|
||||
|
||||
require 'stringio'
|
||||
|
||||
class SIO < StringIO
|
||||
attr_accessor :filename
|
||||
|
||||
def initialize(filename, *args, &block)
|
||||
@filename = filename
|
||||
super(*args, &block)
|
||||
end
|
||||
end
|
147
test/mongoid-grid_fs_test.rb
Normal file
147
test/mongoid-grid_fs_test.rb
Normal file
@ -0,0 +1,147 @@
|
||||
require_relative 'helper'
|
||||
|
||||
Testing GridFs do
|
||||
##
|
||||
#
|
||||
prepare do
|
||||
GridFS::File.destroy_all
|
||||
GridFS::Chunk.destroy_all
|
||||
end
|
||||
|
||||
##
|
||||
#
|
||||
context '#put' do
|
||||
|
||||
test 'default' do
|
||||
filename = __FILE__
|
||||
basename = File.basename(filename)
|
||||
|
||||
g = assert{ GridFS.put(filename) }
|
||||
|
||||
assert{ g.filename =~ %r| #{ object_id_re } / #{ basename } \Z|imox }
|
||||
assert{ g.content_type == "application/x-ruby" }
|
||||
assert{ g.data == IO.read(filename) }
|
||||
end
|
||||
|
||||
test 'with a :filename' do
|
||||
filename = 'path/info/a.rb'
|
||||
|
||||
g = assert{ GridFS.put(__FILE__, :filename => filename) }
|
||||
|
||||
assert{ g.filename == filename }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
##
|
||||
#
|
||||
context '#get' do
|
||||
|
||||
test 'default' do
|
||||
id = assert{ GridFS::File.last.id }
|
||||
g = assert{ GridFs.get(id) }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
##
|
||||
#
|
||||
context '#delete' do
|
||||
|
||||
test 'default' do
|
||||
id = assert{ GridFS::File.last.id }
|
||||
g = assert{ GridFs.get(id) }
|
||||
assert{ GridFs.delete(id) }
|
||||
assert_raises( Mongoid::Errors::DocumentNotFound){ GridFs.get(id) }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
##
|
||||
#
|
||||
context '[] and []=' do
|
||||
|
||||
test 'default' do
|
||||
path = 'a.rb'
|
||||
data = IO.read(__FILE__)
|
||||
|
||||
sio = SIO.new(path, data)
|
||||
|
||||
g = assert{ GridFs[path] = sio and GridFs[path] }
|
||||
|
||||
assert{ g.data == data }
|
||||
assert{ g.content_type == "application/x-ruby" }
|
||||
|
||||
before = GridFs::File.count
|
||||
|
||||
assert{ GridFs[path] = SIO.new(path, 'foobar') }
|
||||
assert{ GridFs[path].data == 'foobar' }
|
||||
|
||||
after = GridFs::File.count
|
||||
|
||||
created = after - before
|
||||
|
||||
assert{ created.zero? }
|
||||
end
|
||||
|
||||
##
|
||||
#
|
||||
context 'data uris' do
|
||||
|
||||
test 'default' do
|
||||
id = assert{ GridFS::File.last.id }
|
||||
g = assert{ GridFs.get(id) }
|
||||
|
||||
content_type = g.content_type
|
||||
base64 = [g.to_s].pack('m').chomp
|
||||
|
||||
data_uri = "data:#{ content_type };base64,".concat(base64)
|
||||
|
||||
assert{ g.data_uri == data_uri }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
##
|
||||
#
|
||||
context 'namespaces' do
|
||||
test 'default' do
|
||||
assert{ GridFs.namespace.prefix == 'fs' }
|
||||
assert{ GridFs.file_model.collection_name == 'fs.files' }
|
||||
assert{ GridFs.chunk_model.collection_name == 'fs.chunks' }
|
||||
end
|
||||
|
||||
test 'new' do
|
||||
ns = GridFs.namespace_for(:ns)
|
||||
|
||||
assert{ ns.prefix == 'ns' }
|
||||
|
||||
assert{ ns.file_model < Mongoid::Document }
|
||||
assert{ ns.file_model.collection_name == 'ns.files' }
|
||||
|
||||
assert{ ns.chunk_model < Mongoid::Document }
|
||||
assert{ ns.chunk_model.collection_name == 'ns.chunks' }
|
||||
|
||||
assert{ ns.file_model.destroy_all }
|
||||
|
||||
count = GridFs::File.count
|
||||
|
||||
assert{ ns.file_model.count == 0}
|
||||
assert{ ns.put __FILE__ }
|
||||
assert{ ns.file_model.count == 1}
|
||||
|
||||
assert{ count == GridFs::File.count }
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
##
|
||||
#
|
||||
|
||||
protected
|
||||
def object_id_re
|
||||
%r| \w{#{ BSON::ObjectId.new.to_s.size }} |iomx
|
||||
end
|
||||
end
|
196
test/testing.rb
Normal file
196
test/testing.rb
Normal file
@ -0,0 +1,196 @@
|
||||
# -*- encoding : utf-8 -*-
|
||||
require 'test/unit'
|
||||
|
||||
testdir = File.expand_path(File.dirname(__FILE__))
|
||||
rootdir = File.dirname(testdir)
|
||||
libdir = File.join(rootdir, 'lib')
|
||||
|
||||
STDOUT.sync = true
|
||||
|
||||
$:.unshift(testdir) unless $:.include?(testdir)
|
||||
$:.unshift(libdir) unless $:.include?(libdir)
|
||||
$:.unshift(rootdir) unless $:.include?(rootdir)
|
||||
|
||||
class Testing
|
||||
class Slug < ::String
|
||||
def Slug.for(*args)
|
||||
string = args.flatten.compact.join('-')
|
||||
words = string.to_s.scan(%r/\w+/)
|
||||
words.map!{|word| word.gsub %r/[^0-9a-zA-Z_-]/, ''}
|
||||
words.delete_if{|word| word.nil? or word.strip.empty?}
|
||||
new(words.join('-').downcase)
|
||||
end
|
||||
end
|
||||
|
||||
class Context
|
||||
attr_accessor :name
|
||||
|
||||
def initialize(name, *args)
|
||||
@name = name
|
||||
end
|
||||
|
||||
def to_s
|
||||
Slug.for(name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def Testing(*args, &block)
|
||||
Class.new(::Test::Unit::TestCase) do
|
||||
|
||||
## class methods
|
||||
#
|
||||
class << self
|
||||
def contexts
|
||||
@contexts ||= []
|
||||
end
|
||||
|
||||
def context(*args, &block)
|
||||
return contexts.last if(args.empty? and block.nil?)
|
||||
|
||||
context = Testing::Context.new(*args)
|
||||
contexts.push(context)
|
||||
|
||||
begin
|
||||
block.call(context)
|
||||
ensure
|
||||
contexts.pop
|
||||
end
|
||||
end
|
||||
|
||||
def slug_for(*args)
|
||||
string = [context, args].flatten.compact.join('-')
|
||||
words = string.to_s.scan(%r/\w+/)
|
||||
words.map!{|word| word.gsub %r/[^0-9a-zA-Z_-]/, ''}
|
||||
words.delete_if{|word| word.nil? or word.strip.empty?}
|
||||
words.join('-').downcase.sub(/_$/, '')
|
||||
end
|
||||
|
||||
def name() const_get(:Name) end
|
||||
|
||||
def testno()
|
||||
'%05d' % (@testno ||= 0)
|
||||
ensure
|
||||
@testno += 1
|
||||
end
|
||||
|
||||
def testing(*args, &block)
|
||||
method = ["test", testno, slug_for(*args)].delete_if{|part| part.empty?}.join('_')
|
||||
define_method(method, &block)
|
||||
end
|
||||
|
||||
def test(*args, &block)
|
||||
testing(*args, &block)
|
||||
end
|
||||
|
||||
def setup(&block)
|
||||
define_method(:setup, &block) if block
|
||||
end
|
||||
|
||||
def teardown(&block)
|
||||
define_method(:teardown, &block) if block
|
||||
end
|
||||
|
||||
def prepare(&block)
|
||||
@prepare ||= []
|
||||
@prepare.push(block) if block
|
||||
@prepare
|
||||
end
|
||||
|
||||
def cleanup(&block)
|
||||
@cleanup ||= []
|
||||
@cleanup.push(block) if block
|
||||
@cleanup
|
||||
end
|
||||
end
|
||||
|
||||
## configure the subclass!
|
||||
#
|
||||
const_set(:Testno, '0')
|
||||
slug = slug_for(*args).gsub(%r/-/,'_')
|
||||
name = ['TESTING', '%03d' % const_get(:Testno), slug].delete_if{|part| part.empty?}.join('_')
|
||||
name = name.upcase!
|
||||
const_set(:Name, name)
|
||||
const_set(:Missing, Object.new.freeze)
|
||||
|
||||
## instance methods
|
||||
#
|
||||
alias_method('__assert__', 'assert')
|
||||
|
||||
def assert(*args, &block)
|
||||
if args.size == 1 and args.first.is_a?(Hash)
|
||||
options = args.first
|
||||
expected = getopt(:expected, options){ missing }
|
||||
actual = getopt(:actual, options){ missing }
|
||||
if expected == missing and actual == missing
|
||||
actual, expected, *ignored = options.to_a.flatten
|
||||
end
|
||||
expected = expected.call() if expected.respond_to?(:call)
|
||||
actual = actual.call() if actual.respond_to?(:call)
|
||||
assert_equal(expected, actual)
|
||||
end
|
||||
|
||||
if block
|
||||
label = "assert(#{ args.join(' ') })"
|
||||
result = nil
|
||||
assert_nothing_raised{ result = block.call }
|
||||
__assert__(result, label)
|
||||
result
|
||||
else
|
||||
result = args.shift
|
||||
label = "assert(#{ args.join(' ') })"
|
||||
__assert__(result, label)
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
def missing
|
||||
self.class.const_get(:Missing)
|
||||
end
|
||||
|
||||
def getopt(opt, hash, options = nil, &block)
|
||||
[opt.to_s, opt.to_s.to_sym].each do |key|
|
||||
return hash[key] if hash.has_key?(key)
|
||||
end
|
||||
default =
|
||||
if block
|
||||
block.call
|
||||
else
|
||||
options.is_a?(Hash) ? options[:default] : nil
|
||||
end
|
||||
return default
|
||||
end
|
||||
|
||||
def subclass_of exception
|
||||
class << exception
|
||||
def ==(other) super or self > other end
|
||||
end
|
||||
exception
|
||||
end
|
||||
|
||||
##
|
||||
#
|
||||
module_eval(&block)
|
||||
|
||||
self.setup()
|
||||
self.prepare.each{|b| b.call()}
|
||||
|
||||
at_exit{
|
||||
self.teardown()
|
||||
self.cleanup.each{|b| b.call()}
|
||||
}
|
||||
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if $0 == __FILE__
|
||||
|
||||
Testing 'Testing' do
|
||||
testing('foo'){ assert true }
|
||||
test{ assert true }
|
||||
p instance_methods.grep(/test/)
|
||||
end
|
||||
|
||||
end
|
Loading…
Reference in New Issue
Block a user