mongo_odm 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
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