mongo_odm 0.1.8
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +11 -0
- data/LICENSE +20 -0
- data/README.rdoc +251 -0
- data/Rakefile +70 -0
- data/lib/mongo_odm.rb +48 -0
- data/lib/mongo_odm/collection.rb +42 -0
- data/lib/mongo_odm/core_ext/conversions.rb +262 -0
- data/lib/mongo_odm/cursor.rb +11 -0
- data/lib/mongo_odm/document.rb +34 -0
- data/lib/mongo_odm/document/associations.rb +39 -0
- data/lib/mongo_odm/document/associations/has_many.rb +19 -0
- data/lib/mongo_odm/document/associations/has_one.rb +19 -0
- data/lib/mongo_odm/document/attribute_methods.rb +103 -0
- data/lib/mongo_odm/document/attribute_methods/dirty.rb +51 -0
- data/lib/mongo_odm/document/attribute_methods/localization.rb +67 -0
- data/lib/mongo_odm/document/attribute_methods/query.rb +35 -0
- data/lib/mongo_odm/document/attribute_methods/read.rb +39 -0
- data/lib/mongo_odm/document/attribute_methods/write.rb +37 -0
- data/lib/mongo_odm/document/callbacks.rb +29 -0
- data/lib/mongo_odm/document/fields.rb +66 -0
- data/lib/mongo_odm/document/inspect.rb +28 -0
- data/lib/mongo_odm/document/persistence.rb +96 -0
- data/lib/mongo_odm/document/validations.rb +46 -0
- data/lib/mongo_odm/errors.rb +18 -0
- data/lib/mongo_odm/version.rb +7 -0
- data/spec/models/00-blank_slate.rb +4 -0
- data/spec/models/01-shape.rb +8 -0
- data/spec/models/02-circle.rb +7 -0
- data/spec/models/03-big_red_circle.rb +8 -0
- data/spec/mongo_odm/core_ext/conversions_spec.rb +423 -0
- data/spec/mongo_odm/document/fields_spec.rb +187 -0
- data/spec/mongo_odm/document/inspect_spec.rb +19 -0
- data/spec/mongo_odm/document_spec.rb +12 -0
- data/spec/mongo_odm/mongo_odm_spec.rb +84 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +12 -0
- metadata +185 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'active_support/core_ext/module/aliasing'
|
3
|
+
|
4
|
+
module MongoODM
|
5
|
+
module Document
|
6
|
+
module AttributeMethods
|
7
|
+
module Dirty
|
8
|
+
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
included do
|
12
|
+
include ActiveModel::Dirty
|
13
|
+
|
14
|
+
alias_method_chain :initialize, :dirty
|
15
|
+
alias_method_chain :write_attribute, :dirty
|
16
|
+
alias_method_chain :save, :dirty
|
17
|
+
alias_method_chain :reload, :dirty
|
18
|
+
end
|
19
|
+
|
20
|
+
module InstanceMethods
|
21
|
+
def initialize_with_dirty(*args)
|
22
|
+
initialize_without_dirty(*args)
|
23
|
+
changed_attributes.clear
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def write_attribute_with_dirty(attr_name, value)
|
28
|
+
send("#{attr_name}_will_change!") if respond_to?("#{attr_name}_will_change!")
|
29
|
+
write_attribute_without_dirty(attr_name, value)
|
30
|
+
end
|
31
|
+
|
32
|
+
def save_with_dirty(*args)
|
33
|
+
if status = save_without_dirty(*args)
|
34
|
+
@previously_changed = changes
|
35
|
+
changed_attributes.clear
|
36
|
+
end
|
37
|
+
status
|
38
|
+
end
|
39
|
+
|
40
|
+
def reload_with_dirty(*args)
|
41
|
+
reload_without_dirty(*args).tap do
|
42
|
+
previously_changed_attributes.clear
|
43
|
+
changed_attributes.clear
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
3
|
+
|
4
|
+
module MongoODM
|
5
|
+
module Document
|
6
|
+
module AttributeMethods
|
7
|
+
module Localization
|
8
|
+
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
included do
|
12
|
+
attribute_method_suffix "_localized?"
|
13
|
+
attribute_method_suffix "_without_localization"
|
14
|
+
|
15
|
+
alias_method_chain :read_attribute, :localization
|
16
|
+
alias_method_chain :write_attribute, :localization
|
17
|
+
alias_method_chain :inspect, :localization
|
18
|
+
end
|
19
|
+
|
20
|
+
module ClassMethods
|
21
|
+
def define_method_attribute_localized?(attr_name)
|
22
|
+
generated_attribute_methods.module_eval("def #{attr_name}_localized?; localized_attribute?('#{attr_name}'); end", __FILE__, __LINE__)
|
23
|
+
end
|
24
|
+
protected :define_method_attribute_localized?
|
25
|
+
|
26
|
+
def define_method_attribute_without_localization(attr_name)
|
27
|
+
generated_attribute_methods.module_eval("def #{attr_name}_without_localization; read_attribute_without_localization('#{attr_name}'); end", __FILE__, __LINE__)
|
28
|
+
end
|
29
|
+
protected :define_method_attribute_localized?
|
30
|
+
end
|
31
|
+
|
32
|
+
module InstanceMethods
|
33
|
+
def localized_attribute?(attr_name)
|
34
|
+
field = self.class.fields[attr_name]
|
35
|
+
localized = field ? !!field.options[:localized] : false
|
36
|
+
raise MongoODM::Errors::InvalidLocalizedField.new(field.name, field.type) if localized && field && field.type != Hash
|
37
|
+
localized
|
38
|
+
end
|
39
|
+
|
40
|
+
def read_attribute_with_localization(attr_name, locale = nil)
|
41
|
+
if localized_attribute?(attr_name)
|
42
|
+
locale = I18n.locale || I18n.default_locale if locale.nil?
|
43
|
+
(read_attribute_without_localization(attr_name) || {}.with_indifferent_access)[locale]
|
44
|
+
else
|
45
|
+
read_attribute_without_localization(attr_name)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def write_attribute_with_localization(attr_name, value, locale = nil)
|
50
|
+
if localized_attribute?(attr_name)
|
51
|
+
locale = I18n.locale || I18n.default_locale if locale.nil?
|
52
|
+
current_value = read_attribute_without_localization(attr_name) || {}.with_indifferent_access
|
53
|
+
write_attribute_without_localization(attr_name, current_value.merge(locale => value))
|
54
|
+
else
|
55
|
+
write_attribute_without_localization(attr_name, value)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def inspect_with_localization
|
60
|
+
"#<#{self.class.name} #{attributes.except(*private_attribute_names).keys.map{|k| "#{localized_attribute?(k) ? "#{k}[#{I18n.locale}]" : k}: #{read_attribute(k).inspect}"}.join(", ")}>"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module MongoODM
|
4
|
+
module Document
|
5
|
+
module AttributeMethods
|
6
|
+
module Query
|
7
|
+
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
attribute_method_suffix "?"
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
def define_method_attribute?(attr_name)
|
16
|
+
generated_attribute_methods.module_eval("def #{attr_name}?; query_attribute('#{attr_name}'); end", __FILE__, __LINE__)
|
17
|
+
end
|
18
|
+
protected :define_method_attribute?
|
19
|
+
end
|
20
|
+
|
21
|
+
module InstanceMethods
|
22
|
+
def query_attribute(attr_name)
|
23
|
+
!!@attributes[attr_name]
|
24
|
+
end
|
25
|
+
|
26
|
+
def attribute?(attr_name)
|
27
|
+
query_attribute(attr_name)
|
28
|
+
end
|
29
|
+
private :attribute?
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module MongoODM
|
4
|
+
module Document
|
5
|
+
module AttributeMethods
|
6
|
+
module Read
|
7
|
+
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
attribute_method_suffix ""
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
def define_method_attribute(attr_name)
|
16
|
+
generated_attribute_methods.module_eval("def #{attr_name}; read_attribute('#{attr_name}'); end", __FILE__, __LINE__)
|
17
|
+
end
|
18
|
+
protected :define_method_attribute
|
19
|
+
end
|
20
|
+
|
21
|
+
module InstanceMethods
|
22
|
+
def read_attribute(attr_name)
|
23
|
+
@attributes[attr_name]
|
24
|
+
end
|
25
|
+
|
26
|
+
def [](attr_name)
|
27
|
+
read_attribute[attr_name]
|
28
|
+
end
|
29
|
+
|
30
|
+
def attribute(attr_name)
|
31
|
+
read_attribute(attr_name)
|
32
|
+
end
|
33
|
+
private :attribute
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module MongoODM
|
4
|
+
module Document
|
5
|
+
module AttributeMethods
|
6
|
+
module Write
|
7
|
+
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
attribute_method_suffix "="
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
def define_method_attribute=(attr_name)
|
16
|
+
generated_attribute_methods.module_eval("def #{attr_name}=(new_value); write_attribute('#{attr_name}', new_value); end", __FILE__, __LINE__)
|
17
|
+
end
|
18
|
+
protected :define_method_attribute=
|
19
|
+
end
|
20
|
+
|
21
|
+
module InstanceMethods
|
22
|
+
def write_attribute(attr_name, value)
|
23
|
+
field = self.class.fields[attr_name]
|
24
|
+
type = field ? field.type : value.class
|
25
|
+
@attributes[attr_name] = attr_name.to_sym == :_id ? value : type.type_cast(value)
|
26
|
+
end
|
27
|
+
|
28
|
+
def attribute=(attr_name, value)
|
29
|
+
write_attribute(attr_name, value)
|
30
|
+
end
|
31
|
+
private :attribute=
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'active_support/core_ext/module/aliasing'
|
3
|
+
|
4
|
+
module MongoODM
|
5
|
+
module Document
|
6
|
+
module Callbacks
|
7
|
+
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
extend ActiveModel::Callbacks
|
12
|
+
|
13
|
+
alias_method_chain :initialize, :callbacks
|
14
|
+
|
15
|
+
define_model_callbacks :initialize, :only => :after
|
16
|
+
define_model_callbacks :save, :destroy
|
17
|
+
end
|
18
|
+
|
19
|
+
module InstanceMethods
|
20
|
+
def initialize_with_callbacks(*args)
|
21
|
+
initialize_without_callbacks(*args)
|
22
|
+
_run_initialize_callbacks
|
23
|
+
self
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
3
|
+
require 'active_support/core_ext/hash/keys'
|
4
|
+
require 'mongo_odm/core_ext/conversions'
|
5
|
+
|
6
|
+
module MongoODM
|
7
|
+
module Document
|
8
|
+
module Fields
|
9
|
+
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
|
12
|
+
class Field
|
13
|
+
# @return [Symbol] the name of this field in the Document.
|
14
|
+
attr_reader :name
|
15
|
+
# @return [Class] the Ruby type of field.
|
16
|
+
attr_reader :type
|
17
|
+
# @return [Hash] configuration options.
|
18
|
+
attr_reader :options
|
19
|
+
|
20
|
+
# Creates a new document field.
|
21
|
+
# @param [String, Symbol] name the name of the field.
|
22
|
+
# @param [Class] type the Ruby type of the field. It should respond to 'type_cast'. Use {Boolean} for true or false types.
|
23
|
+
# @param [Hash] options configuration options.
|
24
|
+
# @option options [Object, Proc] :default (nil) a default value for the field, or a lambda to evaluate when providing the default.
|
25
|
+
def initialize(name, type, options = {})
|
26
|
+
@options = options.to_options
|
27
|
+
@name = name.to_sym
|
28
|
+
@type = type
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [Object] The default value for this field if defined, or nil.
|
32
|
+
def default
|
33
|
+
if default = options[:default]
|
34
|
+
type_cast(default.respond_to?(:call) ? default.call : default)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Attempt to coerce the passed value into this field's type
|
39
|
+
# @param [Object] value the value to coerce
|
40
|
+
# @return [Object] the value coerced into this field's type
|
41
|
+
# @raise [MongoODM::Errors::TypeCastMissing] if the value cannot be coerced into the field's type
|
42
|
+
def type_cast(value)
|
43
|
+
raise MongoODM::Errors::TypeCastMissing.new(value, @type) unless @type.respond_to?(:type_cast)
|
44
|
+
@type.type_cast(value)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
module ClassMethods
|
49
|
+
def inherited(subclass)
|
50
|
+
super
|
51
|
+
subclass.fields.merge!(fields)
|
52
|
+
end
|
53
|
+
|
54
|
+
def fields
|
55
|
+
@fields ||= {}.with_indifferent_access
|
56
|
+
end
|
57
|
+
|
58
|
+
def field(name, type = String, options = {})
|
59
|
+
fields[name] = Field.new(name, type, options)
|
60
|
+
fields[name]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'active_support/core_ext/hash/except'
|
3
|
+
|
4
|
+
module MongoODM
|
5
|
+
module Document
|
6
|
+
module Inspect
|
7
|
+
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def inspect
|
12
|
+
"#{name}(#{fields.map{|k, v| "#{k}: #{v.type}"}.join(", ")})"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module InstanceMethods
|
17
|
+
def private_attribute_names
|
18
|
+
%w(_class)
|
19
|
+
end
|
20
|
+
|
21
|
+
def inspect
|
22
|
+
"#<#{self.class.name} #{attributes.except(*private_attribute_names).keys.map{|k| "#{k}: #{read_attribute(k).inspect}"}.join(", ")}>"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'active_support/core_ext/string/inflections'
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module MongoODM
|
6
|
+
module Document
|
7
|
+
module Persistence
|
8
|
+
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
module InstanceMethods
|
12
|
+
def id
|
13
|
+
attributes[:_id]
|
14
|
+
end
|
15
|
+
|
16
|
+
def new_record?
|
17
|
+
id.nil?
|
18
|
+
end
|
19
|
+
|
20
|
+
def reload
|
21
|
+
self.load_attributes_or_defaults(self.class.find_one(:_id => id).attributes) unless new_record?
|
22
|
+
end
|
23
|
+
|
24
|
+
# Save a document to its collection.
|
25
|
+
#
|
26
|
+
# @return [ObjectID] the _id of the saved document.
|
27
|
+
#
|
28
|
+
# @option opts [Boolean] :safe (+false+)
|
29
|
+
# If true, check that the save succeeded. OperationFailure
|
30
|
+
# will be raised on an error. Note that a safe check requires an extra
|
31
|
+
# round-trip to the database.
|
32
|
+
def save(options = {})
|
33
|
+
_run_save_callbacks do
|
34
|
+
write_attribute(:_id, self.class.save(to_mongo, options))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def update_attributes(attributes)
|
39
|
+
self.attributes = attributes
|
40
|
+
save
|
41
|
+
end
|
42
|
+
|
43
|
+
def destroy
|
44
|
+
return false if new_record?
|
45
|
+
_run_destroy_callbacks do
|
46
|
+
self.class.remove({ :_id => self.id })
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_mongo
|
51
|
+
attributes.inject({}.with_indifferent_access) do |attrs, (key, value)|
|
52
|
+
attrs[key] = value.to_mongo unless value.nil?
|
53
|
+
attrs
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
module ClassMethods
|
59
|
+
def collection
|
60
|
+
@collection ||= MongoODM::Collection.new(MongoODM.database, name.tableize)
|
61
|
+
end
|
62
|
+
|
63
|
+
def set_collection(name_or_collection)
|
64
|
+
@collection = case name_or_collection
|
65
|
+
when MongoODM::Collection
|
66
|
+
name_or_collection
|
67
|
+
when String, Symbol
|
68
|
+
MongoODM::Collection.new(MongoODM.database, name_or_collection)
|
69
|
+
else
|
70
|
+
raise "MongoODM::Collection instance or valid collection name required"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def destroy_all(*args)
|
75
|
+
documents = find(*args)
|
76
|
+
count = documents.count
|
77
|
+
documents.each { |doc| doc.destroy }
|
78
|
+
count
|
79
|
+
end
|
80
|
+
|
81
|
+
def type_cast(value)
|
82
|
+
return nil if value.nil?
|
83
|
+
return value if value.class == self
|
84
|
+
new(value)
|
85
|
+
end
|
86
|
+
|
87
|
+
extend Forwardable
|
88
|
+
def_delegators :collection, :db, :hint, :hint=, :pk_factory, :[], :count, :create_index, :distinct,
|
89
|
+
:drop, :drop_index, :drop_indexes, :find, :find_and_modify, :find_one, :group,
|
90
|
+
:index_information, :insert, :<<, :map_reduce, :mapreduce, :options, :remove, :rename,
|
91
|
+
:save, :stats, :update
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|