ramsingla-mongomapper 0.2.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.
Files changed (42) hide show
  1. data/.gitignore +7 -0
  2. data/History +30 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +39 -0
  5. data/Rakefile +71 -0
  6. data/VERSION +1 -0
  7. data/lib/mongomapper.rb +60 -0
  8. data/lib/mongomapper/associations.rb +69 -0
  9. data/lib/mongomapper/associations/array_proxy.rb +6 -0
  10. data/lib/mongomapper/associations/base.rb +50 -0
  11. data/lib/mongomapper/associations/belongs_to_proxy.rb +26 -0
  12. data/lib/mongomapper/associations/has_many_embedded_proxy.rb +19 -0
  13. data/lib/mongomapper/associations/has_many_proxy.rb +28 -0
  14. data/lib/mongomapper/associations/polymorphic_belongs_to_proxy.rb +31 -0
  15. data/lib/mongomapper/associations/proxy.rb +60 -0
  16. data/lib/mongomapper/callbacks.rb +106 -0
  17. data/lib/mongomapper/document.rb +263 -0
  18. data/lib/mongomapper/embedded_document.rb +295 -0
  19. data/lib/mongomapper/finder_options.rb +81 -0
  20. data/lib/mongomapper/key.rb +82 -0
  21. data/lib/mongomapper/observing.rb +50 -0
  22. data/lib/mongomapper/pagination.rb +52 -0
  23. data/lib/mongomapper/rails_compatibility.rb +23 -0
  24. data/lib/mongomapper/save_with_validation.rb +19 -0
  25. data/lib/mongomapper/serialization.rb +55 -0
  26. data/lib/mongomapper/serializers/json_serializer.rb +77 -0
  27. data/lib/mongomapper/validations.rb +47 -0
  28. data/mongomapper.gemspec +105 -0
  29. data/test/serializers/test_json_serializer.rb +104 -0
  30. data/test/test_associations.rb +211 -0
  31. data/test/test_callbacks.rb +84 -0
  32. data/test/test_document.rb +995 -0
  33. data/test/test_embedded_document.rb +253 -0
  34. data/test/test_finder_options.rb +148 -0
  35. data/test/test_helper.rb +62 -0
  36. data/test/test_key.rb +200 -0
  37. data/test/test_mongomapper.rb +28 -0
  38. data/test/test_observing.rb +101 -0
  39. data/test/test_rails_compatibility.rb +29 -0
  40. data/test/test_serializations.rb +54 -0
  41. data/test/test_validations.rb +409 -0
  42. metadata +156 -0
@@ -0,0 +1,81 @@
1
+ module MongoMapper
2
+ class FinderOptions
3
+ attr_reader :options
4
+
5
+ def self.to_mongo_criteria(conditions)
6
+ conditions = conditions.dup
7
+ criteria = {}
8
+ conditions.each_pair do |field, value|
9
+ case value
10
+ when Array
11
+ criteria[field] = {'$in' => value}
12
+ when Hash
13
+ criteria[field] = to_mongo_criteria(value)
14
+ else
15
+ criteria[field] = value
16
+ end
17
+ end
18
+
19
+ criteria
20
+ end
21
+
22
+ def self.to_mongo_options(options)
23
+ options = options.dup
24
+ {
25
+ :fields => to_mongo_fields(options.delete(:fields) || options.delete(:select)),
26
+ :offset => (options.delete(:offset) || 0).to_i,
27
+ :limit => (options.delete(:limit) || 0).to_i,
28
+ :sort => to_mongo_sort(options.delete(:order))
29
+ }
30
+ end
31
+
32
+ def initialize(options)
33
+ raise ArgumentError, "FinderOptions must be a hash" unless options.is_a?(Hash)
34
+ @options = options.symbolize_keys
35
+ @conditions = @options.delete(:conditions) || {}
36
+ end
37
+
38
+ def criteria
39
+ self.class.to_mongo_criteria(@conditions)
40
+ end
41
+
42
+ def options
43
+ self.class.to_mongo_options(@options)
44
+ end
45
+
46
+ def to_a
47
+ [criteria, options]
48
+ end
49
+
50
+ private
51
+ def self.to_mongo_fields(fields)
52
+ return if fields.blank?
53
+
54
+ if fields.is_a?(String)
55
+ fields.split(',').map { |field| field.strip }
56
+ else
57
+ fields.flatten.compact
58
+ end
59
+ end
60
+
61
+ def self.to_mongo_sort(sort)
62
+ return if sort.blank?
63
+ pieces = sort.split(',')
64
+ pairs = pieces.map { |s| to_mongo_sort_piece(s) }
65
+
66
+ hash = OrderedHash.new
67
+ pairs.each do |pair|
68
+ field, sort_direction = pair
69
+ hash[field] = sort_direction
70
+ end
71
+ hash.symbolize_keys
72
+ end
73
+
74
+ def self.to_mongo_sort_piece(str)
75
+ field, direction = str.strip.split(' ')
76
+ direction ||= 'ASC'
77
+ direction = direction.upcase == 'ASC' ? 1 : -1
78
+ [field, direction]
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,82 @@
1
+ class Boolean; end
2
+ class Ref; end
3
+
4
+ module MongoMapper
5
+ class Key
6
+ # DateTime and Date are currently not supported by mongo's bson so just use Time
7
+ NativeTypes = [String, Float, Time, Integer, Boolean, Array, Hash, Ref]
8
+
9
+ attr_accessor :name, :type, :options, :default_value
10
+
11
+ def initialize(name, type, options={})
12
+ @name, @type = name.to_s, type
13
+ self.options = options.symbolize_keys
14
+ self.default_value = options.delete(:default)
15
+ end
16
+
17
+ def ==(other)
18
+ @name == other.name && @type == other.type
19
+ end
20
+
21
+ def set(value)
22
+ typecast(value)
23
+ end
24
+
25
+ def native?
26
+ @native ||= NativeTypes.include?(type)
27
+ end
28
+
29
+ def embedded_document?
30
+ type.ancestors.include?(EmbeddedDocument) && !type.ancestors.include?(Document)
31
+ end
32
+
33
+ def get(value)
34
+ return default_value if value.nil? && !default_value.nil?
35
+ if type == Array
36
+ value || []
37
+ elsif type == Hash
38
+ HashWithIndifferentAccess.new(value || {})
39
+ else
40
+ value
41
+ end
42
+ end
43
+
44
+ private
45
+ def typecast(value)
46
+ return HashWithIndifferentAccess.new(value) if value.is_a?(Hash) && type == Hash
47
+ return value if value.kind_of?(type) || value.nil?
48
+ begin
49
+ if type == String then value.to_s
50
+ elsif type == Float then value.to_f
51
+ elsif type == Array then value.to_a
52
+ elsif type == Time then Time.parse(value.to_s)
53
+ #elsif type == Date then Date.parse(value.to_s)
54
+ elsif type == Boolean then ['true', 't', '1'].include?(value.to_s.downcase)
55
+ elsif type == Integer
56
+ # ganked from datamapper
57
+ value_to_i = value.to_i
58
+ if value_to_i == 0 && value != '0'
59
+ value_to_s = value.to_s
60
+ begin
61
+ Integer(value_to_s =~ /^(\d+)/ ? $1 : value_to_s)
62
+ rescue ArgumentError
63
+ nil
64
+ end
65
+ else
66
+ value_to_i
67
+ end
68
+ elsif embedded_document?
69
+ typecast_embedded_document(value)
70
+ else
71
+ value
72
+ end
73
+ rescue
74
+ value
75
+ end
76
+ end
77
+
78
+ def typecast_embedded_document(value)
79
+ value.is_a?(type) ? value : type.new(value)
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,50 @@
1
+ require 'observer'
2
+ require 'singleton'
3
+ require 'set'
4
+
5
+ module MongoMapper
6
+ module Observing #:nodoc:
7
+ def self.included(model)
8
+ model.class_eval do
9
+ extend Observable
10
+ end
11
+ end
12
+ end
13
+
14
+ class Observer
15
+ include Singleton
16
+
17
+ class << self
18
+ def observe(*models)
19
+ models.flatten!
20
+ models.collect! { |model| model.is_a?(Symbol) ? model.to_s.camelize.constantize : model }
21
+ define_method(:observed_classes) { Set.new(models) }
22
+ end
23
+
24
+ def observed_class
25
+ if observed_class_name = name[/(.*)Observer/, 1]
26
+ observed_class_name.constantize
27
+ else
28
+ nil
29
+ end
30
+ end
31
+ end
32
+
33
+ def initialize
34
+ Set.new(observed_classes).each { |klass| add_observer! klass }
35
+ end
36
+
37
+ def update(observed_method, object) #:nodoc:
38
+ send(observed_method, object) if respond_to?(observed_method)
39
+ end
40
+
41
+ protected
42
+ def observed_classes
43
+ Set.new([self.class.observed_class].compact.flatten)
44
+ end
45
+
46
+ def add_observer!(klass)
47
+ klass.add_observer(self)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,52 @@
1
+ module MongoMapper
2
+ module Pagination
3
+ class PaginationProxy < BlankSlate
4
+ attr_accessor :subject
5
+ attr_reader :total_entries, :per_page, :current_page
6
+ alias limit per_page
7
+
8
+ def initialize(total_entries, current_page, per_page=nil)
9
+ @total_entries = total_entries.to_i
10
+ self.per_page = per_page
11
+ self.current_page = current_page
12
+ end
13
+
14
+ def total_pages
15
+ (total_entries / per_page.to_f).ceil
16
+ end
17
+
18
+ def out_of_bounds?
19
+ current_page > total_pages
20
+ end
21
+
22
+ def previous_page
23
+ current_page > 1 ? (current_page - 1) : nil
24
+ end
25
+
26
+ def next_page
27
+ current_page < total_pages ? (current_page + 1) : nil
28
+ end
29
+
30
+ def skip
31
+ (current_page - 1) * per_page
32
+ end
33
+ alias offset skip
34
+
35
+ def method_missing(name, *args, &block)
36
+ @subject.send(name, *args, &block)
37
+ end
38
+
39
+ private
40
+ def per_page=(value)
41
+ value = 25 if value.blank?
42
+ @per_page = value.to_i
43
+ end
44
+
45
+ def current_page=(value)
46
+ value = value.to_i
47
+ value = 1 if value < 1
48
+ @current_page = value
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,23 @@
1
+ module MongoMapper
2
+ module RailsCompatibility
3
+ def self.included(model)
4
+ model.class_eval do
5
+ alias_method :new_record?, :new?
6
+ extend ClassMethods
7
+ end
8
+ class << model
9
+ alias_method :has_many, :many
10
+ end
11
+ end
12
+
13
+ module ClassMethods
14
+ def column_names
15
+ keys.keys
16
+ end
17
+ end
18
+
19
+ def to_param
20
+ _root.id
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,19 @@
1
+ module MongoMapper
2
+ module SaveWithValidation
3
+ def self.included(base)
4
+ base.class_eval do
5
+ alias_method_chain :save, :validation
6
+ alias_method_chain :save!, :validation
7
+ end
8
+ end
9
+
10
+ private
11
+ def save_with_validation
12
+ valid? ? save_without_validation : false
13
+ end
14
+
15
+ def save_with_validation!
16
+ valid? ? save_without_validation! : raise(DocumentNotValid.new(self))
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,55 @@
1
+ require 'active_support/json'
2
+
3
+ module MongoMapper #:nodoc:
4
+ module Serialization
5
+ class Serializer #:nodoc:
6
+ attr_reader :options
7
+
8
+ def initialize(record, options = {})
9
+ @record, @options = record, options.dup
10
+ end
11
+
12
+ def serializable_key_names
13
+ key_names = @record.send :defined_key_names
14
+
15
+ if options[:only]
16
+ options.delete(:except)
17
+ key_names = key_names & Array(options[:only]).collect { |n| n.to_s }
18
+ else
19
+ options[:except] = Array(options[:except])
20
+ key_names = key_names - options[:except].collect { |n| n.to_s }
21
+ end
22
+
23
+ key_names
24
+ end
25
+
26
+ def serializable_method_names
27
+ Array(options[:methods]).inject([]) do |method_attributes, name|
28
+ method_attributes << name if @record.respond_to?(name.to_s)
29
+ method_attributes
30
+ end
31
+ end
32
+
33
+ def serializable_names
34
+ serializable_key_names + serializable_method_names
35
+ end
36
+
37
+ def serializable_record
38
+ returning(serializable_record = {}) do
39
+ serializable_names.each { |name| serializable_record[name] = @record.send(name) }
40
+ end
41
+ end
42
+
43
+ def serialize
44
+ # overwrite to implement
45
+ end
46
+
47
+ def to_s(&block)
48
+ serialize(&block)
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ dir = Pathname(__FILE__).dirname.expand_path + 'serializers'
55
+ require dir + 'json_serializer'
@@ -0,0 +1,77 @@
1
+ module MongoMapper #:nodoc:
2
+ module Serialization
3
+ def self.included(base)
4
+ base.cattr_accessor :include_root_in_json, :instance_writer => false
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ # Returns a JSON string representing the model. Some configuration is
9
+ # available through +options+.
10
+ #
11
+ # The option <tt>include_root_in_json</tt> controls the top-level behavior of
12
+ # to_json. When it is <tt>true</tt>, to_json will emit a single root node named
13
+ # after the object's type. For example:
14
+ #
15
+ # konata = User.find(1)
16
+ # User.include_root_in_json = true
17
+ # konata.to_json
18
+ # # => { "user": {"id": 1, "name": "Konata Izumi", "age": 16,
19
+ # "created_at": "2006/08/01", "awesome": true} }
20
+ #
21
+ # User.include_root_in_json = false
22
+ # konata.to_json
23
+ # # => {"id": 1, "name": "Konata Izumi", "age": 16,
24
+ # "created_at": "2006/08/01", "awesome": true}
25
+ #
26
+ # The remainder of the examples in this section assume include_root_in_json is set to
27
+ # <tt>false</tt>.
28
+ #
29
+ # Without any +options+, the returned JSON string will include all
30
+ # the model's attributes. For example:
31
+ #
32
+ # konata = User.find(1)
33
+ # konata.to_json
34
+ # # => {"id": 1, "name": "Konata Izumi", "age": 16,
35
+ # "created_at": "2006/08/01", "awesome": true}
36
+ #
37
+ # The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the attributes
38
+ # included, and work similar to the +attributes+ method. For example:
39
+ #
40
+ # konata.to_json(:only => [ :id, :name ])
41
+ # # => {"id": 1, "name": "Konata Izumi"}
42
+ #
43
+ # konata.to_json(:except => [ :id, :created_at, :age ])
44
+ # # => {"name": "Konata Izumi", "awesome": true}
45
+ #
46
+ # To include any methods on the model, use <tt>:methods</tt>.
47
+ #
48
+ # konata.to_json(:methods => :permalink)
49
+ # # => {"id": 1, "name": "Konata Izumi", "age": 16,
50
+ # "created_at": "2006/08/01", "awesome": true,
51
+ # "permalink": "1-konata-izumi"}
52
+ def to_json(options = {})
53
+ if include_root_in_json
54
+ "{#{self.class.json_class_name}: #{JsonSerializer.new(self, options).to_s}}"
55
+ else
56
+ JsonSerializer.new(self, options).to_s
57
+ end
58
+ end
59
+
60
+ def from_json(json)
61
+ self.attributes = ActiveSupport::JSON.decode(json)
62
+ self
63
+ end
64
+
65
+ class JsonSerializer < MongoMapper::Serialization::Serializer #:nodoc:
66
+ def serialize
67
+ serializable_record.to_json
68
+ end
69
+ end
70
+
71
+ module ClassMethods
72
+ def json_class_name
73
+ @json_class_name ||= name.demodulize.underscore.inspect
74
+ end
75
+ end
76
+ end
77
+ end