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.
Files changed (37) hide show
  1. data/Gemfile +11 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +251 -0
  4. data/Rakefile +70 -0
  5. data/lib/mongo_odm.rb +48 -0
  6. data/lib/mongo_odm/collection.rb +42 -0
  7. data/lib/mongo_odm/core_ext/conversions.rb +262 -0
  8. data/lib/mongo_odm/cursor.rb +11 -0
  9. data/lib/mongo_odm/document.rb +34 -0
  10. data/lib/mongo_odm/document/associations.rb +39 -0
  11. data/lib/mongo_odm/document/associations/has_many.rb +19 -0
  12. data/lib/mongo_odm/document/associations/has_one.rb +19 -0
  13. data/lib/mongo_odm/document/attribute_methods.rb +103 -0
  14. data/lib/mongo_odm/document/attribute_methods/dirty.rb +51 -0
  15. data/lib/mongo_odm/document/attribute_methods/localization.rb +67 -0
  16. data/lib/mongo_odm/document/attribute_methods/query.rb +35 -0
  17. data/lib/mongo_odm/document/attribute_methods/read.rb +39 -0
  18. data/lib/mongo_odm/document/attribute_methods/write.rb +37 -0
  19. data/lib/mongo_odm/document/callbacks.rb +29 -0
  20. data/lib/mongo_odm/document/fields.rb +66 -0
  21. data/lib/mongo_odm/document/inspect.rb +28 -0
  22. data/lib/mongo_odm/document/persistence.rb +96 -0
  23. data/lib/mongo_odm/document/validations.rb +46 -0
  24. data/lib/mongo_odm/errors.rb +18 -0
  25. data/lib/mongo_odm/version.rb +7 -0
  26. data/spec/models/00-blank_slate.rb +4 -0
  27. data/spec/models/01-shape.rb +8 -0
  28. data/spec/models/02-circle.rb +7 -0
  29. data/spec/models/03-big_red_circle.rb +8 -0
  30. data/spec/mongo_odm/core_ext/conversions_spec.rb +423 -0
  31. data/spec/mongo_odm/document/fields_spec.rb +187 -0
  32. data/spec/mongo_odm/document/inspect_spec.rb +19 -0
  33. data/spec/mongo_odm/document_spec.rb +12 -0
  34. data/spec/mongo_odm/mongo_odm_spec.rb +84 -0
  35. data/spec/spec.opts +4 -0
  36. data/spec/spec_helper.rb +12 -0
  37. 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