mongodbmodel 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +11 -0
- data/lib/mongo/model.rb +50 -0
- data/lib/mongo/model/assignment.rb +65 -0
- data/lib/mongo/model/attribute_convertors.rb +54 -0
- data/lib/mongo/model/callbacks.rb +26 -0
- data/lib/mongo/model/crud.rb +57 -0
- data/lib/mongo/model/db.rb +53 -0
- data/lib/mongo/model/file_model.rb +27 -0
- data/lib/mongo/model/misc.rb +44 -0
- data/lib/mongo/model/model.rb +13 -0
- data/lib/mongo/model/query.rb +46 -0
- data/lib/mongo/model/query_mixin.rb +45 -0
- data/lib/mongo/model/scope.rb +84 -0
- data/lib/mongo/model/spec.rb +12 -0
- data/lib/mongo/model/support/types.rb +110 -0
- data/lib/mongo/model/validation.rb +41 -0
- data/lib/mongo/model/validation/uniqueness_validator.rb +30 -0
- data/lib/mongodb_model/gems.rb +8 -0
- data/readme.md +72 -0
- data/spec/assignment_spec.rb +80 -0
- data/spec/associations_spec.rb +43 -0
- data/spec/attribute_convertors_spec.rb +73 -0
- data/spec/callbacks_spec.rb +36 -0
- data/spec/crud_spec.rb +151 -0
- data/spec/db_spec.rb +63 -0
- data/spec/file_model_spec.rb +23 -0
- data/spec/misc_spec.rb +67 -0
- data/spec/query_spec.rb +67 -0
- data/spec/scope_spec.rb +150 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/validation_spec.rb +127 -0
- metadata +75 -0
data/Rakefile
ADDED
data/lib/mongo/model.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'mongodb_model/gems'
|
2
|
+
|
3
|
+
require 'validatable'
|
4
|
+
require 'file_model'
|
5
|
+
require 'i18n'
|
6
|
+
require 'ruby_ext'
|
7
|
+
require 'mongo/object'
|
8
|
+
|
9
|
+
module Mongo::Model; end
|
10
|
+
|
11
|
+
%w(
|
12
|
+
support/types
|
13
|
+
|
14
|
+
db
|
15
|
+
assignment
|
16
|
+
callbacks
|
17
|
+
validation
|
18
|
+
validation/uniqueness_validator
|
19
|
+
crud
|
20
|
+
query
|
21
|
+
query_mixin
|
22
|
+
scope
|
23
|
+
attribute_convertors
|
24
|
+
file_model
|
25
|
+
misc
|
26
|
+
model
|
27
|
+
).each{|f| require "mongo/model/#{f}"}
|
28
|
+
|
29
|
+
module Mongo
|
30
|
+
module Model
|
31
|
+
inherit \
|
32
|
+
Db,
|
33
|
+
Assignment,
|
34
|
+
Callbacks,
|
35
|
+
Validation,
|
36
|
+
Crud,
|
37
|
+
QueryMixin,
|
38
|
+
Scope,
|
39
|
+
AttributeConvertors,
|
40
|
+
Mongo::Model::FileModel,
|
41
|
+
Misc
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
Mongo.defaults.merge! \
|
46
|
+
symbolize: true,
|
47
|
+
convert_underscore_to_dollar: true,
|
48
|
+
batch_size: 50,
|
49
|
+
multi: true,
|
50
|
+
safe: true
|
@@ -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,26 @@
|
|
1
|
+
module Mongo::Model::Callbacks
|
2
|
+
inherit RubyExt::Callbacks
|
3
|
+
|
4
|
+
module ClassMethods
|
5
|
+
[:validate, :create, :update, :save, :destroy].each do |method_name|
|
6
|
+
define_method "before_#{method_name}" do |*args, &block|
|
7
|
+
opt = args.extract_options!
|
8
|
+
if block
|
9
|
+
set_callback method_name, :before, opt, &block
|
10
|
+
else
|
11
|
+
opt[:terminator] = false unless opt.include? :terminator
|
12
|
+
args.each{|executor| set_callback method_name, :before, executor, opt}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
define_method "after_#{method_name}" do |*args, &block|
|
17
|
+
opt = args.extract_options!
|
18
|
+
if block
|
19
|
+
set_callback method_name, :after, opt, &block
|
20
|
+
else
|
21
|
+
args.each{|executor| set_callback method_name, :after, executor, opt}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Mongo::Model::Crud
|
2
|
+
def save options = {}
|
3
|
+
with_collection options do |collection, options|
|
4
|
+
collection.save self, options
|
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 options = {}
|
13
|
+
with_collection options do |collection, options|
|
14
|
+
collection.destroy self, options
|
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 = {}, options = {}
|
24
|
+
self.new.set attributes, options
|
25
|
+
end
|
26
|
+
|
27
|
+
def create attributes = {}, options = {}
|
28
|
+
o = build attributes, options
|
29
|
+
o.save
|
30
|
+
o
|
31
|
+
end
|
32
|
+
|
33
|
+
def create! attributes = {}, options = {}
|
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 = {}, options = {}
|
40
|
+
success = true
|
41
|
+
collection = options[:collection] || self.collection
|
42
|
+
each(selector){|o| success = false unless o.destroy}
|
43
|
+
success
|
44
|
+
end
|
45
|
+
|
46
|
+
def destroy_all! selector = {}, options = {}
|
47
|
+
destroy_all(selector, options) || raise(Mongo::Error, "can't destroy #{selector.inspect}!")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
protected
|
52
|
+
def with_collection options, &block
|
53
|
+
options = options.clone
|
54
|
+
collection = options.delete(:collection) || self.class.collection
|
55
|
+
block.call collection, options
|
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,27 @@
|
|
1
|
+
module Mongo::Model::FileModel
|
2
|
+
inherit ::FileModel::Helper
|
3
|
+
|
4
|
+
def attribute_get name
|
5
|
+
instance_variable_get :"@#{name}"
|
6
|
+
end
|
7
|
+
|
8
|
+
def attribute_set name, value
|
9
|
+
instance_variable_set :"@#{name}", value
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def mount_file attr_name, file_model_class
|
14
|
+
super
|
15
|
+
|
16
|
+
before_validate do |model|
|
17
|
+
file_model = model.send(attr_name)
|
18
|
+
file_model.run_validations
|
19
|
+
model.errors[attr_name] = file_model.errors unless file_model.errors.empty?
|
20
|
+
end
|
21
|
+
|
22
|
+
after_save{|model| model.send(attr_name).save}
|
23
|
+
|
24
|
+
after_destroy{|model| model.send(attr_name).destroy}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,44 @@
|
|
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
|
+
def dom_id
|
17
|
+
# new_record? ? "new_#{self.class.name.underscore}" : to_param
|
18
|
+
to_param
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_param
|
22
|
+
(_id || '').to_s
|
23
|
+
end
|
24
|
+
|
25
|
+
delegate :t, to: I18n
|
26
|
+
|
27
|
+
def reload
|
28
|
+
obj = self.class.by_id!(_id || raise("can't reload new document (#{self})!"))
|
29
|
+
instance_variables.each{|n| remove_instance_variable n}
|
30
|
+
obj.instance_variables.each do |n|
|
31
|
+
instance_variable_set n, obj.instance_variable_get(n)
|
32
|
+
end
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
|
36
|
+
module ClassMethods
|
37
|
+
delegate :t, to: I18n
|
38
|
+
|
39
|
+
def timestamps!
|
40
|
+
attr_accessor :created_at, :updated_at
|
41
|
+
before_save :update_timestamps
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class Mongo::Model::Query < Object
|
2
|
+
attr_reader :model, :selector, :options
|
3
|
+
|
4
|
+
def initialize model, selector = {}, options = {} *args
|
5
|
+
@model, @selector, @options = model, selector, options
|
6
|
+
end
|
7
|
+
|
8
|
+
def class
|
9
|
+
::Mongo::Model::Query
|
10
|
+
end
|
11
|
+
|
12
|
+
def merge query
|
13
|
+
raise "can't merge queries with different models!" unless model == query.model
|
14
|
+
self.class.new model, selector.merge(query.selector), options.merge(query.options)
|
15
|
+
end
|
16
|
+
|
17
|
+
def inspect
|
18
|
+
"#<Mongo::Model::Query: #{model} #{@selector.inspect} #{@options.inspect}>"
|
19
|
+
end
|
20
|
+
alias_method :to_s, :inspect
|
21
|
+
|
22
|
+
def == o
|
23
|
+
self.class == o.class and ([model, selector, options] == [o.model, o.selector, o.options])
|
24
|
+
end
|
25
|
+
|
26
|
+
def build attributes = {}, options = {}
|
27
|
+
model.build selector.merge(attributes), options
|
28
|
+
end
|
29
|
+
|
30
|
+
def create attributes = {}, options = {}
|
31
|
+
model.create selector.merge(attributes), options
|
32
|
+
end
|
33
|
+
|
34
|
+
def create! attributes = {}, options = {}
|
35
|
+
model.create! selector.merge(attributes), options
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
def method_missing method, *args, &block
|
40
|
+
model.with_scope selector, options do
|
41
|
+
result = model.send method, *args, &block
|
42
|
+
result = self.merge result if result.is_a? self.class
|
43
|
+
result
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|