mongo_odm 0.1.8
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 +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
|