associo 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +50 -0
- data/Gemfile +9 -0
- data/LICENSE +20 -0
- data/README.rdoc +39 -0
- data/Rakefile +12 -0
- data/associo.gemspec +25 -0
- data/lib/associo.rb +29 -0
- data/lib/associo/attachment_proxy.rb +51 -0
- data/lib/associo/class_methods.rb +64 -0
- data/lib/associo/file_helpers.rb +20 -0
- data/lib/associo/instance_methods.rb +59 -0
- data/lib/associo/io.rb +29 -0
- data/lib/associo/version.rb +3 -0
- data/specs.watchr +50 -0
- data/test/fixtures/example.m4r +0 -0
- data/test/fixtures/font.eot +0 -0
- data/test/fixtures/harmony.png +0 -0
- data/test/fixtures/mr_t.jpg +0 -0
- data/test/fixtures/test1.txt +1 -0
- data/test/fixtures/test2.txt +1 -0
- data/test/fixtures/unixref.pdf +0 -0
- data/test/helper.rb +96 -0
- data/test/joint/test_file_helpers.rb +52 -0
- data/test/joint/test_io.rb +39 -0
- data/test/test_joint.rb +554 -0
- metadata +121 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d303af488b234afa9287b1047241a993511df12f
|
4
|
+
data.tar.gz: 427468da4bec28be43c75d87559c6cf7c9115226
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e5272729031e9951c3150af06233b1a63ea1e830f06ae66a6a6eb089a76f24a57ca95489bcac85e4554f99e57996f7675426646e09b47243b45546d313d0e866
|
7
|
+
data.tar.gz: c19d6f2a7718d000403e763289ced24a0dc1d1adaa122961b1496023edd168890dd4a5e55dec6c6a8c10c47c11bb537adb925126362d20db74a5053f311d619b
|
data/.gitignore
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/spec/examples.txt
|
9
|
+
/test/tmp/
|
10
|
+
/test/version_tmp/
|
11
|
+
/tmp/
|
12
|
+
|
13
|
+
# Used by dotenv library to load environment variables.
|
14
|
+
# .env
|
15
|
+
|
16
|
+
## Specific to RubyMotion:
|
17
|
+
.dat*
|
18
|
+
.repl_history
|
19
|
+
build/
|
20
|
+
*.bridgesupport
|
21
|
+
build-iPhoneOS/
|
22
|
+
build-iPhoneSimulator/
|
23
|
+
|
24
|
+
## Specific to RubyMotion (use of CocoaPods):
|
25
|
+
#
|
26
|
+
# We recommend against adding the Pods directory to your .gitignore. However
|
27
|
+
# you should judge for yourself, the pros and cons are mentioned at:
|
28
|
+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
29
|
+
#
|
30
|
+
# vendor/Pods/
|
31
|
+
|
32
|
+
## Documentation cache and generated files:
|
33
|
+
/.yardoc/
|
34
|
+
/_yardoc/
|
35
|
+
/doc/
|
36
|
+
/rdoc/
|
37
|
+
|
38
|
+
## Environment normalization:
|
39
|
+
/.bundle/
|
40
|
+
/vendor/bundle
|
41
|
+
/lib/bundler/man/
|
42
|
+
|
43
|
+
# for a library or gem, you might want to ignore these files since the code is
|
44
|
+
# intended to run in multiple environments; otherwise, check them in:
|
45
|
+
# Gemfile.lock
|
46
|
+
# .ruby-version
|
47
|
+
# .ruby-gemset
|
48
|
+
|
49
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
50
|
+
.rvmrc
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 John Nunemaker
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
= DEAD AND UNMAINTAINED
|
2
|
+
|
3
|
+
== Joint
|
4
|
+
|
5
|
+
MongoMapper and GridFS joined in file upload love.
|
6
|
+
|
7
|
+
== Usage
|
8
|
+
|
9
|
+
Declare the plugin and use the attachment method to make attachments.
|
10
|
+
|
11
|
+
class Foo
|
12
|
+
include MongoMapper::Document
|
13
|
+
plugin Joint
|
14
|
+
|
15
|
+
attachment :image
|
16
|
+
attachment :pdf
|
17
|
+
end
|
18
|
+
|
19
|
+
This gives you #image, #image=, #pdf, and #pdf=. The = methods take any IO that responds to read (File, Tempfile, etc). The image and pdf methods return a GridIO instance (can be found in the ruby driver).
|
20
|
+
|
21
|
+
Also, #image and #pdf are proxies so you can do stuff like:
|
22
|
+
|
23
|
+
doc.image.id, doc.image.size, doc.image.type, doc.image.name
|
24
|
+
|
25
|
+
If you call a method other than those in the proxy it calls it on the GridIO instance so you can still get at all the GridIO instance methods.
|
26
|
+
|
27
|
+
== Note on Patches/Pull Requests
|
28
|
+
|
29
|
+
* Fork the project.
|
30
|
+
* Make your feature addition or bug fix.
|
31
|
+
* Add tests for it. This is important so I don't break it in a
|
32
|
+
future version unintentionally.
|
33
|
+
* Commit, do not mess with rakefile, version, or history.
|
34
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
35
|
+
* Send me a pull request. Bonus points for topic branches.
|
36
|
+
|
37
|
+
== Copyright
|
38
|
+
|
39
|
+
Copyright (c) 2010 John Nunemaker. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
require 'rake/testtask'
|
5
|
+
Rake::TestTask.new do |test|
|
6
|
+
test.libs << 'lib' << 'test'
|
7
|
+
test.pattern = 'test/**/test_*.rb'
|
8
|
+
test.ruby_opts = ['-rubygems']
|
9
|
+
test.verbose = true
|
10
|
+
end
|
11
|
+
|
12
|
+
task :default => :test
|
data/associo.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require File.expand_path('../lib/associo/version', __FILE__)
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'associo'
|
7
|
+
s.summary = %(MongoMapper and GridFS joined in file upload love.)
|
8
|
+
s.description = %(Implements an easy to use GridFS API for MongoMapper.)
|
9
|
+
s.email = 'syntruth@gmail.com'
|
10
|
+
s.homepage = 'https://github.com/syntruth/Associo'
|
11
|
+
s.authors = ['John Nunemaker', 'Randy Carnahan']
|
12
|
+
s.version = Associo::VERSION
|
13
|
+
|
14
|
+
s.add_dependency 'wand', '~> 0.4'
|
15
|
+
s.add_dependency 'mime-types'
|
16
|
+
s.add_dependency 'mongo_mapper', '~> 0.9'
|
17
|
+
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f|
|
21
|
+
File.basename(f)
|
22
|
+
}
|
23
|
+
|
24
|
+
s.require_paths = ['lib']
|
25
|
+
end
|
data/lib/associo.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'mime/types'
|
3
|
+
require 'wand'
|
4
|
+
require 'active_support/concern'
|
5
|
+
|
6
|
+
# Main Associo module
|
7
|
+
module Associo
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
class_attribute :attachment_names
|
12
|
+
|
13
|
+
self.attachment_names = Set.new
|
14
|
+
|
15
|
+
include attachment_accessor_module
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.blank?(str)
|
19
|
+
str.nil? || str !~ /\S/
|
20
|
+
end
|
21
|
+
|
22
|
+
private_class_method :blank?
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'associo/class_methods'
|
26
|
+
require 'associo/instance_methods'
|
27
|
+
require 'associo/attachment_proxy'
|
28
|
+
require 'associo/io'
|
29
|
+
require 'associo/file_helpers'
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Associo
|
2
|
+
# Proxy for the Attached file.
|
3
|
+
class AttachmentProxy
|
4
|
+
def initialize(instance, name)
|
5
|
+
@instance = instance
|
6
|
+
@name = name
|
7
|
+
end
|
8
|
+
|
9
|
+
def id
|
10
|
+
@instance.send "#{@name}_id"
|
11
|
+
end
|
12
|
+
|
13
|
+
def name
|
14
|
+
@instance.send "#{@name}_name"
|
15
|
+
end
|
16
|
+
|
17
|
+
def size
|
18
|
+
@instance.send "#{@name}_size"
|
19
|
+
end
|
20
|
+
|
21
|
+
def type
|
22
|
+
@instance.send "#{@name}_type"
|
23
|
+
end
|
24
|
+
|
25
|
+
def chunk_size
|
26
|
+
@instance.send "#{@name}_chunk_size"
|
27
|
+
end
|
28
|
+
|
29
|
+
def nil?
|
30
|
+
!@instance.send("#{@name}?")
|
31
|
+
end
|
32
|
+
|
33
|
+
alias blank? nil?
|
34
|
+
|
35
|
+
def grid_io
|
36
|
+
@grid_io ||= @instance.grid.get(id)
|
37
|
+
end
|
38
|
+
|
39
|
+
def method_missing(method, *args, &block)
|
40
|
+
if grid_io.respond_to? method.to_s
|
41
|
+
grid_io.send(method, *args, &block)
|
42
|
+
else
|
43
|
+
super
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def respond_to_missing?(method, include_private = false)
|
48
|
+
super
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Associo
|
2
|
+
# Define new class methods for documents that
|
3
|
+
# include the Associo module.
|
4
|
+
module ClassMethods
|
5
|
+
DEFAULT_CHUNK_SIZE = 261_120
|
6
|
+
|
7
|
+
def attachment_accessor_module
|
8
|
+
@attachment_accessor_module ||= Module.new
|
9
|
+
end
|
10
|
+
|
11
|
+
# rubocop:disable AbcSize, MethodLength
|
12
|
+
# TODO: refactor this.
|
13
|
+
def attachment(name, options = {})
|
14
|
+
options.symbolize_keys!
|
15
|
+
|
16
|
+
name = name.to_sym
|
17
|
+
|
18
|
+
chunk_size = options.fetch(:chunk_size, DEFAULT_CHUNK_SIZE).to_i
|
19
|
+
chunk_size = DEFAULT_CHUNK_SIZE if chunk_size.zero?
|
20
|
+
|
21
|
+
self.attachment_names = attachment_names.dup.add(name)
|
22
|
+
|
23
|
+
after_save :save_attachments
|
24
|
+
before_save :nullify_nil_attachments_attributes
|
25
|
+
after_save :destroy_nil_attachments
|
26
|
+
before_destroy :destroy_all_attachments
|
27
|
+
|
28
|
+
key :"#{name}_id", ObjectId
|
29
|
+
key :"#{name}_name", String
|
30
|
+
key :"#{name}_size", Integer
|
31
|
+
key :"#{name}_type", String
|
32
|
+
|
33
|
+
# Allow for optional, custom chunk size, in bytes.
|
34
|
+
# Default size is 255k, set above.
|
35
|
+
key :"#{name}_chunk_size", Integer, default: chunk_size
|
36
|
+
|
37
|
+
validates_presence_of(name) if options[:required]
|
38
|
+
|
39
|
+
attachment_accessor_module.module_eval <<-EOC
|
40
|
+
def #{name}
|
41
|
+
@#{name} ||= AttachmentProxy.new(self, :#{name})
|
42
|
+
end
|
43
|
+
|
44
|
+
def #{name}?
|
45
|
+
!nil_attachments.has_key?(:#{name}) && send(:#{name}_id?)
|
46
|
+
end
|
47
|
+
|
48
|
+
def #{name}=(file)
|
49
|
+
if file.nil?
|
50
|
+
nil_attachments[:#{name}] = send("#{name}_id")
|
51
|
+
assigned_attachments.delete(:#{name})
|
52
|
+
else
|
53
|
+
send("#{name}_id=", BSON::ObjectId.new) if send("#{name}_id").nil?
|
54
|
+
send("#{name}_name=", Associo::FileHelpers.name(file))
|
55
|
+
send("#{name}_size=", Associo::FileHelpers.size(file))
|
56
|
+
send("#{name}_type=", Associo::FileHelpers.type(file))
|
57
|
+
assigned_attachments[:#{name}] = file
|
58
|
+
nil_attachments.delete(:#{name})
|
59
|
+
end
|
60
|
+
end
|
61
|
+
EOC
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Associo
|
2
|
+
# Syntax helper for accessing file attributes.
|
3
|
+
module FileHelpers
|
4
|
+
def self.name(file)
|
5
|
+
return file.original_filename if file.respond_to?(:original_filename)
|
6
|
+
|
7
|
+
File.basename(file.path)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.size(file)
|
11
|
+
file.respond_to?(:size) ? file.size : File.size(file)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.type(file)
|
15
|
+
return file.type if file.is_a? Associo::IO
|
16
|
+
|
17
|
+
Wand.wave file.path, original_filename: Associo::FileHelpers.name(file)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# Define Instance Methods
|
2
|
+
module Associo
|
3
|
+
def grid
|
4
|
+
@grid ||= Mongo::Grid.new(database)
|
5
|
+
end
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def assigned_attachments
|
10
|
+
@assigned_attachments ||= {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def nil_attachments
|
14
|
+
@nil_attachments ||= {}
|
15
|
+
end
|
16
|
+
|
17
|
+
# IO must respond to read and rewind
|
18
|
+
def save_attachments
|
19
|
+
assigned_attachments.each_pair do |name, io|
|
20
|
+
next unless io.respond_to?(:read)
|
21
|
+
|
22
|
+
io.rewind if io.respond_to?(:rewind)
|
23
|
+
|
24
|
+
grid.delete send(name).id
|
25
|
+
|
26
|
+
grid.put io, save_attachment_hash(name)
|
27
|
+
end
|
28
|
+
|
29
|
+
assigned_attachments.clear
|
30
|
+
end
|
31
|
+
|
32
|
+
def save_attachment_hash(name)
|
33
|
+
{
|
34
|
+
_id: send(name).id,
|
35
|
+
filename: send(name).name,
|
36
|
+
content_type: send(name).type,
|
37
|
+
chunk_size: send(name).chunk_size
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
def nullify_nil_attachments_attributes
|
42
|
+
nil_attachments.each_key do |name|
|
43
|
+
send :"#{name}_id=", nil
|
44
|
+
send :"#{name}_size=", nil
|
45
|
+
send :"#{name}_type=", nil
|
46
|
+
send :"#{name}_name=", nil
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def destroy_nil_attachments
|
51
|
+
nil_attachments.each_value { |id| grid.delete id }
|
52
|
+
|
53
|
+
nil_attachments.clear
|
54
|
+
end
|
55
|
+
|
56
|
+
def destroy_all_attachments
|
57
|
+
self.class.attachment_names.map { |name| grid.delete send(name).id }
|
58
|
+
end
|
59
|
+
end
|
data/lib/associo/io.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
module Associo
|
4
|
+
# Defines the file io for the attached file.
|
5
|
+
class IO
|
6
|
+
attr_accessor :name, :content, :type, :size
|
7
|
+
|
8
|
+
def initialize(attrs = {})
|
9
|
+
attrs.each { |key, value| send("#{key}=", value) }
|
10
|
+
|
11
|
+
@type ||= 'plain/text'
|
12
|
+
end
|
13
|
+
|
14
|
+
def content=(value)
|
15
|
+
@io = StringIO.new(value || nil)
|
16
|
+
@size = value ? value.size : 0
|
17
|
+
end
|
18
|
+
|
19
|
+
def read(*args)
|
20
|
+
@io.read(*args)
|
21
|
+
end
|
22
|
+
|
23
|
+
def rewind
|
24
|
+
@io.rewind if @io.respond_to?(:rewind)
|
25
|
+
end
|
26
|
+
|
27
|
+
alias path name
|
28
|
+
end
|
29
|
+
end
|
data/specs.watchr
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
def run(cmd)
|
2
|
+
puts(cmd)
|
3
|
+
|
4
|
+
output = ''
|
5
|
+
|
6
|
+
IO.popen(cmd) do |com|
|
7
|
+
com.each_char do |c|
|
8
|
+
print c
|
9
|
+
output << c
|
10
|
+
$stdout.flush
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
output
|
15
|
+
end
|
16
|
+
|
17
|
+
def run_test_file(file)
|
18
|
+
run %(ruby -I"lib:test" -rubygems #{file})
|
19
|
+
end
|
20
|
+
|
21
|
+
def run_all_tests
|
22
|
+
run 'rake test'
|
23
|
+
end
|
24
|
+
|
25
|
+
def related_test_files(path)
|
26
|
+
Dir['test/**/*.rb'].select { |file| file =~ /test_#{File.basename(path)}/ }
|
27
|
+
end
|
28
|
+
|
29
|
+
watch('test/test_helper\.rb') do
|
30
|
+
system 'clear'
|
31
|
+
run_all_test
|
32
|
+
end
|
33
|
+
|
34
|
+
watch('test/.*test_.*\.rb') do |m|
|
35
|
+
system 'clear'
|
36
|
+
run_test_file m[0]
|
37
|
+
end
|
38
|
+
|
39
|
+
watch('lib/.*') do |m|
|
40
|
+
related_test_files(m[0]).each { |file| run_test_file(file) }
|
41
|
+
end
|
42
|
+
|
43
|
+
# Ctrl-\
|
44
|
+
Signal.trap('QUIT') do
|
45
|
+
puts " --- Running all tests ---\n\n"
|
46
|
+
run_all_tests
|
47
|
+
end
|
48
|
+
|
49
|
+
# Ctrl-C
|
50
|
+
Signal.trap('INT') { abort("\n") }
|