mongomapper_ext 0.0.4
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/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +59 -0
- data/VERSION +1 -0
- data/examples/filter.rb +23 -0
- data/examples/helper.rb +6 -0
- data/examples/slugizer.rb +15 -0
- data/examples/storage.rb +22 -0
- data/examples/types.rb +32 -0
- data/examples/update.rb +14 -0
- data/lib/mongomapper_ext/file.rb +35 -0
- data/lib/mongomapper_ext/filter.rb +76 -0
- data/lib/mongomapper_ext/slugizer.rb +46 -0
- data/lib/mongomapper_ext/storage.rb +73 -0
- data/lib/mongomapper_ext/types/open_struct.rb +19 -0
- data/lib/mongomapper_ext/types/set.rb +9 -0
- data/lib/mongomapper_ext/types/timestamp.rb +15 -0
- data/lib/mongomapper_ext/update.rb +10 -0
- data/lib/mongomapper_ext.rb +23 -0
- data/test/helper.rb +23 -0
- data/test/models.rb +52 -0
- data/test/support/custom_matchers.rb +55 -0
- data/test/test_filter.rb +43 -0
- data/test/test_slugizer.rb +40 -0
- data/test/test_storage.rb +64 -0
- data/test/test_update.rb +16 -0
- data/test/types/test_open_struct.rb +19 -0
- data/test/types/test_set.rb +26 -0
- data/test/types/test_timestamp.rb +40 -0
- metadata +160 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 David A. Cuadrado
|
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,19 @@
|
|
1
|
+
= mongomapper_ext
|
2
|
+
|
3
|
+
MongoMapper extensions.
|
4
|
+
|
5
|
+
== Note on Patches/Pull Requests
|
6
|
+
|
7
|
+
* Fork the project.
|
8
|
+
* Make your feature addition or bug fix.
|
9
|
+
* Add tests for it. This is important so I don't break it in a
|
10
|
+
future version unintentionally.
|
11
|
+
* Commit, do not mess with rakefile, version, or history.
|
12
|
+
(if you want to have your own version, that is fine but
|
13
|
+
bump version in a commit by itself I can ignore when I pull)
|
14
|
+
* Send me a pull request. Bonus points for topic branches.
|
15
|
+
|
16
|
+
== Copyright
|
17
|
+
|
18
|
+
Copyright (c) 2009 David A. Cuadrado. See LICENSE for details.
|
19
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "mongomapper_ext"
|
8
|
+
gem.summary = %Q{MongoMapper extensions}
|
9
|
+
gem.description = %Q{MongoMapper extensions}
|
10
|
+
gem.email = "krawek@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/dcu/mongomapper_ext"
|
12
|
+
gem.authors = ["David A. Cuadrado"]
|
13
|
+
|
14
|
+
gem.add_dependency('mongo_mapper', '>= 0.5.6')
|
15
|
+
gem.add_dependency('ruby-stemmer', '>= 0.5.3')
|
16
|
+
gem.add_dependency('uuidtools', '>= 2.0.0')
|
17
|
+
|
18
|
+
gem.add_development_dependency("shoulda", ">= 2.10.2")
|
19
|
+
gem.add_development_dependency('jnunemaker-matchy', '0.4.0')
|
20
|
+
gem.add_development_dependency('mocha', '>= 0.9.4')
|
21
|
+
end
|
22
|
+
rescue LoadError
|
23
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
24
|
+
end
|
25
|
+
|
26
|
+
require 'rake/testtask'
|
27
|
+
Rake::TestTask.new(:test) do |test|
|
28
|
+
test.libs << 'lib' << 'test'
|
29
|
+
test.pattern = 'test/**/test_*.rb'
|
30
|
+
test.verbose = true
|
31
|
+
end
|
32
|
+
|
33
|
+
begin
|
34
|
+
require 'rcov/rcovtask'
|
35
|
+
Rcov::RcovTask.new do |test|
|
36
|
+
test.libs << 'test'
|
37
|
+
test.pattern = 'test/**/test_*.rb'
|
38
|
+
test.verbose = true
|
39
|
+
end
|
40
|
+
rescue LoadError
|
41
|
+
task :rcov do
|
42
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
task :test => :check_dependencies
|
47
|
+
|
48
|
+
task :default => :test
|
49
|
+
|
50
|
+
require 'rake/rdoctask'
|
51
|
+
Rake::RDocTask.new do |rdoc|
|
52
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
53
|
+
|
54
|
+
rdoc.rdoc_dir = 'rdoc'
|
55
|
+
rdoc.title = "mongomapper_ext #{version}"
|
56
|
+
rdoc.rdoc_files.include('README*')
|
57
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
58
|
+
end
|
59
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.4
|
data/examples/filter.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require './helper'
|
2
|
+
|
3
|
+
class FilterEx
|
4
|
+
include MongoMapper::Document
|
5
|
+
include MongoMapperExt::Filter
|
6
|
+
|
7
|
+
key :title, String
|
8
|
+
key :body, String
|
9
|
+
|
10
|
+
filterable_keys :title, :body
|
11
|
+
end
|
12
|
+
|
13
|
+
FilterEx.delete_all
|
14
|
+
o1 = FilterEx.create(:title => "A Great Title", :body => "A great Body")
|
15
|
+
o2 = FilterEx.create(:title => "A Good Title", :body => "A good Body")
|
16
|
+
|
17
|
+
|
18
|
+
puts "filter: #{FilterEx.filter("title").size}"
|
19
|
+
puts "filter: #{FilterEx.filter("title", :limit => 1).inspect}"
|
20
|
+
puts "filter: #{FilterEx.filter("great").inspect}"
|
21
|
+
|
22
|
+
|
23
|
+
FilterEx.delete_all
|
data/examples/helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require './helper'
|
2
|
+
|
3
|
+
class SlugizerEx
|
4
|
+
include MongoMapper::Document
|
5
|
+
include MongoMapperExt::Slugizer
|
6
|
+
|
7
|
+
slug_key :title
|
8
|
+
|
9
|
+
key :title
|
10
|
+
end
|
11
|
+
|
12
|
+
u = SlugizerEx.create(:title => "The title of the blogpost!!!")
|
13
|
+
|
14
|
+
puts u.slug
|
15
|
+
puts SlugizerEx.by_slug(u.slug).inspect
|
data/examples/storage.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require './helper'
|
2
|
+
|
3
|
+
class StorageEx
|
4
|
+
include MongoMapper::Document
|
5
|
+
include MongoMapperExt::Storage
|
6
|
+
|
7
|
+
file_key :my_file
|
8
|
+
end
|
9
|
+
|
10
|
+
file = StringIO.new("file content")
|
11
|
+
file2 = StringIO.new("file content 2")
|
12
|
+
|
13
|
+
s = StorageEx.new
|
14
|
+
s.put_file("filename.txt", file)
|
15
|
+
s.my_file = file2
|
16
|
+
|
17
|
+
s.save
|
18
|
+
|
19
|
+
from_db = StorageEx.find(s.id)
|
20
|
+
|
21
|
+
puts from_db.fetch_file("filename.txt").read
|
22
|
+
puts from_db.my_file.read
|
data/examples/types.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require './helper'
|
2
|
+
|
3
|
+
class TypeEx
|
4
|
+
include MongoMapper::Document
|
5
|
+
|
6
|
+
key :time, Timestamp
|
7
|
+
key :config, OpenStruct
|
8
|
+
key :set, Set
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
type = TypeEx.new(:set => Set.new, :config => OpenStruct.new)
|
13
|
+
|
14
|
+
type.config.name = "Alan"
|
15
|
+
type.config.last_name = "Turing"
|
16
|
+
|
17
|
+
|
18
|
+
type.time = Time.now
|
19
|
+
type.set += [1,1,2,3,2,2]
|
20
|
+
|
21
|
+
type.save
|
22
|
+
|
23
|
+
from_db = TypeEx.find(type.id)
|
24
|
+
|
25
|
+
puts from_db.config.inspect
|
26
|
+
puts from_db.set.inspect
|
27
|
+
|
28
|
+
Time.zone = "Hawaii"
|
29
|
+
puts from_db.time.inspect
|
30
|
+
|
31
|
+
from_db.destroy
|
32
|
+
|
data/examples/update.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module MongoMapperExt
|
2
|
+
class File < GridFS::GridStore
|
3
|
+
attr_reader :id, :attributes
|
4
|
+
|
5
|
+
def initialize(owner, attrs = {})
|
6
|
+
@owner = owner
|
7
|
+
@id = attrs.delete("_id")
|
8
|
+
|
9
|
+
class_eval do
|
10
|
+
attrs.each do |k,v|
|
11
|
+
define_method(k) do
|
12
|
+
v
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
super(@owner.class.database, attrs["filename"], "r", :root => @owner.collection.name)
|
18
|
+
end
|
19
|
+
|
20
|
+
def [](name)
|
21
|
+
@attributes[name.to_s]
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.fetch(owner, filename)
|
25
|
+
db = owner.class.database
|
26
|
+
criteria, options = MongoMapper::FinderOptions.new(owner.class, :filename => filename, :metadata => {:_id => owner.id}, :limit => 1).to_a
|
27
|
+
|
28
|
+
obj = db.collection("#{owner.collection.name}.files").find(criteria, options).next_object
|
29
|
+
|
30
|
+
if obj
|
31
|
+
self.new(owner, obj)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module MongoMapperExt
|
2
|
+
module Filter
|
3
|
+
def self.included(klass)
|
4
|
+
require 'lingua/stemmer'
|
5
|
+
|
6
|
+
klass.class_eval do
|
7
|
+
extend ClassMethods
|
8
|
+
|
9
|
+
key :_keywords, Array
|
10
|
+
ensure_index :_keywords
|
11
|
+
|
12
|
+
before_save :_update_keywords
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
def filterable_keys(*keys)
|
18
|
+
@filterable_keys ||= Set.new
|
19
|
+
@filterable_keys += keys
|
20
|
+
|
21
|
+
@filterable_keys
|
22
|
+
end
|
23
|
+
|
24
|
+
def language(lang = 'en')
|
25
|
+
@language ||= lang
|
26
|
+
end
|
27
|
+
|
28
|
+
def filter(query, opts = {})
|
29
|
+
q = query.downcase.split.map do |k|
|
30
|
+
Regexp.escape(k)
|
31
|
+
end.join("|")
|
32
|
+
if opts[:per_page]
|
33
|
+
self.paginate(opts.deep_merge(:conditions => {:_keywords => /^(#{q}).*/ }))
|
34
|
+
else
|
35
|
+
self.find(:all, opts.deep_merge(:conditions => {:_keywords => /^(#{q}).*/ }))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
def _update_keywords
|
42
|
+
lang = self.class.language
|
43
|
+
if lang.kind_of?(Symbol)
|
44
|
+
lang = send(lang)
|
45
|
+
elsif lang.kind_of?(Proc)
|
46
|
+
lang = lang.call(self)
|
47
|
+
end
|
48
|
+
|
49
|
+
s = Lingua::Stemmer.new(:language => lang)
|
50
|
+
|
51
|
+
self._keywords = []
|
52
|
+
self.class.filterable_keys.each do |key|
|
53
|
+
self._keywords += keywords_for_value(s, read_attribute(key))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
def keywords_for_value(stemmer, val)
|
59
|
+
if val.kind_of?(String)
|
60
|
+
val.downcase.split.map do |word|
|
61
|
+
stem = stemmer.stem(word)
|
62
|
+
if stem != word
|
63
|
+
[stem, word]
|
64
|
+
else
|
65
|
+
word
|
66
|
+
end
|
67
|
+
end.flatten
|
68
|
+
elsif val.kind_of?(Array)
|
69
|
+
val.map { |e| keywords_for_value(stemmer, e) }.flatten
|
70
|
+
else
|
71
|
+
[val]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module MongoMapperExt
|
2
|
+
module Slugizer
|
3
|
+
def self.included(klass)
|
4
|
+
klass.class_eval do
|
5
|
+
extend ClassMethods
|
6
|
+
extend Finder
|
7
|
+
|
8
|
+
key :slug, String
|
9
|
+
ensure_index :slug
|
10
|
+
|
11
|
+
before_validation_on_create :generate_slug
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_param
|
16
|
+
if self.slug.blank?
|
17
|
+
self.id
|
18
|
+
else
|
19
|
+
self.slug
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
def generate_slug
|
25
|
+
if self.slug.blank?
|
26
|
+
key = UUIDTools::UUID.random_create.hexdigest[0,4] #optimize
|
27
|
+
self.slug = key+"-"+self[self.class.slug_key].gsub(/[^A-Za-z0-9\s\-]/, "")[0,20].strip.gsub(/\s+/, "-").downcase
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module ClassMethods
|
32
|
+
def slug_key(key = :name)
|
33
|
+
@slug_key ||= key
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
module Finder
|
38
|
+
def by_slug(id)
|
39
|
+
self.find_by_slug(id) || self.find_by_id(id)
|
40
|
+
end
|
41
|
+
alias :find_by_slug_or_id :by_slug
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
MongoMapper::Associations::Proxy.send(:include, MongoMapperExt::Slugizer::Finder)
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module MongoMapperExt
|
2
|
+
module Storage
|
3
|
+
def self.included(model)
|
4
|
+
model.class_eval do
|
5
|
+
extend ClassMethods
|
6
|
+
after_create :_sync_pending_files
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
# FIXME: enable metadata. re http://jira.mongodb.org/browse/SERVER-377
|
11
|
+
def put_file(filename, io, metadata = {})
|
12
|
+
if !new?
|
13
|
+
# :metadata => metadata.deep_merge({:_id => self.id})
|
14
|
+
GridFS::GridStore.open(self.class.database, filename, "w",
|
15
|
+
:root => self.collection.name,
|
16
|
+
:metadata => {:_id => self.id}) do |f|
|
17
|
+
while data = io.read(256)
|
18
|
+
f.write(data)
|
19
|
+
end
|
20
|
+
io.close
|
21
|
+
end
|
22
|
+
else
|
23
|
+
(@_pending_files ||= {})[filename] = io
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def fetch_file(filename)
|
28
|
+
if !new?
|
29
|
+
MongoMapperExt::File.fetch(self, filename)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def files
|
34
|
+
criteria, options = MongoMapper::FinderOptions.new(self.class, :metadata => {:_id => self.id}).to_a
|
35
|
+
coll = self.class.database.collection("#{self.collection.name}.files")
|
36
|
+
@files = coll.find(criteria, options).map do |a|
|
37
|
+
MongoMapperExt::File.new(self, a)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
def _sync_pending_files
|
43
|
+
if @_pending_files
|
44
|
+
@_pending_files.each do |filename, data|
|
45
|
+
put_file(filename, data)
|
46
|
+
end
|
47
|
+
@_pending_files = nil
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
module ClassMethods
|
52
|
+
def file_key(name)
|
53
|
+
define_method("#{name}=") do |file|
|
54
|
+
file_id = UUIDTools::UUID.random_create.hexdigest
|
55
|
+
filename = name
|
56
|
+
|
57
|
+
if file.respond_to?(:original_filename)
|
58
|
+
filename = file.original_filename
|
59
|
+
elsif file.respond_to?(:path)
|
60
|
+
filename = file.path
|
61
|
+
end
|
62
|
+
|
63
|
+
put_file(file_id, file, :original_filename => filename)
|
64
|
+
self["_#{name}"] = file_id
|
65
|
+
end
|
66
|
+
|
67
|
+
define_method(name) do
|
68
|
+
fetch_file(self["_#{name}"])
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
class OpenStruct
|
4
|
+
def self.to_mongo(value)
|
5
|
+
if value.kind_of?(self)
|
6
|
+
value.send(:table)
|
7
|
+
else
|
8
|
+
value
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.from_mongo(value)
|
13
|
+
if value.kind_of?(self)
|
14
|
+
value
|
15
|
+
else
|
16
|
+
OpenStruct.new(value || {})
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
|
3
|
+
require 'mongo/gridfs'
|
4
|
+
require 'mongo_mapper'
|
5
|
+
require 'uuidtools'
|
6
|
+
|
7
|
+
# types
|
8
|
+
require 'mongomapper_ext/types/open_struct'
|
9
|
+
require 'mongomapper_ext/types/timestamp'
|
10
|
+
require 'mongomapper_ext/types/set'
|
11
|
+
|
12
|
+
# storage
|
13
|
+
require 'mongomapper_ext/file'
|
14
|
+
require 'mongomapper_ext/storage'
|
15
|
+
|
16
|
+
# update
|
17
|
+
require 'mongomapper_ext/update'
|
18
|
+
|
19
|
+
# filter
|
20
|
+
require 'mongomapper_ext/filter'
|
21
|
+
|
22
|
+
# slug
|
23
|
+
require 'mongomapper_ext/slugizer'
|
data/test/helper.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
gem 'jnunemaker-matchy'
|
4
|
+
|
5
|
+
require 'matchy'
|
6
|
+
require 'shoulda'
|
7
|
+
require 'timecop'
|
8
|
+
require 'mocha'
|
9
|
+
require 'pp'
|
10
|
+
|
11
|
+
require 'support/custom_matchers'
|
12
|
+
|
13
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
14
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
15
|
+
|
16
|
+
require 'mongomapper_ext'
|
17
|
+
require 'models'
|
18
|
+
|
19
|
+
class Test::Unit::TestCase
|
20
|
+
include CustomMatchers
|
21
|
+
end
|
22
|
+
|
23
|
+
MongoMapper.database = 'test'
|
data/test/models.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
|
2
|
+
class Event # for safe_update, and Timestamp
|
3
|
+
include MongoMapper::Document
|
4
|
+
|
5
|
+
key :start_date, Timestamp
|
6
|
+
key :end_date, Timestamp
|
7
|
+
|
8
|
+
key :password, String
|
9
|
+
end
|
10
|
+
|
11
|
+
class Recipe # for Set
|
12
|
+
include MongoMapper::Document
|
13
|
+
include MongoMapperExt::Filter
|
14
|
+
|
15
|
+
language Proc.new { |d| d.language }
|
16
|
+
filterable_keys :language
|
17
|
+
|
18
|
+
key :ingredients, Set
|
19
|
+
key :description, String
|
20
|
+
key :language, String, :default => 'en'
|
21
|
+
end
|
22
|
+
|
23
|
+
class Avatar # for Storage and File
|
24
|
+
include MongoMapper::Document
|
25
|
+
include MongoMapperExt::Storage
|
26
|
+
|
27
|
+
file_key :data
|
28
|
+
end
|
29
|
+
|
30
|
+
class UserConfig #for OpenStruct
|
31
|
+
include MongoMapper::Document
|
32
|
+
key :entries, OpenStruct
|
33
|
+
end
|
34
|
+
|
35
|
+
class BlogPost # for Slug and Filter
|
36
|
+
include MongoMapper::Document
|
37
|
+
include MongoMapperExt::Filter
|
38
|
+
include MongoMapperExt::Slugizer
|
39
|
+
|
40
|
+
filterable_keys :title, :body, :tags, :date
|
41
|
+
slug_key :title
|
42
|
+
language :find_language
|
43
|
+
|
44
|
+
key :title, String
|
45
|
+
key :body, String
|
46
|
+
key :tags, Array
|
47
|
+
key :date, Time
|
48
|
+
|
49
|
+
def find_language
|
50
|
+
'en'
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module CustomMatchers
|
2
|
+
custom_matcher :be_nil do |receiver, matcher, args|
|
3
|
+
matcher.positive_failure_message = "Expected #{receiver} to be nil but it wasn't"
|
4
|
+
matcher.negative_failure_message = "Expected #{receiver} not to be nil but it was"
|
5
|
+
receiver.nil?
|
6
|
+
end
|
7
|
+
|
8
|
+
custom_matcher :be_blank do |receiver, matcher, args|
|
9
|
+
matcher.positive_failure_message = "Expected #{receiver} to be blank but it wasn't"
|
10
|
+
matcher.negative_failure_message = "Expected #{receiver} not to be blank but it was"
|
11
|
+
receiver.blank?
|
12
|
+
end
|
13
|
+
|
14
|
+
custom_matcher :be_true do |receiver, matcher, args|
|
15
|
+
matcher.positive_failure_message = "Expected #{receiver} to be true but it wasn't"
|
16
|
+
matcher.negative_failure_message = "Expected #{receiver} not to be true but it was"
|
17
|
+
receiver.eql?(true)
|
18
|
+
end
|
19
|
+
|
20
|
+
custom_matcher :be_false do |receiver, matcher, args|
|
21
|
+
matcher.positive_failure_message = "Expected #{receiver} to be false but it wasn't"
|
22
|
+
matcher.negative_failure_message = "Expected #{receiver} not to be false but it was"
|
23
|
+
receiver.eql?(false)
|
24
|
+
end
|
25
|
+
|
26
|
+
custom_matcher :be_valid do |receiver, matcher, args|
|
27
|
+
matcher.positive_failure_message = "Expected to be valid but it was invalid #{receiver.errors.inspect}"
|
28
|
+
matcher.negative_failure_message = "Expected to be invalid but it was valid #{receiver.errors.inspect}"
|
29
|
+
receiver.valid?
|
30
|
+
end
|
31
|
+
|
32
|
+
custom_matcher :have_error_on do |receiver, matcher, args|
|
33
|
+
receiver.valid?
|
34
|
+
attribute = args[0]
|
35
|
+
expected_message = args[1]
|
36
|
+
|
37
|
+
if expected_message.nil?
|
38
|
+
matcher.positive_failure_message = "#{receiver} had no errors on #{attribute}"
|
39
|
+
matcher.negative_failure_message = "#{receiver} had errors on #{attribute} #{receiver.errors.inspect}"
|
40
|
+
!receiver.errors.on(attribute).blank?
|
41
|
+
else
|
42
|
+
actual = receiver.errors.on(attribute)
|
43
|
+
matcher.positive_failure_message = %Q(Expected error on #{attribute} to be "#{expected_message}" but was "#{actual}")
|
44
|
+
matcher.negative_failure_message = %Q(Expected error on #{attribute} not to be "#{expected_message}" but was "#{actual}")
|
45
|
+
actual == expected_message
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
custom_matcher :have_index do |receiver, matcher, args|
|
50
|
+
index_name = args[0]
|
51
|
+
matcher.positive_failure_message = "#{receiver} does not have index named #{index_name}, but should"
|
52
|
+
matcher.negative_failure_message = "#{receiver} does have index named #{index_name}, but should not"
|
53
|
+
!receiver.collection.index_information.detect { |index| index[0] == index_name }.nil?
|
54
|
+
end
|
55
|
+
end
|
data/test/test_filter.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestFilter < Test::Unit::TestCase
|
4
|
+
context "filtering data" do
|
5
|
+
setup do
|
6
|
+
BlogPost.delete_all
|
7
|
+
@blogpost = BlogPost.create(:title => "%How dOEs tHIs Work?!",
|
8
|
+
:body => "HeRe is tHe Body of the bLog pOsT",
|
9
|
+
:tags => ["my", "list", "of", "tags"],
|
10
|
+
:date => Time.parse('01-01-2009'))
|
11
|
+
end
|
12
|
+
|
13
|
+
should "be case insensitive" do
|
14
|
+
BlogPost.filter("body").should == [@blogpost]
|
15
|
+
end
|
16
|
+
|
17
|
+
should "be able to find by title" do
|
18
|
+
BlogPost.filter("this").should == [@blogpost]
|
19
|
+
end
|
20
|
+
|
21
|
+
should "be able to find by body" do
|
22
|
+
BlogPost.filter("blog").should == [@blogpost]
|
23
|
+
end
|
24
|
+
|
25
|
+
should "be able to find by tags" do
|
26
|
+
BlogPost.filter("list").should == [@blogpost]
|
27
|
+
end
|
28
|
+
|
29
|
+
should "be able to find by title or body" do
|
30
|
+
BlogPost.filter("work blog").should == [@blogpost]
|
31
|
+
end
|
32
|
+
|
33
|
+
should "ignore inexistant words" do
|
34
|
+
BlogPost.filter("work lalala").should == [@blogpost]
|
35
|
+
end
|
36
|
+
|
37
|
+
should "allow to paginate results" do
|
38
|
+
results = BlogPost.filter("tag", :per_page => 1, :page => 1)
|
39
|
+
results.should == [@blogpost]
|
40
|
+
results.total_pages.should == 1
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestSlugizer < Test::Unit::TestCase
|
4
|
+
context "working with slugs" do
|
5
|
+
setup do
|
6
|
+
BlogPost.delete_all
|
7
|
+
@blogpost = BlogPost.create(:title => "%bLog pOSt tiTLe!",
|
8
|
+
:body => "HeRe is tHe Body of the bLog pOsT")
|
9
|
+
end
|
10
|
+
|
11
|
+
should "generate the slug" do
|
12
|
+
@blogpost.slug.should =~ /\w+-blog-post-title/
|
13
|
+
end
|
14
|
+
|
15
|
+
should "return the slug as param" do
|
16
|
+
@blogpost.to_param =~ /\w+-blog-post-title/
|
17
|
+
end
|
18
|
+
|
19
|
+
should "return the id if slug was not generated" do
|
20
|
+
@blogpost.slug = nil
|
21
|
+
@blogpost.to_param.should == @blogpost.id
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context "finding objects" do
|
26
|
+
setup do
|
27
|
+
BlogPost.delete_all
|
28
|
+
@blogpost = BlogPost.create(:title => "%bLog pOSt tiTLe!",
|
29
|
+
:body => "HeRe is tHe Body of the bLog pOsT")
|
30
|
+
end
|
31
|
+
|
32
|
+
should "be able to find by slug" do
|
33
|
+
BlogPost.by_slug(@blogpost.slug).should == @blogpost
|
34
|
+
end
|
35
|
+
|
36
|
+
should "be able to find by id" do
|
37
|
+
BlogPost.by_slug(@blogpost.id).should == @blogpost
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class StorageTest < Test::Unit::TestCase
|
4
|
+
context "Storing files" do
|
5
|
+
setup do
|
6
|
+
@avatar = Avatar.create
|
7
|
+
@data = StringIO.new("my avatar image")
|
8
|
+
end
|
9
|
+
|
10
|
+
should "store the file" do
|
11
|
+
@avatar.put_file("an_avatar.png", @data)
|
12
|
+
data = Avatar.find(@avatar.id).fetch_file("an_avatar.png").read
|
13
|
+
data.should == "my avatar image"
|
14
|
+
end
|
15
|
+
|
16
|
+
should "close the file after storing" do
|
17
|
+
@avatar.put_file("an_avatar.png", @data)
|
18
|
+
@data.should be_closed
|
19
|
+
end
|
20
|
+
|
21
|
+
context "in attributes" do
|
22
|
+
should "store the given file" do
|
23
|
+
@avatar.data = @data
|
24
|
+
@avatar.save!
|
25
|
+
@avatar.data.should_not be_nil
|
26
|
+
@avatar.data.read.should == "my avatar image"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "with new objects" do
|
31
|
+
setup do
|
32
|
+
@avatar = Avatar.new
|
33
|
+
end
|
34
|
+
|
35
|
+
should "store the file after saving" do
|
36
|
+
@avatar.put_file("an_avatar.png", @data)
|
37
|
+
@avatar.save
|
38
|
+
@avatar.fetch_file("an_avatar.png").read.should == "my avatar image"
|
39
|
+
end
|
40
|
+
|
41
|
+
should "store not the file if object is new" do
|
42
|
+
@avatar.put_file("an_avatar.png", @data)
|
43
|
+
@avatar.fetch_file("an_avatar.png").should be_nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "Fetching files" do
|
49
|
+
setup do
|
50
|
+
@avatar = Avatar.create
|
51
|
+
@data = StringIO.new("my avatar image")
|
52
|
+
end
|
53
|
+
|
54
|
+
should "fetch the list of files" do
|
55
|
+
@avatar.put_file("file1", StringIO.new("data1"))
|
56
|
+
@avatar.put_file("file2", StringIO.new("data2"))
|
57
|
+
@avatar.put_file("file3", StringIO.new("data3"))
|
58
|
+
file_names = @avatar.files.map { |f| f.filename }
|
59
|
+
file_names.should include("file1")
|
60
|
+
file_names.should include("file2")
|
61
|
+
file_names.should include("file3")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/test/test_update.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class UpdateTest < Test::Unit::TestCase
|
4
|
+
should "only update the given white listed attributes" do
|
5
|
+
event = Event.new(:password => "original")
|
6
|
+
start_date = Time.zone.now
|
7
|
+
end_date = start_date.tomorrow
|
8
|
+
|
9
|
+
event.safe_update(%w[start_date end_date], {"start_date" => start_date,
|
10
|
+
"end_date" => end_date,
|
11
|
+
"password" => "hacked"})
|
12
|
+
event.password.should == "original"
|
13
|
+
event.start_date.to_s.should == start_date.to_s
|
14
|
+
event.end_date.to_s.should == end_date.to_s
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class OpenStructTest < Test::Unit::TestCase
|
4
|
+
def from_db
|
5
|
+
UserConfig.find(@config.id)
|
6
|
+
end
|
7
|
+
|
8
|
+
context "working with sets" do
|
9
|
+
setup do
|
10
|
+
@config = UserConfig.create!(:entries => OpenStruct.new({}))
|
11
|
+
end
|
12
|
+
|
13
|
+
should "allow to add new keys" do
|
14
|
+
@config.entries.new_key = "my new key"
|
15
|
+
@config.save!
|
16
|
+
from_db.entries.new_key.should == "my new key"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class SetTest < Test::Unit::TestCase
|
4
|
+
def from_db
|
5
|
+
Recipe.find(@recipe.id)
|
6
|
+
end
|
7
|
+
|
8
|
+
context "working with sets" do
|
9
|
+
setup do
|
10
|
+
@recipe = Recipe.create!(:ingredients => %w[salt sugar water salt sugar water])
|
11
|
+
end
|
12
|
+
|
13
|
+
should "not have duplicates" do
|
14
|
+
from_db.ingredients.size.should == 3
|
15
|
+
from_db.ingredients.should include("salt")
|
16
|
+
from_db.ingredients.should include("sugar")
|
17
|
+
from_db.ingredients.should include("water")
|
18
|
+
end
|
19
|
+
|
20
|
+
should "not add duplicates" do
|
21
|
+
original_size = @recipe.ingredients.size
|
22
|
+
@recipe.ingredients << "salt"
|
23
|
+
@recipe.ingredients.size.should == original_size
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TimestampTest < Test::Unit::TestCase
|
4
|
+
def from_db
|
5
|
+
Event.find(@event.id)
|
6
|
+
end
|
7
|
+
|
8
|
+
context "working with timestamps" do
|
9
|
+
setup do
|
10
|
+
Event.delete_all
|
11
|
+
|
12
|
+
Time.zone = 'UTC'
|
13
|
+
@start_time = Time.zone.parse('01-01-2009')
|
14
|
+
@end_time = @start_time.tomorrow
|
15
|
+
|
16
|
+
@event = Event.create!(:start_date => @start_time, :end_date => @end_time)
|
17
|
+
end
|
18
|
+
|
19
|
+
should "store the date" do
|
20
|
+
from_db.start_date.to_s.should == @start_time.to_s
|
21
|
+
end
|
22
|
+
|
23
|
+
should "be able to convert the time to the given timezone" do
|
24
|
+
Time.zone = 'Hawaii'
|
25
|
+
from_db.start_date.to_s.should == "2008-12-31 14:00:00 -1000"
|
26
|
+
end
|
27
|
+
|
28
|
+
should "be able to compare dates" do
|
29
|
+
start_time = @start_time.tomorrow.tomorrow
|
30
|
+
end_time = start_time.tomorrow
|
31
|
+
|
32
|
+
@event2 = Event.create!(:start_date => start_time, :end_datime => end_time)
|
33
|
+
|
34
|
+
Event.count.should == 2
|
35
|
+
events = Event.find(:all, :$where => ("this.start_date >= %d && this.start_date <= %d" % [@event.start_date.yesterday.to_i, @event2.start_date.yesterday.to_i]))
|
36
|
+
|
37
|
+
events.should == [@event]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
metadata
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mongomapper_ext
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- David A. Cuadrado
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-25 00:00:00 -05:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: mongo_mapper
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.5.6
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: ruby-stemmer
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.5.3
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: uuidtools
|
37
|
+
type: :runtime
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 2.0.0
|
44
|
+
version:
|
45
|
+
- !ruby/object:Gem::Dependency
|
46
|
+
name: shoulda
|
47
|
+
type: :development
|
48
|
+
version_requirement:
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 2.10.2
|
54
|
+
version:
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: jnunemaker-matchy
|
57
|
+
type: :development
|
58
|
+
version_requirement:
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - "="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: 0.4.0
|
64
|
+
version:
|
65
|
+
- !ruby/object:Gem::Dependency
|
66
|
+
name: mocha
|
67
|
+
type: :development
|
68
|
+
version_requirement:
|
69
|
+
version_requirements: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: 0.9.4
|
74
|
+
version:
|
75
|
+
description: MongoMapper extensions
|
76
|
+
email: krawek@gmail.com
|
77
|
+
executables: []
|
78
|
+
|
79
|
+
extensions: []
|
80
|
+
|
81
|
+
extra_rdoc_files:
|
82
|
+
- LICENSE
|
83
|
+
- README.rdoc
|
84
|
+
files:
|
85
|
+
- .document
|
86
|
+
- .gitignore
|
87
|
+
- LICENSE
|
88
|
+
- README.rdoc
|
89
|
+
- Rakefile
|
90
|
+
- VERSION
|
91
|
+
- examples/filter.rb
|
92
|
+
- examples/helper.rb
|
93
|
+
- examples/slugizer.rb
|
94
|
+
- examples/storage.rb
|
95
|
+
- examples/types.rb
|
96
|
+
- examples/update.rb
|
97
|
+
- lib/mongomapper_ext.rb
|
98
|
+
- lib/mongomapper_ext/file.rb
|
99
|
+
- lib/mongomapper_ext/filter.rb
|
100
|
+
- lib/mongomapper_ext/slugizer.rb
|
101
|
+
- lib/mongomapper_ext/storage.rb
|
102
|
+
- lib/mongomapper_ext/types/open_struct.rb
|
103
|
+
- lib/mongomapper_ext/types/set.rb
|
104
|
+
- lib/mongomapper_ext/types/timestamp.rb
|
105
|
+
- lib/mongomapper_ext/update.rb
|
106
|
+
- test/helper.rb
|
107
|
+
- test/models.rb
|
108
|
+
- test/support/custom_matchers.rb
|
109
|
+
- test/test_filter.rb
|
110
|
+
- test/test_slugizer.rb
|
111
|
+
- test/test_storage.rb
|
112
|
+
- test/test_update.rb
|
113
|
+
- test/types/test_open_struct.rb
|
114
|
+
- test/types/test_set.rb
|
115
|
+
- test/types/test_timestamp.rb
|
116
|
+
has_rdoc: true
|
117
|
+
homepage: http://github.com/dcu/mongomapper_ext
|
118
|
+
licenses: []
|
119
|
+
|
120
|
+
post_install_message:
|
121
|
+
rdoc_options:
|
122
|
+
- --charset=UTF-8
|
123
|
+
require_paths:
|
124
|
+
- lib
|
125
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - ">="
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: "0"
|
130
|
+
version:
|
131
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: "0"
|
136
|
+
version:
|
137
|
+
requirements: []
|
138
|
+
|
139
|
+
rubyforge_project:
|
140
|
+
rubygems_version: 1.3.5
|
141
|
+
signing_key:
|
142
|
+
specification_version: 3
|
143
|
+
summary: MongoMapper extensions
|
144
|
+
test_files:
|
145
|
+
- test/test_slugizer.rb
|
146
|
+
- test/test_filter.rb
|
147
|
+
- test/test_storage.rb
|
148
|
+
- test/test_update.rb
|
149
|
+
- test/support/custom_matchers.rb
|
150
|
+
- test/models.rb
|
151
|
+
- test/helper.rb
|
152
|
+
- test/types/test_open_struct.rb
|
153
|
+
- test/types/test_timestamp.rb
|
154
|
+
- test/types/test_set.rb
|
155
|
+
- examples/types.rb
|
156
|
+
- examples/filter.rb
|
157
|
+
- examples/update.rb
|
158
|
+
- examples/storage.rb
|
159
|
+
- examples/helper.rb
|
160
|
+
- examples/slugizer.rb
|