datamapper-dm-core 0.9.11 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. data/.autotest +17 -14
  2. data/.gitignore +3 -1
  3. data/FAQ +6 -5
  4. data/History.txt +5 -39
  5. data/Manifest.txt +67 -76
  6. data/QUICKLINKS +1 -1
  7. data/README.txt +21 -15
  8. data/Rakefile +16 -15
  9. data/SPECS +2 -29
  10. data/TODO +1 -1
  11. data/dm-core.gemspec +11 -15
  12. data/lib/dm-core/adapters/abstract_adapter.rb +182 -185
  13. data/lib/dm-core/adapters/data_objects_adapter.rb +482 -534
  14. data/lib/dm-core/adapters/in_memory_adapter.rb +90 -69
  15. data/lib/dm-core/adapters/mysql_adapter.rb +22 -115
  16. data/lib/dm-core/adapters/oracle_adapter.rb +249 -0
  17. data/lib/dm-core/adapters/postgres_adapter.rb +7 -173
  18. data/lib/dm-core/adapters/sqlite3_adapter.rb +4 -97
  19. data/lib/dm-core/adapters/yaml_adapter.rb +116 -0
  20. data/lib/dm-core/adapters.rb +135 -16
  21. data/lib/dm-core/associations/many_to_many.rb +372 -90
  22. data/lib/dm-core/associations/many_to_one.rb +220 -73
  23. data/lib/dm-core/associations/one_to_many.rb +319 -255
  24. data/lib/dm-core/associations/one_to_one.rb +66 -53
  25. data/lib/dm-core/associations/relationship.rb +560 -158
  26. data/lib/dm-core/collection.rb +1104 -381
  27. data/lib/dm-core/core_ext/kernel.rb +12 -0
  28. data/lib/dm-core/core_ext/symbol.rb +10 -0
  29. data/lib/dm-core/identity_map.rb +4 -34
  30. data/lib/dm-core/migrations.rb +1283 -0
  31. data/lib/dm-core/model/descendant_set.rb +81 -0
  32. data/lib/dm-core/model/hook.rb +45 -0
  33. data/lib/dm-core/model/is.rb +32 -0
  34. data/lib/dm-core/model/property.rb +248 -0
  35. data/lib/dm-core/model/relationship.rb +335 -0
  36. data/lib/dm-core/model/scope.rb +90 -0
  37. data/lib/dm-core/model.rb +570 -369
  38. data/lib/dm-core/property.rb +753 -280
  39. data/lib/dm-core/property_set.rb +141 -98
  40. data/lib/dm-core/query/conditions/comparison.rb +814 -0
  41. data/lib/dm-core/query/conditions/operation.rb +247 -0
  42. data/lib/dm-core/query/direction.rb +43 -0
  43. data/lib/dm-core/query/operator.rb +42 -0
  44. data/lib/dm-core/query/path.rb +102 -0
  45. data/lib/dm-core/query/sort.rb +45 -0
  46. data/lib/dm-core/query.rb +974 -492
  47. data/lib/dm-core/repository.rb +147 -107
  48. data/lib/dm-core/resource.rb +644 -429
  49. data/lib/dm-core/spec/adapter_shared_spec.rb +294 -0
  50. data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +106 -0
  51. data/lib/dm-core/support/chainable.rb +20 -0
  52. data/lib/dm-core/support/deprecate.rb +12 -0
  53. data/lib/dm-core/support/equalizer.rb +23 -0
  54. data/lib/dm-core/support/logger.rb +13 -0
  55. data/lib/dm-core/{naming_conventions.rb → support/naming_conventions.rb} +6 -6
  56. data/lib/dm-core/transaction.rb +333 -92
  57. data/lib/dm-core/type.rb +98 -60
  58. data/lib/dm-core/types/boolean.rb +1 -1
  59. data/lib/dm-core/types/discriminator.rb +34 -20
  60. data/lib/dm-core/types/object.rb +7 -4
  61. data/lib/dm-core/types/paranoid_boolean.rb +11 -9
  62. data/lib/dm-core/types/paranoid_datetime.rb +11 -9
  63. data/lib/dm-core/types/serial.rb +3 -3
  64. data/lib/dm-core/types/text.rb +3 -4
  65. data/lib/dm-core/version.rb +1 -1
  66. data/lib/dm-core.rb +106 -110
  67. data/script/performance.rb +102 -109
  68. data/script/profile.rb +169 -38
  69. data/spec/lib/adapter_helpers.rb +105 -0
  70. data/spec/lib/collection_helpers.rb +18 -0
  71. data/spec/lib/counter_adapter.rb +34 -0
  72. data/spec/lib/pending_helpers.rb +27 -0
  73. data/spec/lib/rspec_immediate_feedback_formatter.rb +53 -0
  74. data/spec/public/associations/many_to_many_spec.rb +193 -0
  75. data/spec/public/associations/many_to_one_spec.rb +73 -0
  76. data/spec/public/associations/one_to_many_spec.rb +77 -0
  77. data/spec/public/associations/one_to_one_spec.rb +156 -0
  78. data/spec/public/collection_spec.rb +65 -0
  79. data/spec/public/model/relationship_spec.rb +924 -0
  80. data/spec/public/model_spec.rb +159 -0
  81. data/spec/public/property_spec.rb +829 -0
  82. data/spec/public/resource_spec.rb +71 -0
  83. data/spec/public/sel_spec.rb +44 -0
  84. data/spec/public/setup_spec.rb +145 -0
  85. data/spec/public/shared/association_collection_shared_spec.rb +317 -0
  86. data/spec/public/shared/collection_shared_spec.rb +1723 -0
  87. data/spec/public/shared/finder_shared_spec.rb +1619 -0
  88. data/spec/public/shared/resource_shared_spec.rb +924 -0
  89. data/spec/public/shared/sel_shared_spec.rb +112 -0
  90. data/spec/public/transaction_spec.rb +129 -0
  91. data/spec/public/types/discriminator_spec.rb +130 -0
  92. data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
  93. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +12 -0
  94. data/spec/semipublic/adapters/mysql_adapter_spec.rb +17 -0
  95. data/spec/semipublic/adapters/oracle_adapter_spec.rb +194 -0
  96. data/spec/semipublic/adapters/postgres_adapter_spec.rb +17 -0
  97. data/spec/semipublic/adapters/sqlite3_adapter_spec.rb +17 -0
  98. data/spec/semipublic/adapters/yaml_adapter_spec.rb +12 -0
  99. data/spec/semipublic/associations/many_to_one_spec.rb +53 -0
  100. data/spec/semipublic/associations/relationship_spec.rb +194 -0
  101. data/spec/semipublic/associations_spec.rb +177 -0
  102. data/spec/semipublic/collection_spec.rb +142 -0
  103. data/spec/semipublic/property_spec.rb +61 -0
  104. data/spec/semipublic/query/conditions_spec.rb +528 -0
  105. data/spec/semipublic/query/path_spec.rb +443 -0
  106. data/spec/semipublic/query_spec.rb +2626 -0
  107. data/spec/semipublic/resource_spec.rb +47 -0
  108. data/spec/semipublic/shared/resource_shared_spec.rb +126 -0
  109. data/spec/spec.opts +3 -1
  110. data/spec/spec_helper.rb +80 -57
  111. data/tasks/ci.rb +19 -31
  112. data/tasks/dm.rb +43 -48
  113. data/tasks/doc.rb +8 -11
  114. data/tasks/gemspec.rb +5 -5
  115. data/tasks/hoe.rb +15 -16
  116. data/tasks/install.rb +8 -10
  117. metadata +72 -93
  118. data/lib/dm-core/associations/relationship_chain.rb +0 -81
  119. data/lib/dm-core/associations.rb +0 -207
  120. data/lib/dm-core/auto_migrations.rb +0 -105
  121. data/lib/dm-core/dependency_queue.rb +0 -32
  122. data/lib/dm-core/hook.rb +0 -11
  123. data/lib/dm-core/is.rb +0 -16
  124. data/lib/dm-core/logger.rb +0 -232
  125. data/lib/dm-core/migrations/destructive_migrations.rb +0 -17
  126. data/lib/dm-core/migrator.rb +0 -29
  127. data/lib/dm-core/scope.rb +0 -58
  128. data/lib/dm-core/support/array.rb +0 -13
  129. data/lib/dm-core/support/assertions.rb +0 -8
  130. data/lib/dm-core/support/errors.rb +0 -23
  131. data/lib/dm-core/support/kernel.rb +0 -11
  132. data/lib/dm-core/support/symbol.rb +0 -41
  133. data/lib/dm-core/support.rb +0 -7
  134. data/lib/dm-core/type_map.rb +0 -80
  135. data/lib/dm-core/types.rb +0 -19
  136. data/script/all +0 -4
  137. data/spec/integration/association_spec.rb +0 -1382
  138. data/spec/integration/association_through_spec.rb +0 -203
  139. data/spec/integration/associations/many_to_many_spec.rb +0 -449
  140. data/spec/integration/associations/many_to_one_spec.rb +0 -163
  141. data/spec/integration/associations/one_to_many_spec.rb +0 -188
  142. data/spec/integration/auto_migrations_spec.rb +0 -413
  143. data/spec/integration/collection_spec.rb +0 -1073
  144. data/spec/integration/data_objects_adapter_spec.rb +0 -32
  145. data/spec/integration/dependency_queue_spec.rb +0 -46
  146. data/spec/integration/model_spec.rb +0 -197
  147. data/spec/integration/mysql_adapter_spec.rb +0 -85
  148. data/spec/integration/postgres_adapter_spec.rb +0 -731
  149. data/spec/integration/property_spec.rb +0 -253
  150. data/spec/integration/query_spec.rb +0 -514
  151. data/spec/integration/repository_spec.rb +0 -61
  152. data/spec/integration/resource_spec.rb +0 -513
  153. data/spec/integration/sqlite3_adapter_spec.rb +0 -352
  154. data/spec/integration/sti_spec.rb +0 -273
  155. data/spec/integration/strategic_eager_loading_spec.rb +0 -156
  156. data/spec/integration/transaction_spec.rb +0 -75
  157. data/spec/integration/type_spec.rb +0 -275
  158. data/spec/lib/logging_helper.rb +0 -18
  159. data/spec/lib/mock_adapter.rb +0 -27
  160. data/spec/lib/model_loader.rb +0 -100
  161. data/spec/lib/publicize_methods.rb +0 -28
  162. data/spec/models/content.rb +0 -16
  163. data/spec/models/vehicles.rb +0 -34
  164. data/spec/models/zoo.rb +0 -48
  165. data/spec/unit/adapters/abstract_adapter_spec.rb +0 -133
  166. data/spec/unit/adapters/adapter_shared_spec.rb +0 -15
  167. data/spec/unit/adapters/data_objects_adapter_spec.rb +0 -632
  168. data/spec/unit/adapters/in_memory_adapter_spec.rb +0 -98
  169. data/spec/unit/adapters/postgres_adapter_spec.rb +0 -133
  170. data/spec/unit/associations/many_to_many_spec.rb +0 -32
  171. data/spec/unit/associations/many_to_one_spec.rb +0 -159
  172. data/spec/unit/associations/one_to_many_spec.rb +0 -393
  173. data/spec/unit/associations/one_to_one_spec.rb +0 -7
  174. data/spec/unit/associations/relationship_spec.rb +0 -71
  175. data/spec/unit/associations_spec.rb +0 -242
  176. data/spec/unit/auto_migrations_spec.rb +0 -111
  177. data/spec/unit/collection_spec.rb +0 -182
  178. data/spec/unit/data_mapper_spec.rb +0 -35
  179. data/spec/unit/identity_map_spec.rb +0 -126
  180. data/spec/unit/is_spec.rb +0 -80
  181. data/spec/unit/migrator_spec.rb +0 -33
  182. data/spec/unit/model_spec.rb +0 -321
  183. data/spec/unit/naming_conventions_spec.rb +0 -36
  184. data/spec/unit/property_set_spec.rb +0 -90
  185. data/spec/unit/property_spec.rb +0 -753
  186. data/spec/unit/query_spec.rb +0 -571
  187. data/spec/unit/repository_spec.rb +0 -93
  188. data/spec/unit/resource_spec.rb +0 -649
  189. data/spec/unit/scope_spec.rb +0 -142
  190. data/spec/unit/transaction_spec.rb +0 -493
  191. data/spec/unit/type_map_spec.rb +0 -114
  192. data/spec/unit/type_spec.rb +0 -119
data/lib/dm-core/model.rb CHANGED
@@ -1,316 +1,421 @@
1
- require 'set'
1
+ # TODO: add Model#create!, Model#update, Model#update!, Model#destroy and Model#destroy!
2
2
 
3
3
  module DataMapper
4
4
  module Model
5
- ##
6
- #
7
- # Extends the model with this module after DataMapper::Resource has been
8
- # included.
5
+ extend Chainable
6
+
7
+ # Creates a new Model class with default_storage_name +storage_name+
9
8
  #
10
- # This is a useful way to extend DataMapper::Model while
11
- # still retaining a self.extended method.
9
+ # If a block is passed, it will be eval'd in the context of the new Model
12
10
  #
13
- # @param [Module] extensions the module that is to be extend the model after
14
- # after DataMapper::Model
11
+ # @param [Proc] block
12
+ # a block that will be eval'd in the context of the new Model class
15
13
  #
16
- # @return [TrueClass, FalseClass] whether or not the inclusions have been
17
- # successfully appended to the list
18
- #-
19
- # @api public
14
+ # @return [Model]
15
+ # the newly created Model class
20
16
  #
21
- # TODO: Move this do DataMapper::Model when DataMapper::Model is created
22
- def self.append_extensions(*extensions)
23
- extra_extensions.concat extensions
24
- true
25
- end
26
-
27
- def self.extra_extensions
28
- @extra_extensions ||= []
29
- end
30
-
31
- def self.extended(model)
32
- model.instance_variable_set(:@storage_names, {})
33
- model.instance_variable_set(:@properties, {})
34
- model.instance_variable_set(:@field_naming_conventions, {})
35
- extra_extensions.each { |extension| model.extend(extension) }
36
- end
17
+ # @api semipublic
18
+ def self.new(storage_name = nil, &block)
19
+ model = Class.new
37
20
 
38
- def inherited(target)
39
- target.instance_variable_set(:@storage_names, @storage_names.dup)
40
- target.instance_variable_set(:@properties, {})
41
- target.instance_variable_set(:@base_model, self.base_model)
42
- target.instance_variable_set(:@paranoid_properties, @paranoid_properties)
43
- target.instance_variable_set(:@field_naming_conventions, @field_naming_conventions.dup)
21
+ model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
22
+ include DataMapper::Resource
44
23
 
45
- if self.respond_to?(:validators)
46
- @validations.contexts.each do |context, validators|
47
- validators.each { |validator| target.validators.context(context) << validator }
24
+ def self.name
25
+ to_s
48
26
  end
49
- end
27
+ RUBY
50
28
 
51
- @properties.each do |repository_name,properties|
52
- repository(repository_name) do
53
- properties.each do |property|
54
- next if target.properties(repository_name).has_property?(property.name)
55
- target.property(property.name, property.type, property.options.dup)
29
+ if storage_name
30
+ warn "Passing in +storage_name+ to #{name}.new is deprecated (#{caller[0]})"
31
+ model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
32
+ def self.default_storage_name
33
+ #{Extlib::Inflection.classify(storage_name).inspect}.freeze
56
34
  end
57
- end
35
+ RUBY
58
36
  end
59
37
 
60
- if @relationships
61
- duped_relationships = {}
62
- @relationships.each do |repository_name,relationships|
63
- relationships.each do |name, relationship|
64
- dup = relationship.dup
65
- dup.instance_variable_set(:@child_model, target) if dup.instance_variable_get(:@child_model) == self
66
- dup.instance_variable_set(:@parent_model, target) if dup.instance_variable_get(:@parent_model) == self
67
- duped_relationships[repository_name] ||= {}
68
- duped_relationships[repository_name][name] = dup
69
- end
70
- end
71
- target.instance_variable_set(:@relationships, duped_relationships)
72
- end
73
- end
74
-
75
- def self.new(storage_name, &block)
76
- model = Class.new
77
- model.send(:include, Resource)
78
- model.class_eval <<-EOS, __FILE__, __LINE__
79
- def self.default_storage_name
80
- #{Extlib::Inflection.classify(storage_name).inspect}
81
- end
82
- EOS
83
- model.instance_eval(&block) if block_given?
38
+ model.instance_eval(&block) if block
84
39
  model
85
40
  end
86
41
 
87
- def base_model
88
- @base_model ||= self
89
- end
90
-
91
- def repository_name
92
- Repository.context.any? ? Repository.context.last.name : default_repository_name
42
+ # Return all models that extend the Model module
43
+ #
44
+ # class Foo
45
+ # include DataMapper::Resource
46
+ # end
47
+ #
48
+ # DataMapper::Model.descendants.first #=> Foo
49
+ #
50
+ # @return [DescendantSet]
51
+ # Set containing the descendant models
52
+ #
53
+ # @api private
54
+ def self.descendants
55
+ @descendants ||= DescendantSet.new
93
56
  end
94
57
 
95
- ##
96
- # Get the repository with a given name, or the default one for the current
97
- # context, or the default one for this class.
58
+ # Return all models that inherit from a Model
98
59
  #
99
- # @param name<Symbol> the name of the repository wanted
100
- # @param block<Block> block to execute with the fetched repository as parameter
60
+ # class Foo
61
+ # include DataMapper::Resource
62
+ # end
101
63
  #
102
- # @return <Object, DataMapper::Respository> whatever the block returns,
103
- # if given a block, otherwise the requested repository.
104
- #-
105
- # @api public
106
- def repository(name = nil)
107
- #
108
- # There has been a couple of different strategies here, but me (zond) and dkubb are at least
109
- # united in the concept of explicitness over implicitness. That is - the explicit wish of the
110
- # caller (+name+) should be given more priority than the implicit wish of the caller (Repository.context.last).
111
- #
112
- if block_given?
113
- DataMapper.repository(name || repository_name) { |*block_args| yield(*block_args) }
114
- else
115
- DataMapper.repository(name || repository_name)
116
- end
117
- end
64
+ # class Bar < Foo
65
+ # end
66
+ #
67
+ # Foo.descendants.first #=> Bar
68
+ #
69
+ # @return [Set]
70
+ # Set containing the descendant classes
71
+ #
72
+ # @api private
73
+ attr_reader :descendants
118
74
 
119
- ##
120
- # the name of the storage recepticle for this resource. IE. table name, for database stores
75
+ # Appends a module for inclusion into the model class after Resource.
121
76
  #
122
- # @return <String> the storage name (IE table name, for database stores) associated with this resource in the given repository
123
- def storage_name(repository_name = default_repository_name)
124
- @storage_names[repository_name] ||= repository(repository_name).adapter.resource_naming_convention.call(base_model.send(:default_storage_name))
77
+ # This is a useful way to extend Resource while still retaining a
78
+ # self.included method.
79
+ #
80
+ # @param [Module] inclusions
81
+ # the module that is to be appended to the module after Resource
82
+ #
83
+ # @return [Boolean]
84
+ # true if the inclusions have been successfully appended to the list
85
+ #
86
+ # @api semipublic
87
+ def self.append_inclusions(*inclusions)
88
+ extra_inclusions.concat inclusions
89
+ true
125
90
  end
126
91
 
127
- ##
128
- # the names of the storage recepticles for this resource across all repositories
92
+ # The current registered extra inclusions
129
93
  #
130
- # @return <Hash(Symbol => String)> All available names of storage recepticles
131
- def storage_names
132
- @storage_names
94
+ # @return [Set]
95
+ #
96
+ # @api private
97
+ def self.extra_inclusions
98
+ @extra_inclusions ||= []
133
99
  end
134
100
 
135
- ##
136
- # The field naming conventions for this resource across all repositories.
101
+ # Extends the model with this module after Resource has been included.
137
102
  #
138
- # @return <String> The naming convention for the given repository
139
- def field_naming_convention(repository_name = default_storage_name)
140
- @field_naming_conventions[repository_name] ||= repository(repository_name).adapter.field_naming_convention
103
+ # This is a useful way to extend Model while still retaining a self.extended method.
104
+ #
105
+ # @param [Module] extensions
106
+ # List of modules that will extend the model after it is extended by Model
107
+ #
108
+ # @return [Boolean]
109
+ # whether or not the inclusions have been successfully appended to the list
110
+ #
111
+ # @api semipublic
112
+ def self.append_extensions(*extensions)
113
+ extra_extensions.concat extensions
114
+ true
141
115
  end
142
116
 
143
- ##
144
- # defines a property on the resource
117
+ # The current registered extra extensions
145
118
  #
146
- # @param <Symbol> name the name for which to call this property
147
- # @param <Type> type the type to define this property ass
148
- # @param <Hash(Symbol => String)> options a hash of available options
149
- # @see DataMapper::Property
150
- def property(name, type, options = {})
151
- property = Property.new(self, name, type, options)
152
-
153
- create_property_getter(property)
154
- create_property_setter(property)
155
-
156
- properties(repository_name)[property.name] = property
157
- @_valid_relations = false
119
+ # @return [Set]
120
+ #
121
+ # @api private
122
+ def self.extra_extensions
123
+ @extra_extensions ||= []
124
+ end
158
125
 
159
- # Add property to the other mappings as well if this is for the default
160
- # repository.
161
- if repository_name == default_repository_name
162
- @properties.each_pair do |repository_name, properties|
163
- next if repository_name == default_repository_name
164
- properties << property unless properties.has_property?(property.name)
165
- end
166
- end
126
+ # TODO: document
127
+ # @api private
128
+ def self.extended(model)
129
+ descendants << model
167
130
 
168
- # Add the property to the lazy_loads set for this resources repository
169
- # only.
170
- # TODO Is this right or should we add the lazy contexts to all
171
- # repositories?
172
- if property.lazy?
173
- context = options.fetch(:lazy, :default)
174
- context = :default if context == true
131
+ model.instance_variable_set(:@valid, false)
132
+ model.instance_variable_set(:@base_model, model)
133
+ model.instance_variable_set(:@storage_names, {})
134
+ model.instance_variable_set(:@default_order, {})
135
+ model.instance_variable_set(:@descendants, descendants.class.new(model, descendants))
175
136
 
176
- Array(context).each do |item|
177
- properties(repository_name).lazy_context(item) << name
178
- end
179
- end
137
+ extra_extensions.each { |mod| model.extend(mod) }
138
+ extra_inclusions.each { |mod| model.send(:include, mod) }
139
+ end
180
140
 
181
- # add the property to the child classes only if the property was
182
- # added after the child classes' properties have been copied from
183
- # the parent
184
- if respond_to?(:descendants)
185
- descendants.each do |model|
186
- next if model.properties(repository_name).has_property?(name)
187
- model.property(name, type, options)
141
+ # TODO: document
142
+ # @api private
143
+ chainable do
144
+ def inherited(model)
145
+ descendants << model
146
+
147
+ model.instance_variable_set(:@valid, false)
148
+ model.instance_variable_set(:@base_model, base_model)
149
+ model.instance_variable_set(:@storage_names, @storage_names.dup)
150
+ model.instance_variable_set(:@default_order, @default_order.dup)
151
+ model.instance_variable_set(:@descendants, descendants.class.new(model, descendants))
152
+
153
+ # TODO: move this into dm-validations
154
+ if respond_to?(:validators)
155
+ validators.contexts.each do |context, validators|
156
+ model.validators.context(context).concat(validators)
157
+ end
188
158
  end
189
159
  end
190
-
191
- property
192
160
  end
193
161
 
194
- def repositories
195
- [ repository ].to_set + @properties.keys.collect { |repository_name| DataMapper.repository(repository_name) }
162
+ # Gets the name of the storage receptacle for this resource in the given
163
+ # Repository (ie., table name, for database stores).
164
+ #
165
+ # @return [String]
166
+ # the storage name (ie., table name, for database stores) associated with
167
+ # this resource in the given repository
168
+ #
169
+ # @api public
170
+ def storage_name(repository_name = default_repository_name)
171
+ storage_names[repository_name] ||= repository(repository_name).adapter.resource_naming_convention.call(default_storage_name).freeze
196
172
  end
197
173
 
198
- def properties(repository_name = default_repository_name)
199
- # We need to check whether all relations are already set up.
200
- # If this isn't the case, we try to reload them here
201
- if !@_valid_relations && respond_to?(:many_to_one_relationships)
202
- @_valid_relations = true
203
- begin
204
- many_to_one_relationships.each do |r|
205
- r.child_key
206
- end
207
- rescue NameError
208
- # Apparently not all relations are loaded,
209
- # so we will try again later on
210
- @_valid_relations = false
211
- end
212
- end
213
- @properties[repository_name] ||= repository_name == Repository.default_name ? PropertySet.new : properties(Repository.default_name).dup
174
+ # the names of the storage receptacles for this resource across all repositories
175
+ #
176
+ # @return [Hash(Symbol => String)]
177
+ # All available names of storage recepticles
178
+ #
179
+ # @api public
180
+ def storage_names
181
+ @storage_names
214
182
  end
215
183
 
216
- def eager_properties(repository_name = default_repository_name)
217
- properties(repository_name).defaults
218
- end
184
+ # Grab a single record by its key. Supports natural and composite key
185
+ # lookups as well.
186
+ #
187
+ # Zoo.get(1) # get the zoo with primary key of 1.
188
+ # Zoo.get!(1) # Or get! if you want an ObjectNotFoundError on failure
189
+ # Zoo.get('DFW') # wow, support for natural primary keys
190
+ # Zoo.get('Metro', 'DFW') # more wow, composite key look-up
191
+ #
192
+ # @param [Object] *key
193
+ # The primary key or keys to use for lookup
194
+ #
195
+ # @return [Resource,NilClass]
196
+ # A single model that was found
197
+ # If no instance was found matching +key+
198
+ #
199
+ # @api public
200
+ def get(*key)
201
+ repository = self.repository
202
+ key = self.key(repository.name).typecast(key)
219
203
 
220
- # @api private
221
- def properties_with_subclasses(repository_name = default_repository_name)
222
- properties = PropertySet.new
223
- ([ self ].to_set + (respond_to?(:descendants) ? descendants : [])).each do |model|
224
- model.relationships(repository_name).each_value { |relationship| relationship.child_key }
225
- model.many_to_one_relationships.each do |relationship| relationship.child_key end
226
- model.properties(repository_name).each do |property|
227
- properties << property unless properties.has_property?(property.name)
228
- end
229
- end
230
- properties
204
+ repository.identity_map(self)[key] || first(key_conditions(repository, key))
231
205
  end
232
206
 
233
- def key(repository_name = default_repository_name)
234
- properties(repository_name).key
207
+ # Grab a single record just like #get, but raise an ObjectNotFoundError
208
+ # if the record doesn't exist.
209
+ #
210
+ # @param [Object] *key
211
+ # The primary key or keys to use for lookup
212
+ # @return [Resource]
213
+ # A single model that was found
214
+ # @raise [ObjectNotFoundError]
215
+ # The record was not found
216
+ #
217
+ # @api public
218
+ def get!(*key)
219
+ get(*key) || raise(ObjectNotFoundError, "Could not find #{self.name} with key #{key.inspect}")
235
220
  end
236
221
 
237
- def inheritance_property(repository_name = default_repository_name)
238
- @properties[repository_name].detect { |property| property.type == DataMapper::Types::Discriminator }
222
+ def [](*args)
223
+ all[*args]
239
224
  end
240
225
 
241
- def default_order(repository_name = default_repository_name)
242
- @default_order ||= {}
243
- @default_order[repository_name] ||= key(repository_name).map { |property| Query::Direction.new(property) }
226
+ alias slice []
227
+
228
+ def at(*args)
229
+ all.at(*args)
244
230
  end
245
231
 
246
- def get(*key)
247
- key = typecast_key(key)
248
- repository.identity_map(self).get(key) || first(to_query(repository, key))
232
+ def reverse
233
+ all.reverse
249
234
  end
250
235
 
251
- def get!(*key)
252
- get(*key) || raise(ObjectNotFoundError, "Could not find #{self.name} with key #{key.inspect}")
236
+ # TODO: spec this
237
+ def entries
238
+ all.entries
253
239
  end
254
240
 
255
- def all(query = {})
256
- query = scoped_query(query)
257
- query.repository.read_many(query)
241
+ alias to_a entries
242
+
243
+ # Find a set of records matching an optional set of conditions. Additionally,
244
+ # specify the order that the records are return.
245
+ #
246
+ # Zoo.all # all zoos
247
+ # Zoo.all(:open => true) # all zoos that are open
248
+ # Zoo.all(:opened_on => start..end) # all zoos that opened on a date in the date-range
249
+ # Zoo.all(:order => [ :tiger_count.desc ]) # Ordered by tiger_count
250
+ #
251
+ # @param [Hash] query
252
+ # A hash describing the conditions and order for the query
253
+ # @return [Collection]
254
+ # A set of records found matching the conditions in +query+
255
+ # @see Collection
256
+ #
257
+ # @api public
258
+ def all(query = nil)
259
+ if query.nil? || (query.kind_of?(Hash) && query.empty?)
260
+ # TODO: after adding Enumerable methods to Model, try to return self here
261
+ new_collection(self.query.dup)
262
+ else
263
+ new_collection(scoped_query(query))
264
+ end
258
265
  end
259
266
 
267
+ # Return the first Resource or the first N Resources for the Model with an optional query
268
+ #
269
+ # When there are no arguments, return the first Resource in the
270
+ # Model. When the first argument is an Integer, return a
271
+ # Collection containing the first N Resources. When the last
272
+ # (optional) argument is a Hash scope the results to the query.
273
+ #
274
+ # @param [Integer] limit (optional)
275
+ # limit the returned Collection to a specific number of entries
276
+ # @param [Hash] query (optional)
277
+ # scope the returned Resource or Collection to the supplied query
278
+ #
279
+ # @return [Resource, Collection]
280
+ # The first resource in the entries of this collection,
281
+ # or a new collection whose query has been merged
282
+ #
283
+ # @api public
260
284
  def first(*args)
261
- query = args.last.respond_to?(:merge) ? args.pop : {}
262
- query = scoped_query(query.merge(:limit => args.first || 1))
285
+ last_arg = args.last
286
+
287
+ limit = args.first if args.first.kind_of?(Integer)
288
+ with_query = last_arg.respond_to?(:merge) && !last_arg.blank?
289
+
290
+ query = with_query ? last_arg : {}
263
291
 
264
- if args.any?
265
- query.repository.read_many(query)
292
+ query = if query.kind_of?(Query)
293
+ query.slice(0, limit || 1)
266
294
  else
267
- query.repository.read_one(query)
295
+ offset = query.fetch(:offset, 0)
296
+ query = query.except(:offset)
297
+ scoped_query(query).slice(offset, limit || 1)
298
+ end
299
+
300
+ if limit
301
+ all(query)
302
+ else
303
+ query.repository.read(query).first
268
304
  end
269
305
  end
270
306
 
271
- def [](*key)
272
- warn("#{name}[] is deprecated. Use #{name}.get! instead.")
273
- get!(*key)
307
+ # Return the last Resource or the last N Resources for the Model with an optional query
308
+ #
309
+ # When there are no arguments, return the last Resource for the
310
+ # Model. When the first argument is an Integer, return a
311
+ # Collection containing the last N Resources. When the last
312
+ # (optional) argument is a Hash scope the results to the query.
313
+ #
314
+ # @param [Integer] limit (optional)
315
+ # limit the returned Collection to a specific number of entries
316
+ # @param [Hash] query (optional)
317
+ # scope the returned Resource or Collection to the supplied query
318
+ #
319
+ # @return [Resource, Collection]
320
+ # The last resource in the entries of this collection,
321
+ # or a new collection whose query has been merged
322
+ #
323
+ # @api public
324
+ def last(*args)
325
+ last_arg = args.last
326
+
327
+ limit = args.first if args.first.kind_of?(Integer)
328
+ with_query = last_arg.respond_to?(:merge) && !last_arg.blank?
329
+
330
+ query = with_query ? last_arg : {}
331
+
332
+ query = if query.kind_of?(Query)
333
+ query.slice(0, limit || 1).reverse!
334
+ else
335
+ offset = query.fetch(:offset, 0)
336
+ query = query.except(:offset)
337
+ scoped_query(query).slice(offset, limit || 1).reverse!
338
+ end
339
+
340
+ if limit
341
+ all(query)
342
+ else
343
+ query.repository.read(query).last
344
+ end
274
345
  end
275
346
 
276
- def first_or_create(query, attributes = {})
277
- first(query) || begin
278
- resource = allocate
279
- query = query.dup
347
+ # Finds the first Resource by conditions, or initializes a new
348
+ # Resource with the attributes if none found
349
+ #
350
+ # @param [Hash] conditions
351
+ # The conditions to be used to search
352
+ # @param [Hash] attributes
353
+ # The attributes to be used to create the record of none is found.
354
+ # @return [Resource]
355
+ # The instance found by +query+, or created with +attributes+ if none found
356
+ #
357
+ # @api public
358
+ def first_or_new(conditions = {}, attributes = {})
359
+ first(conditions) || new(conditions.merge(attributes))
360
+ end
280
361
 
281
- properties(repository_name).key.each do |property|
282
- if value = query.delete(property.name)
283
- resource.send("#{property.name}=", value)
284
- end
285
- end
362
+ # Finds the first Resource by conditions, or creates a new
363
+ # Resource with the attributes if none found
364
+ #
365
+ # @param [Hash] conditions
366
+ # The conditions to be used to search
367
+ # @param [Hash] attributes
368
+ # The attributes to be used to create the record of none is found.
369
+ # @return [Resource]
370
+ # The instance found by +query+, or created with +attributes+ if none found
371
+ #
372
+ # @api public
373
+ def first_or_create(conditions = {}, attributes = {})
374
+ first(conditions) || create(conditions.merge(attributes))
375
+ end
286
376
 
287
- resource.attributes = query.merge(attributes)
288
- resource.save
289
- resource
377
+ # Initializes an instance of Resource with the given attributes
378
+ #
379
+ # @param [Hash(Symbol => Object)] attributes
380
+ # hash of attributes to set
381
+ #
382
+ # @return [Resource]
383
+ # the newly initialized Resource instance
384
+ #
385
+ # @api public
386
+ chainable do
387
+ def new(*args, &block)
388
+ assert_valid
389
+ super
290
390
  end
291
391
  end
292
392
 
293
- ##
294
- # Create an instance of Resource with the given attributes
393
+ # Create a Resource
295
394
  #
296
- # @param <Hash(Symbol => Object)> attributes hash of attributes to set
395
+ # @param [Hash(Symbol => Object)] attributes
396
+ # attributes to set
397
+ #
398
+ # @return [Resource]
399
+ # the newly created Resource instance
400
+ #
401
+ # @api public
297
402
  def create(attributes = {})
298
- resource = new(attributes)
299
- resource.save
300
- resource
403
+ _create(true, attributes)
301
404
  end
302
405
 
303
- ##
304
- # This method is deprecated, and will be removed from dm-core.
406
+ # Create a Resource, bypassing hooks
407
+ #
408
+ # @param [Hash(Symbol => Object)] attributes
409
+ # attributes to set
305
410
  #
411
+ # @return [Resource]
412
+ # the newly created Resource instance
413
+ #
414
+ # @api public
306
415
  def create!(attributes = {})
307
- warn("Model#create! is deprecated. It is moving to dm-validations, and will be used to create a record without validations")
308
- resource = create(attributes)
309
- raise PersistenceError, "Resource not saved: :new_record => #{resource.new_record?}, :dirty_attributes => #{resource.dirty_attributes.inspect}" if resource.new_record?
310
- resource
416
+ _create(false, attributes)
311
417
  end
312
418
 
313
- ##
314
419
  # Copy a set of records from one repository to another.
315
420
  #
316
421
  # @param [String] source
@@ -321,7 +426,7 @@ module DataMapper
321
426
  # The conditions with which to find the records to copy. These
322
427
  # conditions are merged with Model.query
323
428
  #
324
- # @return [DataMapper::Collection]
429
+ # @return [Collection]
325
430
  # A Collection of the Resource instances created in the operation
326
431
  #
327
432
  # @api public
@@ -329,198 +434,294 @@ module DataMapper
329
434
 
330
435
  # get the list of properties that exist in the source and destination
331
436
  destination_properties = properties(destination)
332
- fields = query[:fields] ||= properties(source).select { |p| destination_properties.has_property?(p.name) }
437
+ fields = query[:fields] ||= properties(source).select { |property| destination_properties.include?(property) }
333
438
 
334
439
  repository(destination) do
335
- all(query.merge(:repository => repository(source))).map do |resource|
336
- create(fields.map { |p| [ p.name, p.get(resource) ] }.to_hash)
440
+ all(query.merge(:repository => source)).map do |resource|
441
+ create(fields.map { |property| [ property.name, property.get(resource) ] }.to_hash)
337
442
  end
338
443
  end
339
444
  end
340
445
 
341
- # @api private
342
- # TODO: spec this
343
- def load(values, query)
344
- repository = query.repository
345
- model = self
446
+ # Loads an instance of this Model, taking into account IdentityMap lookup,
447
+ # inheritance columns(s) and Property typecasting.
448
+ #
449
+ # @param [Enumerable(Object)] records
450
+ # an Array of Resource or Hashes to load a Resource with
451
+ #
452
+ # @return [Resource]
453
+ # the loaded Resource instance
454
+ #
455
+ # @api semipublic
456
+ def load(records, query)
457
+ repository = query.repository
458
+ repository_name = repository.name
459
+ fields = query.fields
460
+ discriminator = properties(repository_name).discriminator
461
+ no_reload = !query.reload?
346
462
 
347
- if inheritance_property_index = query.inheritance_property_index
348
- model = values.at(inheritance_property_index) || model
349
- end
463
+ field_map = fields.map { |property| [ property, property.field ] }.to_hash
350
464
 
351
- key_values = nil
352
- identity_map = nil
465
+ records.map do |record|
466
+ identity_map = nil
467
+ key_values = nil
468
+ resource = nil
353
469
 
354
- if key_property_indexes = query.key_property_indexes(repository)
355
- key_values = values.values_at(*key_property_indexes)
356
- identity_map = repository.identity_map(model)
470
+ case record
471
+ when Hash
472
+ # remap fields to use the Property object
473
+ record = record.dup
474
+ field_map.each { |property, field| record[property] = record.delete(field) if record.key?(field) }
357
475
 
358
- if resource = identity_map.get(key_values)
359
- return resource unless query.reload?
360
- else
361
- resource = model.allocate
362
- resource.instance_variable_set(:@repository, repository)
363
- end
364
- else
365
- resource = model.allocate
366
- resource.readonly!
367
- end
476
+ model = discriminator && record[discriminator] || self
368
477
 
369
- resource.instance_variable_set(:@new_record, false)
478
+ resource = if (key_values = record.values_at(*model.key(repository_name))).all?
479
+ identity_map = repository.identity_map(model)
480
+ identity_map[key_values]
481
+ end
370
482
 
371
- query.fields.zip(values) do |property,value|
372
- value = property.custom? ? property.type.load(value, property) : property.typecast(value)
373
- property.set!(resource, value)
483
+ resource ||= model.allocate
374
484
 
375
- if track = property.track
376
- case track
377
- when :hash
378
- resource.original_values[property.name] = value.dup.hash unless resource.original_values.has_key?(property.name) rescue value.hash
379
- when :load
380
- resource.original_values[property.name] = value unless resource.original_values.has_key?(property.name)
381
- end
485
+ fields.each do |property|
486
+ next if no_reload && property.loaded?(resource)
487
+
488
+ value = record[property]
489
+
490
+ # TODO: typecasting should happen inside the Adapter
491
+ # and all values should come back as expected objects
492
+ if property.custom?
493
+ value = property.type.load(value, property)
494
+ end
495
+
496
+ property.set!(resource, value)
497
+ end
498
+
499
+ when Resource
500
+ model = record.model
501
+
502
+ resource = if (key_values = record.key).all?
503
+ identity_map = repository.identity_map(model)
504
+ identity_map[key_values]
505
+ end
506
+
507
+ resource ||= model.allocate
508
+
509
+ fields.each do |property|
510
+ next if no_reload && property.loaded?(resource)
511
+
512
+ property.set!(resource, property.get!(record))
513
+ end
382
514
  end
383
- end
384
515
 
385
- if key_values && identity_map
386
- identity_map.set(key_values, resource)
516
+ resource.instance_variable_set(:@repository, repository)
517
+ resource.instance_variable_set(:@saved, true)
518
+
519
+ if identity_map
520
+ # defer setting the IdentityMap so second level caches can
521
+ # record the state of the resource after loaded
522
+ identity_map[key_values] = resource
523
+ else
524
+ resource.freeze
525
+ end
526
+
527
+ resource
387
528
  end
529
+ end
388
530
 
389
- resource
531
+ # TODO: document
532
+ # @api semipublic
533
+ attr_reader :base_model
534
+
535
+ # TODO: document
536
+ # @api semipublic
537
+ def default_repository_name
538
+ Repository.default_name
390
539
  end
391
540
 
392
- # TODO: spec this
393
- def to_query(repository, key, query = {})
394
- conditions = Hash[ *self.key(repository.name).zip(key).flatten ]
395
- Query.new(repository, self, query.merge(conditions))
541
+ # TODO: document
542
+ # @api semipublic
543
+ def default_order(repository_name = default_repository_name)
544
+ @default_order[repository_name] ||= key(repository_name).map { |property| Query::Direction.new(property) }.freeze
396
545
  end
397
546
 
398
- # TODO: add docs
547
+ # Get the repository with a given name, or the default one for the current
548
+ # context, or the default one for this class.
549
+ #
550
+ # @param [Symbol] name
551
+ # the name of the repository wanted
552
+ # @param [Block] block
553
+ # block to execute with the fetched repository as parameter
554
+ #
555
+ # @return [Object, Respository]
556
+ # whatever the block returns, if given a block,
557
+ # otherwise the requested repository.
558
+ #
399
559
  # @api private
400
- def _load(marshalled)
401
- resource = allocate
402
- Marshal.load(marshalled).each { |kv| resource.instance_variable_set(*kv) }
403
- resource
560
+ def repository(name = nil)
561
+ #
562
+ # There has been a couple of different strategies here, but me (zond) and dkubb are at least
563
+ # united in the concept of explicitness over implicitness. That is - the explicit wish of the
564
+ # caller (+name+) should be given more priority than the implicit wish of the caller (Repository.context.last).
565
+ #
566
+ if block_given?
567
+ DataMapper.repository(name || repository_name) { |*block_args| yield(*block_args) }
568
+ else
569
+ DataMapper.repository(name || repository_name)
570
+ end
404
571
  end
405
572
 
406
- def typecast_key(key)
407
- self.key(repository_name).zip(key).map { |k, v| k.typecast(v) }
573
+ # Get the current +repository_name+ for this Model.
574
+ #
575
+ # If there are any Repository contexts, the name of the last one will
576
+ # be returned, else the +default_repository_name+ of this model will be
577
+ #
578
+ # @return [String]
579
+ # the current repository name to use for this Model
580
+ #
581
+ # @api private
582
+ def repository_name
583
+ Repository.context.any? ? Repository.context.last.name : default_repository_name
408
584
  end
409
585
 
410
- def default_repository_name
411
- Repository.default_name
586
+ # Gets the current Set of repositories for which
587
+ # this Model has been defined (beyond default)
588
+ #
589
+ # @return [Set]
590
+ # The Set of repositories for which this Model
591
+ # has been defined (beyond default)
592
+ #
593
+ # @api private
594
+ def repositories
595
+ [ repository ].to_set + @properties.keys.map { |repository_name| DataMapper.repository(repository_name) }
412
596
  end
413
597
 
414
- def paranoid_properties
415
- @paranoid_properties ||= {}
416
- @paranoid_properties
598
+ # TODO: document
599
+ # @api private
600
+ def model_method_defined?(method)
601
+ model_methods.include?(method.to_s)
417
602
  end
418
603
 
419
- private
420
-
421
- def default_storage_name
422
- self.name
604
+ # TODO: document
605
+ # @api private
606
+ def resource_method_defined?(method)
607
+ resource_methods.include?(method.to_s)
423
608
  end
424
609
 
425
- def scoped_query(query = self.query)
426
- assert_kind_of 'query', query, Query, Hash
610
+ private
427
611
 
428
- return self.query if query == self.query
612
+ # TODO: document
613
+ # @api private
614
+ def _create(safe, attributes)
615
+ resource = new(attributes)
616
+ resource.send(safe ? :save : :save!)
617
+ resource
618
+ end
429
619
 
430
- query = if query.kind_of?(Hash)
431
- Query.new(query.has_key?(:repository) ? query.delete(:repository) : self.repository, self, query)
620
+ # TODO: document
621
+ # @api private
622
+ def const_missing(name)
623
+ if name == :DM
624
+ warn "#{name} prefix deprecated and no longer necessary (#{caller[0]})"
625
+ self
626
+ elsif name == :Resource
627
+ Resource
628
+ elsif Types.const_defined?(name)
629
+ Types.const_get(name)
432
630
  else
433
- query
631
+ super
434
632
  end
633
+ end
435
634
 
436
- if self.query
437
- self.query.merge(query)
438
- else
439
- merge_with_default_scope(query)
440
- end
635
+ # TODO: document
636
+ # @api private
637
+ def default_storage_name
638
+ base_model.name
441
639
  end
442
640
 
443
- def set_paranoid_property(name, &block)
444
- self.paranoid_properties[name] = block
641
+ # Initializes a new Collection
642
+ #
643
+ # @return [Collection]
644
+ # A new Collection object
645
+ #
646
+ # @api private
647
+ def new_collection(query, resources = nil, &block)
648
+ Collection.new(query, resources, &block)
445
649
  end
446
650
 
447
- # defines the getter for the property
448
- def create_property_getter(property)
449
- class_eval <<-EOS, __FILE__, __LINE__
450
- #{property.reader_visibility}
451
- def #{property.getter}
452
- attribute_get(#{property.name.inspect})
651
+ # @api private
652
+ # TODO: move the logic to create relative query into Query
653
+ def scoped_query(query)
654
+ if query.kind_of?(Query)
655
+ query.dup
656
+ else
657
+ repository = if query.key?(:repository)
658
+ query = query.dup
659
+ repository = query.delete(:repository)
660
+
661
+ if repository.kind_of?(Symbol)
662
+ DataMapper.repository(repository)
663
+ else
664
+ repository
665
+ end
666
+ else
667
+ self.repository
453
668
  end
454
- EOS
455
669
 
456
- if property.primitive == TrueClass && !instance_methods.map { |m| m.to_s }.include?(property.name.to_s)
457
- class_eval <<-EOS, __FILE__, __LINE__
458
- #{property.reader_visibility}
459
- alias #{property.name} #{property.getter}
460
- EOS
670
+ if self.query.repository == repository
671
+ self.query.merge(query)
672
+ else
673
+ Query.new(repository, self, self.query.merge(query).options)
674
+ end
461
675
  end
462
676
  end
463
677
 
464
- # defines the setter for the property
465
- def create_property_setter(property)
466
- unless instance_methods.map { |m| m.to_s }.include?("#{property.name}=")
467
- class_eval <<-EOS, __FILE__, __LINE__
468
- #{property.writer_visibility}
469
- def #{property.name}=(value)
470
- attribute_set(#{property.name.inspect}, value)
471
- end
472
- EOS
473
- end
474
- end
678
+ # @api private
679
+ def assert_valid # :nodoc:
680
+ return if @valid
681
+ @valid = true
475
682
 
476
- def relationships(*args)
477
- # DO NOT REMOVE!
478
- # method_missing depends on these existing. Without this stub,
479
- # a missing module can cause misleading recursive errors.
480
- raise NotImplementedError.new
481
- end
683
+ if properties(repository_name).empty? &&
684
+ !relationships(repository_name).any? { |(relationship_name, relationship)| relationship.kind_of?(Associations::ManyToOne::Relationship) }
685
+ raise IncompleteModelError, "#{name} must have at least one property or many to one relationship to be valid"
686
+ end
482
687
 
483
- def method_missing(method, *args, &block)
484
- if relationship = self.relationships(repository_name)[method]
485
- klass = self == relationship.child_model ? relationship.parent_model : relationship.child_model
486
- return DataMapper::Query::Path.new(repository, [ relationship ], klass)
688
+ if key(repository_name).empty?
689
+ raise IncompleteModelError, "#{name} must have a key to be valid"
487
690
  end
488
691
 
489
- property_set = properties(repository_name)
490
- if property_set.has_property?(method)
491
- return property_set[method]
692
+ # initialize join models and target keys
693
+ @relationships.each_value do |relationships|
694
+ relationships.each_value do |relationship|
695
+ relationship.child_key
696
+ relationship.through if relationship.respond_to?(:through)
697
+ relationship.via if relationship.respond_to?(:via)
698
+ end
492
699
  end
700
+ end
493
701
 
494
- super
702
+ # TODO: document
703
+ # @api private
704
+ def model_methods
705
+ @model_methods ||= ancestor_instance_methods { |mod| mod.meta_class }
495
706
  end
496
707
 
497
- # TODO: move to dm-more/dm-transactions
498
- module Transaction
499
- #
500
- # Produce a new Transaction for this Resource class
501
- #
502
- # @return <DataMapper::Adapters::Transaction
503
- # a new DataMapper::Adapters::Transaction with all DataMapper::Repositories
504
- # of the class of this DataMapper::Resource added.
505
- #-
506
- # @api public
507
- #
508
- # TODO: move to dm-more/dm-transactions
509
- def transaction
510
- DataMapper::Transaction.new(self) { |block_args| yield(*block_args) }
511
- end
512
- end # module Transaction
708
+ # TODO: document
709
+ # @api private
710
+ def resource_methods
711
+ @resource_methods ||= ancestor_instance_methods { |mod| mod }
712
+ end
513
713
 
514
- include Transaction
714
+ # TODO: document
715
+ # @api private
716
+ def ancestor_instance_methods
717
+ methods = Set.new
515
718
 
516
- # TODO: move to dm-more/dm-migrations
517
- module Migration
518
- # TODO: move to dm-more/dm-migrations
519
- def storage_exists?(repository_name = default_repository_name)
520
- repository(repository_name).storage_exists?(storage_name(repository_name))
719
+ ancestors.each do |mod|
720
+ next unless mod <= DataMapper::Resource
721
+ methods.merge(yield(mod).instance_methods(false).map { |method| method.to_s })
521
722
  end
522
- end # module Migration
523
723
 
524
- include Migration
724
+ methods
725
+ end
525
726
  end # module Model
526
727
  end # module DataMapper