mongoid_nested_fields 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/.autotest ADDED
@@ -0,0 +1 @@
1
+ require 'autotest/growl'
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,21 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ gem 'mongoid', "2.0.0.beta.20"
7
+ gem "bson_ext", "1.1.5"
8
+ gem 'yajl-ruby', ">= 0.7.8"
9
+ gem 'activesupport', ">= 3.0.3"
10
+ gem 'activemodel', ">= 3.0.3"
11
+
12
+ # Add dependencies to develop your gem here.
13
+ # Include everything needed to run rake, tests, features, etc.
14
+ group :development do
15
+ gem 'autotest'
16
+ gem 'autotest-growl'
17
+ gem "shoulda", ">= 0"
18
+ gem "bundler", "~> 1.0.0"
19
+ gem "jeweler", "~> 1.5.2"
20
+ gem "rcov", ">= 0"
21
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,51 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ ZenTest (4.4.2)
5
+ activemodel (3.0.3)
6
+ activesupport (= 3.0.3)
7
+ builder (~> 2.1.2)
8
+ i18n (~> 0.4)
9
+ activesupport (3.0.3)
10
+ autotest (4.4.6)
11
+ ZenTest (>= 4.4.1)
12
+ autotest-growl (0.2.4)
13
+ autotest (>= 4.2.4)
14
+ bson (1.1.5)
15
+ bson_ext (1.1.5)
16
+ builder (2.1.2)
17
+ git (1.2.5)
18
+ i18n (0.5.0)
19
+ jeweler (1.5.2)
20
+ bundler (~> 1.0.0)
21
+ git (>= 1.2.5)
22
+ rake
23
+ mongo (1.1.5)
24
+ bson (>= 1.1.5)
25
+ mongoid (2.0.0.beta.20)
26
+ activemodel (~> 3.0)
27
+ mongo (~> 1.1)
28
+ tzinfo (~> 0.3.22)
29
+ will_paginate (~> 3.0.pre)
30
+ rake (0.8.7)
31
+ rcov (0.9.9)
32
+ shoulda (2.11.3)
33
+ tzinfo (0.3.23)
34
+ will_paginate (3.0.pre2)
35
+ yajl-ruby (0.7.8)
36
+
37
+ PLATFORMS
38
+ ruby
39
+
40
+ DEPENDENCIES
41
+ activemodel (>= 3.0.3)
42
+ activesupport (>= 3.0.3)
43
+ autotest
44
+ autotest-growl
45
+ bson_ext (= 1.1.5)
46
+ bundler (~> 1.0.0)
47
+ jeweler (~> 1.5.2)
48
+ mongoid (= 2.0.0.beta.20)
49
+ rcov
50
+ shoulda
51
+ yajl-ruby (>= 0.7.8)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Mihael Konjevic - retro - konjevic@gmail.com
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,55 @@
1
+ = mongoid_nested_fields
2
+
3
+ MongoidNestedFields is a gem aimed at CMS projects that need handling of complex types in one field. Nested fields can be nested into each other. Validations are performed on whole tree.
4
+
5
+ == Example
6
+
7
+ class Article
8
+ include Mongoid::Document
9
+
10
+ field :title
11
+ nested_field :body, :allowed_types => [ParagraphWithSubtitle, ImageWithCaption, ImageGallery]
12
+
13
+ end
14
+
15
+ class ParagraphWithSubtitle
16
+ include MongoidNestedFields::NestedField
17
+
18
+ field :paragraph
19
+ field :subtitle
20
+
21
+ validates_presence_of :paragraph
22
+ end
23
+
24
+ class ImageWithCaption
25
+ include MongoidNestedFields::NestedField
26
+
27
+ field :image
28
+ field :caption
29
+
30
+ end
31
+
32
+ class ImageGallery
33
+ include MongoidNestedFields::NestedField
34
+
35
+ field :gallery_name
36
+ nested_field :gallery, :allowed_types => [ImageWithCaption]
37
+
38
+ end
39
+
40
+
41
+ == Contributing to mongoid_nested_fields
42
+
43
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
44
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
45
+ * Fork the project
46
+ * Start a feature/bugfix branch
47
+ * Commit and push until you are happy with your contribution
48
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
49
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
50
+
51
+ == Copyright
52
+
53
+ Copyright (c) 2010 Mihael Konjevic - retro. See LICENSE.txt for
54
+ further details.
55
+
data/Rakefile ADDED
@@ -0,0 +1,53 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "mongoid_nested_fields"
16
+ gem.homepage = "http://github.com/retro/mongoid_nested_fields"
17
+ gem.license = "MIT"
18
+ gem.summary = %Q{MongoidNestedFields allows you to handle complex data structures inside one field in MongoDB.}
19
+ gem.description = %Q{MongoidNestedFields allows you to handle complex data structures inside one field in MongoDB. It also validates whole object graph on field validation}
20
+ gem.email = "konjevic@gmail.com"
21
+ gem.authors = ["retro"]
22
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
23
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
24
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
25
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
26
+ end
27
+ Jeweler::RubygemsDotOrgTasks.new
28
+
29
+ require 'rake/testtask'
30
+ Rake::TestTask.new(:test) do |test|
31
+ test.libs << 'lib' << 'test'
32
+ test.pattern = 'test/**/test_*.rb'
33
+ test.verbose = true
34
+ end
35
+
36
+ require 'rcov/rcovtask'
37
+ Rcov::RcovTask.new do |test|
38
+ test.libs << 'test'
39
+ test.pattern = 'test/**/test_*.rb'
40
+ test.verbose = true
41
+ end
42
+
43
+ task :default => :test
44
+
45
+ require 'rake/rdoctask'
46
+ Rake::RDocTask.new do |rdoc|
47
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
48
+
49
+ rdoc.rdoc_dir = 'rdoc'
50
+ rdoc.title = "mongoid_content_block #{version}"
51
+ rdoc.rdoc_files.include('README*')
52
+ rdoc.rdoc_files.include('lib/**/*.rb')
53
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.1
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+
3
+ module MongoidNestedFields
4
+ module Errors
5
+ class UnexpectedType < TypeError
6
+ def initialize(type, field)
7
+ super("Unexpected type #{type} in field #{field}")
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ # encoding: utf-8
2
+
3
+ require 'mongoid_nested_fields/errors/unexpected_type'
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ module MongoidNestedFields
4
+ class NestedFieldHash < Hash
5
+ attr_accessor :origin
6
+ end
7
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+
3
+ module MongoidNestedFields
4
+ class NestedFieldHolder
5
+ def self.get(value)
6
+ if value.is_a? Array
7
+ value = value.map do |v|
8
+ if((v.is_a?(Hash) or v.is_a?(BSON::OrderedHash)) and !v['_type'].nil?)
9
+ v = v['_type'].classify.constantize.new(v.to_hash).to_mongo
10
+ end
11
+ v
12
+ end
13
+ end
14
+ value
15
+ end
16
+
17
+ def self.set(value)
18
+ if value.is_a? Array
19
+ value = value.map{ |v| v.respond_to?(:to_mongo) ? v.to_mongo : v}
20
+ end
21
+ value
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,154 @@
1
+ # encoding: utf-8
2
+
3
+ module MongoidNestedFields
4
+ module NestedField
5
+ module ClassMethods
6
+
7
+ def nested_field(name, options = {})
8
+ add_nested_field(name)
9
+ set_validations
10
+ options[:type] = Array
11
+ set_allowed_types_in_field(name, options.delete(:allowed_types))
12
+ define_method("#{name}=") do |value|
13
+ raise TypeError unless [String, Array].include?(value.class)
14
+ if value.is_a? String
15
+ parser = Yajl::Parser.new
16
+ value = parser.parse(value)
17
+ end
18
+ processed_values = []
19
+ value.to_a.each do |v|
20
+ v.stringify_keys!
21
+ if(v.is_a?(Hash) && !v['_type'].nil?)
22
+ v = v['_type'].classify.constantize.new(v)
23
+
24
+ end
25
+ raise MongoidNestedFields::Errors::UnexpectedType.new(v.class, name) unless self.class.is_allowed_type?(name, v.class)
26
+ processed_values << v
27
+ end
28
+ write_attribute(name, processed_values)
29
+ end
30
+
31
+ define_method(name) do
32
+ read_attribute(name).map{ |v| v.respond_to?(:to_mongo) ? v.to_mongo : v }
33
+
34
+ end
35
+
36
+
37
+ end
38
+
39
+ def set_validations
40
+ validate :nested_fields_must_be_valid
41
+ end
42
+
43
+ def set_allowed_types_in_field(field, type)
44
+ field = field.to_sym
45
+ @_allowed_types_in_field ||= {}
46
+ @_allowed_types_in_field[field] ||= []
47
+ @_allowed_types_in_field[field] << type
48
+ @_allowed_types_in_field[field].flatten!.uniq!
49
+ end
50
+ def allowed_types_in_field(field)
51
+ field = field.to_sym
52
+ @_allowed_types_in_field ||= {}
53
+ return @_allowed_types_in_field[field] || []
54
+ end
55
+ def is_allowed_type?(field, type)
56
+ field = field.to_sym
57
+ allowed_types_in_field(field).include?(type)
58
+ end
59
+
60
+ def field(name, options = {})
61
+ add_field(name)
62
+
63
+ define_method("#{name}=") do |value|
64
+ klass = options[:type].nil? ? String : options.delete(:type).classify.constantize
65
+ write_attribute(name, klass.new(value))
66
+ end
67
+
68
+ define_method(name) do
69
+ read_attribute(name)
70
+ end
71
+
72
+ end
73
+
74
+ def add_field(name)
75
+ @_fields ||= []
76
+ @_fields << name.to_sym
77
+ end
78
+
79
+ def add_nested_field(name)
80
+ add_field(name)
81
+ @_nested_fields ||= []
82
+ @_nested_fields << name.to_sym
83
+ end
84
+
85
+ end
86
+
87
+
88
+
89
+ def self.included(base)
90
+ base.extend(ClassMethods)
91
+ base.send(:include, ActiveModel::Validations)
92
+ end
93
+
94
+ def _type=(t)
95
+ end
96
+
97
+ def _type
98
+ self.class.to_s.underscore
99
+ end
100
+
101
+ def initialize(attrs)
102
+ attrs.each_pair do |k,v|
103
+ if v.is_a?(Hash) && !v['_type'].nil?
104
+ v = v['_type'].classify.constantize.new(v)
105
+ end
106
+ self.send("#{k}=", v)
107
+ end
108
+ end
109
+
110
+ def write_attribute(name, value)
111
+ @_attributes ||= {}
112
+ @_attributes[name.to_sym] = value
113
+ end
114
+
115
+ def read_attribute(name)
116
+ @_attributes ||= {}
117
+ @_attributes[name.to_sym].respond_to?(:to_mongo) ? @_attributes[name.to_sym].to_mongo : @_attributes[name.to_sym]
118
+ end
119
+
120
+ def attributes
121
+ @_attributes
122
+ end
123
+
124
+ def to_mongo
125
+ attrs = MongoidNestedFields::NestedFieldHash.new
126
+ attributes.each_key do |key|
127
+ attrs[key.to_s] = self.send(key.to_sym)
128
+ end
129
+ attrs['_type'] = _type
130
+ attrs.origin = self
131
+ attrs
132
+ end
133
+
134
+ def _nested_fields
135
+ self.class.instance_variable_get(:@_nested_fields)
136
+ end
137
+
138
+ def nested_fields_must_be_valid
139
+ _nested_fields.each do |field|
140
+ value = read_attribute(field)
141
+ i = 0
142
+ field_errors = {}
143
+ value.each do |v|
144
+ if v.respond_to?(:invalid?) and v.invalid?
145
+ field_errors[i.to_s] = v.errors
146
+ i += 1
147
+ end
148
+ end if value.respond_to? :each
149
+ errors.add(field, field_errors) unless field_errors.empty?
150
+ end unless _nested_fields.nil?
151
+ end
152
+
153
+ end
154
+ end
@@ -0,0 +1,90 @@
1
+ # encoding: utf-8
2
+
3
+ module MongoidNestedFields
4
+ module NestedFieldSetter
5
+ module ClassMethods
6
+ def nested_field(name, options = {})
7
+ @_nested_fields ||= []
8
+ @_nested_fields << name.to_s
9
+ options[:type] = MongoidNestedFields::NestedFieldHolder
10
+
11
+ set_allowed_types_in_field(name, options.delete(:allowed_types))
12
+ set_validations
13
+
14
+ field(name, options)
15
+ meth = options.delete(:as) || name
16
+ define_method("#{meth}=") do |value|
17
+ raise TypeError unless [String, Array, NilClass].include?(value.class)
18
+ if value.is_a? String
19
+ parser = Yajl::Parser.new
20
+ value = parser.parse(value)
21
+ end
22
+ processed_values = []
23
+ value.to_a.each do |v|
24
+ v.stringify_keys! if v.is_a? Hash
25
+ if((v.is_a?(Hash) or v.is_a?(BSON::OrderedHash)) and !v['_type'].nil?)
26
+ v = v['_type'].classify.constantize.new(v.to_hash)
27
+ end
28
+ raise MongoidNestedFields::Errors::UnexpectedType.new(v.class, name) unless self.class.is_allowed_type?(name, v.class)
29
+ processed_values << v
30
+ end
31
+ write_attribute(name, processed_values)
32
+ end
33
+
34
+ end
35
+
36
+ def set_validations
37
+ validate :nested_fields_must_be_valid
38
+ end
39
+
40
+ def set_allowed_types_in_field(field, type)
41
+ field = field.to_sym
42
+ @_allowed_types_in_field ||= {}
43
+ @_allowed_types_in_field[field] ||= []
44
+ @_allowed_types_in_field[field] << type
45
+ @_allowed_types_in_field[field].flatten!.uniq!
46
+ end
47
+ def allowed_types_in_field(field)
48
+ field = field.to_sym
49
+ @_allowed_types_in_field ||= {}
50
+ return @_allowed_types_in_field[field] || []
51
+ end
52
+ def is_allowed_type?(field, type)
53
+ field = field.to_sym
54
+ allowed_types_in_field(field).include?(type)
55
+ end
56
+ end
57
+
58
+ def self.included(reciever)
59
+ reciever::ClassMethods.send :include, ClassMethods
60
+ end
61
+
62
+ def _nested_fields
63
+ self.class.instance_variable_get(:@_nested_fields)
64
+ end
65
+
66
+ def rebuild_nested_fields
67
+ _nested_fields.each do |c|
68
+ value = self.send(c)
69
+ self.send("#{c}=", value) unless value.nil?
70
+ end
71
+ end
72
+
73
+ def nested_fields_must_be_valid
74
+
75
+ _nested_fields.each do |field|
76
+ value = read_attribute(field)
77
+ i = 0
78
+ field_errors = {}
79
+ value.each do |v|
80
+ if v.respond_to?(:origin) and v.origin.respond_to?(:invalid?) and v.origin.invalid?
81
+ field_errors[i.to_s] = v.origin.errors
82
+ i += 1
83
+ end
84
+ end if value.respond_to? :each
85
+ errors.add(field, field_errors) unless field_errors.empty?
86
+ end unless _nested_fields.nil?
87
+ end
88
+
89
+ end
90
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+
3
+ require 'mongoid'
4
+ require 'active_support/inflector'
5
+ require 'active_model/validations'
6
+ require 'yajl'
7
+
8
+ %w(nested_field_setter nested_field_holder nested_field_part nested_field_hash errors).each do |f|
9
+ require "mongoid_nested_fields/#{f}"
10
+ end
11
+
12
+ Mongoid::Document.send(:include, MongoidNestedFields::NestedFieldSetter)
13
+
14
+ module MongoidNestedFields
15
+
16
+ end
@@ -0,0 +1,92 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{mongoid_nested_fields}
8
+ s.version = "0.1.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["retro"]
12
+ s.date = %q{2010-12-26}
13
+ s.description = %q{MongoidNestedFields allows you to handle complex data structures inside one field in MongoDB. It also validates whole object graph on field validation}
14
+ s.email = %q{konjevic@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".autotest",
21
+ ".document",
22
+ "Gemfile",
23
+ "Gemfile.lock",
24
+ "LICENSE.txt",
25
+ "README.rdoc",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "lib/mongoid_nested_fields.rb",
29
+ "lib/mongoid_nested_fields/errors.rb",
30
+ "lib/mongoid_nested_fields/errors/unexpected_type.rb",
31
+ "lib/mongoid_nested_fields/nested_field_hash.rb",
32
+ "lib/mongoid_nested_fields/nested_field_holder.rb",
33
+ "lib/mongoid_nested_fields/nested_field_part.rb",
34
+ "lib/mongoid_nested_fields/nested_field_setter.rb",
35
+ "mongoid_nested_fields.gemspec",
36
+ "test/helper.rb",
37
+ "test/test_mongoid_nested_fields.rb"
38
+ ]
39
+ s.homepage = %q{http://github.com/retro/mongoid_nested_fields}
40
+ s.licenses = ["MIT"]
41
+ s.require_paths = ["lib"]
42
+ s.rubygems_version = %q{1.3.7}
43
+ s.summary = %q{MongoidNestedFields allows you to handle complex data structures inside one field in MongoDB.}
44
+ s.test_files = [
45
+ "test/helper.rb",
46
+ "test/test_mongoid_nested_fields.rb"
47
+ ]
48
+
49
+ if s.respond_to? :specification_version then
50
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
51
+ s.specification_version = 3
52
+
53
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
54
+ s.add_runtime_dependency(%q<mongoid>, ["= 2.0.0.beta.20"])
55
+ s.add_runtime_dependency(%q<bson_ext>, ["= 1.1.5"])
56
+ s.add_runtime_dependency(%q<yajl-ruby>, [">= 0.7.8"])
57
+ s.add_runtime_dependency(%q<activesupport>, [">= 3.0.3"])
58
+ s.add_runtime_dependency(%q<activemodel>, [">= 3.0.3"])
59
+ s.add_development_dependency(%q<autotest>, [">= 0"])
60
+ s.add_development_dependency(%q<autotest-growl>, [">= 0"])
61
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
62
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
63
+ s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
64
+ s.add_development_dependency(%q<rcov>, [">= 0"])
65
+ else
66
+ s.add_dependency(%q<mongoid>, ["= 2.0.0.beta.20"])
67
+ s.add_dependency(%q<bson_ext>, ["= 1.1.5"])
68
+ s.add_dependency(%q<yajl-ruby>, [">= 0.7.8"])
69
+ s.add_dependency(%q<activesupport>, [">= 3.0.3"])
70
+ s.add_dependency(%q<activemodel>, [">= 3.0.3"])
71
+ s.add_dependency(%q<autotest>, [">= 0"])
72
+ s.add_dependency(%q<autotest-growl>, [">= 0"])
73
+ s.add_dependency(%q<shoulda>, [">= 0"])
74
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
75
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
76
+ s.add_dependency(%q<rcov>, [">= 0"])
77
+ end
78
+ else
79
+ s.add_dependency(%q<mongoid>, ["= 2.0.0.beta.20"])
80
+ s.add_dependency(%q<bson_ext>, ["= 1.1.5"])
81
+ s.add_dependency(%q<yajl-ruby>, [">= 0.7.8"])
82
+ s.add_dependency(%q<activesupport>, [">= 3.0.3"])
83
+ s.add_dependency(%q<activemodel>, [">= 3.0.3"])
84
+ s.add_dependency(%q<autotest>, [">= 0"])
85
+ s.add_dependency(%q<autotest-growl>, [">= 0"])
86
+ s.add_dependency(%q<shoulda>, [">= 0"])
87
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
88
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
89
+ s.add_dependency(%q<rcov>, [">= 0"])
90
+ end
91
+ end
92
+
data/test/helper.rb ADDED
@@ -0,0 +1,250 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+
6
+ begin
7
+ Bundler.setup(:default, :development)
8
+ rescue Bundler::BundlerError => e
9
+ $stderr.puts e.message
10
+ $stderr.puts "Run `bundle install` to install missing gems"
11
+ exit e.status_code
12
+ end
13
+
14
+ require 'test/unit'
15
+ require 'shoulda'
16
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
17
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
18
+
19
+ require 'mongoid_nested_fields'
20
+
21
+ class Test::Unit::TestCase
22
+ end
23
+
24
+ # For the queasy, this is MIT licensed. See comment at the end.
25
+
26
+ module ExceptionMacros
27
+ # Make sure a block raises an exception.
28
+ # Call with optional arguments :instance_of/:kind_of and :message
29
+ # If :instance_of or :kind_of is specified, assert on the given type.
30
+ # Otherwise only assert that an exception is raised.
31
+ # Note: The shorthand should_raise(LoadError) is equivalent to should_raise(:instance_of => LoadError)
32
+ # If :message is specified, will assert that exception.message =~ :message.
33
+ #
34
+ # Examples:
35
+ # should_raise {a block}
36
+ # should_raise(LoadError) {a block}
37
+ # should_raise(:instance_of => LoadError) {a block}
38
+ # should_raise(:kind_of => LoadError) {a block}
39
+ # should_raise(:message => "no such file to load") {a block}
40
+ # should_raise(:message => /load/) {a block}
41
+ # should_raise(LoadError, :message => /load/) {a block}
42
+ # should_raise(:kind_of => LoadError, :message => /load/) {a block}
43
+ # should_raise(:instance_of => LoadError, :message => /load/) {a block}
44
+ def should_raise(*args, &block)
45
+ opts = args.last.is_a?(Hash) ? args.pop : {}
46
+
47
+ if args.first.is_a?(Class)
48
+ type = args.first
49
+ exact = true
50
+ else
51
+ type = opts[:instance_of] || opts[:kind_of]
52
+ exact = !!opts[:instance_of]
53
+ end
54
+ message = opts[:message]
55
+
56
+ # Make sure we don't have a false sense of security and bork if incorrect options are supplied.
57
+ [:message, :instance_of, :kind_of].each { |acceptable_arg| opts.delete(acceptable_arg) }
58
+ raise ArgumentError, "Unknown parameter(s): #{opts.keys.inspect}. Only :message, :instance_of and :kind_of are supported." if opts.size > 0
59
+
60
+ context "block #{block.inspect}" do # To avoid dupes
61
+ if type
62
+
63
+ should "raise an exception of type #{type.inspect}" do
64
+ begin
65
+ yield
66
+ rescue Exception => ex
67
+ @raised_exception = ex
68
+ end
69
+ if exact
70
+ assert_instance_of type, @raised_exception
71
+ else
72
+ assert_kind_of type, @raised_exception
73
+ end
74
+ end
75
+
76
+ else
77
+
78
+ should "raise an exception" do
79
+ has_raised = false
80
+ begin
81
+ yield
82
+ rescue Exception => ex
83
+ has_raised = true
84
+ end
85
+ assert has_raised, "The block was expected to raise an exception, but didn't"
86
+ end
87
+
88
+ end
89
+ end
90
+
91
+ if message
92
+ context "raising an exception" do
93
+ setup do
94
+ begin
95
+ yield
96
+ rescue Exception => ex
97
+ @raised_exception = ex
98
+ end
99
+ end
100
+
101
+ should "contain a message that matches #{message.inspect}" do
102
+ assert_match message, @raised_exception.message
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ Test::Unit::TestCase.send :extend, ExceptionMacros
110
+
111
+ # A test suite for the macro
112
+
113
+ =begin
114
+ require "#{ File.dirname(__FILE__) }/../test_helper"
115
+
116
+ class ExceptionMacroTest < Test::Unit::TestCase
117
+ # All of the basic Ruby 1.8 exceptions, except for 'fatal'
118
+ EXCEPTIONS = [
119
+ Exception,
120
+ NoMemoryError,
121
+ ScriptError,
122
+ LoadError,
123
+ NotImplementedError,
124
+ SyntaxError,
125
+ SecurityError,
126
+ SignalException,
127
+ Interrupt,
128
+ StandardError,
129
+ ArgumentError,
130
+ IndexError,
131
+ IOError,
132
+ EOFError,
133
+ LocalJumpError,
134
+ NameError,
135
+ NoMethodError,
136
+ RangeError,
137
+ FloatDomainError,
138
+ RegexpError,
139
+ RuntimeError,
140
+ SystemCallError,
141
+ ThreadError,
142
+ TypeError,
143
+ ZeroDivisionError,
144
+ SystemExit,
145
+ SystemStackError]
146
+
147
+ EXCEPTIONS_WITH_PARAMS = {
148
+ Interrupt => 0,
149
+ SystemCallError => 2,
150
+ SignalException => 2
151
+ }
152
+
153
+ PLAIN_EXCEPTIONS = EXCEPTIONS - EXCEPTIONS_WITH_PARAMS.keys
154
+
155
+ context "Raising all the types of exceptions" do
156
+ PLAIN_EXCEPTIONS.each do |x|
157
+ context "for exception #{x.inspect}, with a straight exception class argument" do
158
+ should_raise(x) { raise x }
159
+ end
160
+ end
161
+
162
+ EXCEPTIONS_WITH_PARAMS.each_pair do |x, p|
163
+ context "for exception #{x.inspect} with param #{p.inspect}, with a :kind_of exception" do
164
+ should_raise(:kind_of => x) { raise x, p }
165
+ end
166
+ end
167
+
168
+ (PLAIN_EXCEPTIONS - [SystemCallError]).each do |x|
169
+ context "for exception #{x.inspect}, with an :instance_of exception" do
170
+ should_raise(:instance_of => x) { raise x }
171
+ end
172
+ end
173
+ end
174
+
175
+ context "Using an unknown param_type" do
176
+ should_raise(ArgumentError, :message => /:unknown_param_type/) do
177
+ should_raise(:unknown_param_type => 'foo') {}
178
+ end
179
+ end
180
+
181
+ should_raise do
182
+ require "more vespene gas"
183
+ end
184
+ # 1 assertion
185
+
186
+ should_raise(LoadError) do
187
+ require "more vespene gas"
188
+ end
189
+ # 1 more restrictive assertion
190
+
191
+ should_raise(:instance_of => LoadError) do
192
+ require "more vespene gas"
193
+ end
194
+ # 1 assertion, the same as should_raise(LoadError)
195
+
196
+ should_raise(:kind_of => ScriptError) do
197
+ require "more vespene gas"
198
+ end
199
+ # 1 assertion, slightly less strict than with :instance_of (note: LoadError < ScriptError)
200
+
201
+ should_raise(:message => "no such file to load") do
202
+ require "more vespene gas"
203
+ end
204
+ # 2 assertions
205
+
206
+ should_raise(:message => /vespene/) do
207
+ require "more vespene gas"
208
+ end
209
+ # 2 assertions
210
+
211
+ should_raise(LoadError, :message => "such file to load") do
212
+ require "more vespene gas"
213
+ end
214
+ # 2 assertions
215
+
216
+ should_raise(:kind_of => LoadError, :message => "file to load") do
217
+ require "more vespene gas"
218
+ end
219
+ # 2 assertions
220
+
221
+ should_raise(:instance_of => LoadError, :message => "to load") do
222
+ require "more vespene gas"
223
+ end
224
+ # 2 assertions
225
+ end
226
+ =end
227
+
228
+
229
+ =begin
230
+ Copyright (c) 2008 Mathieu Martin
231
+
232
+ Permission is hereby granted, free of charge, to any person obtaining
233
+ a copy of this software and associated documentation files (the
234
+ "Software"), to deal in the Software without restriction, including
235
+ without limitation the rights to use, copy, modify, merge, publish,
236
+ distribute, sublicense, and/or sell copies of the Software, and to
237
+ permit persons to whom the Software is furnished to do so, subject to
238
+ the following conditions:
239
+
240
+ The above copyright notice and this permission notice shall be
241
+ included in all copies or substantial portions of the Software.
242
+
243
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
244
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
245
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
246
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
247
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
248
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
249
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
250
+ =end
@@ -0,0 +1,195 @@
1
+ # encoding: utf-8
2
+
3
+ require 'helper'
4
+
5
+ class TestMongoidNestedFields < Test::Unit::TestCase
6
+ context "MongoidNestedFields" do
7
+ setup do
8
+ Mongoid.configure do |config|
9
+ name = "mongoid_content_block_development"
10
+ host = "localhost"
11
+ config.master = Mongo::Connection.new.db(name)
12
+ config.persist_in_safe_mode = false
13
+ end
14
+
15
+ class ContentItem
16
+ include Mongoid::Document
17
+ nested_field :paragraph, :allowed_types => [String, Hash]
18
+ end
19
+ ContentItem.destroy_all
20
+ end
21
+
22
+ context "Class that includes Mongoid::Document" do
23
+
24
+ should "have nested_field class method" do
25
+ assert ContentItem.respond_to?(:nested_field)
26
+ end
27
+
28
+ should "correctly set field via nested_field" do
29
+ c = ContentItem.new
30
+ assert c.respond_to?(:paragraph)
31
+ end
32
+
33
+ should "set nested_field as MongoidNestedFields::NestedFieldHolder" do
34
+ assert_equal ContentItem.fields['paragraph'].type, MongoidNestedFields::NestedFieldHolder
35
+ end
36
+
37
+ should_raise MongoidNestedFields::Errors::UnexpectedType do
38
+ # Test if we can put unexpected type in value array
39
+ c = ContentItem.new
40
+ c.paragraph = [1]
41
+ end
42
+
43
+ should_raise TypeError do
44
+ c = ContentItem.new
45
+ c.paragraph = {:k => 'v'}
46
+ end
47
+
48
+ should "set nested_field to array of values with allowed types" do
49
+ c = ContentItem.new
50
+ c.paragraph = ["String", {'k' => 'Value'}]
51
+ assert_equal c.paragraph, ["String", {'k' => 'Value'}]
52
+ end
53
+
54
+ should "set nested_field from JSON" do
55
+ c = ContentItem.new
56
+ c.paragraph = "[\"String\",{\"k\":\"Value\"}]"
57
+ assert_equal c.paragraph, ["String", {'k' => 'Value'}]
58
+ end
59
+
60
+ should "use correct class to set value if _type attribute is present" do
61
+
62
+ class ::ImageWithCaption
63
+ include MongoidNestedFields::NestedField
64
+ field :image
65
+ field :caption
66
+ def image
67
+ read_attribute('image').upcase
68
+ end
69
+ def caption
70
+ read_attribute('caption').upcase
71
+ end
72
+ end
73
+
74
+ ContentItem.nested_field :image_with_caption, :allowed_types => [ImageWithCaption]
75
+ c = ContentItem.new
76
+ c.image_with_caption = [
77
+ {'_type' => 'ImageWithCaption', :image => 'Image', :caption => 'Caption'}
78
+ ]
79
+ c.save
80
+
81
+ assert_equal c.image_with_caption.first, {'_type' => 'image_with_caption', 'image' => 'IMAGE', 'caption' => 'CAPTION'}
82
+ end
83
+ should "allow of nesting content fields and content field parts" do
84
+ class ::Image
85
+ include MongoidNestedFields::NestedField
86
+ field :image
87
+
88
+ def image
89
+ read_attribute('image').upcase
90
+ end
91
+
92
+ end
93
+
94
+ class ::ImageGallery
95
+ include MongoidNestedFields::NestedField
96
+ nested_field :gallery, :allowed_types => [Image]
97
+ field :gallery_name
98
+ end
99
+
100
+ ContentItem.nested_field :gallery, :allowed_types => [ImageGallery]
101
+ c = ContentItem.new
102
+ c.gallery = [{
103
+ '_type' => 'image_gallery',
104
+ 'gallery_name' => 'Test',
105
+ :gallery => [
106
+ {:_type => 'image', :image => 'img'}
107
+ ]
108
+ }]
109
+
110
+ assert_equal c.gallery, [{
111
+ '_type' => 'image_gallery',
112
+ 'gallery_name' => 'Test',
113
+ 'gallery' => [
114
+ {'_type' => 'image', 'image' => 'IMG'}
115
+ ]
116
+ }]
117
+
118
+ end
119
+
120
+ should "validate each content field" do
121
+ class ::ParagraphWithTitle
122
+ include MongoidNestedFields::NestedField
123
+ field :title
124
+ field :paragraph
125
+
126
+ validates_presence_of :paragraph
127
+ end
128
+
129
+ ContentItem.nested_field :paragraph_with_title, :allowed_types => [ParagraphWithTitle]
130
+ c = ContentItem.new
131
+ c.paragraph_with_title = [{
132
+ :_type => 'paragraph_with_title',
133
+ :title => 'Title'
134
+ }]
135
+ c.invalid?
136
+ assert c.invalid?
137
+ end
138
+
139
+ should "validate nested fields" do
140
+ class ::Para
141
+ include MongoidNestedFields::NestedField
142
+ field :para
143
+ field :subtitle
144
+ validates_presence_of :para
145
+ end
146
+ class ::Title
147
+ include MongoidNestedFields::NestedField
148
+ field :title
149
+ nested_field :paragraph, :allowed_types => [Para]
150
+ end
151
+
152
+ ContentItem.nested_field :test_para_title, :allowed_types => [Title]
153
+
154
+ c = ContentItem.new(:test_para_title => [{
155
+ '_type' => 'title',
156
+ :title => 'Title',
157
+ :paragraph => [{
158
+ '_type' => 'para',
159
+ :subtitle => 'Subtitle'
160
+ }]
161
+ }])
162
+
163
+ assert c.invalid?
164
+ end
165
+
166
+ should "load correctly from database" do
167
+ class ::ParagraphWithTitle
168
+ include MongoidNestedFields::NestedField
169
+ field :title
170
+ field :paragraph
171
+
172
+ validates_presence_of :paragraph
173
+ end
174
+
175
+ ContentItem.destroy_all
176
+
177
+ ContentItem.nested_field :paragraph_with_title, :allowed_types => [ParagraphWithTitle]
178
+ c = ContentItem.new
179
+ c.paragraph_with_title = [{
180
+ :_type => 'paragraph_with_title',
181
+ :title => 'Title',
182
+ :paragraph => 'Para'
183
+ }]
184
+
185
+ c.save
186
+
187
+ document = ContentItem.first
188
+
189
+ assert_equal document.paragraph_with_title.first.origin.class, ::ParagraphWithTitle
190
+
191
+ end
192
+
193
+ end
194
+ end
195
+ end
metadata ADDED
@@ -0,0 +1,242 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mongoid_nested_fields
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 1
9
+ version: 0.1.1
10
+ platform: ruby
11
+ authors:
12
+ - retro
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-12-26 00:00:00 +01:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: mongoid
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ none: false
24
+ requirements:
25
+ - - "="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 2
29
+ - 0
30
+ - 0
31
+ - beta
32
+ - 20
33
+ version: 2.0.0.beta.20
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: bson_ext
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - "="
43
+ - !ruby/object:Gem::Version
44
+ segments:
45
+ - 1
46
+ - 1
47
+ - 5
48
+ version: 1.1.5
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
53
+ name: yajl-ruby
54
+ requirement: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ segments:
60
+ - 0
61
+ - 7
62
+ - 8
63
+ version: 0.7.8
64
+ type: :runtime
65
+ prerelease: false
66
+ version_requirements: *id003
67
+ - !ruby/object:Gem::Dependency
68
+ name: activesupport
69
+ requirement: &id004 !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ segments:
75
+ - 3
76
+ - 0
77
+ - 3
78
+ version: 3.0.3
79
+ type: :runtime
80
+ prerelease: false
81
+ version_requirements: *id004
82
+ - !ruby/object:Gem::Dependency
83
+ name: activemodel
84
+ requirement: &id005 !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ segments:
90
+ - 3
91
+ - 0
92
+ - 3
93
+ version: 3.0.3
94
+ type: :runtime
95
+ prerelease: false
96
+ version_requirements: *id005
97
+ - !ruby/object:Gem::Dependency
98
+ name: autotest
99
+ requirement: &id006 !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ segments:
105
+ - 0
106
+ version: "0"
107
+ type: :development
108
+ prerelease: false
109
+ version_requirements: *id006
110
+ - !ruby/object:Gem::Dependency
111
+ name: autotest-growl
112
+ requirement: &id007 !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ segments:
118
+ - 0
119
+ version: "0"
120
+ type: :development
121
+ prerelease: false
122
+ version_requirements: *id007
123
+ - !ruby/object:Gem::Dependency
124
+ name: shoulda
125
+ requirement: &id008 !ruby/object:Gem::Requirement
126
+ none: false
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ segments:
131
+ - 0
132
+ version: "0"
133
+ type: :development
134
+ prerelease: false
135
+ version_requirements: *id008
136
+ - !ruby/object:Gem::Dependency
137
+ name: bundler
138
+ requirement: &id009 !ruby/object:Gem::Requirement
139
+ none: false
140
+ requirements:
141
+ - - ~>
142
+ - !ruby/object:Gem::Version
143
+ segments:
144
+ - 1
145
+ - 0
146
+ - 0
147
+ version: 1.0.0
148
+ type: :development
149
+ prerelease: false
150
+ version_requirements: *id009
151
+ - !ruby/object:Gem::Dependency
152
+ name: jeweler
153
+ requirement: &id010 !ruby/object:Gem::Requirement
154
+ none: false
155
+ requirements:
156
+ - - ~>
157
+ - !ruby/object:Gem::Version
158
+ segments:
159
+ - 1
160
+ - 5
161
+ - 2
162
+ version: 1.5.2
163
+ type: :development
164
+ prerelease: false
165
+ version_requirements: *id010
166
+ - !ruby/object:Gem::Dependency
167
+ name: rcov
168
+ requirement: &id011 !ruby/object:Gem::Requirement
169
+ none: false
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ segments:
174
+ - 0
175
+ version: "0"
176
+ type: :development
177
+ prerelease: false
178
+ version_requirements: *id011
179
+ description: MongoidNestedFields allows you to handle complex data structures inside one field in MongoDB. It also validates whole object graph on field validation
180
+ email: konjevic@gmail.com
181
+ executables: []
182
+
183
+ extensions: []
184
+
185
+ extra_rdoc_files:
186
+ - LICENSE.txt
187
+ - README.rdoc
188
+ files:
189
+ - .autotest
190
+ - .document
191
+ - Gemfile
192
+ - Gemfile.lock
193
+ - LICENSE.txt
194
+ - README.rdoc
195
+ - Rakefile
196
+ - VERSION
197
+ - lib/mongoid_nested_fields.rb
198
+ - lib/mongoid_nested_fields/errors.rb
199
+ - lib/mongoid_nested_fields/errors/unexpected_type.rb
200
+ - lib/mongoid_nested_fields/nested_field_hash.rb
201
+ - lib/mongoid_nested_fields/nested_field_holder.rb
202
+ - lib/mongoid_nested_fields/nested_field_part.rb
203
+ - lib/mongoid_nested_fields/nested_field_setter.rb
204
+ - mongoid_nested_fields.gemspec
205
+ - test/helper.rb
206
+ - test/test_mongoid_nested_fields.rb
207
+ has_rdoc: true
208
+ homepage: http://github.com/retro/mongoid_nested_fields
209
+ licenses:
210
+ - MIT
211
+ post_install_message:
212
+ rdoc_options: []
213
+
214
+ require_paths:
215
+ - lib
216
+ required_ruby_version: !ruby/object:Gem::Requirement
217
+ none: false
218
+ requirements:
219
+ - - ">="
220
+ - !ruby/object:Gem::Version
221
+ hash: -4348957618995795359
222
+ segments:
223
+ - 0
224
+ version: "0"
225
+ required_rubygems_version: !ruby/object:Gem::Requirement
226
+ none: false
227
+ requirements:
228
+ - - ">="
229
+ - !ruby/object:Gem::Version
230
+ segments:
231
+ - 0
232
+ version: "0"
233
+ requirements: []
234
+
235
+ rubyforge_project:
236
+ rubygems_version: 1.3.7
237
+ signing_key:
238
+ specification_version: 3
239
+ summary: MongoidNestedFields allows you to handle complex data structures inside one field in MongoDB.
240
+ test_files:
241
+ - test/helper.rb
242
+ - test/test_mongoid_nested_fields.rb