humanoid 1.2.7
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.
- data/.gitignore +6 -0
- data/.watchr +29 -0
- data/HISTORY +342 -0
- data/MIT_LICENSE +20 -0
- data/README.rdoc +56 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/caliper.yml +4 -0
- data/humanoid.gemspec +374 -0
- data/lib/humanoid.rb +111 -0
- data/lib/humanoid/associations.rb +258 -0
- data/lib/humanoid/associations/belongs_to.rb +64 -0
- data/lib/humanoid/associations/belongs_to_related.rb +62 -0
- data/lib/humanoid/associations/has_many.rb +180 -0
- data/lib/humanoid/associations/has_many_related.rb +109 -0
- data/lib/humanoid/associations/has_one.rb +95 -0
- data/lib/humanoid/associations/has_one_related.rb +81 -0
- data/lib/humanoid/associations/options.rb +57 -0
- data/lib/humanoid/associations/proxy.rb +31 -0
- data/lib/humanoid/attributes.rb +184 -0
- data/lib/humanoid/callbacks.rb +23 -0
- data/lib/humanoid/collection.rb +118 -0
- data/lib/humanoid/collections/cyclic_iterator.rb +34 -0
- data/lib/humanoid/collections/master.rb +28 -0
- data/lib/humanoid/collections/mimic.rb +46 -0
- data/lib/humanoid/collections/operations.rb +41 -0
- data/lib/humanoid/collections/slaves.rb +44 -0
- data/lib/humanoid/commands.rb +182 -0
- data/lib/humanoid/commands/create.rb +21 -0
- data/lib/humanoid/commands/delete.rb +16 -0
- data/lib/humanoid/commands/delete_all.rb +23 -0
- data/lib/humanoid/commands/deletion.rb +18 -0
- data/lib/humanoid/commands/destroy.rb +19 -0
- data/lib/humanoid/commands/destroy_all.rb +23 -0
- data/lib/humanoid/commands/save.rb +27 -0
- data/lib/humanoid/components.rb +24 -0
- data/lib/humanoid/config.rb +84 -0
- data/lib/humanoid/contexts.rb +25 -0
- data/lib/humanoid/contexts/enumerable.rb +117 -0
- data/lib/humanoid/contexts/ids.rb +25 -0
- data/lib/humanoid/contexts/mongo.rb +224 -0
- data/lib/humanoid/contexts/paging.rb +42 -0
- data/lib/humanoid/criteria.rb +259 -0
- data/lib/humanoid/criterion/complex.rb +21 -0
- data/lib/humanoid/criterion/exclusion.rb +65 -0
- data/lib/humanoid/criterion/inclusion.rb +91 -0
- data/lib/humanoid/criterion/optional.rb +128 -0
- data/lib/humanoid/cursor.rb +82 -0
- data/lib/humanoid/document.rb +300 -0
- data/lib/humanoid/enslavement.rb +38 -0
- data/lib/humanoid/errors.rb +77 -0
- data/lib/humanoid/extensions.rb +84 -0
- data/lib/humanoid/extensions/array/accessors.rb +17 -0
- data/lib/humanoid/extensions/array/aliasing.rb +4 -0
- data/lib/humanoid/extensions/array/assimilation.rb +26 -0
- data/lib/humanoid/extensions/array/conversions.rb +29 -0
- data/lib/humanoid/extensions/array/parentization.rb +13 -0
- data/lib/humanoid/extensions/boolean/conversions.rb +16 -0
- data/lib/humanoid/extensions/date/conversions.rb +15 -0
- data/lib/humanoid/extensions/datetime/conversions.rb +17 -0
- data/lib/humanoid/extensions/float/conversions.rb +16 -0
- data/lib/humanoid/extensions/hash/accessors.rb +38 -0
- data/lib/humanoid/extensions/hash/assimilation.rb +30 -0
- data/lib/humanoid/extensions/hash/conversions.rb +15 -0
- data/lib/humanoid/extensions/hash/criteria_helpers.rb +20 -0
- data/lib/humanoid/extensions/hash/scoping.rb +12 -0
- data/lib/humanoid/extensions/integer/conversions.rb +16 -0
- data/lib/humanoid/extensions/nil/assimilation.rb +13 -0
- data/lib/humanoid/extensions/object/conversions.rb +33 -0
- data/lib/humanoid/extensions/proc/scoping.rb +12 -0
- data/lib/humanoid/extensions/string/conversions.rb +15 -0
- data/lib/humanoid/extensions/string/inflections.rb +97 -0
- data/lib/humanoid/extensions/symbol/inflections.rb +36 -0
- data/lib/humanoid/extensions/time/conversions.rb +18 -0
- data/lib/humanoid/factory.rb +19 -0
- data/lib/humanoid/field.rb +39 -0
- data/lib/humanoid/fields.rb +62 -0
- data/lib/humanoid/finders.rb +224 -0
- data/lib/humanoid/identity.rb +39 -0
- data/lib/humanoid/indexes.rb +30 -0
- data/lib/humanoid/matchers.rb +36 -0
- data/lib/humanoid/matchers/all.rb +11 -0
- data/lib/humanoid/matchers/default.rb +26 -0
- data/lib/humanoid/matchers/exists.rb +13 -0
- data/lib/humanoid/matchers/gt.rb +11 -0
- data/lib/humanoid/matchers/gte.rb +11 -0
- data/lib/humanoid/matchers/in.rb +11 -0
- data/lib/humanoid/matchers/lt.rb +11 -0
- data/lib/humanoid/matchers/lte.rb +11 -0
- data/lib/humanoid/matchers/ne.rb +11 -0
- data/lib/humanoid/matchers/nin.rb +11 -0
- data/lib/humanoid/matchers/size.rb +11 -0
- data/lib/humanoid/memoization.rb +27 -0
- data/lib/humanoid/named_scope.rb +40 -0
- data/lib/humanoid/scope.rb +75 -0
- data/lib/humanoid/timestamps.rb +30 -0
- data/lib/humanoid/versioning.rb +28 -0
- data/perf/benchmark.rb +77 -0
- data/spec/integration/humanoid/associations_spec.rb +301 -0
- data/spec/integration/humanoid/attributes_spec.rb +22 -0
- data/spec/integration/humanoid/commands_spec.rb +216 -0
- data/spec/integration/humanoid/contexts/enumerable_spec.rb +33 -0
- data/spec/integration/humanoid/criteria_spec.rb +224 -0
- data/spec/integration/humanoid/document_spec.rb +587 -0
- data/spec/integration/humanoid/extensions_spec.rb +26 -0
- data/spec/integration/humanoid/finders_spec.rb +119 -0
- data/spec/integration/humanoid/inheritance_spec.rb +137 -0
- data/spec/integration/humanoid/named_scope_spec.rb +46 -0
- data/spec/models/address.rb +39 -0
- data/spec/models/animal.rb +6 -0
- data/spec/models/comment.rb +8 -0
- data/spec/models/country_code.rb +6 -0
- data/spec/models/employer.rb +5 -0
- data/spec/models/game.rb +6 -0
- data/spec/models/inheritance.rb +56 -0
- data/spec/models/location.rb +5 -0
- data/spec/models/mixed_drink.rb +4 -0
- data/spec/models/name.rb +13 -0
- data/spec/models/namespacing.rb +11 -0
- data/spec/models/patient.rb +4 -0
- data/spec/models/person.rb +98 -0
- data/spec/models/pet.rb +7 -0
- data/spec/models/pet_owner.rb +6 -0
- data/spec/models/phone.rb +7 -0
- data/spec/models/post.rb +15 -0
- data/spec/models/translation.rb +5 -0
- data/spec/models/vet_visit.rb +5 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/unit/mongoid/associations/belongs_to_related_spec.rb +141 -0
- data/spec/unit/mongoid/associations/belongs_to_spec.rb +193 -0
- data/spec/unit/mongoid/associations/has_many_related_spec.rb +387 -0
- data/spec/unit/mongoid/associations/has_many_spec.rb +471 -0
- data/spec/unit/mongoid/associations/has_one_related_spec.rb +179 -0
- data/spec/unit/mongoid/associations/has_one_spec.rb +282 -0
- data/spec/unit/mongoid/associations/options_spec.rb +191 -0
- data/spec/unit/mongoid/associations_spec.rb +545 -0
- data/spec/unit/mongoid/attributes_spec.rb +484 -0
- data/spec/unit/mongoid/callbacks_spec.rb +55 -0
- data/spec/unit/mongoid/collection_spec.rb +171 -0
- data/spec/unit/mongoid/collections/cyclic_iterator_spec.rb +75 -0
- data/spec/unit/mongoid/collections/master_spec.rb +41 -0
- data/spec/unit/mongoid/collections/mimic_spec.rb +43 -0
- data/spec/unit/mongoid/collections/slaves_spec.rb +81 -0
- data/spec/unit/mongoid/commands/create_spec.rb +30 -0
- data/spec/unit/mongoid/commands/delete_all_spec.rb +58 -0
- data/spec/unit/mongoid/commands/delete_spec.rb +35 -0
- data/spec/unit/mongoid/commands/destroy_all_spec.rb +23 -0
- data/spec/unit/mongoid/commands/destroy_spec.rb +44 -0
- data/spec/unit/mongoid/commands/save_spec.rb +105 -0
- data/spec/unit/mongoid/commands_spec.rb +282 -0
- data/spec/unit/mongoid/config_spec.rb +165 -0
- data/spec/unit/mongoid/contexts/enumerable_spec.rb +374 -0
- data/spec/unit/mongoid/contexts/mongo_spec.rb +505 -0
- data/spec/unit/mongoid/contexts_spec.rb +25 -0
- data/spec/unit/mongoid/criteria_spec.rb +769 -0
- data/spec/unit/mongoid/criterion/complex_spec.rb +19 -0
- data/spec/unit/mongoid/criterion/exclusion_spec.rb +91 -0
- data/spec/unit/mongoid/criterion/inclusion_spec.rb +211 -0
- data/spec/unit/mongoid/criterion/optional_spec.rb +329 -0
- data/spec/unit/mongoid/cursor_spec.rb +74 -0
- data/spec/unit/mongoid/document_spec.rb +986 -0
- data/spec/unit/mongoid/enslavement_spec.rb +63 -0
- data/spec/unit/mongoid/errors_spec.rb +103 -0
- data/spec/unit/mongoid/extensions/array/accessors_spec.rb +50 -0
- data/spec/unit/mongoid/extensions/array/assimilation_spec.rb +24 -0
- data/spec/unit/mongoid/extensions/array/conversions_spec.rb +35 -0
- data/spec/unit/mongoid/extensions/array/parentization_spec.rb +20 -0
- data/spec/unit/mongoid/extensions/boolean/conversions_spec.rb +49 -0
- data/spec/unit/mongoid/extensions/date/conversions_spec.rb +102 -0
- data/spec/unit/mongoid/extensions/datetime/conversions_spec.rb +70 -0
- data/spec/unit/mongoid/extensions/float/conversions_spec.rb +61 -0
- data/spec/unit/mongoid/extensions/hash/accessors_spec.rb +184 -0
- data/spec/unit/mongoid/extensions/hash/assimilation_spec.rb +46 -0
- data/spec/unit/mongoid/extensions/hash/conversions_spec.rb +21 -0
- data/spec/unit/mongoid/extensions/hash/criteria_helpers_spec.rb +17 -0
- data/spec/unit/mongoid/extensions/hash/scoping_spec.rb +14 -0
- data/spec/unit/mongoid/extensions/integer/conversions_spec.rb +61 -0
- data/spec/unit/mongoid/extensions/nil/assimilation_spec.rb +24 -0
- data/spec/unit/mongoid/extensions/object/conversions_spec.rb +43 -0
- data/spec/unit/mongoid/extensions/proc/scoping_spec.rb +34 -0
- data/spec/unit/mongoid/extensions/string/conversions_spec.rb +17 -0
- data/spec/unit/mongoid/extensions/string/inflections_spec.rb +208 -0
- data/spec/unit/mongoid/extensions/symbol/inflections_spec.rb +91 -0
- data/spec/unit/mongoid/extensions/time/conversions_spec.rb +70 -0
- data/spec/unit/mongoid/factory_spec.rb +31 -0
- data/spec/unit/mongoid/field_spec.rb +81 -0
- data/spec/unit/mongoid/fields_spec.rb +158 -0
- data/spec/unit/mongoid/finders_spec.rb +368 -0
- data/spec/unit/mongoid/identity_spec.rb +88 -0
- data/spec/unit/mongoid/indexes_spec.rb +93 -0
- data/spec/unit/mongoid/matchers/all_spec.rb +27 -0
- data/spec/unit/mongoid/matchers/default_spec.rb +27 -0
- data/spec/unit/mongoid/matchers/exists_spec.rb +56 -0
- data/spec/unit/mongoid/matchers/gt_spec.rb +39 -0
- data/spec/unit/mongoid/matchers/gte_spec.rb +49 -0
- data/spec/unit/mongoid/matchers/in_spec.rb +27 -0
- data/spec/unit/mongoid/matchers/lt_spec.rb +39 -0
- data/spec/unit/mongoid/matchers/lte_spec.rb +49 -0
- data/spec/unit/mongoid/matchers/ne_spec.rb +27 -0
- data/spec/unit/mongoid/matchers/nin_spec.rb +27 -0
- data/spec/unit/mongoid/matchers/size_spec.rb +27 -0
- data/spec/unit/mongoid/matchers_spec.rb +329 -0
- data/spec/unit/mongoid/memoization_spec.rb +75 -0
- data/spec/unit/mongoid/named_scope_spec.rb +123 -0
- data/spec/unit/mongoid/scope_spec.rb +240 -0
- data/spec/unit/mongoid/timestamps_spec.rb +25 -0
- data/spec/unit/mongoid/versioning_spec.rb +41 -0
- data/spec/unit/mongoid_spec.rb +37 -0
- metadata +431 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
module Humanoid #:nodoc:
|
|
3
|
+
module Criterion #:nodoc:
|
|
4
|
+
module Optional
|
|
5
|
+
# Tells the criteria that the cursor that gets returned needs to be
|
|
6
|
+
# cached. This is so multiple iterations don't hit the database multiple
|
|
7
|
+
# times, however this is not advisable when working with large data sets
|
|
8
|
+
# as the entire results will get stored in memory.
|
|
9
|
+
#
|
|
10
|
+
# Example:
|
|
11
|
+
#
|
|
12
|
+
# <tt>criteria.cache</tt>
|
|
13
|
+
def cache
|
|
14
|
+
@options.merge!(:cache => true); self
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Will return true if the cache option has been set.
|
|
18
|
+
#
|
|
19
|
+
# Example:
|
|
20
|
+
#
|
|
21
|
+
# <tt>criteria.cached?</tt>
|
|
22
|
+
def cached?
|
|
23
|
+
@cached ||= @options.delete(:cache)
|
|
24
|
+
@cached == true
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Flags the criteria to execute against a read-only slave in the pool
|
|
28
|
+
# instead of master.
|
|
29
|
+
#
|
|
30
|
+
# Example:
|
|
31
|
+
#
|
|
32
|
+
# <tt>criteria.enslave</tt>
|
|
33
|
+
def enslave
|
|
34
|
+
@options.merge!(:enslave => true); self
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Adds a criterion to the +Criteria+ that specifies additional options
|
|
38
|
+
# to be passed to the Ruby driver, in the exact format for the driver.
|
|
39
|
+
#
|
|
40
|
+
# Options:
|
|
41
|
+
#
|
|
42
|
+
# extras: A +Hash+ that gets set to the driver options.
|
|
43
|
+
#
|
|
44
|
+
# Example:
|
|
45
|
+
#
|
|
46
|
+
# <tt>criteria.extras(:limit => 20, :skip => 40)</tt>
|
|
47
|
+
#
|
|
48
|
+
# Returns: <tt>self</tt>
|
|
49
|
+
def extras(extras)
|
|
50
|
+
@options.merge!(extras); filter_options; self
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Adds a criterion to the +Criteria+ that specifies an id that must be matched.
|
|
54
|
+
#
|
|
55
|
+
# Options:
|
|
56
|
+
#
|
|
57
|
+
# object_id: A +String+ representation of a <tt>Mongo::ObjectID</tt>
|
|
58
|
+
#
|
|
59
|
+
# Example:
|
|
60
|
+
#
|
|
61
|
+
# <tt>criteria.id("4ab2bc4b8ad548971900005c")</tt>
|
|
62
|
+
#
|
|
63
|
+
# Returns: <tt>self</tt>
|
|
64
|
+
def id(*args)
|
|
65
|
+
(args.flatten.size > 1) ? self.in(:_id => args.flatten) : (@selector[:_id] = args.first)
|
|
66
|
+
self
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Adds a criterion to the +Criteria+ that specifies the maximum number of
|
|
70
|
+
# results to return. This is mostly used in conjunction with <tt>skip()</tt>
|
|
71
|
+
# to handle paginated results.
|
|
72
|
+
#
|
|
73
|
+
# Options:
|
|
74
|
+
#
|
|
75
|
+
# value: An +Integer+ specifying the max number of results. Defaults to 20.
|
|
76
|
+
#
|
|
77
|
+
# Example:
|
|
78
|
+
#
|
|
79
|
+
# <tt>criteria.limit(100)</tt>
|
|
80
|
+
#
|
|
81
|
+
# Returns: <tt>self</tt>
|
|
82
|
+
def limit(value = 20)
|
|
83
|
+
@options[:limit] = value; self
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Returns the offset option. If a per_page option is in the list then it
|
|
87
|
+
# will replace it with a skip parameter and return the same value. Defaults
|
|
88
|
+
# to 20 if nothing was provided.
|
|
89
|
+
def offset(*args)
|
|
90
|
+
args.size > 0 ? skip(args.first) : @options[:skip]
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Adds a criterion to the +Criteria+ that specifies the sort order of
|
|
94
|
+
# the returned documents in the database. Similar to a SQL "ORDER BY".
|
|
95
|
+
#
|
|
96
|
+
# Options:
|
|
97
|
+
#
|
|
98
|
+
# params: An +Array+ of [field, direction] sorting pairs.
|
|
99
|
+
#
|
|
100
|
+
# Example:
|
|
101
|
+
#
|
|
102
|
+
# <tt>criteria.order_by([[:field1, :asc], [:field2, :desc]])</tt>
|
|
103
|
+
#
|
|
104
|
+
# Returns: <tt>self</tt>
|
|
105
|
+
def order_by(params = [])
|
|
106
|
+
@options[:sort] = params; self
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Adds a criterion to the +Criteria+ that specifies how many results to skip
|
|
110
|
+
# when returning Documents. This is mostly used in conjunction with
|
|
111
|
+
# <tt>limit()</tt> to handle paginated results, and is similar to the
|
|
112
|
+
# traditional "offset" parameter.
|
|
113
|
+
#
|
|
114
|
+
# Options:
|
|
115
|
+
#
|
|
116
|
+
# value: An +Integer+ specifying the number of results to skip. Defaults to 0.
|
|
117
|
+
#
|
|
118
|
+
# Example:
|
|
119
|
+
#
|
|
120
|
+
# <tt>criteria.skip(20)</tt>
|
|
121
|
+
#
|
|
122
|
+
# Returns: <tt>self</tt>
|
|
123
|
+
def skip(value = 0)
|
|
124
|
+
@options[:skip] = value; self
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
module Humanoid #:nodoc
|
|
3
|
+
class Cursor
|
|
4
|
+
include Enumerable
|
|
5
|
+
# Operations on the Mongo::Cursor object that will not get overriden by the
|
|
6
|
+
# Humanoid::Cursor are defined here.
|
|
7
|
+
OPERATIONS = [
|
|
8
|
+
:admin,
|
|
9
|
+
:close,
|
|
10
|
+
:closed?,
|
|
11
|
+
:count,
|
|
12
|
+
:explain,
|
|
13
|
+
:fields,
|
|
14
|
+
:full_collection_name,
|
|
15
|
+
:hint,
|
|
16
|
+
:limit,
|
|
17
|
+
:order,
|
|
18
|
+
:query_options_hash,
|
|
19
|
+
:query_opts,
|
|
20
|
+
:selector,
|
|
21
|
+
:skip,
|
|
22
|
+
:snapshot,
|
|
23
|
+
:sort,
|
|
24
|
+
:timeout
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
attr_reader :collection
|
|
28
|
+
|
|
29
|
+
# The operations above will all delegate to the proxied Mongo::Cursor.
|
|
30
|
+
#
|
|
31
|
+
# Example:
|
|
32
|
+
#
|
|
33
|
+
# <tt>cursor.close</tt>
|
|
34
|
+
OPERATIONS.each do |name|
|
|
35
|
+
define_method(name) { |*args| @cursor.send(name, *args) }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Iterate over each document in the cursor and yield to it.
|
|
39
|
+
#
|
|
40
|
+
# Example:
|
|
41
|
+
#
|
|
42
|
+
# <tt>cursor.each { |doc| p doc.title }</tt>
|
|
43
|
+
def each
|
|
44
|
+
@cursor.each do |document|
|
|
45
|
+
yield Humanoid::Factory.build(@klass, document)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Create the new +Humanoid::Cursor+.
|
|
50
|
+
#
|
|
51
|
+
# Options:
|
|
52
|
+
#
|
|
53
|
+
# collection: The Humanoid::Collection instance.
|
|
54
|
+
# cursor: The Mongo::Cursor to be proxied.
|
|
55
|
+
#
|
|
56
|
+
# Example:
|
|
57
|
+
#
|
|
58
|
+
# <tt>Humanoid::Cursor.new(Person, cursor)</tt>
|
|
59
|
+
def initialize(klass, collection, cursor)
|
|
60
|
+
@klass, @collection, @cursor = klass, collection, cursor
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Return the next document in the cursor. Will instantiate a new Humanoid
|
|
64
|
+
# document with the attributes.
|
|
65
|
+
#
|
|
66
|
+
# Example:
|
|
67
|
+
#
|
|
68
|
+
# <tt>cursor.next_document</tt>
|
|
69
|
+
def next_document
|
|
70
|
+
Humanoid::Factory.build(@klass, @cursor.next_document)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Returns an array of all the documents in the cursor.
|
|
74
|
+
#
|
|
75
|
+
# Example:
|
|
76
|
+
#
|
|
77
|
+
# <tt>cursor.to_a</tt>
|
|
78
|
+
def to_a
|
|
79
|
+
@cursor.to_a.collect { |attrs| Humanoid::Factory.build(@klass, attrs) }
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
module Humanoid #:nodoc:
|
|
3
|
+
module Document
|
|
4
|
+
def self.included(base)
|
|
5
|
+
base.class_eval do
|
|
6
|
+
include Components
|
|
7
|
+
include InstanceMethods
|
|
8
|
+
extend ClassMethods
|
|
9
|
+
|
|
10
|
+
cattr_accessor :_collection, :collection_name, :embedded, :primary_key, :hereditary
|
|
11
|
+
|
|
12
|
+
self.embedded = false
|
|
13
|
+
self.hereditary = false
|
|
14
|
+
self.collection_name = self.name.collectionize
|
|
15
|
+
|
|
16
|
+
attr_accessor :association_name, :_parent
|
|
17
|
+
attr_reader :new_record
|
|
18
|
+
|
|
19
|
+
delegate :collection, :db, :embedded, :primary_key, :to => "self.class"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
module ClassMethods
|
|
24
|
+
# Return the database associated with this class.
|
|
25
|
+
def db
|
|
26
|
+
collection.db
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Returns the collection associated with this +Document+. If the
|
|
30
|
+
# document is embedded, there will be no collection associated
|
|
31
|
+
# with it.
|
|
32
|
+
#
|
|
33
|
+
# Returns: <tt>Mongo::Collection</tt>
|
|
34
|
+
def collection
|
|
35
|
+
raise Errors::InvalidCollection.new(self) if embedded
|
|
36
|
+
self._collection ||= Humanoid::Collection.new(self, self.collection_name)
|
|
37
|
+
add_indexes; self._collection
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Perform default behavior but mark the hierarchy as being hereditary.
|
|
41
|
+
def inherited(subclass)
|
|
42
|
+
super(subclass)
|
|
43
|
+
self.hereditary = true
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Returns a human readable version of the class.
|
|
47
|
+
#
|
|
48
|
+
# Example:
|
|
49
|
+
#
|
|
50
|
+
# <tt>MixedDrink.human_name # returns "Mixed Drink"</tt>
|
|
51
|
+
def human_name
|
|
52
|
+
name.labelize
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Instantiate a new object, only when loaded from the database or when
|
|
56
|
+
# the attributes have already been typecast.
|
|
57
|
+
#
|
|
58
|
+
# Example:
|
|
59
|
+
#
|
|
60
|
+
# <tt>Person.instantiate(:title => "Sir", :age => 30)</tt>
|
|
61
|
+
def instantiate(attrs = nil, allocating = false)
|
|
62
|
+
attributes = attrs || {}
|
|
63
|
+
if attributes["_id"] || allocating
|
|
64
|
+
document = allocate
|
|
65
|
+
document.instance_variable_set(:@attributes, attributes)
|
|
66
|
+
return document
|
|
67
|
+
else
|
|
68
|
+
return new(attrs)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Defines the field that will be used for the id of this +Document+. This
|
|
73
|
+
# set the id of this +Document+ before save to a parameterized version of
|
|
74
|
+
# the field that was supplied. This is good for use for readable URLS in
|
|
75
|
+
# web applications.
|
|
76
|
+
#
|
|
77
|
+
# Example:
|
|
78
|
+
#
|
|
79
|
+
# class Person
|
|
80
|
+
# include Humanoid::Document
|
|
81
|
+
# field :first_name
|
|
82
|
+
# field :last_name
|
|
83
|
+
# key :first_name, :last_name
|
|
84
|
+
# end
|
|
85
|
+
def key(*fields)
|
|
86
|
+
self.primary_key = fields
|
|
87
|
+
before_save :identify
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Macro for setting the collection name to store in.
|
|
91
|
+
#
|
|
92
|
+
# Example:
|
|
93
|
+
#
|
|
94
|
+
# <tt>Person.store_in :populdation</tt>
|
|
95
|
+
def store_in(name)
|
|
96
|
+
self.collection_name = name.to_s
|
|
97
|
+
self._collection = Humanoid::Collection.new(self, name.to_s)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Returns all types to query for when using this class as the base.
|
|
101
|
+
def _types
|
|
102
|
+
@_type ||= (self.subclasses + [ self.name ])
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
module InstanceMethods
|
|
108
|
+
# Performs equality checking on the attributes. For now we chack against
|
|
109
|
+
# all attributes excluding timestamps on the object.
|
|
110
|
+
def ==(other)
|
|
111
|
+
return false unless other.is_a?(Document)
|
|
112
|
+
attributes.except(:modified_at).except(:created_at) ==
|
|
113
|
+
other.attributes.except(:modified_at).except(:created_at)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Delegates to ==
|
|
117
|
+
def eql?(comparison_object)
|
|
118
|
+
self == (comparison_object)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Delegates to id in order to allow two records of the same type and id to work with something like:
|
|
122
|
+
# [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
|
|
123
|
+
def hash
|
|
124
|
+
id.hash
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Introduces a child object into the +Document+ object graph. This will
|
|
128
|
+
# set up the relationships between the parent and child and update the
|
|
129
|
+
# attributes of the parent +Document+.
|
|
130
|
+
#
|
|
131
|
+
# Options:
|
|
132
|
+
#
|
|
133
|
+
# parent: The +Document+ to assimilate with.
|
|
134
|
+
# options: The association +Options+ for the child.
|
|
135
|
+
#
|
|
136
|
+
# Example:
|
|
137
|
+
#
|
|
138
|
+
# <tt>name.assimilate(person, options)</tt>
|
|
139
|
+
def assimilate(parent, options)
|
|
140
|
+
parentize(parent, options.name); notify; self
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Return the attributes hash with indifferent access.
|
|
144
|
+
def attributes
|
|
145
|
+
@attributes.with_indifferent_access
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Clone the current +Document+. This will return all attributes with the
|
|
149
|
+
# exception of the document's id and versions.
|
|
150
|
+
def clone
|
|
151
|
+
self.class.instantiate(@attributes.except("_id").except("versions").dup, true)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Generate an id for this +Document+.
|
|
155
|
+
def identify
|
|
156
|
+
Identity.create(self)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Instantiate a new +Document+, setting the Document's attributes if
|
|
160
|
+
# given. If no attributes are provided, they will be initialized with
|
|
161
|
+
# an empty +Hash+.
|
|
162
|
+
#
|
|
163
|
+
# If a primary key is defined, the document's id will be set to that key,
|
|
164
|
+
# otherwise it will be set to a fresh +Mongo::ObjectID+ string.
|
|
165
|
+
#
|
|
166
|
+
# Options:
|
|
167
|
+
#
|
|
168
|
+
# attrs: The attributes +Hash+ to set up the document with.
|
|
169
|
+
def initialize(attrs = nil)
|
|
170
|
+
@attributes = {}
|
|
171
|
+
process(attrs)
|
|
172
|
+
@attributes = attributes_with_defaults(@attributes)
|
|
173
|
+
@new_record = true if id.nil?
|
|
174
|
+
document = yield self if block_given?
|
|
175
|
+
identify
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Returns the class name plus its attributes.
|
|
179
|
+
def inspect
|
|
180
|
+
attrs = fields.map { |name, field| "#{name}: #{@attributes[name].inspect}" } * ", "
|
|
181
|
+
"#<#{self.class.name} _id: #{id}, #{attrs}>"
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Returns true is the +Document+ has not been persisted to the database,
|
|
185
|
+
# false if it has. This is determined by the variable @new_record
|
|
186
|
+
# and NOT if the object has an id.
|
|
187
|
+
def new_record?
|
|
188
|
+
@new_record == true
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Sets the new_record boolean - used after document is saved.
|
|
192
|
+
def new_record=(saved)
|
|
193
|
+
@new_record = saved
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Set the changed state of the +Document+ then notify observers that it has changed.
|
|
197
|
+
#
|
|
198
|
+
# Example:
|
|
199
|
+
#
|
|
200
|
+
# <tt>person.notify</tt>
|
|
201
|
+
def notify
|
|
202
|
+
changed(true)
|
|
203
|
+
notify_observers(self)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Sets up a child/parent association. This is used for newly created
|
|
207
|
+
# objects so they can be properly added to the graph and have the parent
|
|
208
|
+
# observers set up properly.
|
|
209
|
+
#
|
|
210
|
+
# Options:
|
|
211
|
+
#
|
|
212
|
+
# abject: The parent object that needs to be set for the child.
|
|
213
|
+
# association_name: The name of the association for the child.
|
|
214
|
+
#
|
|
215
|
+
# Example:
|
|
216
|
+
#
|
|
217
|
+
# <tt>address.parentize(person, :addresses)</tt>
|
|
218
|
+
def parentize(object, association_name)
|
|
219
|
+
self._parent = object
|
|
220
|
+
self.association_name = association_name.to_s
|
|
221
|
+
add_observer(object)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Return the attributes hash.
|
|
225
|
+
def raw_attributes
|
|
226
|
+
@attributes
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Reloads the +Document+ attributes from the database.
|
|
230
|
+
def reload
|
|
231
|
+
@attributes = collection.find_one(:_id => id)
|
|
232
|
+
self
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# Remove a child document from this parent +Document+. Will reset the
|
|
236
|
+
# memoized association and notify the parent of the change.
|
|
237
|
+
def remove(child)
|
|
238
|
+
name = child.association_name
|
|
239
|
+
reset(name) { @attributes.remove(name, child.raw_attributes) }
|
|
240
|
+
notify
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Return the root +Document+ in the object graph. If the current +Document+
|
|
244
|
+
# is the root object in the graph it will return self.
|
|
245
|
+
def _root
|
|
246
|
+
object = self
|
|
247
|
+
while (object._parent) do object = object._parent; end
|
|
248
|
+
object || self
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Return an array with this +Document+ only in it.
|
|
252
|
+
def to_a
|
|
253
|
+
[ self ]
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Return this document as a JSON string. Nothing special is required here
|
|
257
|
+
# since Humanoid bubbles up all the child associations to the parent
|
|
258
|
+
# attribute +Hash+ using observers throughout the +Document+ lifecycle.
|
|
259
|
+
#
|
|
260
|
+
# Example:
|
|
261
|
+
#
|
|
262
|
+
# <tt>person.to_json</tt>
|
|
263
|
+
def to_json(options = nil)
|
|
264
|
+
attributes.to_json(options)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Returns the id of the Document, used in Rails compatibility.
|
|
268
|
+
def to_param
|
|
269
|
+
id
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Observe a notify call from a child +Document+. This will either update
|
|
273
|
+
# existing attributes on the +Document+ or clear them out for the child if
|
|
274
|
+
# the clear boolean is provided.
|
|
275
|
+
#
|
|
276
|
+
# Options:
|
|
277
|
+
#
|
|
278
|
+
# child: The child +Document+ that sent the notification.
|
|
279
|
+
# clear: Will clear out the child's attributes if set to true.
|
|
280
|
+
#
|
|
281
|
+
# This will also cause the observing +Document+ to notify it's parent if
|
|
282
|
+
# there is any.
|
|
283
|
+
def update(child, clear = false)
|
|
284
|
+
name = child.association_name
|
|
285
|
+
attrs = child.instance_variable_get(:@attributes)
|
|
286
|
+
clear ? @attributes.delete(name) : @attributes.insert(name, attrs)
|
|
287
|
+
notify
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
protected
|
|
291
|
+
# apply default values to attributes - calling procs as required
|
|
292
|
+
def attributes_with_defaults(attributes = {})
|
|
293
|
+
default_values = defaults.merge(attributes)
|
|
294
|
+
default_values.each_pair do |key, val|
|
|
295
|
+
default_values[key] = val.call if val.respond_to?(:call)
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
end
|