mongodbmodel 0.0.1
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/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
|