fcoury-mongomapper 0.2.0

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 +70 -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 +54 -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 +29 -0
  14. data/lib/mongomapper/associations/polymorphic_belongs_to_proxy.rb +31 -0
  15. data/lib/mongomapper/associations/proxy.rb +66 -0
  16. data/lib/mongomapper/callbacks.rb +106 -0
  17. data/lib/mongomapper/document.rb +276 -0
  18. data/lib/mongomapper/document_rails_compatibility.rb +13 -0
  19. data/lib/mongomapper/embedded_document.rb +248 -0
  20. data/lib/mongomapper/embedded_document_rails_compatibility.rb +22 -0
  21. data/lib/mongomapper/finder_options.rb +81 -0
  22. data/lib/mongomapper/key.rb +82 -0
  23. data/lib/mongomapper/observing.rb +50 -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 +444 -0
  31. data/test/test_callbacks.rb +84 -0
  32. data/test/test_document.rb +1002 -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 +73 -0
  40. data/test/test_serializations.rb +54 -0
  41. data/test/test_validations.rb +409 -0
  42. metadata +155 -0
@@ -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,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
@@ -0,0 +1,47 @@
1
+ module MongoMapper
2
+ module Validations
3
+ class ValidatesUniquenessOf < Validatable::ValidationBase
4
+ def valid?(instance)
5
+ # TODO: scope
6
+ doc = instance.class.find(:first, :conditions => {self.attribute => instance[attribute]}, :limit => 1)
7
+ doc.nil? || instance.id == doc.id
8
+ end
9
+
10
+ def message(instance)
11
+ super || "has already been taken"
12
+ end
13
+ end
14
+
15
+ class ValidatesExclusionOf < Validatable::ValidationBase
16
+ required_option :within
17
+
18
+ def valid?(instance)
19
+ value = instance[attribute]
20
+ return true if allow_nil && value.nil?
21
+ return true if allow_blank && value.blank?
22
+
23
+ !within.include?(instance[attribute])
24
+ end
25
+
26
+ def message(instance)
27
+ super || "is reserved"
28
+ end
29
+ end
30
+
31
+ class ValidatesInclusionOf < Validatable::ValidationBase
32
+ required_option :within
33
+
34
+ def valid?(instance)
35
+ value = instance[attribute]
36
+ return true if allow_nil && value.nil?
37
+ return true if allow_blank && value.blank?
38
+
39
+ within.include?(value)
40
+ end
41
+
42
+ def message(instance)
43
+ super || "is not in the list"
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,105 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{mongomapper}
5
+ s.version = "0.2.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["John Nunemaker"]
9
+ s.date = %q{2009-07-07}
10
+ s.email = %q{nunemaker@gmail.com}
11
+ s.extra_rdoc_files = [
12
+ "LICENSE",
13
+ "README.rdoc"
14
+ ]
15
+ s.files = [
16
+ ".gitignore",
17
+ "History",
18
+ "LICENSE",
19
+ "README.rdoc",
20
+ "Rakefile",
21
+ "VERSION",
22
+ "lib/mongomapper.rb",
23
+ "lib/mongomapper/associations.rb",
24
+ "lib/mongomapper/associations/array_proxy.rb",
25
+ "lib/mongomapper/associations/base.rb",
26
+ "lib/mongomapper/associations/belongs_to_proxy.rb",
27
+ "lib/mongomapper/associations/has_many_embedded_proxy.rb",
28
+ "lib/mongomapper/associations/has_many_proxy.rb",
29
+ "lib/mongomapper/associations/polymorphic_belongs_to_proxy.rb",
30
+ "lib/mongomapper/associations/proxy.rb",
31
+ "lib/mongomapper/callbacks.rb",
32
+ "lib/mongomapper/document.rb",
33
+ "lib/mongomapper/embedded_document.rb",
34
+ "lib/mongomapper/finder_options.rb",
35
+ "lib/mongomapper/key.rb",
36
+ "lib/mongomapper/observing.rb",
37
+ "lib/mongomapper/document_rails_compatibility.rb",
38
+ "lib/mongomapper/embedded_document_rails_compatibility.rb",
39
+ "lib/mongomapper/save_with_validation.rb",
40
+ "lib/mongomapper/serialization.rb",
41
+ "lib/mongomapper/serializers/json_serializer.rb",
42
+ "lib/mongomapper/validations.rb",
43
+ "mongomapper.gemspec",
44
+ "test/serializers/test_json_serializer.rb",
45
+ "test/test_associations.rb",
46
+ "test/test_callbacks.rb",
47
+ "test/test_document.rb",
48
+ "test/test_embedded_document.rb",
49
+ "test/test_finder_options.rb",
50
+ "test/test_helper.rb",
51
+ "test/test_key.rb",
52
+ "test/test_mongomapper.rb",
53
+ "test/test_observing.rb",
54
+ "test/test_rails_compatibility.rb",
55
+ "test/test_serializations.rb",
56
+ "test/test_validations.rb"
57
+ ]
58
+ s.has_rdoc = true
59
+ s.homepage = %q{http://github.com/jnunemaker/mongomapper}
60
+ s.rdoc_options = ["--charset=UTF-8"]
61
+ s.require_paths = ["lib"]
62
+ s.rubyforge_project = %q{mongomapper}
63
+ s.rubygems_version = %q{1.3.1}
64
+ s.summary = %q{Awesome gem for modeling your domain and storing it in mongo}
65
+ s.test_files = [
66
+ "test/serializers/test_json_serializer.rb",
67
+ "test/test_associations.rb",
68
+ "test/test_callbacks.rb",
69
+ "test/test_document.rb",
70
+ "test/test_embedded_document.rb",
71
+ "test/test_finder_options.rb",
72
+ "test/test_helper.rb",
73
+ "test/test_key.rb",
74
+ "test/test_mongomapper.rb",
75
+ "test/test_observing.rb",
76
+ "test/test_rails_compatibility.rb",
77
+ "test/test_serializations.rb",
78
+ "test/test_validations.rb"
79
+ ]
80
+
81
+ if s.respond_to? :specification_version then
82
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
83
+ s.specification_version = 2
84
+
85
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
86
+ s.add_runtime_dependency(%q<activesupport>, [">= 0"])
87
+ s.add_runtime_dependency(%q<mongodb-mongo>, ["= 0.9"])
88
+ s.add_runtime_dependency(%q<jnunemaker-validatable>, ["= 1.7.1"])
89
+ s.add_development_dependency(%q<mocha>, ["= 0.9.4"])
90
+ s.add_development_dependency(%q<jnunemaker-matchy>, ["= 0.4.0"])
91
+ else
92
+ s.add_dependency(%q<activesupport>, [">= 0"])
93
+ s.add_dependency(%q<mongodb-mongo>, ["= 0.9"])
94
+ s.add_dependency(%q<jnunemaker-validatable>, ["= 1.7.1"])
95
+ s.add_dependency(%q<mocha>, ["= 0.9.4"])
96
+ s.add_dependency(%q<jnunemaker-matchy>, ["= 0.4.0"])
97
+ end
98
+ else
99
+ s.add_dependency(%q<activesupport>, [">= 0"])
100
+ s.add_dependency(%q<mongodb-mongo>, ["= 0.9"])
101
+ s.add_dependency(%q<jnunemaker-validatable>, ["= 1.7.1"])
102
+ s.add_dependency(%q<mocha>, ["= 0.9.4"])
103
+ s.add_dependency(%q<jnunemaker-matchy>, ["= 0.4.0"])
104
+ end
105
+ end
@@ -0,0 +1,104 @@
1
+ require 'test_helper'
2
+
3
+ class JsonSerializationTest < Test::Unit::TestCase
4
+ class Contact
5
+ include MongoMapper::EmbeddedDocument
6
+ key :name, String
7
+ key :age, Integer
8
+ key :created_at, Time
9
+ key :awesome, Boolean
10
+ key :preferences, Hash
11
+ end
12
+
13
+ def setup
14
+ Contact.include_root_in_json = false
15
+ @contact = Contact.new(
16
+ :name => 'Konata Izumi',
17
+ :age => 16,
18
+ :created_at => Time.utc(2006, 8, 1),
19
+ :awesome => true,
20
+ :preferences => { :shows => 'anime' }
21
+ )
22
+ end
23
+
24
+ should "include demodulized root" do
25
+ Contact.include_root_in_json = true
26
+ assert_match %r{^\{"contact": \{}, @contact.to_json
27
+ end
28
+
29
+ should "encode all encodable attributes" do
30
+ json = @contact.to_json
31
+
32
+ assert_match %r{"name": "Konata Izumi"}, json
33
+ assert_match %r{"age": 16}, json
34
+ assert json.include?(%("created_at": #{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
35
+ assert_match %r{"awesome": true}, json
36
+ assert_match %r{"preferences": \{"shows": "anime"\}}, json
37
+ end
38
+
39
+ should "allow attribute filtering with only" do
40
+ json = @contact.to_json(:only => [:name, :age])
41
+
42
+ assert_match %r{"name": "Konata Izumi"}, json
43
+ assert_match %r{"age": 16}, json
44
+ assert_no_match %r{"awesome"}, json
45
+ assert_no_match %r{"created_at"}, json
46
+ assert_no_match %r{"preferences"}, json
47
+ end
48
+
49
+ should "allow attribute filtering with except" do
50
+ json = @contact.to_json(:except => [:name, :age])
51
+
52
+ assert_no_match %r{"name"}, json
53
+ assert_no_match %r{"age"}, json
54
+ assert_match %r{"awesome"}, json
55
+ assert_match %r{"created_at"}, json
56
+ assert_match %r{"preferences"}, json
57
+ end
58
+
59
+ context "including methods" do
60
+ setup do
61
+ def @contact.label; "Has cheezburger"; end
62
+ def @contact.favorite_quote; "Constraints are liberating"; end
63
+ end
64
+
65
+ should "include single method" do
66
+ # Single method.
67
+ assert_match %r{"label": "Has cheezburger"}, @contact.to_json(:only => :name, :methods => :label)
68
+ end
69
+
70
+ should "include multiple methods" do
71
+ json = @contact.to_json(:only => :name, :methods => [:label, :favorite_quote])
72
+ assert_match %r{"label": "Has cheezburger"}, json
73
+ assert_match %r{"favorite_quote": "Constraints are liberating"}, json
74
+ end
75
+ end
76
+
77
+ context "array of records" do
78
+ setup do
79
+ @contacts = [
80
+ Contact.new(:name => 'David', :age => 39),
81
+ Contact.new(:name => 'Mary', :age => 14)
82
+ ]
83
+ end
84
+
85
+ should "allow attribute filtering with only" do
86
+ assert_equal %([{"name": "David"}, {"name": "Mary"}]), @contacts.to_json(:only => :name)
87
+ end
88
+
89
+ should "allow attribute filtering with except" do
90
+ json = @contacts.to_json(:except => [:name, :preferences, :awesome, :created_at])
91
+ assert_equal %([{"age": 39}, {"age": 14}]), json
92
+ end
93
+ end
94
+
95
+ should "allow options for hash of records" do
96
+ contacts = {
97
+ 1 => Contact.new(:name => 'David', :age => 39),
98
+ 2 => Contact.new(:name => 'Mary', :age => 14)
99
+ }
100
+
101
+ assert_equal %({"1": {"name": "David"}}), contacts.to_json(:only => [1, :name])
102
+ end
103
+
104
+ end