mongomodel 0.2.6 → 0.2.7
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/Gemfile +5 -6
- data/Rakefile +3 -4
- data/lib/mongomodel/concerns/properties.rb +15 -9
- data/lib/mongomodel/document/optimistic_locking.rb +1 -1
- data/lib/mongomodel/document/persistence.rb +2 -2
- data/lib/mongomodel/document/validations/uniqueness.rb +65 -36
- data/lib/mongomodel/support/map.rb +4 -0
- data/lib/mongomodel/support/mongo_options.rb +1 -1
- data/lib/mongomodel/support/scope/query_methods.rb +6 -0
- data/lib/mongomodel/support/types/time.rb +2 -3
- data/lib/mongomodel/version.rb +1 -1
- data/mongomodel.gemspec +11 -14
- data/spec/mongomodel/attributes/store_spec.rb +5 -0
- data/spec/mongomodel/concerns/properties_spec.rb +13 -0
- data/spec/mongomodel/support/map_spec.rb +16 -4
- data/spec/mongomodel/support/scope_spec.rb +4 -0
- data/spec/support/models.rb +16 -0
- metadata +16 -32
data/Gemfile
CHANGED
@@ -1,12 +1,11 @@
|
|
1
1
|
source "http://rubygems.org"
|
2
|
-
git "git://github.com/rails/rails.git"
|
3
2
|
|
4
|
-
gem "activemodel", "
|
5
|
-
gem "activesupport", "
|
3
|
+
gem "activemodel", "~> 3.0.0"
|
4
|
+
gem "activesupport", "~> 3.0.0"
|
6
5
|
gem "tzinfo"
|
7
6
|
|
8
|
-
gem "mongo", '
|
9
|
-
gem "bson", '
|
10
|
-
gem "bson_ext", '
|
7
|
+
gem "mongo", '= 1.0.7'
|
8
|
+
gem "bson", '= 1.0.7'
|
9
|
+
gem "bson_ext", '= 1.0.7'
|
11
10
|
|
12
11
|
gem "rspec"
|
data/Rakefile
CHANGED
@@ -46,10 +46,9 @@ begin
|
|
46
46
|
gem.authors = ["Sam Pohlenz"]
|
47
47
|
gem.version = MongoModel::VERSION
|
48
48
|
|
49
|
-
gem.add_dependency('activesupport', '
|
50
|
-
gem.add_dependency('activemodel', '
|
51
|
-
gem.add_dependency('mongo', '
|
52
|
-
gem.add_dependency('bson', '>= 1.0')
|
49
|
+
gem.add_dependency('activesupport', '~> 3.0.0')
|
50
|
+
gem.add_dependency('activemodel', '~> 3.0.0')
|
51
|
+
gem.add_dependency('mongo', '~> 1.0.7')
|
53
52
|
gem.add_development_dependency('rspec', '>= 1.3.0')
|
54
53
|
end
|
55
54
|
|
@@ -14,7 +14,9 @@ module MongoModel
|
|
14
14
|
|
15
15
|
module ClassMethods
|
16
16
|
def property(name, type, options={})
|
17
|
-
properties[name.to_sym] = Property.new(name, type, options)
|
17
|
+
properties[name.to_sym] = Property.new(name, type, options).tap do |property|
|
18
|
+
include type.mongomodel_accessors(property) if type.respond_to?(:mongomodel_accessors)
|
19
|
+
end
|
18
20
|
end
|
19
21
|
|
20
22
|
def model_properties
|
@@ -40,15 +42,19 @@ module MongoModel
|
|
40
42
|
end
|
41
43
|
|
42
44
|
def default(instance)
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
45
|
+
if options.key?(:default)
|
46
|
+
default = options[:default]
|
47
|
+
|
48
|
+
if default.respond_to?(:call)
|
49
|
+
case default.arity
|
50
|
+
when 0 then default.call
|
51
|
+
else default.call(instance)
|
52
|
+
end
|
53
|
+
else
|
54
|
+
default.duplicable? ? default.dup : default
|
49
55
|
end
|
50
|
-
|
51
|
-
|
56
|
+
elsif type.respond_to?(:mongomodel_default)
|
57
|
+
type.mongomodel_default(instance)
|
52
58
|
end
|
53
59
|
end
|
54
60
|
|
@@ -31,7 +31,7 @@ module MongoModel
|
|
31
31
|
if locking_enabled? && _lock_version > 1
|
32
32
|
begin
|
33
33
|
collection.update({ '_id' => id, '_lock_version' => _lock_version-1 }, to_mongo)
|
34
|
-
success = database.
|
34
|
+
success = database.get_last_error['updatedExisting']
|
35
35
|
|
36
36
|
self._lock_version -= 1 unless success
|
37
37
|
|
@@ -68,10 +68,10 @@ module MongoModel
|
|
68
68
|
self.class.database
|
69
69
|
end
|
70
70
|
|
71
|
-
# Generate a new BSON::
|
71
|
+
# Generate a new BSON::ObjectId for the record.
|
72
72
|
# Override in subclasses for custom ID generation.
|
73
73
|
def generate_id
|
74
|
-
::BSON::
|
74
|
+
::BSON::ObjectId.new.to_s
|
75
75
|
end
|
76
76
|
|
77
77
|
module ClassMethods
|
@@ -1,6 +1,70 @@
|
|
1
|
+
require 'active_support/core_ext/array/wrap'
|
2
|
+
|
1
3
|
module MongoModel
|
2
4
|
module DocumentExtensions
|
3
5
|
module Validations
|
6
|
+
class UniquenessValidator < ActiveModel::EachValidator
|
7
|
+
def initialize(options)
|
8
|
+
super(options.reverse_merge(:case_sensitive => true))
|
9
|
+
end
|
10
|
+
|
11
|
+
def setup(klass)
|
12
|
+
@klass = klass
|
13
|
+
|
14
|
+
# Enable safety checks on save
|
15
|
+
klass.save_safely = true
|
16
|
+
|
17
|
+
# Create unique indexes to deal with race condition
|
18
|
+
attributes.each do |attr_name|
|
19
|
+
if options[:case_sensitive]
|
20
|
+
klass.index *[attr_name] + Array.wrap(options[:scope]) << { :unique => true }
|
21
|
+
else
|
22
|
+
lowercase_key = "_lowercase_#{attr_name}"
|
23
|
+
klass.before_save { attributes[lowercase_key] = send(attr_name).downcase }
|
24
|
+
klass.index *[lowercase_key] + Array.wrap(options[:scope]) << { :unique => true }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def validate_each(record, attribute, value)
|
30
|
+
finder_class = find_finder_class_for(record)
|
31
|
+
unique_scope = finder_class.scoped
|
32
|
+
|
33
|
+
if options[:case_sensitive] || !value.is_a?(String)
|
34
|
+
unique_scope = unique_scope.where(attribute => value)
|
35
|
+
else
|
36
|
+
unique_scope = unique_scope.where("_lowercase_#{attribute}" => value.downcase)
|
37
|
+
end
|
38
|
+
|
39
|
+
Array.wrap(options[:scope]).each do |scope|
|
40
|
+
unique_scope = unique_scope.where(scope => record.send(scope))
|
41
|
+
end
|
42
|
+
|
43
|
+
unique_scope = unique_scope.where(:id.ne => record.id) unless record.new_record?
|
44
|
+
|
45
|
+
if unique_scope.any?
|
46
|
+
record.errors.add(attribute, :taken, :message => options[:message], :value => value)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
# The check for an existing value should be run from a class that
|
53
|
+
# isn't abstract. This means working down from the current class
|
54
|
+
# (self), to the first non-abstract class. Since classes don't know
|
55
|
+
# their subclasses, we have to build the hierarchy between self and
|
56
|
+
# the record's class.
|
57
|
+
def find_finder_class_for(record) #:nodoc:
|
58
|
+
class_hierarchy = [record.class]
|
59
|
+
|
60
|
+
while class_hierarchy.first != @klass
|
61
|
+
class_hierarchy.insert(0, class_hierarchy.first.superclass)
|
62
|
+
end
|
63
|
+
|
64
|
+
class_hierarchy.detect { |klass| !klass.abstract_class? }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
4
68
|
module ClassMethods
|
5
69
|
# Validates whether the value of the specified attributes are unique across the system. Useful for making sure that only one user
|
6
70
|
# can be named "davidhh".
|
@@ -37,42 +101,7 @@ module MongoModel
|
|
37
101
|
# Note that this validation method does not have the same race condition suffered by ActiveRecord and other ORMs.
|
38
102
|
# A unique index is added to the collection to ensure that the collection never ends up in an invalid state.
|
39
103
|
def validates_uniqueness_of(*attr_names)
|
40
|
-
|
41
|
-
configuration.update(attr_names.extract_options!)
|
42
|
-
|
43
|
-
# Enable safety checks on save
|
44
|
-
self.save_safely = true
|
45
|
-
|
46
|
-
# Create unique indexes to deal with race condition
|
47
|
-
attr_names.each do |attr_name|
|
48
|
-
if configuration[:case_sensitive]
|
49
|
-
index *[attr_name] + Array(configuration[:scope]) << { :unique => true }
|
50
|
-
else
|
51
|
-
lowercase_key = "_lowercase_#{attr_name}"
|
52
|
-
before_save { attributes[lowercase_key] = send(attr_name).downcase }
|
53
|
-
index *[lowercase_key] + Array(configuration[:scope]) << { :unique => true }
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
validates_each(attr_names, configuration) do |record, attr_name, value|
|
58
|
-
unique_scope = scoped
|
59
|
-
|
60
|
-
if configuration[:case_sensitive] || !value.is_a?(String)
|
61
|
-
unique_scope = unique_scope.where(attr_name => value)
|
62
|
-
else
|
63
|
-
unique_scope = unique_scope.where("_lowercase_#{attr_name}" => value.downcase)
|
64
|
-
end
|
65
|
-
|
66
|
-
Array(configuration[:scope]).each do |scope|
|
67
|
-
unique_scope = unique_scope.where(scope => record.send(scope))
|
68
|
-
end
|
69
|
-
|
70
|
-
unique_scope = unique_scope.where(:id.ne => record.id) unless record.new_record?
|
71
|
-
|
72
|
-
if unique_scope.any?
|
73
|
-
record.errors.add(attr_name, :taken, :default => configuration[:message], :value => value)
|
74
|
-
end
|
75
|
-
end
|
104
|
+
validates_with UniquenessValidator, _merge_attributes(attr_names)
|
76
105
|
end
|
77
106
|
end
|
78
107
|
end
|
@@ -71,7 +71,7 @@ module MongoModel
|
|
71
71
|
|
72
72
|
def add_type_to_selector
|
73
73
|
unless selector['_type'] || @model.superclass.abstract_class?
|
74
|
-
selector['_type'] = { '$in' => [@model.to_s] + @model.subclasses }
|
74
|
+
selector['_type'] = { '$in' => [@model.to_s] + @model.subclasses.map { |m| m.to_s } }
|
75
75
|
end
|
76
76
|
end
|
77
77
|
end
|
@@ -31,6 +31,12 @@ module MongoModel
|
|
31
31
|
CEVAL
|
32
32
|
end
|
33
33
|
|
34
|
+
def from(value, &block)
|
35
|
+
new_scope = clone
|
36
|
+
new_scope.from_value = value.is_a?(String) ? klass.database.collection(value) : value
|
37
|
+
new_scope
|
38
|
+
end
|
39
|
+
|
34
40
|
def reverse_order
|
35
41
|
if order_values.empty?
|
36
42
|
order(:id.desc)
|
@@ -5,9 +5,8 @@ module MongoModel
|
|
5
5
|
module Types
|
6
6
|
class Time < Object
|
7
7
|
def cast(value)
|
8
|
-
time = value.to_time
|
9
|
-
|
10
|
-
::Time.at((time.to_f * 1000).floor / 1000.0)
|
8
|
+
time = value.to_time
|
9
|
+
time.change(:usec => (time.usec / 1000.0).floor * 1000)
|
11
10
|
rescue
|
12
11
|
nil
|
13
12
|
end
|
data/lib/mongomodel/version.rb
CHANGED
data/mongomodel.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{mongomodel}
|
8
|
-
s.version = "0.2.
|
8
|
+
s.version = "0.2.7"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Sam Pohlenz"]
|
12
|
-
s.date = %q{2010-
|
12
|
+
s.date = %q{2010-09-05}
|
13
13
|
s.default_executable = %q{console}
|
14
14
|
s.description = %q{MongoModel is a MongoDB ORM for Ruby/Rails similar to ActiveRecord and DataMapper.}
|
15
15
|
s.email = %q{sam@sampohlenz.com}
|
@@ -217,23 +217,20 @@ Gem::Specification.new do |s|
|
|
217
217
|
s.specification_version = 3
|
218
218
|
|
219
219
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
220
|
-
s.add_runtime_dependency(%q<activesupport>, ["
|
221
|
-
s.add_runtime_dependency(%q<activemodel>, ["
|
222
|
-
s.add_runtime_dependency(%q<mongo>, ["
|
223
|
-
s.add_runtime_dependency(%q<bson>, [">= 1.0"])
|
220
|
+
s.add_runtime_dependency(%q<activesupport>, ["~> 3.0.0"])
|
221
|
+
s.add_runtime_dependency(%q<activemodel>, ["~> 3.0.0"])
|
222
|
+
s.add_runtime_dependency(%q<mongo>, ["~> 1.0.7"])
|
224
223
|
s.add_development_dependency(%q<rspec>, [">= 1.3.0"])
|
225
224
|
else
|
226
|
-
s.add_dependency(%q<activesupport>, ["
|
227
|
-
s.add_dependency(%q<activemodel>, ["
|
228
|
-
s.add_dependency(%q<mongo>, ["
|
229
|
-
s.add_dependency(%q<bson>, [">= 1.0"])
|
225
|
+
s.add_dependency(%q<activesupport>, ["~> 3.0.0"])
|
226
|
+
s.add_dependency(%q<activemodel>, ["~> 3.0.0"])
|
227
|
+
s.add_dependency(%q<mongo>, ["~> 1.0.7"])
|
230
228
|
s.add_dependency(%q<rspec>, [">= 1.3.0"])
|
231
229
|
end
|
232
230
|
else
|
233
|
-
s.add_dependency(%q<activesupport>, ["
|
234
|
-
s.add_dependency(%q<activemodel>, ["
|
235
|
-
s.add_dependency(%q<mongo>, ["
|
236
|
-
s.add_dependency(%q<bson>, [">= 1.0"])
|
231
|
+
s.add_dependency(%q<activesupport>, ["~> 3.0.0"])
|
232
|
+
s.add_dependency(%q<activemodel>, ["~> 3.0.0"])
|
233
|
+
s.add_dependency(%q<mongo>, ["~> 1.0.7"])
|
237
234
|
s.add_dependency(%q<rspec>, [">= 1.3.0"])
|
238
235
|
end
|
239
236
|
end
|
@@ -15,6 +15,7 @@ module MongoModel
|
|
15
15
|
properties[:date] = MongoModel::Properties::Property.new(:date, Date)
|
16
16
|
properties[:time] = MongoModel::Properties::Property.new(:time, Time)
|
17
17
|
properties[:custom] = MongoModel::Properties::Property.new(:custom, CustomClass)
|
18
|
+
properties[:custom_default] = MongoModel::Properties::Property.new(:custom_default, CustomClassWithDefault)
|
18
19
|
properties[:default] = MongoModel::Properties::Property.new(:default, String, :default => 'Default')
|
19
20
|
properties[:as] = MongoModel::Properties::Property.new(:as, String, :as => '_custom_as')
|
20
21
|
properties
|
@@ -28,6 +29,10 @@ module MongoModel
|
|
28
29
|
subject[:default].should == 'Default'
|
29
30
|
end
|
30
31
|
|
32
|
+
it "should set default property value using mongomodel_default if defined by class" do
|
33
|
+
subject[:custom_default].should == CustomClassWithDefault.new("Custom class default")
|
34
|
+
end
|
35
|
+
|
31
36
|
describe "setting to nil" do
|
32
37
|
specify "all property types should be nullable" do
|
33
38
|
properties.keys.each do |property|
|
@@ -52,5 +52,18 @@ module MongoModel
|
|
52
52
|
factory.manager.should be_nil
|
53
53
|
end
|
54
54
|
end
|
55
|
+
|
56
|
+
describe "when using a property type that defines #mongomodel_accessors" do
|
57
|
+
define_class(:ParentClass, described_class) do
|
58
|
+
property :custom, CustomClassWithAccessors
|
59
|
+
end
|
60
|
+
|
61
|
+
subject { ParentClass.new }
|
62
|
+
|
63
|
+
it "should include methods from the module" do
|
64
|
+
subject.should respond_to(:custom_accessor)
|
65
|
+
subject.custom_accessor.should == "Custom accessor method"
|
66
|
+
end
|
67
|
+
end
|
55
68
|
end
|
56
69
|
end
|
@@ -101,8 +101,14 @@ module MongoModel
|
|
101
101
|
subject.value?(456).should be_true
|
102
102
|
end
|
103
103
|
|
104
|
-
|
105
|
-
|
104
|
+
if Hash.method_defined?(:key)
|
105
|
+
it "should cast values on #key" do
|
106
|
+
subject.key(456).should == "123"
|
107
|
+
end
|
108
|
+
else
|
109
|
+
it "should cast values on #index" do
|
110
|
+
subject.index(456).should == "123"
|
111
|
+
end
|
106
112
|
end
|
107
113
|
|
108
114
|
it "should cast key/values on #replace" do
|
@@ -177,8 +183,14 @@ module MongoModel
|
|
177
183
|
subject.value?("Another").should be_true
|
178
184
|
end
|
179
185
|
|
180
|
-
|
181
|
-
|
186
|
+
if Hash.method_defined?(:key)
|
187
|
+
it "should cast values on #key" do
|
188
|
+
subject.key("First").should == :abc
|
189
|
+
end
|
190
|
+
else
|
191
|
+
it "should cast values on #index" do
|
192
|
+
subject.index("First").should == :abc
|
193
|
+
end
|
182
194
|
end
|
183
195
|
|
184
196
|
it "should cast key/values on #replace" do
|
@@ -309,6 +309,10 @@ module MongoModel
|
|
309
309
|
it "should override collection" do
|
310
310
|
subject.from(NotAPost.collection).collection.should == NotAPost.collection
|
311
311
|
end
|
312
|
+
|
313
|
+
it "should allow collection to be set using string" do
|
314
|
+
subject.from(NotAPost.collection.name).collection.name.should == NotAPost.collection.name
|
315
|
+
end
|
312
316
|
end
|
313
317
|
|
314
318
|
describe "#first" do
|
data/spec/support/models.rb
CHANGED
@@ -21,3 +21,19 @@ class CustomClass
|
|
21
21
|
new(value.to_s)
|
22
22
|
end
|
23
23
|
end
|
24
|
+
|
25
|
+
class CustomClassWithDefault < CustomClass
|
26
|
+
def self.mongomodel_default(doc)
|
27
|
+
new("Custom class default")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class CustomClassWithAccessors < CustomClass
|
32
|
+
def self.mongomodel_accessors(property)
|
33
|
+
Module.new do
|
34
|
+
define_method(:custom_accessor) do
|
35
|
+
"Custom accessor method"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mongomodel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 25
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 2
|
9
|
-
-
|
10
|
-
version: 0.2.
|
9
|
+
- 7
|
10
|
+
version: 0.2.7
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Sam Pohlenz
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-
|
18
|
+
date: 2010-09-05 00:00:00 +09:30
|
19
19
|
default_executable: console
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -24,15 +24,14 @@ dependencies:
|
|
24
24
|
requirement: &id001 !ruby/object:Gem::Requirement
|
25
25
|
none: false
|
26
26
|
requirements:
|
27
|
-
- -
|
27
|
+
- - ~>
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
hash:
|
29
|
+
hash: 7
|
30
30
|
segments:
|
31
31
|
- 3
|
32
32
|
- 0
|
33
33
|
- 0
|
34
|
-
|
35
|
-
version: 3.0.0.beta4
|
34
|
+
version: 3.0.0
|
36
35
|
type: :runtime
|
37
36
|
version_requirements: *id001
|
38
37
|
- !ruby/object:Gem::Dependency
|
@@ -41,15 +40,14 @@ dependencies:
|
|
41
40
|
requirement: &id002 !ruby/object:Gem::Requirement
|
42
41
|
none: false
|
43
42
|
requirements:
|
44
|
-
- -
|
43
|
+
- - ~>
|
45
44
|
- !ruby/object:Gem::Version
|
46
|
-
hash:
|
45
|
+
hash: 7
|
47
46
|
segments:
|
48
47
|
- 3
|
49
48
|
- 0
|
50
49
|
- 0
|
51
|
-
|
52
|
-
version: 3.0.0.beta4
|
50
|
+
version: 3.0.0
|
53
51
|
type: :runtime
|
54
52
|
version_requirements: *id002
|
55
53
|
- !ruby/object:Gem::Dependency
|
@@ -58,34 +56,20 @@ dependencies:
|
|
58
56
|
requirement: &id003 !ruby/object:Gem::Requirement
|
59
57
|
none: false
|
60
58
|
requirements:
|
61
|
-
- -
|
59
|
+
- - ~>
|
62
60
|
- !ruby/object:Gem::Version
|
63
|
-
hash:
|
61
|
+
hash: 25
|
64
62
|
segments:
|
65
63
|
- 1
|
66
64
|
- 0
|
67
|
-
|
65
|
+
- 7
|
66
|
+
version: 1.0.7
|
68
67
|
type: :runtime
|
69
68
|
version_requirements: *id003
|
70
|
-
- !ruby/object:Gem::Dependency
|
71
|
-
name: bson
|
72
|
-
prerelease: false
|
73
|
-
requirement: &id004 !ruby/object:Gem::Requirement
|
74
|
-
none: false
|
75
|
-
requirements:
|
76
|
-
- - ">="
|
77
|
-
- !ruby/object:Gem::Version
|
78
|
-
hash: 15
|
79
|
-
segments:
|
80
|
-
- 1
|
81
|
-
- 0
|
82
|
-
version: "1.0"
|
83
|
-
type: :runtime
|
84
|
-
version_requirements: *id004
|
85
69
|
- !ruby/object:Gem::Dependency
|
86
70
|
name: rspec
|
87
71
|
prerelease: false
|
88
|
-
requirement: &
|
72
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
89
73
|
none: false
|
90
74
|
requirements:
|
91
75
|
- - ">="
|
@@ -97,7 +81,7 @@ dependencies:
|
|
97
81
|
- 0
|
98
82
|
version: 1.3.0
|
99
83
|
type: :development
|
100
|
-
version_requirements: *
|
84
|
+
version_requirements: *id004
|
101
85
|
description: MongoModel is a MongoDB ORM for Ruby/Rails similar to ActiveRecord and DataMapper.
|
102
86
|
email: sam@sampohlenz.com
|
103
87
|
executables:
|