mongodb_model 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/mongodb_model/assignment.rb +65 -0
- data/lib/mongodb_model/attribute_convertors.rb +54 -0
- data/lib/mongodb_model/callbacks.rb +36 -0
- data/lib/mongodb_model/crud.rb +57 -0
- data/lib/mongodb_model/db.rb +53 -0
- data/lib/mongodb_model/gems.rb +7 -0
- data/lib/mongodb_model/misc.rb +33 -0
- data/lib/mongodb_model/model.rb +11 -0
- data/lib/mongodb_model/query.rb +36 -0
- data/lib/mongodb_model/scope.rb +99 -0
- data/lib/mongodb_model/spec.rb +12 -0
- data/lib/mongodb_model/support/types.rb +110 -0
- data/lib/mongodb_model/validation.rb +5 -0
- data/lib/mongodb_model.rb +36 -0
- data/readme.md +72 -0
- data/spec/assignment_spec.rb +80 -0
- data/spec/attribute_convertors_spec.rb +73 -0
- data/spec/callbacks_spec.rb +47 -0
- data/spec/crud_spec.rb +151 -0
- data/spec/db_spec.rb +63 -0
- data/spec/misc_spec.rb +58 -0
- data/spec/query_spec.rb +46 -0
- data/spec/scope_spec.rb +149 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/validatable2_spec.rb +40 -0
- data/spec/validation_spec.rb +37 -0
- metadata +71 -2
@@ -0,0 +1,65 @@
|
|
1
|
+
module Mongo::Model::Assignment
|
2
|
+
class Dsl < BasicObject
|
3
|
+
def initialize
|
4
|
+
@attributes = {}
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.const_missing name
|
8
|
+
# BasicObject doesn't have access to any constants like String, Symbol, ...
|
9
|
+
::Object.const_get name
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_h; attributes end
|
13
|
+
|
14
|
+
protected
|
15
|
+
attr_reader :attributes
|
16
|
+
|
17
|
+
def method_missing attribute_name, *args
|
18
|
+
attribute_name.must_be.a Symbol
|
19
|
+
|
20
|
+
args.size.must_be.in 1..2
|
21
|
+
if args.first.is_a? Class
|
22
|
+
type, mass_assignment = args
|
23
|
+
mass_assignment ||= false
|
24
|
+
type.must.respond_to :cast
|
25
|
+
else
|
26
|
+
type, mass_assignment = nil, args.first
|
27
|
+
end
|
28
|
+
|
29
|
+
attributes[attribute_name] = [type, mass_assignment]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def set attributes, options = {}
|
34
|
+
if rules = self.class._assign
|
35
|
+
force = options[:force]
|
36
|
+
attributes.each do |n, v|
|
37
|
+
n = n.to_sym
|
38
|
+
if rule = rules[n]
|
39
|
+
type, mass_assignment = rule
|
40
|
+
if mass_assignment or force
|
41
|
+
v = type.cast(v) if type
|
42
|
+
send "#{n}=", v
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
else
|
47
|
+
attributes.each{|n, v| send "#{n}=", v}
|
48
|
+
end
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
def set! attributes, options = {}
|
53
|
+
set attributes, options.merge(force: true)
|
54
|
+
end
|
55
|
+
|
56
|
+
module ClassMethods
|
57
|
+
inheritable_accessor :_assign, nil
|
58
|
+
|
59
|
+
def assign &block
|
60
|
+
dsl = ::Mongo::Model::Assignment::Dsl.new
|
61
|
+
dsl.instance_eval &block
|
62
|
+
self._assign = (_assign || {}).merge dsl.to_h
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module Mongo::Model::AttributeConvertors
|
5
|
+
CONVERTORS = {
|
6
|
+
line: {
|
7
|
+
from_string: -> s {(s || "").split(',').collect{|s| s.strip}},
|
8
|
+
to_string: -> v {v.join(', ')}
|
9
|
+
},
|
10
|
+
column: {
|
11
|
+
from_string: -> s {(s || "").split("\n").collect{|s| s.strip}},
|
12
|
+
to_string: -> v {v.join("\n")}
|
13
|
+
},
|
14
|
+
yaml: {
|
15
|
+
from_string: -> s {YAML.load s rescue {}},
|
16
|
+
to_string: -> v {v.to_yaml.strip}
|
17
|
+
},
|
18
|
+
json: {
|
19
|
+
from_string: -> s {JSON.parse s rescue {}},
|
20
|
+
to_string: -> v {v.to_json.strip}
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
module ClassMethods
|
25
|
+
def available_as_string name, converter_name
|
26
|
+
converter = CONVERTORS[converter_name]
|
27
|
+
raise "unknown converter name :#{converter_name} for :#{name} field!" unless converter
|
28
|
+
|
29
|
+
from_string, to_string = converter[:from_string], converter[:to_string]
|
30
|
+
name_as_string = "#{name}_as_string".to_sym
|
31
|
+
define_method name_as_string do
|
32
|
+
_cache[name_as_string] ||= to_string.call(send(name))
|
33
|
+
end
|
34
|
+
|
35
|
+
define_method "#{name_as_string}=" do |value|
|
36
|
+
_cache.delete name_as_string
|
37
|
+
self.send "#{name}=", from_string.call(value)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def available_as_yaml name
|
42
|
+
raise "delimiter not specified for :#{name} field!" unless delimiter
|
43
|
+
method = "#{name}_as_string"
|
44
|
+
define_method method do
|
45
|
+
self.send(name).join(delimiter)
|
46
|
+
end
|
47
|
+
define_method "#{method}=" do |value|
|
48
|
+
value = (value || "").split(delimiter.strip).collect{|s| s.strip}
|
49
|
+
self.send "#{name}=", value
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Mongo::Model::Callbacks
|
2
|
+
inherit RubyExt::Callbacks
|
3
|
+
|
4
|
+
def _run_callbacks type, method_name
|
5
|
+
if type == :before
|
6
|
+
run_before_callbacks method_name, method: method_name
|
7
|
+
elsif type == :after
|
8
|
+
run_after_callbacks method_name, method: method_name
|
9
|
+
else
|
10
|
+
raise "invalid callback type (#{type})!"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
[:validate, :update, :save, :destroy].each do |method_name|
|
16
|
+
define_method "before_#{method_name}" do |*args, &block|
|
17
|
+
opt = args.extract_options!
|
18
|
+
if block
|
19
|
+
set_callback method_name, :before, opt, &block
|
20
|
+
else
|
21
|
+
opt[:terminator] = false unless opt.include? :terminator
|
22
|
+
args.each{|executor| set_callback method_name, :before, executor, opt}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
define_method "after_#{method_name}" do |*args, &block|
|
27
|
+
opt = args.extract_options!
|
28
|
+
if block
|
29
|
+
set_callback method_name, :after, opt, &block
|
30
|
+
else
|
31
|
+
args.each{|executor| set_callback method_name, :after, executor, opt}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Mongo::Model::Crud
|
2
|
+
def save opts = {}
|
3
|
+
with_collection opts do |collection, opts|
|
4
|
+
collection.save self, opts
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
def save! *args
|
9
|
+
save(*args) || raise(Mongo::Error, "can't save #{self.inspect}!")
|
10
|
+
end
|
11
|
+
|
12
|
+
def destroy opts = {}
|
13
|
+
with_collection opts do |collection, opts|
|
14
|
+
collection.destroy self, opts
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def destroy! *args
|
19
|
+
destroy(*args) || raise(Mongo::Error, "can't destroy #{self.inspect}!")
|
20
|
+
end
|
21
|
+
|
22
|
+
module ClassMethods
|
23
|
+
def build attributes, opts = {}
|
24
|
+
self.new.set attributes, opts
|
25
|
+
end
|
26
|
+
|
27
|
+
def create attributes, opts = {}
|
28
|
+
o = build attributes, opts
|
29
|
+
o.save
|
30
|
+
o
|
31
|
+
end
|
32
|
+
|
33
|
+
def create! attributes, opts = {}
|
34
|
+
o = create attributes
|
35
|
+
raise(Mongo::Error, "can't create #{attributes.inspect}!") if o.new_record?
|
36
|
+
o
|
37
|
+
end
|
38
|
+
|
39
|
+
def destroy_all selector = {}, opts = {}
|
40
|
+
success = true
|
41
|
+
collection = opts[:collection] || self.collection
|
42
|
+
each(selector){|o| success = false unless o.destroy}
|
43
|
+
success
|
44
|
+
end
|
45
|
+
|
46
|
+
def destroy_all! selector = {}, opts = {}
|
47
|
+
destroy_all(selector, opts) || raise(Mongo::Error, "can't destroy #{selector.inspect}!")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
protected
|
52
|
+
def with_collection opts, &block
|
53
|
+
opts = opts.clone
|
54
|
+
collection = opts.delete(:collection) || self.class.collection
|
55
|
+
block.call collection, opts
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Mongo::Model::Db
|
2
|
+
module ClassMethods
|
3
|
+
inheritable_accessor :_db, nil
|
4
|
+
def db= v
|
5
|
+
self._db = if v.is_a? ::Proc
|
6
|
+
v
|
7
|
+
elsif v.is_a? ::Symbol
|
8
|
+
-> {::Mongo::Model.connection.db v.to_s}
|
9
|
+
else
|
10
|
+
-> {v}
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def db *args, &block
|
15
|
+
if block
|
16
|
+
self.db = block
|
17
|
+
elsif !args.empty?
|
18
|
+
args.size.must == 1
|
19
|
+
self.db = args.first
|
20
|
+
else
|
21
|
+
(_db && _db.call) || ::Mongo::Model.db
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
inheritable_accessor :_collection, nil
|
26
|
+
def collection= v
|
27
|
+
self._collection = if v.is_a? ::Proc
|
28
|
+
v
|
29
|
+
elsif v.is_a? ::Symbol
|
30
|
+
-> {db.collection v}
|
31
|
+
else
|
32
|
+
-> {v}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def collection *args, &block
|
37
|
+
if block
|
38
|
+
self.collection = block
|
39
|
+
elsif !args.empty?
|
40
|
+
args.size.must == 1
|
41
|
+
self.collection = args.first
|
42
|
+
else
|
43
|
+
(_collection && _collection.call) || db.collection(default_collection_name)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def default_collection_name
|
48
|
+
first_ancestor_class = ancestors.find{|a| a.is_a? Class} ||
|
49
|
+
raise("can't evaluate default collection name for #{self}!")
|
50
|
+
first_ancestor_class.alias.pluralize.underscore.to_sym
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Mongo::Model::Misc
|
2
|
+
def update_timestamps
|
3
|
+
now = Time.now.utc
|
4
|
+
self.created_at ||= now
|
5
|
+
self.updated_at = now
|
6
|
+
end
|
7
|
+
|
8
|
+
|
9
|
+
def _cache
|
10
|
+
@_cache ||= {}
|
11
|
+
end
|
12
|
+
def _clear_cache
|
13
|
+
@_cache = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def dom_id
|
18
|
+
# new_record? ? "new_#{self.class.name.underscore}" : to_param
|
19
|
+
to_param
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_param
|
23
|
+
(_id || '').to_s
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
module ClassMethods
|
28
|
+
def timestamps!
|
29
|
+
attr_accessor :created_at, :updated_at
|
30
|
+
before_save :update_timestamps
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Mongo::Model::Query
|
2
|
+
module ClassMethods
|
3
|
+
include Mongo::DynamicFinders
|
4
|
+
|
5
|
+
def count selector = {}, opts = {}
|
6
|
+
collection.count selector, opts
|
7
|
+
end
|
8
|
+
|
9
|
+
def first selector = {}, opts = {}
|
10
|
+
collection.first selector, opts
|
11
|
+
end
|
12
|
+
|
13
|
+
def each selector = {}, opts = {}, &block
|
14
|
+
collection.each selector, opts, &block
|
15
|
+
end
|
16
|
+
|
17
|
+
def all selector = {}, opts = {}, &block
|
18
|
+
if block
|
19
|
+
each selector, opts, &block
|
20
|
+
else
|
21
|
+
list = []
|
22
|
+
each(selector, opts){|doc| list << doc}
|
23
|
+
list
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def first! selector = {}, opts = {}
|
28
|
+
first(selector, opts) || raise(Mongo::NotFound, "document with selector #{selector} not found!")
|
29
|
+
end
|
30
|
+
|
31
|
+
def exists? selector = {}, opts = {}
|
32
|
+
count(selector, opts) > 0
|
33
|
+
end
|
34
|
+
alias :exist? :exists?
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Mongo::Model::Scope
|
2
|
+
class ScopeProxy < BasicObject
|
3
|
+
def initialize model, scope
|
4
|
+
@model, @scope = model, scope
|
5
|
+
end
|
6
|
+
|
7
|
+
def class
|
8
|
+
::Mongo::Model::Scope::ScopeProxy
|
9
|
+
end
|
10
|
+
|
11
|
+
def reverse_merge! scope
|
12
|
+
@scope = scope.merge @scope
|
13
|
+
end
|
14
|
+
|
15
|
+
def inspect
|
16
|
+
"#<ScopeProxy:{#{scope.inspect}}>"
|
17
|
+
end
|
18
|
+
alias_method :to_s, :inspect
|
19
|
+
|
20
|
+
protected
|
21
|
+
attr_reader :model, :scope
|
22
|
+
|
23
|
+
def method_missing method, *args, &block
|
24
|
+
model.with_scope scope do
|
25
|
+
result = model.send method, *args, &block
|
26
|
+
result.reverse_merge! scope if result.class == ::Mongo::Model::Scope::ScopeProxy
|
27
|
+
result
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module ClassMethods
|
33
|
+
def current_scope
|
34
|
+
scope, exclusive = Thread.current[:mongo_model_scope]
|
35
|
+
if exclusive
|
36
|
+
scope
|
37
|
+
elsif scope
|
38
|
+
default_scope.merge scope
|
39
|
+
else
|
40
|
+
default_scope
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def with_exclusive_scope options = {}, &block
|
45
|
+
with_scope options, true, &block
|
46
|
+
end
|
47
|
+
|
48
|
+
def with_scope options = {}, exclusive = false, &block
|
49
|
+
previous_options, previous_exclusive = Thread.current[:mongo_model_scope]
|
50
|
+
raise "exclusive scope already applied!" if previous_exclusive
|
51
|
+
|
52
|
+
begin
|
53
|
+
options = previous_options.merge options if previous_options and !exclusive
|
54
|
+
Thread.current[:mongo_model_scope] = [options, exclusive]
|
55
|
+
return block.call
|
56
|
+
ensure
|
57
|
+
Thread.current[:mongo_model_scope] = [previous_options, false]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
inheritable_accessor :_default_scope, -> {{}}
|
62
|
+
def default_scope *args, &block
|
63
|
+
if block
|
64
|
+
self._default_scope = block
|
65
|
+
elsif !args.empty?
|
66
|
+
args.size.must == 1
|
67
|
+
args.first.must_be.a Hash
|
68
|
+
scope = args.first
|
69
|
+
self._default_scope = -> {args.first}
|
70
|
+
else
|
71
|
+
_default_scope.call
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def scope name, options = nil, &block
|
76
|
+
model = self
|
77
|
+
metaclass.define_method name do
|
78
|
+
scope = (block && block.call) || options
|
79
|
+
ScopeProxy.new model, scope
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
#
|
85
|
+
# finders
|
86
|
+
#
|
87
|
+
def count selector = {}, opts = {}
|
88
|
+
super current_scope.merge(selector), opts
|
89
|
+
end
|
90
|
+
|
91
|
+
def first selector = {}, opts = {}
|
92
|
+
super current_scope.merge(selector), opts
|
93
|
+
end
|
94
|
+
|
95
|
+
def each selector = {}, opts = {}, &block
|
96
|
+
super current_scope.merge(selector), opts, &block
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
#
|
2
|
+
# Boolean
|
3
|
+
#
|
4
|
+
module Mongo::Model::BooleanType
|
5
|
+
Mapping = {
|
6
|
+
true => true,
|
7
|
+
'true' => true,
|
8
|
+
'TRUE' => true,
|
9
|
+
'True' => true,
|
10
|
+
't' => true,
|
11
|
+
'T' => true,
|
12
|
+
'1' => true,
|
13
|
+
1 => true,
|
14
|
+
1.0 => true,
|
15
|
+
false => false,
|
16
|
+
'false' => false,
|
17
|
+
'FALSE' => false,
|
18
|
+
'False' => false,
|
19
|
+
'f' => false,
|
20
|
+
'F' => false,
|
21
|
+
'0' => false,
|
22
|
+
0 => false,
|
23
|
+
0.0 => false,
|
24
|
+
nil => nil
|
25
|
+
}
|
26
|
+
|
27
|
+
def cast value
|
28
|
+
if value.is_a? Boolean
|
29
|
+
value
|
30
|
+
else
|
31
|
+
Mapping[value] || false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class Boolean; end unless defined?(Boolean)
|
37
|
+
|
38
|
+
Boolean.extend Mongo::Model::BooleanType
|
39
|
+
|
40
|
+
|
41
|
+
#
|
42
|
+
# Date
|
43
|
+
#
|
44
|
+
require 'date'
|
45
|
+
Date.class_eval do
|
46
|
+
def self.cast value
|
47
|
+
if value.nil? || value == ''
|
48
|
+
nil
|
49
|
+
else
|
50
|
+
date = value.is_a?(::Date) || value.is_a?(::Time) ? value : ::Date.parse(value.to_s)
|
51
|
+
date.to_date
|
52
|
+
end
|
53
|
+
rescue
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
#
|
60
|
+
# Float
|
61
|
+
#
|
62
|
+
Float.class_eval do
|
63
|
+
def self.cast value
|
64
|
+
value.nil? ? nil : value.to_f
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
#
|
70
|
+
# Integer
|
71
|
+
#
|
72
|
+
Integer.class_eval do
|
73
|
+
def self.cast value
|
74
|
+
value_to_i = value.to_i
|
75
|
+
if value_to_i == 0 && value != value_to_i
|
76
|
+
value.to_s =~ /^(0x|0b)?0+/ ? 0 : nil
|
77
|
+
else
|
78
|
+
value_to_i
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
#
|
85
|
+
# String
|
86
|
+
#
|
87
|
+
String.class_eval do
|
88
|
+
def self.cast value
|
89
|
+
value.nil? ? nil : value.to_s
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
#
|
95
|
+
# Time
|
96
|
+
#
|
97
|
+
Time.class_eval do
|
98
|
+
def self.cast value
|
99
|
+
if value.nil? || value == ''
|
100
|
+
nil
|
101
|
+
else
|
102
|
+
# time_class = ::Time.try(:zone).present? ? ::Time.zone : ::Time
|
103
|
+
# time = value.is_a?(::Time) ? value : time_class.parse(value.to_s)
|
104
|
+
# strip milliseconds as Ruby does micro and bson does milli and rounding rounded wrong
|
105
|
+
# at(time.to_i).utc if time
|
106
|
+
|
107
|
+
value.is_a?(::Time) ? value : Date.parse(value.to_s).to_time
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'mongodb_model/gems'
|
2
|
+
|
3
|
+
require 'validatable'
|
4
|
+
require 'i18n'
|
5
|
+
require 'ruby_ext'
|
6
|
+
require 'mongodb/object'
|
7
|
+
|
8
|
+
module Mongo::Model; end
|
9
|
+
|
10
|
+
%w(
|
11
|
+
support/types
|
12
|
+
|
13
|
+
db
|
14
|
+
assignment
|
15
|
+
callbacks
|
16
|
+
validation
|
17
|
+
crud
|
18
|
+
query
|
19
|
+
scope
|
20
|
+
attribute_convertors
|
21
|
+
misc
|
22
|
+
model
|
23
|
+
).each{|f| require "mongodb_model/#{f}"}
|
24
|
+
|
25
|
+
module Mongo
|
26
|
+
module Model
|
27
|
+
inherit Db, Assignment, Callbacks, Validation, Crud, Query, Scope, AttributeConvertors, Misc
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
Mongo.defaults.merge! \
|
32
|
+
symbolize: true,
|
33
|
+
convert_underscore_to_dollar: true,
|
34
|
+
batch_size: 50,
|
35
|
+
multi: true,
|
36
|
+
safe: true
|
data/readme.md
CHANGED
@@ -0,0 +1,72 @@
|
|
1
|
+
Object Model for MongoDB (callbacks, validations, mass-assignment, finders, ...).
|
2
|
+
|
3
|
+
- The same API for pure driver and Models.
|
4
|
+
- Minimum extra abstractions, trying to keep things as close to the MongoDB semantic as possible.
|
5
|
+
- Schema-less, dynamic (with ability to specify types for mass-assignment).
|
6
|
+
- Models can be saved to any collection.
|
7
|
+
- Full support for embedded objects (validations, callbacks, ...).
|
8
|
+
- Scope, default_scope
|
9
|
+
- Doesn't try to mimic ActiveRecord, MongoDB is differrent and this tool designed to get most of it.
|
10
|
+
- Very small, see [code stats][code_stats].
|
11
|
+
|
12
|
+
Other ODM usually try to cover simple but non-standard API of MongoDB behind complex ORM-like abstractions. This tool **exposes simplicity and power of MongoDB and leverages it's differences**.
|
13
|
+
|
14
|
+
``` ruby
|
15
|
+
# Connecting to MongoDB.
|
16
|
+
require 'mongodb/model'
|
17
|
+
Mongo.defaults.merge! symbolize: true, multi: true, safe: true
|
18
|
+
connection = Mongo::Connection.new
|
19
|
+
db = connection.db 'default_test'
|
20
|
+
db.units.drop
|
21
|
+
Mongo::Model.db = db
|
22
|
+
|
23
|
+
# Let's define the game unit.
|
24
|
+
class Unit
|
25
|
+
inherit Mongo::Model
|
26
|
+
collection :units
|
27
|
+
|
28
|
+
attr_accessor :name, :status, :stats
|
29
|
+
|
30
|
+
scope :alive, status: 'alive'
|
31
|
+
|
32
|
+
class Stats
|
33
|
+
inherit Mongo::Model
|
34
|
+
attr_accessor :attack, :life, :shield
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Create.
|
39
|
+
zeratul = Unit.build(name: 'Zeratul', status: 'alive', stats: Unit::Stats.build(attack: 85, life: 300, shield: 100))
|
40
|
+
tassadar = Unit.build(name: 'Tassadar', status: 'dead', stats: Unit::Stats.build(attack: 0, life: 80, shield: 300))
|
41
|
+
|
42
|
+
zeratul.save
|
43
|
+
tassadar.save
|
44
|
+
|
45
|
+
# Udate (we made error - mistakenly set Tassadar's attack as zero, let's fix it).
|
46
|
+
tassadar.stats.attack = 20
|
47
|
+
tassadar.save
|
48
|
+
|
49
|
+
# Querying first & all, there's also :each, the same as :all.
|
50
|
+
Unit.first name: 'Zeratul' # => zeratul
|
51
|
+
Unit.all name: 'Zeratul' # => [zeratul]
|
52
|
+
Unit.all name: 'Zeratul' do |unit|
|
53
|
+
unit # => zeratul
|
54
|
+
end
|
55
|
+
|
56
|
+
# Simple finders (bang versions also availiable).
|
57
|
+
Unit.by_name 'Zeratul' # => zeratul
|
58
|
+
Unit.first_by_name 'Zeratul' # => zeratul
|
59
|
+
Unit.all_by_name 'Zeratul' # => [zeratul]
|
60
|
+
|
61
|
+
# Scopes.
|
62
|
+
Unit.alive.count # => 1
|
63
|
+
Unit.alive.first # => zeratul
|
64
|
+
|
65
|
+
# Callbacks & callbacks on embedded models.
|
66
|
+
|
67
|
+
# Validations.
|
68
|
+
|
69
|
+
# Save model to any collection.
|
70
|
+
```
|
71
|
+
|
72
|
+
Source: examples/model.rb
|