mack-data_mapper 0.8.1 → 0.8.2

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 (166) hide show
  1. data/lib/gems/addressable-2.0.0/lib/addressable/idna.rb +4867 -0
  2. data/lib/gems/addressable-2.0.0/lib/addressable/uri.rb +2469 -0
  3. data/lib/gems/addressable-2.0.0/lib/addressable/version.rb +35 -0
  4. data/lib/gems/dm-aggregates-0.9.7/lib/dm-aggregates/adapters/data_objects_adapter.rb +85 -0
  5. data/lib/gems/dm-aggregates-0.9.7/lib/dm-aggregates/aggregate_functions.rb +201 -0
  6. data/lib/gems/dm-aggregates-0.9.7/lib/dm-aggregates/collection.rb +11 -0
  7. data/lib/gems/dm-aggregates-0.9.7/lib/dm-aggregates/model.rb +11 -0
  8. data/lib/gems/dm-aggregates-0.9.7/lib/dm-aggregates/repository.rb +7 -0
  9. data/lib/gems/dm-aggregates-0.9.7/lib/dm-aggregates/support/symbol.rb +21 -0
  10. data/lib/gems/dm-aggregates-0.9.7/lib/dm-aggregates/version.rb +7 -0
  11. data/lib/gems/dm-aggregates-0.9.7/lib/dm-aggregates.rb +15 -0
  12. data/lib/gems/dm-core-0.9.7/lib/dm-core/adapters/abstract_adapter.rb +209 -0
  13. data/lib/gems/dm-core-0.9.7/lib/dm-core/adapters/data_objects_adapter.rb +709 -0
  14. data/lib/gems/dm-core-0.9.7/lib/dm-core/adapters/in_memory_adapter.rb +87 -0
  15. data/lib/gems/dm-core-0.9.7/lib/dm-core/adapters/mysql_adapter.rb +136 -0
  16. data/lib/gems/dm-core-0.9.7/lib/dm-core/adapters/postgres_adapter.rb +188 -0
  17. data/lib/gems/dm-core-0.9.7/lib/dm-core/adapters/sqlite3_adapter.rb +105 -0
  18. data/lib/gems/dm-core-0.9.7/lib/dm-core/adapters.rb +22 -0
  19. data/lib/gems/dm-core-0.9.7/lib/dm-core/associations/many_to_many.rb +147 -0
  20. data/lib/gems/dm-core-0.9.7/lib/dm-core/associations/many_to_one.rb +107 -0
  21. data/lib/gems/dm-core-0.9.7/lib/dm-core/associations/one_to_many.rb +318 -0
  22. data/lib/gems/dm-core-0.9.7/lib/dm-core/associations/one_to_one.rb +61 -0
  23. data/lib/gems/dm-core-0.9.7/lib/dm-core/associations/relationship.rb +223 -0
  24. data/lib/gems/dm-core-0.9.7/lib/dm-core/associations/relationship_chain.rb +81 -0
  25. data/lib/gems/dm-core-0.9.7/lib/dm-core/associations.rb +200 -0
  26. data/lib/gems/dm-core-0.9.7/lib/dm-core/auto_migrations.rb +105 -0
  27. data/lib/gems/dm-core-0.9.7/lib/dm-core/collection.rb +642 -0
  28. data/lib/gems/dm-core-0.9.7/lib/dm-core/dependency_queue.rb +32 -0
  29. data/lib/gems/dm-core-0.9.7/lib/dm-core/hook.rb +11 -0
  30. data/lib/gems/dm-core-0.9.7/lib/dm-core/identity_map.rb +42 -0
  31. data/lib/gems/dm-core-0.9.7/lib/dm-core/is.rb +16 -0
  32. data/lib/gems/dm-core-0.9.7/lib/dm-core/logger.rb +232 -0
  33. data/lib/gems/dm-core-0.9.7/lib/dm-core/migrations/destructive_migrations.rb +17 -0
  34. data/lib/gems/dm-core-0.9.7/lib/dm-core/migrator.rb +29 -0
  35. data/lib/gems/dm-core-0.9.7/lib/dm-core/model.rb +488 -0
  36. data/lib/gems/dm-core-0.9.7/lib/dm-core/naming_conventions.rb +84 -0
  37. data/lib/gems/dm-core-0.9.7/lib/dm-core/property.rb +663 -0
  38. data/lib/gems/dm-core-0.9.7/lib/dm-core/property_set.rb +169 -0
  39. data/lib/gems/dm-core-0.9.7/lib/dm-core/query.rb +628 -0
  40. data/lib/gems/dm-core-0.9.7/lib/dm-core/repository.rb +159 -0
  41. data/lib/gems/dm-core-0.9.7/lib/dm-core/resource.rb +637 -0
  42. data/lib/gems/dm-core-0.9.7/lib/dm-core/scope.rb +58 -0
  43. data/lib/gems/dm-core-0.9.7/lib/dm-core/support/array.rb +13 -0
  44. data/lib/gems/dm-core-0.9.7/lib/dm-core/support/assertions.rb +8 -0
  45. data/lib/gems/dm-core-0.9.7/lib/dm-core/support/errors.rb +23 -0
  46. data/lib/gems/dm-core-0.9.7/lib/dm-core/support/kernel.rb +11 -0
  47. data/lib/gems/dm-core-0.9.7/lib/dm-core/support/symbol.rb +41 -0
  48. data/lib/gems/dm-core-0.9.7/lib/dm-core/support.rb +7 -0
  49. data/lib/gems/dm-core-0.9.7/lib/dm-core/transaction.rb +267 -0
  50. data/lib/gems/dm-core-0.9.7/lib/dm-core/type.rb +160 -0
  51. data/lib/gems/dm-core-0.9.7/lib/dm-core/type_map.rb +80 -0
  52. data/lib/gems/dm-core-0.9.7/lib/dm-core/types/boolean.rb +7 -0
  53. data/lib/gems/dm-core-0.9.7/lib/dm-core/types/discriminator.rb +34 -0
  54. data/lib/gems/dm-core-0.9.7/lib/dm-core/types/object.rb +24 -0
  55. data/lib/gems/dm-core-0.9.7/lib/dm-core/types/paranoid_boolean.rb +34 -0
  56. data/lib/gems/dm-core-0.9.7/lib/dm-core/types/paranoid_datetime.rb +33 -0
  57. data/lib/gems/dm-core-0.9.7/lib/dm-core/types/serial.rb +9 -0
  58. data/lib/gems/dm-core-0.9.7/lib/dm-core/types/text.rb +10 -0
  59. data/lib/gems/dm-core-0.9.7/lib/dm-core/types.rb +19 -0
  60. data/lib/gems/dm-core-0.9.7/lib/dm-core/version.rb +3 -0
  61. data/lib/gems/dm-core-0.9.7/lib/dm-core.rb +217 -0
  62. data/lib/gems/dm-core-0.9.7/script/all +5 -0
  63. data/lib/gems/dm-core-0.9.7/script/performance.rb +284 -0
  64. data/lib/gems/dm-core-0.9.7/script/profile.rb +87 -0
  65. data/lib/gems/dm-migrations-0.9.7/lib/dm-migrations/version.rb +5 -0
  66. data/lib/gems/dm-migrations-0.9.7/lib/dm-migrations.rb +1 -0
  67. data/lib/gems/dm-migrations-0.9.7/lib/migration.rb +215 -0
  68. data/lib/gems/dm-migrations-0.9.7/lib/migration_runner.rb +88 -0
  69. data/lib/gems/dm-migrations-0.9.7/lib/spec/example/migration_example_group.rb +73 -0
  70. data/lib/gems/dm-migrations-0.9.7/lib/spec/matchers/migration_matchers.rb +107 -0
  71. data/lib/gems/dm-migrations-0.9.7/lib/sql/column.rb +9 -0
  72. data/lib/gems/dm-migrations-0.9.7/lib/sql/mysql.rb +52 -0
  73. data/lib/gems/dm-migrations-0.9.7/lib/sql/postgresql.rb +78 -0
  74. data/lib/gems/dm-migrations-0.9.7/lib/sql/sqlite3.rb +43 -0
  75. data/lib/gems/dm-migrations-0.9.7/lib/sql/table.rb +19 -0
  76. data/lib/gems/dm-migrations-0.9.7/lib/sql/table_creator.rb +81 -0
  77. data/lib/gems/dm-migrations-0.9.7/lib/sql/table_modifier.rb +53 -0
  78. data/lib/gems/dm-migrations-0.9.7/lib/sql.rb +10 -0
  79. data/lib/gems/dm-observer-0.9.7/lib/dm-observer/version.rb +5 -0
  80. data/lib/gems/dm-observer-0.9.7/lib/dm-observer.rb +91 -0
  81. data/lib/gems/dm-serializer-0.9.7/lib/dm-serializer/version.rb +5 -0
  82. data/lib/gems/dm-serializer-0.9.7/lib/dm-serializer.rb +183 -0
  83. data/lib/gems/dm-timestamps-0.9.7/lib/dm-timestamps/version.rb +5 -0
  84. data/lib/gems/dm-timestamps-0.9.7/lib/dm-timestamps.rb +57 -0
  85. data/lib/gems/dm-types-0.9.7/lib/dm-types/bcrypt_hash.rb +31 -0
  86. data/lib/gems/dm-types-0.9.7/lib/dm-types/csv.rb +28 -0
  87. data/lib/gems/dm-types-0.9.7/lib/dm-types/enum.rb +70 -0
  88. data/lib/gems/dm-types-0.9.7/lib/dm-types/epoch_time.rb +27 -0
  89. data/lib/gems/dm-types-0.9.7/lib/dm-types/file_path.rb +27 -0
  90. data/lib/gems/dm-types-0.9.7/lib/dm-types/flag.rb +61 -0
  91. data/lib/gems/dm-types-0.9.7/lib/dm-types/ip_address.rb +30 -0
  92. data/lib/gems/dm-types-0.9.7/lib/dm-types/json.rb +40 -0
  93. data/lib/gems/dm-types-0.9.7/lib/dm-types/regexp.rb +20 -0
  94. data/lib/gems/dm-types-0.9.7/lib/dm-types/serial.rb +8 -0
  95. data/lib/gems/dm-types-0.9.7/lib/dm-types/slug.rb +37 -0
  96. data/lib/gems/dm-types-0.9.7/lib/dm-types/uri.rb +29 -0
  97. data/lib/gems/dm-types-0.9.7/lib/dm-types/uuid.rb +64 -0
  98. data/lib/gems/dm-types-0.9.7/lib/dm-types/version.rb +5 -0
  99. data/lib/gems/dm-types-0.9.7/lib/dm-types/yaml.rb +36 -0
  100. data/lib/gems/dm-types-0.9.7/lib/dm-types.rb +28 -0
  101. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/absent_field_validator.rb +60 -0
  102. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/acceptance_validator.rb +76 -0
  103. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/auto_validate.rb +153 -0
  104. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/block_validator.rb +60 -0
  105. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/confirmation_validator.rb +80 -0
  106. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/contextual_validators.rb +56 -0
  107. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/custom_validator.rb +72 -0
  108. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/format_validator.rb +97 -0
  109. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/formats/email.rb +40 -0
  110. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/formats/url.rb +20 -0
  111. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/generic_validator.rb +100 -0
  112. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/length_validator.rb +113 -0
  113. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/method_validator.rb +68 -0
  114. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/numeric_validator.rb +83 -0
  115. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/primitive_validator.rb +60 -0
  116. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/required_field_validator.rb +88 -0
  117. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/support/object.rb +5 -0
  118. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/uniqueness_validator.rb +64 -0
  119. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/validation_errors.rb +63 -0
  120. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/version.rb +5 -0
  121. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/within_validator.rb +53 -0
  122. data/lib/gems/dm-validations-0.9.7/lib/dm-validations.rb +234 -0
  123. data/lib/gems/json_pure-1.1.3/GPL +340 -0
  124. data/lib/gems/json_pure-1.1.3/VERSION +1 -0
  125. data/lib/gems/json_pure-1.1.3/bin/edit_json.rb +10 -0
  126. data/lib/gems/json_pure-1.1.3/bin/prettify_json.rb +76 -0
  127. data/lib/gems/json_pure-1.1.3/lib/json/Array.xpm +21 -0
  128. data/lib/gems/json_pure-1.1.3/lib/json/FalseClass.xpm +21 -0
  129. data/lib/gems/json_pure-1.1.3/lib/json/Hash.xpm +21 -0
  130. data/lib/gems/json_pure-1.1.3/lib/json/Key.xpm +73 -0
  131. data/lib/gems/json_pure-1.1.3/lib/json/NilClass.xpm +21 -0
  132. data/lib/gems/json_pure-1.1.3/lib/json/Numeric.xpm +28 -0
  133. data/lib/gems/json_pure-1.1.3/lib/json/String.xpm +96 -0
  134. data/lib/gems/json_pure-1.1.3/lib/json/TrueClass.xpm +21 -0
  135. data/lib/gems/json_pure-1.1.3/lib/json/add/core.rb +135 -0
  136. data/lib/gems/json_pure-1.1.3/lib/json/add/rails.rb +58 -0
  137. data/lib/gems/json_pure-1.1.3/lib/json/common.rb +354 -0
  138. data/lib/gems/json_pure-1.1.3/lib/json/editor.rb +1362 -0
  139. data/lib/gems/json_pure-1.1.3/lib/json/ext.rb +13 -0
  140. data/lib/gems/json_pure-1.1.3/lib/json/json.xpm +1499 -0
  141. data/lib/gems/json_pure-1.1.3/lib/json/pure/generator.rb +394 -0
  142. data/lib/gems/json_pure-1.1.3/lib/json/pure/parser.rb +259 -0
  143. data/lib/gems/json_pure-1.1.3/lib/json/pure.rb +75 -0
  144. data/lib/gems/json_pure-1.1.3/lib/json/version.rb +9 -0
  145. data/lib/gems/json_pure-1.1.3/lib/json.rb +235 -0
  146. data/lib/gems/launchy-0.3.2/bin/launchy +12 -0
  147. data/lib/gems/launchy-0.3.2/lib/launchy/application.rb +163 -0
  148. data/lib/gems/launchy-0.3.2/lib/launchy/browser.rb +85 -0
  149. data/lib/gems/launchy-0.3.2/lib/launchy/command_line.rb +48 -0
  150. data/lib/gems/launchy-0.3.2/lib/launchy/gemspec.rb +53 -0
  151. data/lib/gems/launchy-0.3.2/lib/launchy/specification.rb +133 -0
  152. data/lib/gems/launchy-0.3.2/lib/launchy/version.rb +18 -0
  153. data/lib/gems/launchy-0.3.2/lib/launchy.rb +58 -0
  154. data/lib/gems/uuidtools-1.0.3/lib/uuidtools/version.rb +32 -0
  155. data/lib/gems/uuidtools-1.0.3/lib/uuidtools.rb +648 -0
  156. data/lib/gems.rb +13 -0
  157. data/lib/mack-data_mapper/migration_generator/migration_generator.rb +5 -0
  158. data/lib/mack-data_mapper/migration_generator/templates/db/migrations/%=@migration_name%.rb.template +1 -1
  159. data/lib/mack-data_mapper/model_generator/manifest.yml +3 -3
  160. data/lib/mack-data_mapper/model_generator/model_generator.rb +8 -1
  161. data/lib/mack-data_mapper/model_generator/templates/model.rb.template +1 -1
  162. data/lib/mack-data_mapper/model_generator/templates/rspec.rb.template +1 -1
  163. data/lib/mack-data_mapper/model_generator/templates/test_case.rb.template +1 -1
  164. data/lib/mack-data_mapper.rb +3 -2
  165. data/lib/mack-data_mapper_tasks.rb +7 -0
  166. metadata +235 -86
@@ -0,0 +1,642 @@
1
+ module DataMapper
2
+ class Collection < LazyArray
3
+ include Assertions
4
+
5
+ attr_reader :query
6
+
7
+ ##
8
+ # @return [Repository] the repository the collection is
9
+ # associated with
10
+ #
11
+ # @api public
12
+ def repository
13
+ query.repository
14
+ end
15
+
16
+ ##
17
+ # loads the entries for the collection. Used by the
18
+ # adapters to load the instances of the declared
19
+ # model for this collection's query.
20
+ #
21
+ # @api private
22
+ def load(values)
23
+ add(model.load(values, query))
24
+ end
25
+
26
+ ##
27
+ # reloads the entries associated with this collection
28
+ #
29
+ # @param [DataMapper::Query] query (optional) additional query
30
+ # to scope by. Use this if you want to query a collections result
31
+ # set
32
+ #
33
+ # @see DataMapper::Collection#all
34
+ #
35
+ # @api public
36
+ def reload(query = {})
37
+ @query = scoped_query(query)
38
+ @query.update(:fields => @query.fields | @key_properties)
39
+ replace(all(:reload => true))
40
+ end
41
+
42
+ ##
43
+ # retrieves an entry out of the collection's entry by key
44
+ #
45
+ # @param [DataMapper::Types::*, ...] key keys which uniquely
46
+ # identify a resource in the collection
47
+ #
48
+ # @return [DataMapper::Resource, NilClass] the resource which
49
+ # has the supplied keys
50
+ #
51
+ # @api public
52
+ def get(*key)
53
+ key = model.typecast_key(key)
54
+ if loaded?
55
+ # find indexed resource (create index first if it does not exist)
56
+ each {|r| @cache[r.key] = r } if @cache.empty?
57
+ @cache[key]
58
+ elsif query.limit || query.offset > 0
59
+ # current query is exclusive, find resource within the set
60
+
61
+ # TODO: use a subquery to retrieve the collection and then match
62
+ # it up against the key. This will require some changes to
63
+ # how subqueries are generated, since the key may be a
64
+ # composite key. In the case of DO adapters, it means subselects
65
+ # like the form "(a, b) IN(SELECT a,b FROM ...)", which will
66
+ # require making it so the Query condition key can be a
67
+ # Property or an Array of Property objects
68
+
69
+ # use the brute force approach until subquery lookups work
70
+ lazy_load
71
+ get(*key)
72
+ else
73
+ # current query is all inclusive, lookup using normal approach
74
+ first(model.to_query(repository, key))
75
+ end
76
+ end
77
+
78
+ ##
79
+ # retrieves an entry out of the collection's entry by key,
80
+ # raising an exception if the object cannot be found
81
+ #
82
+ # @param [DataMapper::Types::*, ...] key keys which uniquely
83
+ # identify a resource in the collection
84
+ #
85
+ # @calls DataMapper::Collection#get
86
+ #
87
+ # @raise [ObjectNotFoundError] "Could not find #{model.name} with key #{key.inspect} in collection"
88
+ #
89
+ # @api public
90
+ def get!(*key)
91
+ get(*key) || raise(ObjectNotFoundError, "Could not find #{model.name} with key #{key.inspect} in collection")
92
+ end
93
+
94
+ ##
95
+ # Further refines a collection's conditions. #all provides an
96
+ # interface which simulates a database view.
97
+ #
98
+ # @param [Hash[Symbol, Object], DataMapper::Query] query parameters for
99
+ # an query within the results of the original query.
100
+ #
101
+ # @return [DataMapper::Collection] a collection whose query is the result
102
+ # of a merge
103
+ #
104
+ # @api public
105
+ def all(query = {})
106
+ # TODO: this shouldn't be a kicker if scoped_query() is called
107
+ return self if query.kind_of?(Hash) ? query.empty? : query == self.query
108
+ query = scoped_query(query)
109
+ query.repository.read_many(query)
110
+ end
111
+
112
+ ##
113
+ # Simulates Array#first by returning the first entry (when
114
+ # there are no arguments), or transforms the collection's query
115
+ # by applying :limit => n when you supply an Integer. If you
116
+ # provide a conditions hash, or a Query object, the internal
117
+ # query is scoped and a new collection is returned
118
+ #
119
+ # @param [Integer, Hash[Symbol, Object], Query] args
120
+ #
121
+ # @return [DataMapper::Resource, DataMapper::Collection] The
122
+ # first resource in the entries of this collection, or
123
+ # a new collection whose query has been merged
124
+ #
125
+ # @api public
126
+ def first(*args)
127
+ # TODO: this shouldn't be a kicker if scoped_query() is called
128
+ if loaded?
129
+ if args.empty?
130
+ return super
131
+ elsif args.size == 1 && args.first.kind_of?(Integer)
132
+ limit = args.shift
133
+ return self.class.new(scoped_query(:limit => limit)) { |c| c.replace(super(limit)) }
134
+ end
135
+ end
136
+
137
+ query = args.last.respond_to?(:merge) ? args.pop : {}
138
+ query = scoped_query(query.merge(:limit => args.first || 1))
139
+
140
+ if args.any?
141
+ query.repository.read_many(query)
142
+ else
143
+ query.repository.read_one(query)
144
+ end
145
+ end
146
+
147
+ ##
148
+ # Simulates Array#last by returning the last entry (when
149
+ # there are no arguments), or transforming the collection's
150
+ # query by reversing the declared order, and applying
151
+ # :limit => n when you supply an Integer. If you
152
+ # supply a conditions hash, or a Query object, the
153
+ # internal query is scoped and a new collection is returned
154
+ #
155
+ # @calls Collection#first
156
+ #
157
+ # @api public
158
+ def last(*args)
159
+ return super if loaded? && args.empty?
160
+
161
+ reversed = reverse
162
+
163
+ # tell the collection to reverse the order of the
164
+ # results coming out of the adapter
165
+ reversed.query.add_reversed = !query.add_reversed?
166
+
167
+ reversed.first(*args)
168
+ end
169
+
170
+ ##
171
+ # Simulates Array#at and returns the entry at that index.
172
+ # Also accepts negative indexes and appropriate reverses
173
+ # the order of the query
174
+ #
175
+ # @calls Collection#first
176
+ # @calls Collection#last
177
+ #
178
+ # @api public
179
+ def at(offset)
180
+ return super if loaded?
181
+ offset >= 0 ? first(:offset => offset) : last(:offset => offset.abs - 1)
182
+ end
183
+
184
+ ##
185
+ # Simulates Array#slice and returns a new Collection
186
+ # whose query has a new offset or limit according to the
187
+ # arguments provided.
188
+ #
189
+ # If you provide a range, the min is used as the offset
190
+ # and the max minues the offset is used as the limit.
191
+ #
192
+ # @param [Integer, Array(Integer), Range] args the offset,
193
+ # offset and limit, or range indicating offsets and limits
194
+ #
195
+ # @return [DataMapper::Resource, DataMapper::Collection]
196
+ # The entry which resides at that offset and limit,
197
+ # or a new Collection object with the set limits and offset
198
+ #
199
+ # @raise [ArgumentError] "arguments may be 1 or 2 Integers,
200
+ # or 1 Range object, was: #{args.inspect}"
201
+ #
202
+ # @alias []
203
+ #
204
+ # @api public
205
+ def slice(*args)
206
+ return at(args.first) if args.size == 1 && args.first.kind_of?(Integer)
207
+
208
+ if args.size == 2 && args.first.kind_of?(Integer) && args.last.kind_of?(Integer)
209
+ offset, limit = args
210
+ elsif args.size == 1 && args.first.kind_of?(Range)
211
+ range = args.first
212
+ offset = range.first
213
+ limit = range.last - offset
214
+ limit += 1 unless range.exclude_end?
215
+ else
216
+ raise ArgumentError, "arguments may be 1 or 2 Integers, or 1 Range object, was: #{args.inspect}", caller
217
+ end
218
+
219
+ all(:offset => offset, :limit => limit)
220
+ end
221
+
222
+ alias [] slice
223
+
224
+ ##
225
+ #
226
+ # @return [DataMapper::Collection] a new collection whose
227
+ # query is sorted in the reverse
228
+ #
229
+ # @see Array#reverse, DataMapper#all, DataMapper::Query#reverse
230
+ #
231
+ # @api public
232
+ def reverse
233
+ all(self.query.reverse)
234
+ end
235
+
236
+ ##
237
+ # @see Array#<<
238
+ #
239
+ # @api public
240
+ def <<(resource)
241
+ super
242
+ relate_resource(resource)
243
+ self
244
+ end
245
+
246
+ ##
247
+ # @see Array#push
248
+ #
249
+ # @api public
250
+ def push(*resources)
251
+ super
252
+ resources.each { |resource| relate_resource(resource) }
253
+ self
254
+ end
255
+
256
+ ##
257
+ # @see Array#unshift
258
+ #
259
+ # @api public
260
+ def unshift(*resources)
261
+ super
262
+ resources.each { |resource| relate_resource(resource) }
263
+ self
264
+ end
265
+
266
+ ##
267
+ # @see Array#replace
268
+ #
269
+ # @api public
270
+ def replace(other)
271
+ if loaded?
272
+ each { |resource| orphan_resource(resource) }
273
+ end
274
+ super
275
+ other.each { |resource| relate_resource(resource) }
276
+ self
277
+ end
278
+
279
+ ##
280
+ # @see Array#pop
281
+ #
282
+ # @api public
283
+ def pop
284
+ orphan_resource(super)
285
+ end
286
+
287
+ ##
288
+ # @see Array#shift
289
+ #
290
+ # @api public
291
+ def shift
292
+ orphan_resource(super)
293
+ end
294
+
295
+ ##
296
+ # @see Array#delete
297
+ #
298
+ # @api public
299
+ def delete(resource)
300
+ orphan_resource(super)
301
+ end
302
+
303
+ ##
304
+ # @see Array#delete_at
305
+ #
306
+ # @api public
307
+ def delete_at(index)
308
+ orphan_resource(super)
309
+ end
310
+
311
+ ##
312
+ # @see Array#clear
313
+ #
314
+ # @api public
315
+ def clear
316
+ if loaded?
317
+ each { |resource| orphan_resource(resource) }
318
+ end
319
+ super
320
+ self
321
+ end
322
+
323
+ # builds a new resource and appends it to the collection
324
+ #
325
+ # @param Hash[Symbol => Object] attributes attributes which
326
+ # the new resource should have.
327
+ #
328
+ # @api public
329
+ def build(attributes = {})
330
+ repository.scope do
331
+ resource = model.new(default_attributes.merge(attributes))
332
+ self << resource
333
+ resource
334
+ end
335
+ end
336
+
337
+ ##
338
+ # creates a new resource, saves it, and appends it to the collection
339
+ #
340
+ # @param Hash[Symbol => Object] attributes attributes which
341
+ # the new resource should have.
342
+ #
343
+ # @api public
344
+ def create(attributes = {})
345
+ repository.scope do
346
+ resource = model.create(default_attributes.merge(attributes))
347
+ self << resource unless resource.new_record?
348
+ resource
349
+ end
350
+ end
351
+
352
+ def update(attributes = {}, preload = false)
353
+ raise NotImplementedError, 'update *with* validations has not be written yet, try update!'
354
+ end
355
+
356
+ ##
357
+ # batch updates the entries belongs to this collection, and skip
358
+ # validations for all resources.
359
+ #
360
+ # @example Reached the Age of Alchohol Consumption
361
+ # Person.all(:age.gte => 21).update!(:allow_beer => true)
362
+ #
363
+ # @param attributes Hash[Symbol => Object] attributes to update
364
+ # @param reload [FalseClass, TrueClass] if set to true, collection
365
+ # will have loaded resources reflect updates.
366
+ #
367
+ # @return [TrueClass, FalseClass]
368
+ # TrueClass indicates that all entries were affected
369
+ # FalseClass indicates that some entries were affected
370
+ #
371
+ # @api public
372
+ def update!(attributes = {}, reload = false)
373
+ # TODO: delegate to Model.update
374
+ return true if attributes.empty?
375
+
376
+ dirty_attributes = {}
377
+
378
+ model.properties(repository.name).slice(*attributes.keys).each do |property|
379
+ dirty_attributes[property] = attributes[property.name] if property
380
+ end
381
+
382
+ # this should never be done on update! even if collection is loaded. or?
383
+ # each { |resource| resource.attributes = attributes } if loaded?
384
+
385
+ changes = repository.update(dirty_attributes, scoped_query)
386
+
387
+ # need to decide if this should be done in update!
388
+ query.update(attributes)
389
+
390
+ if identity_map.any? && reload
391
+ reload_query = @key_properties.zip(identity_map.keys.transpose).to_hash
392
+ model.all(reload_query.merge(attributes)).reload(:fields => attributes.keys)
393
+ end
394
+
395
+ # this should return true if there are any changes at all. as it skips validations
396
+ # the only way it could be fewer changes is if some resources already was updated.
397
+ # that should not return false? true = 'now all objects have these new values'
398
+ return loaded? ? changes == size : changes > 0
399
+ end
400
+
401
+ def destroy
402
+ raise NotImplementedError, 'destroy *with* validations has not be written yet, try destroy!'
403
+ end
404
+
405
+ ##
406
+ # batch destroy the entries belongs to this collection, and skip
407
+ # validations for all resources.
408
+ #
409
+ # @example The War On Terror (if only it were this easy)
410
+ # Person.all(:terrorist => true).destroy() #
411
+ #
412
+ # @return [TrueClass, FalseClass]
413
+ # TrueClass indicates that all entries were affected
414
+ # FalseClass indicates that some entries were affected
415
+ #
416
+ # @api public
417
+ def destroy!
418
+ # TODO: delegate to Model.destroy
419
+ if loaded?
420
+ return false unless repository.delete(scoped_query) == size
421
+
422
+ each do |resource|
423
+ resource.instance_variable_set(:@new_record, true)
424
+ identity_map.delete(resource.key)
425
+ resource.dirty_attributes.clear
426
+
427
+ model.properties(repository.name).each do |property|
428
+ next unless resource.attribute_loaded?(property.name)
429
+ resource.dirty_attributes[property] = property.get(resource)
430
+ end
431
+ end
432
+ else
433
+ return false unless repository.delete(scoped_query) > 0
434
+ end
435
+
436
+ clear
437
+
438
+ true
439
+ end
440
+
441
+ ##
442
+ # @return [DataMapper::PropertySet] The set of properties this
443
+ # query will be retrieving
444
+ #
445
+ # @api public
446
+ def properties
447
+ PropertySet.new(query.fields)
448
+ end
449
+
450
+ ##
451
+ # @return [DataMapper::Relationship] The model's relationships
452
+ #
453
+ # @api public
454
+ def relationships
455
+ model.relationships(repository.name)
456
+ end
457
+
458
+ ##
459
+ # default values to use when creating a Resource within the Collection
460
+ #
461
+ # @return [Hash] The default attributes for DataMapper::Collection#create
462
+ #
463
+ # @see DataMapper::Collection#create
464
+ #
465
+ # @api public
466
+ def default_attributes
467
+ default_attributes = {}
468
+ query.conditions.each do |tuple|
469
+ operator, property, bind_value = *tuple
470
+
471
+ next unless operator == :eql &&
472
+ property.kind_of?(DataMapper::Property) &&
473
+ ![ Array, Range ].any? { |k| bind_value.kind_of?(k) }
474
+ !@key_properties.include?(property)
475
+
476
+ default_attributes[property.name] = bind_value
477
+ end
478
+ default_attributes
479
+ end
480
+
481
+ ##
482
+ # check to see if collection can respond to the method
483
+ #
484
+ # @param method [Symbol] method to check in the object
485
+ # @param include_private [FalseClass, TrueClass] if set to true,
486
+ # collection will check private methods
487
+ #
488
+ # @return [TrueClass, FalseClass]
489
+ # TrueClass indicates the method can be responded to by the collection
490
+ # FalseClass indicates the method can not be responded to by the collection
491
+ #
492
+ # @api public
493
+ def respond_to?(method, include_private = false)
494
+ super || model.public_methods(false).include?(method.to_s) || relationships.has_key?(method)
495
+ end
496
+
497
+ protected
498
+
499
+ ##
500
+ # @api private
501
+ def model
502
+ query.model
503
+ end
504
+
505
+ private
506
+
507
+ ##
508
+ # @api public
509
+ def initialize(query, &block)
510
+ assert_kind_of 'query', query, Query
511
+
512
+ unless block_given?
513
+ # It can be helpful (relationship.rb: 112-13, used for SEL) to have a non-lazy Collection.
514
+ block = lambda {}
515
+ end
516
+
517
+ @query = query
518
+ @key_properties = model.key(repository.name)
519
+ @cache = {}
520
+
521
+ super()
522
+
523
+ load_with(&block)
524
+ end
525
+
526
+ ##
527
+ # @api private
528
+ def add(resource)
529
+ query.add_reversed? ? unshift(resource) : push(resource)
530
+ resource
531
+ end
532
+
533
+ ##
534
+ # @api private
535
+ def relate_resource(resource)
536
+ return unless resource
537
+ resource.collection = self
538
+ @cache[resource.key] = resource
539
+ resource
540
+ end
541
+
542
+ ##
543
+ # @api private
544
+ def orphan_resource(resource)
545
+ return unless resource
546
+ resource.collection = nil if resource.collection.object_id == self.object_id
547
+ @cache.delete(resource.key)
548
+ resource
549
+ end
550
+
551
+ ##
552
+ # @api private
553
+ def scoped_query(query = self.query)
554
+ assert_kind_of 'query', query, Query, Hash
555
+
556
+ query.update(keys) if loaded?
557
+
558
+ return self.query if query == self.query
559
+
560
+ query = if query.kind_of?(Hash)
561
+ Query.new(query.has_key?(:repository) ? query.delete(:repository) : self.repository, model, query)
562
+ else
563
+ query
564
+ end
565
+
566
+ if query.limit || query.offset > 0
567
+ set_relative_position(query)
568
+ end
569
+
570
+ self.query.merge(query)
571
+ end
572
+
573
+ ##
574
+ # @api private
575
+ def keys
576
+ keys = map {|r| r.key }
577
+ keys.any? ? @key_properties.zip(keys.transpose).to_hash : {}
578
+ end
579
+
580
+ ##
581
+ # @api private
582
+ def identity_map
583
+ repository.identity_map(model)
584
+ end
585
+
586
+ ##
587
+ # @api private
588
+ def set_relative_position(query)
589
+ return if query == self.query
590
+
591
+ if query.offset == 0
592
+ return if !query.limit.nil? && !self.query.limit.nil? && query.limit <= self.query.limit
593
+ return if query.limit.nil? && self.query.limit.nil?
594
+ end
595
+
596
+ first_pos = self.query.offset + query.offset
597
+ last_pos = self.query.offset + self.query.limit if self.query.limit
598
+
599
+ if limit = query.limit
600
+ if last_pos.nil? || first_pos + limit < last_pos
601
+ last_pos = first_pos + limit
602
+ end
603
+ end
604
+
605
+ if last_pos && first_pos >= last_pos
606
+ raise 'outside range' # TODO: raise a proper exception object
607
+ end
608
+
609
+ query.update(:offset => first_pos)
610
+ query.update(:limit => last_pos - first_pos) if last_pos
611
+ end
612
+
613
+ ##
614
+ # @api private
615
+ def method_missing(method, *args, &block)
616
+ if model.public_methods(false).include?(method.to_s)
617
+ model.send(:with_scope, query) do
618
+ model.send(method, *args, &block)
619
+ end
620
+ elsif relationship = relationships[method]
621
+ klass = model == relationship.child_model ? relationship.parent_model : relationship.child_model
622
+
623
+ # TODO: when self.query includes an offset/limit use it as a
624
+ # subquery to scope the results rather than a join
625
+
626
+ query = Query.new(repository, klass)
627
+ query.conditions.push(*self.query.conditions)
628
+ query.update(relationship.query)
629
+ query.update(args.pop) if args.last.kind_of?(Hash)
630
+
631
+ query.update(
632
+ :fields => klass.properties(repository.name).defaults,
633
+ :links => [ relationship ] + self.query.links
634
+ )
635
+
636
+ klass.all(query, &block)
637
+ else
638
+ super
639
+ end
640
+ end
641
+ end # class Collection
642
+ end # module DataMapper
@@ -0,0 +1,32 @@
1
+ module DataMapper
2
+ ##
3
+ #
4
+ # DataMapper's DependencyQueue is used to store callbacks for classes which
5
+ # may or may not be loaded already.
6
+ #
7
+ class DependencyQueue
8
+ def initialize
9
+ @dependencies = {}
10
+ end
11
+
12
+ def add(class_name, &callback)
13
+ @dependencies[class_name] ||= []
14
+ @dependencies[class_name] << callback
15
+ resolve!
16
+ end
17
+
18
+ def resolve!
19
+ @dependencies.each do |class_name, callbacks|
20
+ begin
21
+ klass = Object.find_const(class_name)
22
+ callbacks.each do |callback|
23
+ callback.call(klass)
24
+ end
25
+ callbacks.clear
26
+ rescue NameError
27
+ end
28
+ end
29
+ end
30
+
31
+ end # class DependencyQueue
32
+ end # module DataMapper
@@ -0,0 +1,11 @@
1
+ module DataMapper
2
+ module Hook
3
+ def self.included(model)
4
+ model.class_eval <<-EOS, __FILE__, __LINE__
5
+ include Extlib::Hook
6
+ register_instance_hooks :save, :create, :update, :destroy
7
+ EOS
8
+ end
9
+ end
10
+ DataMapper::Resource.append_inclusions Hook
11
+ end # module DataMapper